diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml new file mode 100644 index 00000000..93216cb5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml @@ -0,0 +1,75 @@ +name: Report a Bug +description: File a bug report +title: "[Bug] " +labels: [ "bug" ] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a Bug in BCLib! + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: markdown + attributes: + value: | + ## Versions + - type: input + id: bn_version + attributes: + label: BCLib + description: What version of BCLib are you running? + placeholder: 2.x.x + validations: + required: true + - type: input + id: fabric_api_version + attributes: + label: Fabric API + description: What version of Fabric API is installed + placeholder: 0.5x.x + validations: + required: false + - type: input + id: fabric_loader_version + attributes: + label: Fabric Loader + description: What version of Fabric Loader do you use + placeholder: 0.14.x + validations: + required: false + - type: dropdown + id: mc_version + attributes: + label: Minecraft + description: What version of Minecraft is installed? + options: + - 1.19 + - 1.18.2 + - 1.18.1 + - 1.18 + - Older + validations: + required: true + - type: markdown + attributes: + value: | + ## Additional Information + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + - type: textarea + id: other_mods + attributes: + label: Other Mods + description: If you can, please supply a list of installed Mods (besides BetterNether and BCLib). This information may already be included in the log above. + render: shell 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..f6fb6cba 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,6 @@ bin/ # fabric run/ +run-client/ output/ *.log 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/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..8d4d8c13 --- /dev/null +++ b/bclib.gradle @@ -0,0 +1,146 @@ +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' } +} + +loom { + accessWidenerPath = file("src/main/resources/bclib.accesswidener") +} + +dependencies { + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings loom.officialMojangMappings() + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + modCompileOnly "com.terraformersmc:modmenu:${project.modmenu_version}" + + //useApi "vazkii.patchouli:Patchouli:1.16.4-${project.patchouli_version}" +} + +processResources { + println "Version: ${project.mod_version}" + inputs.property "version", project.mod_version + + filesMatching("fabric.mod.json") { + expand "version": project.mod_version + } +} + +// ensure that the encoding is set to UTF-8, no matter what the system default is +// this fixes some edge cases with special characters not displaying correctly +// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" + it.options.release = 17 +} + +javadoc { + options.tags = ["reason"] + options.stylesheetFile = new File(projectDir, "javadoc.css"); +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task +// if it is present. +// If you remove this task, sources will not be generated. +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +jar { + from "LICENSE" +} + +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 9d232758..e1d0a7ff 100644 --- a/build.gradle +++ b/build.gradle @@ -1,164 +1,8 @@ -buildscript { - dependencies { - classpath 'org.kohsuke:github-api:1.114' - } -} - plugins { id 'idea' id 'eclipse' - id 'fabric-loom' version '0.8-SNAPSHOT' + id 'fabric-loom' version "${loom_version}" id 'maven-publish' } -sourceCompatibility = JavaVersion.VERSION_16 -targetCompatibility = JavaVersion.VERSION_16 - -archivesBaseName = project.archives_base_name -version = project.mod_version -group = project.maven_group - -repositories { - maven { url "https://maven.dblsaiko.net/" } - maven { url "https://server.bbkr.space:8081/artifactory/libs-release/" } - maven { url "https://maven.fabricmc.net/" } - maven { url 'https://maven.blamejared.com' } - maven { url "https://maven.shedaniel.me/" } - maven { url 'https://jitpack.io' } -} - -dependencies { - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings minecraft.officialMojangMappings() - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - - //useApi "vazkii.patchouli:Patchouli:1.16.4-${project.patchouli_version}" -} - -def useOptional(String dep) { - dependencies.modRuntime (dep) { - exclude group: "net.fabricmc.fabric-api" - exclude group: "net.fabricmc" - if (!dep.contains("me.shedaniel")) { - exclude group: "me.shedaniel" - } - } - dependencies.modCompileOnly (dep) { - exclude group: "net.fabricmc.fabric-api" - exclude group: "net.fabricmc" - if (!dep.contains("me.shedaniel")) { - exclude group: "me.shedaniel" - } - } -} - -def useApi(String dep) { - dependencies.modApi (dep) { - exclude group: "net.fabricmc.fabric-api" - exclude group: "net.fabricmc" - if (!dep.contains("me.shedaniel")) { - exclude group: "me.shedaniel" - } - } -} - -processResources { - inputs.property "version", project.version - duplicatesStrategy = 'EXCLUDE' - - from(sourceSets.main.resources.srcDirs) { - include "fabric.mod.json" - expand "version": project.version - } - - from(sourceSets.main.resources.srcDirs) { - exclude "fabric.mod.json" - } -} - -// ensure that the encoding is set to UTF-8, no matter what the system default is -// this fixes some edge cases with special characters not displaying correctly -// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html -tasks.withType(JavaCompile) { - options.encoding = "UTF-8" -} - -javadoc { - options.tags = [ "reason" ] -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} - -// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task -// if it is present. -// If you remove this task, sources will not be generated. -task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' - from sourceSets.main.allSource -} - -jar { - from "LICENSE" -} - -artifacts { - archives sourcesJar - archives javadocJar -} - -def env = System.getenv() - -import org.kohsuke.github.GHReleaseBuilder -import org.kohsuke.github.GitHub - -task release(dependsOn: [remapJar, sourcesJar, javadocJar]) { - onlyIf { - env.GITHUB_TOKEN - } - - doLast { - def github = GitHub.connectUsingOAuth(env.GITHUB_TOKEN as String) - def repository = github.getRepository("paulevsGitch/BCLib") - - def releaseBuilder = new GHReleaseBuilder(repository, version as String) - releaseBuilder.name("${archivesBaseName}-${version}") - releaseBuilder.body("A changelog can be found at https://github.com/paulevsGitch/BCLib/commits") - releaseBuilder.commitish("main") - - def ghRelease = releaseBuilder.create() - ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar"), "application/java-archive"); - ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-sources.jar"), "application/java-archive"); - ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-javadoc.jar"), "application/java-archive"); - } -} - -// configure the maven publication -publishing { - publications { - gpr(MavenPublication) { - artifactId archivesBaseName - artifact(remapJar) { - builtBy remapJar - } - artifact(sourcesJar) { - builtBy remapSourcesJar - } - } - } - - // select the repositories you want to publish to - repositories { - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/paulevsgitch/bclib") - credentials { - username = env.GITHUB_USER - password = env.GITHUB_TOKEN - } - } - } -} +apply from: "bclib.gradle" \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index f0252152..a7256a20 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,16 @@ # Done to increase the memory available to gradle. org.gradle.jvmargs=-Xmx2G - +#Loom +loom_version=0.12-SNAPSHOT # Fabric Properties -# check these on https://fabricmc.net/use -minecraft_version= 1.17 -yarn_mappings= 6 -loader_version= 0.11.6 - +# check these on https://fabricmc.net/versions.html +minecraft_version=1.18.2 +loader_version=0.14.8 +fabric_version=0.57.0+1.19 # Mod Properties -mod_version = 0.2.0 -maven_group = ru.bclib -archives_base_name = bclib - +mod_version=2.0.11 +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.36.0+1.17 +patchouli_version=50-FABRIC +modmenu_version=4.0.0 \ 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..e750102e 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-7.3-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..027b233d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,9 +1,9 @@ pluginManagement { - repositories { - maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' - } - gradlePluginPortal() - } + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } } 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..fb4e3bed --- /dev/null +++ b/src/main/java/org/betterx/bclib/BCLib.java @@ -0,0 +1,165 @@ +package org.betterx.bclib; + +import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI; +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.*; +import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource; +import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource; +import org.betterx.bclib.api.v2.generator.GeneratorOptions; +import org.betterx.bclib.api.v2.levelgen.LevelGenEvents; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeBuilder; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; +import org.betterx.bclib.api.v2.levelgen.structures.TemplatePiece; +import org.betterx.bclib.api.v2.levelgen.surface.rules.Conditions; +import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates; +import org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers; +import org.betterx.bclib.commands.CommandRegistry; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.recipes.AnvilRecipe; +import org.betterx.bclib.recipes.CraftingRecipes; +import org.betterx.bclib.registry.BaseBlockEntities; +import org.betterx.bclib.registry.BaseRegistry; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.tag.v3.TagManager; +import org.betterx.worlds.together.util.Logger; +import org.betterx.worlds.together.world.WorldConfig; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Blocks; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; + +import java.util.List; + +public class BCLib implements ModInitializer { + public static final String MOD_ID = "bclib"; + public static final Logger LOGGER = new Logger(MOD_ID); + + public static final boolean RUNS_NULLSCAPE = FabricLoader.getInstance() + .getModContainer("nullscape") + .isPresent(); + + @Override + public void onInitialize() { + LevelGenEvents.register(); + BlockPredicates.ensureStaticInitialization(); + BCLBiomeRegistry.ensureStaticallyLoaded(); + BaseRegistry.register(); + GeneratorOptions.init(); + BaseBlockEntities.register(); + BCLibEndBiomeSource.register(); + BCLibNetherBiomeSource.register(); + TagManager.ensureStaticallyLoaded(); + CraftingRecipes.init(); + WorldConfig.registerModCache(MOD_ID); + DataExchangeAPI.registerMod(MOD_ID); + AnvilRecipe.register(); + Conditions.registerAll(); + CommandRegistry.register(); + + DataExchangeAPI.registerDescriptors(List.of( + HelloClient.DESCRIPTOR, + HelloServer.DESCRIPTOR, + RequestFiles.DESCRIPTOR, + SendFiles.DESCRIPTOR, + Chunker.DESCRIPTOR + ) + ); + + BCLibPatch.register(); + TemplatePiece.ensureStaticInitialization(); + PlacementModifiers.ensureStaticInitialization(); + Configs.save(); + + WorldsTogether.FORCE_SERVER_TO_BETTERX_PRESET = Configs.SERVER_CONFIG.forceBetterXPreset(); + + if (false && isDevEnvironment()) { + BCLBiome theYellow = BCLBiomeBuilder + .start(makeID("the_yellow")) + .precipitation(Biome.Precipitation.NONE) + .temperature(1.0f) + .wetness(1.0f) + .fogColor(0xFFFF00) + .waterColor(0x777700) + .waterFogColor(0xFFFF00) + .skyColor(0xAAAA00) + .addNetherClimateParamater(-1, 1) + .surface(Blocks.YELLOW_CONCRETE) + .build(); + BiomeAPI.registerEndLandBiome(theYellow); + + BCLBiome theBlue = BCLBiomeBuilder + .start(makeID("the_blue")) + .precipitation(Biome.Precipitation.NONE) + .temperature(1.0f) + .wetness(1.0f) + .fogColor(0x0000FF) + .waterColor(0x000077) + .waterFogColor(0x0000FF) + .skyColor(0x0000AA) + .addNetherClimateParamater(-1, 1) + .surface(Blocks.LIGHT_BLUE_CONCRETE) + .build(); + BiomeAPI.registerEndLandBiome(theBlue); + + BCLBiome theGray = BCLBiomeBuilder + .start(makeID("the_gray")) + .precipitation(Biome.Precipitation.NONE) + .temperature(1.0f) + .wetness(1.0f) + .fogColor(0xFFFFFF) + .waterColor(0x777777) + .waterFogColor(0xFFFFFF) + .skyColor(0xAAAAAA) + .addNetherClimateParamater(-1, 1) + .surface(Blocks.GRAY_CONCRETE) + .build(); + BiomeAPI.registerEndVoidBiome(theGray); + + BCLBiome theOrange = BCLBiomeBuilder + .start(makeID("the_orange")) + .precipitation(Biome.Precipitation.NONE) + .temperature(1.0f) + .wetness(1.0f) + .fogColor(0xFF7700) + .waterColor(0x773300) + .waterFogColor(0xFF7700) + .skyColor(0xAA7700) + .addNetherClimateParamater(-1, 1.1f) + .surface(Blocks.ORANGE_CONCRETE) + .build(); + BiomeAPI.registerNetherBiome(theOrange); + + BCLBiome thePurple = BCLBiomeBuilder + .start(makeID("the_purple")) + .precipitation(Biome.Precipitation.NONE) + .temperature(1.0f) + .wetness(1.0f) + .fogColor(0xFF00FF) + .waterColor(0x770077) + .waterFogColor(0xFF00FF) + .skyColor(0xAA00AA) + .addNetherClimateParamater(-1.1f, 1) + .surface(Blocks.PURPLE_CONCRETE) + .build(); + BiomeAPI.registerNetherBiome(thePurple); + + } + } + + public static boolean isDevEnvironment() { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + public static boolean isClient() { + return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT; + } + + public static ResourceLocation makeID(String path) { + return new ResourceLocation(MOD_ID, path); + } +} 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..3f211530 --- /dev/null +++ b/src/main/java/org/betterx/bclib/BCLibPatch.java @@ -0,0 +1,183 @@ +package org.betterx.bclib; + +import org.betterx.bclib.api.v2.datafixer.DataFixerAPI; +import org.betterx.bclib.api.v2.datafixer.ForcedLevelPatch; +import org.betterx.bclib.api.v2.datafixer.MigrationProfile; +import org.betterx.bclib.api.v2.generator.GeneratorOptions; +import org.betterx.bclib.api.v2.levelgen.LevelGenUtil; +import org.betterx.bclib.config.Configs; + +import net.minecraft.core.RegistryAccess; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.dimension.LevelStem; + +public final class BCLibPatch { + public static void register() { + // TODO separate values in config on client side (config screen) + if (Configs.MAIN_CONFIG.repairBiomes() && (GeneratorOptions.fixEndBiomeSource() || GeneratorOptions.fixNetherBiomeSource())) { + DataFixerAPI.registerPatch(BiomeSourcePatch::new); + } + } +} + +final class BiomeSourcePatch extends ForcedLevelPatch { + private static final String NETHER_BIOME_SOURCE = "bclib:nether_biome_source"; + private static final String END_BIOME_SOURCE = "bclib:end_biome_source"; + private static final String MC_NETHER = "minecraft:the_nether"; + private static final String MC_END = "minecraft:the_end"; + + protected BiomeSourcePatch() { + super(BCLib.MOD_ID, "1.2.1"); + } + + + @Override + protected Boolean runLevelDatPatch(CompoundTag root, MigrationProfile profile) { + //make sure we have a working generators file before attempting to patch + LevelGenUtil.migrateGeneratorSettings(); + + final CompoundTag worldGenSettings = root.getCompound("Data").getCompound("WorldGenSettings"); + final CompoundTag dimensions = worldGenSettings.getCompound("dimensions"); + final RegistryAccess registryAccess = RegistryAccess.builtinCopy(); + final RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, registryAccess); + + + boolean result = false; + + result |= checkDimension(worldGenSettings, dimensions, registryAccess, registryOps, LevelStem.NETHER); + result |= checkDimension(worldGenSettings, dimensions, registryAccess, registryOps, LevelStem.END); + + System.out.println("Dimensions:" + dimensions); + return result; +// if (root != null) return false; +// +// boolean result = false; +// +// if (GeneratorOptions.fixNetherBiomeSource()) { +// if (!dimensions.contains(MC_NETHER) || !isBCLibEntry(dimensions.getCompound(MC_NETHER))) { +// CompoundTag dimRoot = new CompoundTag(); +// dimRoot.put("generator", makeNetherGenerator(seed)); +// dimRoot.putString("type", MC_NETHER); +// dimensions.put(MC_NETHER, dimRoot); +// result = true; +// } +// } +// +// if (GeneratorOptions.fixEndBiomeSource()) { +// if (!dimensions.contains(MC_END) || !isBCLibEntry(dimensions.getCompound(MC_END))) { +// CompoundTag dimRoot = new CompoundTag(); +// dimRoot.put("generator", makeEndGenerator(seed)); +// dimRoot.putString("type", MC_END); +// dimensions.put(MC_END, dimRoot); +// result = true; +// } +// } +// +// return result; + } + + private boolean checkDimension( + CompoundTag worldGenSettings, + CompoundTag dimensions, + RegistryAccess registryAccess, + RegistryOps registryOps, + ResourceKey dimensionKey + ) { + boolean result = false; +// final long seed = worldGenSettings.contains("seed") +// ? worldGenSettings.getLong("seed") +// : MHelper.RANDOM.nextLong(); +// +// final boolean genStructures = !worldGenSettings.contains("generate_features") || worldGenSettings.getBoolean( +// "generate_features"); +// +// final boolean genBonusChest = worldGenSettings.contains("bonus_chest") && worldGenSettings.getBoolean( +// "bonus_chest"); +// +// +// CompoundTag dimensionTag = dimensions.getCompound(dimensionKey.location().toString()); +// Optional oWorldGen = WorldGenSettings.CODEC +// .parse(new Dynamic<>(registryOps, worldGenSettings)) +// .result(); +// +// Optional oLevelStem = LevelStem.CODEC +// .parse(new Dynamic<>(registryOps, dimensionTag)) +// .resultOrPartial(BCLib.LOGGER::error); +// +// Optional netherGenerator = oLevelStem.map(l -> l.generator()); +// int biomeSourceVersion = LevelGenUtil.getBiomeVersionForGenerator(netherGenerator.orElse(null)); +// int targetVersion = LevelGenUtil.getBiomeVersionForCurrentWorld(dimensionKey); +// if (biomeSourceVersion != targetVersion) { +// Optional> refLevelStem = LevelGenUtil.referenceStemForVersion( +// dimensionKey, +// targetVersion, +// registryAccess, +// oWorldGen.map(g -> g.seed()).orElse(seed), +// oWorldGen.map(g -> g.generateStructures()).orElse(genStructures), +// oWorldGen.map(g -> g.generateBonusChest()).orElse(genBonusChest) +// ); +// +// BCLib.LOGGER.warning("The world uses the BiomeSource Version " + biomeSourceVersion + " but should have " + targetVersion + "."); +// BCLib.LOGGER.warning("Dimension: " + dimensionKey); +// BCLib.LOGGER.warning("Found: " + netherGenerator); +// BCLib.LOGGER.warning("Should: " + refLevelStem.map(l -> l.value().generator())); +// +// if (refLevelStem.isPresent()) { +// var levelStem = refLevelStem.get(); +// BCLib.LOGGER.warning("Repairing level.dat in order to ensure world continuity."); +// var codec = LevelStem.CODEC.orElse(levelStem.value()); +// var encodeResult = codec.encodeStart(registryOps, levelStem.value()); +// if (encodeResult.result().isPresent()) { +// dimensions.put(dimensionKey.location().toString(), encodeResult.result().get()); +// result = true; +// } else { +// BCLib.LOGGER.error("Unable to encode '" + dimensionKey + "' generator for level.dat."); +// } +// } else { +// BCLib.LOGGER.error("Unable to update '" + dimensionKey + "' generator in level.dat."); +// } +// } + + return result; + } + + private boolean isBCLibEntry(CompoundTag dimRoot) { + String type = dimRoot.getCompound("generator").getCompound("biome_source").getString("type"); + if (type.isEmpty() || type.length() < 5) { + return false; + } + return type.startsWith("bclib"); + } + + public static CompoundTag makeNetherGenerator(long seed) { + CompoundTag generator = new CompoundTag(); + generator.putString("type", "minecraft:noise"); + generator.putString("settings", "minecraft:nether"); + generator.putLong("seed", seed); + + CompoundTag biomeSource = new CompoundTag(); + biomeSource.putString("type", NETHER_BIOME_SOURCE); + biomeSource.putLong("seed", seed); + generator.put("biome_source", biomeSource); + + return generator; + } + + public static CompoundTag makeEndGenerator(long seed) { + CompoundTag generator = new CompoundTag(); + generator.putString("type", "minecraft:noise"); + generator.putString("settings", "minecraft:end"); + generator.putLong("seed", seed); + + CompoundTag biomeSource = new CompoundTag(); + biomeSource.putString("type", END_BIOME_SOURCE); + biomeSource.putLong("seed", seed); + generator.put("biome_source", biomeSource); + + return generator; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/BonemealAPI.java b/src/main/java/org/betterx/bclib/api/v2/BonemealAPI.java new file mode 100644 index 00000000..dc51e68f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/BonemealAPI.java @@ -0,0 +1,201 @@ +package org.betterx.bclib.api.v2; + +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.WeightedList; + +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.function.BiConsumer; + +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 Map SPREADABLE_BLOCKS = Maps.newHashMap(); + private static final Set TERRAIN_TO_SPREAD = Sets.newHashSet(); + private static final Set TERRAIN = Sets.newHashSet(); + + public static void addSpreadableBlock(Block spreadableBlock, Block surfaceForSpread) { + SPREADABLE_BLOCKS.put(spreadableBlock, surfaceForSpread); + TERRAIN_TO_SPREAD.add(surfaceForSpread); + TERRAIN.add(surfaceForSpread); + } + + public static boolean isTerrain(Block block) { + return TERRAIN.contains(block); + } + + public static boolean isSpreadableTerrain(Block block) { + return TERRAIN_TO_SPREAD.contains(block); + } + + public static Block getSpreadable(Block block) { + return SPREADABLE_BLOCKS.get(block); + } + + public static void addLandGrass(Block plant, Block... terrain) { + addLandGrass(makeConsumer(plant), terrain); + } + + public static void addLandGrass(BiConsumer plant, Block... terrain) { + for (Block block : terrain) { + addLandGrass(plant, block, 1F); + } + } + + public static void addLandGrass(ResourceLocation biome, Block plant, Block... terrain) { + addLandGrass(biome, makeConsumer(plant), terrain); + } + + public static void addLandGrass(ResourceLocation biome, BiConsumer plant, Block... terrain) { + for (Block block : terrain) { + addLandGrass(biome, plant, block, 1F); + } + } + + public static void addLandGrass(Block plant, Block terrain, float chance) { + addLandGrass(makeConsumer(plant), terrain, chance); + } + + public static void addLandGrass(BiConsumer plant, Block terrain, float chance) { + WeightedList> list = LAND_GRASS_TYPES.get(terrain); + if (list == null) { + list = new WeightedList<>(); + LAND_GRASS_TYPES.put(terrain, list); + } + TERRAIN.add(terrain); + list.add(plant, chance); + } + + public static void addLandGrass(ResourceLocation biome, Block plant, Block terrain, float chance) { + addLandGrass(biome, makeConsumer(plant), terrain, chance); + } + + public static void addLandGrass( + ResourceLocation biome, + BiConsumer plant, + Block terrain, + 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); + } + TERRAIN.add(terrain); + list.add(plant, chance); + } + + public static void addWaterGrass(Block plant, Block... terrain) { + addWaterGrass(makeConsumer(plant), terrain); + } + + public static void addWaterGrass(BiConsumer plant, Block... terrain) { + for (Block block : terrain) { + addWaterGrass(plant, block, 1F); + } + } + + public static void addWaterGrass(ResourceLocation biome, Block plant, Block... terrain) { + addWaterGrass(biome, makeConsumer(plant), terrain); + } + + public static void addWaterGrass(ResourceLocation biome, BiConsumer plant, Block... terrain) { + for (Block block : terrain) { + addWaterGrass(biome, plant, block, 1F); + } + } + + public static void addWaterGrass(Block plant, Block terrain, float chance) { + addWaterGrass(makeConsumer(plant), terrain, chance); + } + + public static void addWaterGrass(BiConsumer plant, Block terrain, float chance) { + WeightedList> list = WATER_GRASS_TYPES.get(terrain); + if (list == null) { + list = new WeightedList<>(); + WATER_GRASS_TYPES.put(terrain, list); + } + TERRAIN.add(terrain); + list.add(plant, chance); + } + + public static void addWaterGrass(ResourceLocation biome, Block plant, Block terrain, float chance) { + addWaterGrass(biome, makeConsumer(plant), terrain, chance); + } + + public static void addWaterGrass( + ResourceLocation biome, + BiConsumer plant, + Block terrain, + 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); + } + TERRAIN.add(terrain); + list.add(plant, chance); + } + + public static BiConsumer getLandGrass( + ResourceLocation biomeID, + Block terrain, + Random random + ) { + Map>> map = LAND_GRASS_BIOMES.get(biomeID); + WeightedList> list; + if (map != null) { + list = map.get(terrain); + if (list == null) { + list = LAND_GRASS_TYPES.get(terrain); + } + } else { + list = LAND_GRASS_TYPES.get(terrain); + } + return list == null ? null : list.get(random); + } + + public static BiConsumer getWaterGrass( + ResourceLocation biomeID, + Block terrain, + Random random + ) { + Map>> map = WATER_GRASS_BIOMES.get(biomeID); + WeightedList> list; + if (map != null) { + list = map.get(terrain); + if (list == null) { + list = WATER_GRASS_TYPES.get(terrain); + } + } else { + list = WATER_GRASS_TYPES.get(terrain); + } + return list == null ? null : list.get(random); + } + + private static BiConsumer makeConsumer(Block block) { + return (level, pos) -> BlocksHelper.setWithoutUpdate(level, pos, block); + } +} 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..0cd78a48 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/LifeCycleAPI.java @@ -0,0 +1,145 @@ +package org.betterx.bclib.api.v2; + +import org.betterx.bclib.api.v2.datafixer.DataFixerAPI; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.level.CustomSpawner; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.ServerLevelData; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * provides some lifetime hooks for a Minecraft instance + */ +public class LifeCycleAPI { + private final static List 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(Registry.BIOME_REGISTRY); + onLoadLevelBiomes.forEach(c -> c.onLoad(world, seed, biomeRegistry)); + } + + /** + * A callback function that is used for each new ServerLevel instance + */ + public interface BeforeLevelLoadCall { + void beforeLoad(); + } + + /** + * A callback function that is used for each new ServerLevel instance + */ + public interface LevelLoadBiomesCall { + void onLoad(ServerLevel world, long seed, Registry 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..36b49f43 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/PostInitAPI.java @@ -0,0 +1,149 @@ +package org.betterx.bclib.api.v2; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.biomes.InternalBiomeAPI; +import org.betterx.bclib.blocks.BaseBarrelBlock; +import org.betterx.bclib.blocks.BaseChestBlock; +import org.betterx.bclib.blocks.BaseFurnaceBlock; +import org.betterx.bclib.blocks.BaseSignBlock; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.client.render.BaseChestBlockEntityRenderer; +import org.betterx.bclib.client.render.BaseSignBlockEntityRenderer; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.interfaces.PostInitable; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.bclib.interfaces.tools.*; +import org.betterx.bclib.registry.BaseBlockEntities; +import org.betterx.worlds.together.tag.v3.MineableTags; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.Registry; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.function.Consumer; + +public class PostInitAPI { + private static List> 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) { + Registry.BLOCK.forEach(block -> { + processBlockCommon(block); + if (isClient) { + processBlockClient(block); + } + }); + + + Registry.ITEM.forEach(item -> { + processItemCommon(item); + }); + + if (postInitFunctions != null) { + postInitFunctions.forEach(function -> function.accept(isClient)); + postInitFunctions = null; + } + blockTags = null; + itemTags = null; + InternalBiomeAPI.loadFabricAPIBiomes(); + Configs.BIOMES_CONFIG.saveChanges(); + } + + @Environment(EnvType.CLIENT) + private static void processBlockClient(Block block) { + if (block instanceof RenderLayerProvider) { + BCLRenderLayer layer = ((RenderLayerProvider) block).getRenderLayer(); + if (layer == BCLRenderLayer.CUTOUT) BlockRenderLayerMap.INSTANCE.putBlock(block, RenderType.cutout()); + else if (layer == BCLRenderLayer.TRANSLUCENT) + BlockRenderLayerMap.INSTANCE.putBlock(block, RenderType.translucent()); + } + if (block instanceof BaseChestBlock) { + BaseChestBlockEntityRenderer.registerRenderLayer(block); + } else if (block instanceof BaseSignBlock) { + BaseSignBlockEntityRenderer.registerRenderLayer(block); + } + } + + private static void processItemCommon(Item item) { + if (item instanceof TagProvider provider) { + try { + provider.addTags(null, itemTags); + } catch (NullPointerException ex) { + BCLib.LOGGER.error(item + " probably tried to access blockTags.", ex); + } + itemTags.forEach(tag -> TagManager.ITEMS.add(tag, item)); + itemTags.clear(); + } + } + + private static void processBlockCommon(Block block) { + if (block instanceof PostInitable) { + ((PostInitable) block).postInit(); + } + if (block instanceof BaseChestBlock) { + BaseBlockEntities.CHEST.registerBlock(block); + } else if (block instanceof BaseSignBlock) { + BaseBlockEntities.SIGN.registerBlock(block); + } else if (block instanceof BaseBarrelBlock) { + BaseBlockEntities.BARREL.registerBlock(block); + } else if (block instanceof BaseFurnaceBlock) { + BaseBlockEntities.FURNACE.registerBlock(block); + } + if (!(block instanceof PreventMineableAdd)) { + if (block instanceof AddMineableShears) { + TagManager.BLOCKS.add(block, MineableTags.SHEARS); + } + if (block instanceof AddMineableAxe) { + TagManager.BLOCKS.add(block, MineableTags.AXE); + } + if (block instanceof AddMineablePickaxe) { + TagManager.BLOCKS.add(block, MineableTags.PICKAXE); + } + if (block instanceof AddMineableShovel) { + TagManager.BLOCKS.add(block, MineableTags.SHOVEL); + } + if (block instanceof AddMineableHoe) { + TagManager.BLOCKS.add(block, MineableTags.HOE); + } + if (block instanceof AddMineableSword) { + TagManager.BLOCKS.add(block, MineableTags.SWORD); + } + if (block instanceof AddMineableHammer) { + TagManager.BLOCKS.add(block, MineableTags.HAMMER); + } + } + if (block instanceof TagProvider) { + ((TagProvider) block).addTags(blockTags, itemTags); + blockTags.forEach(tag -> TagManager.BLOCKS.add(tag, block)); + itemTags.forEach(tag -> TagManager.ITEMS.add(tag, block.asItem())); + blockTags.clear(); + itemTags.clear(); + } + } +} 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/WorldDataAPI.java b/src/main/java/org/betterx/bclib/api/v2/WorldDataAPI.java new file mode 100644 index 00000000..f3814f75 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/WorldDataAPI.java @@ -0,0 +1,77 @@ +package org.betterx.bclib.api.v2; + +import org.betterx.worlds.together.world.WorldConfig; + +import net.minecraft.nbt.CompoundTag; + +import java.io.File; + +/** + * @deprecated Implementation moved to {@link WorldConfig} + */ +@Deprecated(forRemoval = true) +public class WorldDataAPI { + /** + * @deprecated use {@link WorldConfig#load(File)} instead + */ + @Deprecated(forRemoval = true) + public static void load(File dataDir) { + WorldConfig.load(dataDir); + } + + /** + * @deprecated use {@link WorldConfig#registerModCache(String)} instead + */ + @Deprecated(forRemoval = true) + public static void registerModCache(String modID) { + WorldConfig.registerModCache(modID); + } + + /** + * @deprecated use {@link WorldConfig#getRootTag(String)} instead + */ + @Deprecated(forRemoval = true) + public static CompoundTag getRootTag(String modID) { + return WorldConfig.getRootTag(modID); + } + + /** + * @deprecated use {@link WorldConfig#hasMod(String)} instead + */ + @Deprecated(forRemoval = true) + public static boolean hasMod(String modID) { + return WorldConfig.hasMod(modID); + } + + /** + * @deprecated use {@link WorldConfig#getCompoundTag(String, String)} instead + */ + @Deprecated(forRemoval = true) + public static CompoundTag getCompoundTag(String modID, String path) { + return WorldConfig.getCompoundTag(modID, path); + } + + /** + * @deprecated use {@link WorldConfig#saveFile(String)} instead + */ + @Deprecated(forRemoval = true) + public static void saveFile(String modID) { + WorldConfig.saveFile(modID); + } + + /** + * @deprecated use {@link WorldConfig#getModVersion(String)} instead + */ + @Deprecated(forRemoval = true) + public static String getModVersion(String modID) { + return WorldConfig.getModVersion(modID); + } + + /** + * @deprecated use {@link WorldConfig#getIntModVersion(String)} instead + */ + @Deprecated(forRemoval = true) + public static int getIntModVersion(String modID) { + return WorldConfig.getIntModVersion(modID); + } +} 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..120267dd --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/DataExchangeAPI.java @@ -0,0 +1,217 @@ +package org.betterx.bclib.api.v2.dataexchange; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange; +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSync; +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSyncID; +import org.betterx.bclib.config.Config; +import org.betterx.worlds.together.util.ModUtil; + +import net.minecraft.network.FriendlyByteBuf; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Lists; + +import java.io.File; +import java.util.List; +import java.util.function.BiConsumer; + +public class DataExchangeAPI extends DataExchange { + private final static List MODS = Lists.newArrayList(); + + /** + * You should never need to create a custom instance of this Object. + */ + public DataExchangeAPI() { + super(); + } + + @Environment(EnvType.CLIENT) + protected ConnectorClientside clientSupplier(DataExchange api) { + return new ConnectorClientside(api); + } + + protected ConnectorServerside serverSupplier(DataExchange api) { + return new ConnectorServerside(api); + } + + /** + * Register a mod to participate in the DataExchange. + * + * @param modID - {@link String} modID. + */ + public static void registerMod(String modID) { + if (!MODS.contains(modID)) MODS.add(modID); + } + + /** + * Register a mod dependency to participate in the DataExchange. + * + * @param modID - {@link String} modID. + */ + public static void registerModDependency(String modID) { + if (ModUtil.getModInfo(modID, false) != null && !"0.0.0".equals(ModUtil.getModVersion(modID))) { + registerMod(modID); + } else { + BCLib.LOGGER.info("Mod Dependency '" + modID + "' not found. This is probably OK."); + } + } + + /** + * Returns the IDs of all registered Mods. + * + * @return List of modIDs + */ + public static List 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..31e883f6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/HelloClient.java @@ -0,0 +1,530 @@ +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); + + 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); + BCLib.LOGGER.info(" - Offering " + (entry.isConfigFile() ? "Config " : "File ") + entry); + } + } else { + BCLib.LOGGER.info("Server will neither offer Files nor Configs."); + buf.writeInt(0); + } + + if (Configs.SERVER_CONFIG.isOfferingFiles()) { + buf.writeInt(AutoSync.syncFolderDescriptions.size()); + AutoSync.syncFolderDescriptions.forEach(desc -> { + BCLib.LOGGER.info(" - Offering Folder " + desc.localFolder + " (allowDelete=" + desc.removeAdditionalFiles + ")"); + desc.serialize(buf); + }); + } else { + BCLib.LOGGER.info("Server will not offer Sync Folders."); + buf.writeInt(0); + } + + buf.writeBoolean(Configs.SERVER_CONFIG.isOfferingInfosForMods()); + } + + String bclibVersion = "0.0.0"; + + + IServerModMap modVersion = new ServerModMap(); + List 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) { + BCLib.LOGGER.info("Folders offered by Server:"); + } + + autoSynFolders.forEach(desc -> { + //desc contains the fileCache sent from the server, load the local version to get hold of the actual file cache on the client + SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(desc.folderID); + if (localDescriptor != null) { + BCLib.LOGGER.info(" - " + desc.folderID + " (" + desc.localFolder + ", allowRemove=" + desc.removeAdditionalFiles + ")"); + localDescriptor.invalidateCache(); + + desc.relativeFilesStream() + .filter(desc::discardChildElements) + .forEach(subFile -> { + BCLib.LOGGER.warning(" * " + subFile.relPath + " (REJECTED)"); + }); + + + if (desc.removeAdditionalFiles) { + List additionalFiles = localDescriptor.relativeFilesStream() + .filter(subFile -> !desc.hasRelativeFile( + subFile)) + .map(desc::mapAbsolute) + .filter(desc::acceptChildElements) + .map(absPath -> new AutoSyncID.ForDirectFileRequest( + desc.folderID, + absPath.toFile() + )) + .collect(Collectors.toList()); + + additionalFiles.forEach(aid -> BCLib.LOGGER.info(" * " + desc.localFolder.relativize(aid.relFile.toPath()) + " (missing on server)")); + filesToRemove.addAll(additionalFiles); + } + + desc.relativeFilesStream() + .filter(desc::acceptChildElements) + .forEach(subFile -> { + SyncFolderDescriptor.SubFile localSubFile = localDescriptor.getLocalSubFile(subFile.relPath); + if (localSubFile != null) { + //the file exists locally, check if the hashes match + if (!localSubFile.hash.equals(subFile.hash)) { + BCLib.LOGGER.info(" * " + subFile.relPath + " (changed)"); + filesToRequest.add(new AutoSyncID.ForDirectFileRequest( + desc.folderID, + new File(subFile.relPath) + )); + } else { + BCLib.LOGGER.info(" * " + subFile.relPath); + } + } else { + //the file is missing locally + BCLib.LOGGER.info(" * " + subFile.relPath + " (missing on client)"); + filesToRequest.add(new AutoSyncID.ForDirectFileRequest( + desc.folderID, + new File(subFile.relPath) + )); + } + }); + + //free some memory + localDescriptor.invalidateCache(); + } else { + BCLib.LOGGER.info(" - " + desc.folderID + " (Failed to find)"); + } + }); + } + + @Environment(EnvType.CLIENT) + private void processSingleFileSync(final List filesToRequest) { + final boolean debugHashes = Configs.CLIENT_CONFIG.shouldPrintDebugHashes(); + + if (autoSyncedFiles.size() > 0) { + BCLib.LOGGER.info("Files offered by Server:"); + } + + //Handle single sync files + //Single files need to be registered for sync on both client and server + //There are no restrictions to the target folder, but the client decides the final + //location. + for (AutoSync.AutoSyncTriple e : autoSyncedFiles) { + String actionString = ""; + FileContentWrapper contentWrapper = new FileContentWrapper(e.serverContent); + if (e.localMatch == null) { + actionString = "(unknown source -> omitting)"; + //filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID)); + } else if (e.localMatch.needTransfer.test(e.localMatch.getFileHash(), e.serverHash, contentWrapper)) { + actionString = "(prepare update)"; + //we did not yet receive the new content + if (contentWrapper.getRawContent() == null) { + filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID)); + } else { + filesToRequest.add(new AutoSyncID.WithContentOverride( + e.serverHash.modID, + e.serverHash.uniqueID, + contentWrapper, + e.localMatch.fileName + )); + } + } + + BCLib.LOGGER.info(" - " + e + ": " + actionString); + if (debugHashes) { + BCLib.LOGGER.info(" * " + e.serverHash + " (Server)"); + BCLib.LOGGER.info(" * " + e.localMatch.getFileHash() + " (Client)"); + BCLib.LOGGER.info(" * local Content " + (contentWrapper.getRawContent() == null)); + } + } + } + + + @Environment(EnvType.CLIENT) + private void processModFileSync(final List 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; + + 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..35c4ecbe --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/RequestFiles.java @@ -0,0 +1,109 @@ +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); + + BCLib.LOGGER.info("Client requested " + size + " Files:"); + for (int i = 0; i < size; i++) { + AutoSyncID asid = AutoSyncID.deserializeData(buf); + files.add(asid); + BCLib.LOGGER.info(" - " + asid); + } + + + } + + @Override + protected void runOnServerGameThread(MinecraftServer server, Player player) { + if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) { + BCLib.LOGGER.info("Auto-Sync was disabled on the server."); + return; + } + + List 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..5d16a6f5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/SendFiles.java @@ -0,0 +1,225 @@ +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()); + + BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:"); + for (AutoFileSyncEntry entry : existingFiles) { + int length = entry.serializeContent(buf); + BCLib.LOGGER.info(" - " + entry + " (" + PathUtil.humanReadableFileSize(length) + ")"); + } + } + + private List> receivedFiles; + + @Environment(EnvType.CLIENT) + @Override + protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) { + if (Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) { + token = readString(buf); + if (!token.equals(RequestFiles.currentToken)) { + RequestFiles.newToken(); + BCLib.LOGGER.error("Unrequested File Transfer!"); + receivedFiles = new ArrayList<>(0); + return; + } + RequestFiles.newToken(); + + int size = buf.readInt(); + receivedFiles = new ArrayList<>(size); + BCLib.LOGGER.info("Server sent " + size + " Files:"); + for (int i = 0; i < size; i++) { + Triple p = AutoFileSyncEntry.deserializeContent(buf); + if (p.first != null) { + final String type; + if (p.first.isConfigFile() && Configs.CLIENT_CONFIG.isAcceptingConfigs()) { + receivedFiles.add(p); + type = "Accepted Config "; + } else if (p.first instanceof AutoFileSyncEntry.ForModFileRequest && Configs.CLIENT_CONFIG.isAcceptingMods()) { + receivedFiles.add(p); + type = "Accepted Mod "; + } else if (Configs.CLIENT_CONFIG.isAcceptingFiles()) { + receivedFiles.add(p); + type = "Accepted File "; + } else { + type = "Ignoring "; + } + BCLib.LOGGER.info(" - " + type + p.first + " (" + PathUtil.humanReadableFileSize(p.second.length) + ")"); + } else { + BCLib.LOGGER.error(" - Failed to receive File " + p.third + ", possibly sent from a Mod that is not installed on the client."); + } + } + } + } + + @Environment(EnvType.CLIENT) + @Override + protected void runOnClientGameThread(Minecraft client) { + if (Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) { + BCLib.LOGGER.info("Writing Files:"); + + for (Pair 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..b214047b --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/SyncFolderDescriptor.java @@ -0,0 +1,218 @@ +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 -> { + 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/generator/BCLBiomeSource.java b/src/main/java/org/betterx/bclib/api/v2/generator/BCLBiomeSource.java new file mode 100644 index 00000000..8c406bb8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/BCLBiomeSource.java @@ -0,0 +1,124 @@ +package org.betterx.bclib.api.v2.generator; + +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; +import org.betterx.worlds.together.biomesource.BiomeSourceFromRegistry; +import org.betterx.worlds.together.biomesource.MergeableBiomeSource; +import org.betterx.worlds.together.world.BiomeSourceWithNoiseRelatedSettings; +import org.betterx.worlds.together.world.BiomeSourceWithSeed; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; + +import com.google.common.collect.Sets; + +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +public abstract class BCLBiomeSource extends BiomeSource implements BiomeSourceWithSeed, MergeableBiomeSource, BiomeSourceWithNoiseRelatedSettings, BiomeSourceFromRegistry { + protected final Registry biomeRegistry; + protected long currentSeed; + protected int maxHeight; + + private static List> preInit(Registry biomeRegistry, List> biomes) { + biomes = biomes.stream().sorted(Comparator.comparing(holder -> holder.unwrapKey() + .get() + .location() + .toString())) + .toList(); + biomes.forEach(biome -> BiomeAPI.sortBiomeFeatures(biome)); + return biomes; + } + + protected BCLBiomeSource( + Registry biomeRegistry, + List> list, + long seed + ) { + super(preInit(biomeRegistry, list)); + + this.biomeRegistry = biomeRegistry; + this.currentSeed = seed; + } + + final public void setSeed(long seed) { + if (seed != currentSeed) { + System.out.println(this + " set Seed: " + seed); + this.currentSeed = seed; + initMap(seed); + } + } + + /** + * Set world height + * + * @param maxHeight height of the World. + */ + final public void setMaxHeight(int maxHeight) { + if (this.maxHeight != maxHeight) { + System.out.println(this + " set Max Height: " + maxHeight); + this.maxHeight = maxHeight; + onHeightChange(maxHeight); + } + } + + protected final void initMap(long seed) { + System.out.println(this + " updates Map"); + onInitMap(seed); + } + + protected abstract void onInitMap(long newSeed); + protected abstract void onHeightChange(int newHeight); + + public BCLBiomeSource createCopyForDatapack(Set> datapackBiomes) { + Set> mutableSet = Sets.newHashSet(); + mutableSet.addAll(datapackBiomes); + return cloneForDatapack(mutableSet); + } + + protected abstract BCLBiomeSource cloneForDatapack(Set> datapackBiomes); + + public interface ValidBiomePredicate { + boolean isValid(Holder biome, ResourceLocation location); + } + + protected static List> getBiomes( + Registry biomeRegistry, + List exclude, + List include, + BCLibNetherBiomeSource.ValidBiomePredicate test + ) { + return biomeRegistry.stream() + .filter(biome -> biomeRegistry.getResourceKey(biome).isPresent()) + + .map(biome -> biomeRegistry.getOrCreateHolderOrThrow(biomeRegistry.getResourceKey(biome) + .get())) + .filter(biome -> { + ResourceLocation location = biome.unwrapKey().orElseThrow().location(); + final String strLocation = location.toString(); + if (exclude.contains(strLocation)) return false; + if (include.contains(strLocation)) return true; + + return test.isValid(biome, location); + }) + .toList(); + } + + @Override + public BCLBiomeSource mergeWithBiomeSource(BiomeSource inputBiomeSource) { + final Set> datapackBiomes = inputBiomeSource.possibleBiomes(); + return this.createCopyForDatapack(datapackBiomes); + } + + public void onLoadGeneratorSettings(NoiseGeneratorSettings generator) { + this.setMaxHeight(generator.noiseSettings().height()); + } + + public Registry getBiomeRegistry() { + return biomeRegistry; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/BCLChunkGenerator.java b/src/main/java/org/betterx/bclib/api/v2/generator/BCLChunkGenerator.java new file mode 100644 index 00000000..ac69013c --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/BCLChunkGenerator.java @@ -0,0 +1,230 @@ +package org.betterx.bclib.api.v2.generator; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.LevelGenUtil; +import org.betterx.bclib.interfaces.NoiseGeneratorSettingsProvider; +import org.betterx.bclib.mixin.common.ChunkGeneratorAccessor; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.biomesource.MergeableBiomeSource; +import org.betterx.worlds.together.biomesource.ReloadableBiomeSource; +import org.betterx.worlds.together.chunkgenerator.EnforceableChunkGenerator; +import org.betterx.worlds.together.chunkgenerator.InjectableSurfaceRules; +import org.betterx.worlds.together.chunkgenerator.RestorableBiomeSource; +import org.betterx.worlds.together.world.BiomeSourceWithNoiseRelatedSettings; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeGenerationSettings; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.FeatureSorter; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.synth.NormalNoise; + +import com.google.common.base.Suppliers; + +import java.util.List; +import java.util.function.Function; + +public class BCLChunkGenerator extends NoiseBasedChunkGenerator implements RestorableBiomeSource, InjectableSurfaceRules, EnforceableChunkGenerator { + + public static final Codec CODEC = RecordCodecBuilder + .create((RecordCodecBuilder.Instance builderInstance) -> { + final RecordCodecBuilder> noiseGetter = RegistryOps + .retrieveRegistry( + Registry.NOISE_REGISTRY) + .forGetter( + BCLChunkGenerator::getNoises); + + RecordCodecBuilder biomeSourceCodec = BiomeSource.CODEC + .fieldOf("biome_source") + .forGetter((BCLChunkGenerator generator) -> generator.biomeSource); + + RecordCodecBuilder> settingsCodec = NoiseGeneratorSettings.CODEC + .fieldOf("settings") + .forGetter((BCLChunkGenerator generator) -> generator.settings); + + + return NoiseBasedChunkGenerator + .commonCodec(builderInstance) + .and(builderInstance.group(noiseGetter, biomeSourceCodec, settingsCodec)) + .apply(builderInstance, builderInstance.stable(BCLChunkGenerator::new)); + }); + public final BiomeSource initialBiomeSource; + + public BCLChunkGenerator( + Registry registry, + Registry registry2, + BiomeSource biomeSource, + Holder holder + ) { + super(registry, registry2, biomeSource, holder); + initialBiomeSource = biomeSource; + if (biomeSource instanceof BiomeSourceWithNoiseRelatedSettings bcl) { + bcl.onLoadGeneratorSettings(holder.value()); + } + + if (WorldsTogether.RUNS_TERRABLENDER) { + BCLib.LOGGER.info("Make sure features are loaded from terrablender for " + biomeSource); + + //terrablender is invalidating the feature initialization + //we redo it at this point, otherwise we will get blank biomes + rebuildFeaturesPerStep(biomeSource); + } + System.out.println("Chunk Generator: " + this + " (biomeSource: " + biomeSource + ")"); + } + + private void rebuildFeaturesPerStep(BiomeSource biomeSource) { + if (this instanceof ChunkGeneratorAccessor acc) { + Function, BiomeGenerationSettings> function = (Holder hh) -> hh.value() + .getGenerationSettings(); + + acc.bcl_setFeaturesPerStep(Suppliers.memoize(() -> FeatureSorter.buildFeaturesPerStep( + List.copyOf(biomeSource.possibleBiomes()), + (hh) -> function.apply(hh).features(), + true + ))); + } + } + + /** + * Other Mods like TerraBlender might inject new BiomeSources. We und that change after the world setup did run. + * + * @param dimensionKey The Dimension where this ChunkGenerator is used from + */ + @Override + public void restoreInitialBiomeSource(ResourceKey dimensionKey) { + if (initialBiomeSource != getBiomeSource()) { + if (this instanceof ChunkGeneratorAccessor acc) { + if (initialBiomeSource instanceof MergeableBiomeSource bs) { + acc.bcl_setBiomeSource(bs.mergeWithBiomeSource(getBiomeSource())); + } else if (initialBiomeSource instanceof ReloadableBiomeSource bs) { + bs.reloadBiomes(); + } + + rebuildFeaturesPerStep(getBiomeSource()); + } + } + } + + + @Override + protected Codec codec() { + return CODEC; + } + + + private Registry getNoises() { + if (this instanceof NoiseGeneratorSettingsProvider p) { + return p.bclib_getNoises(); + } + return null; + } + + @Override + public String toString() { + return "BCLib - Chunk Generator (" + Integer.toHexString(hashCode()) + ")"; + } + + // This method is injected by Terrablender. + // We make sure terrablender does not rewrite the feature-set for our ChunkGenerator by overwriting the + // Mixin-Method with an empty implementation + public void appendFeaturesPerStep() { + } + + public static RandomState createRandomState(ServerLevel level, ChunkGenerator generator) { + if (generator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) { + return RandomState.create( + noiseBasedChunkGenerator.generatorSettings().value(), + level.registryAccess().registryOrThrow(Registry.NOISE_REGISTRY), + level.getSeed() + ); + } else { + return RandomState.create(level.registryAccess(), NoiseGeneratorSettings.OVERWORLD, level.getSeed()); + } + } + + @Override + public WorldGenSettings enforceGeneratorInWorldGenSettings( + RegistryAccess access, + ResourceKey dimensionKey, + ResourceKey dimensionTypeKey, + ChunkGenerator loadedChunkGenerator, + WorldGenSettings settings + ) { + BCLib.LOGGER.info("Enforcing Correct Generator for " + dimensionKey.location().toString() + "."); + + ChunkGenerator referenceGenerator = this; + if (loadedChunkGenerator instanceof org.betterx.bclib.interfaces.ChunkGeneratorAccessor generator) { + if (loadedChunkGenerator instanceof NoiseGeneratorSettingsProvider noiseProvider) { + if (referenceGenerator instanceof NoiseGeneratorSettingsProvider referenceProvider) { + final BiomeSource bs; + if (referenceGenerator.getBiomeSource() instanceof MergeableBiomeSource mbs) { + bs = mbs.mergeWithBiomeSource(loadedChunkGenerator.getBiomeSource()); + } else { + bs = referenceGenerator.getBiomeSource(); + } + + referenceGenerator = new BCLChunkGenerator( + generator.bclib_getStructureSetsRegistry(), + noiseProvider.bclib_getNoises(), + bs, + buildGeneratorSettings( + referenceProvider.bclib_getNoiseGeneratorSettingHolders(), + noiseProvider.bclib_getNoiseGeneratorSettingHolders(), + bs + ) + ); + } + } + } + + return LevelGenUtil.replaceGenerator( + dimensionKey, + dimensionTypeKey, + access, + settings, + referenceGenerator + ); + + } + + private static Holder buildGeneratorSettings( + Holder reference, + Holder settings, + BiomeSource biomeSource + ) { + return settings; +// NoiseGeneratorSettings old = settings.value(); +// NoiseGeneratorSettings noise = new NoiseGeneratorSettings( +// old.noiseSettings(), +// old.defaultBlock(), +// old.defaultFluid(), +// old.noiseRouter(), +// SurfaceRuleRegistry.mergeSurfaceRulesFromBiomes(old.surfaceRule(), biomeSource), +// //SurfaceRuleUtil.addRulesForBiomeSource(old.surfaceRule(), biomeSource), +// old.spawnTarget(), +// old.seaLevel(), +// old.disableMobGeneration(), +// old.aquifersEnabled(), +// old.oreVeinsEnabled(), +// old.useLegacyRandom() +// ); +// +// +// return Holder.direct(noise); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/BCLibEndBiomeSource.java b/src/main/java/org/betterx/bclib/api/v2/generator/BCLibEndBiomeSource.java new file mode 100644 index 00000000..927c3709 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/BCLibEndBiomeSource.java @@ -0,0 +1,444 @@ +package org.betterx.bclib.api.v2.generator; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.interfaces.BiomeMap; +import org.betterx.worlds.together.biomesource.BiomeSourceWithConfig; +import org.betterx.worlds.together.biomesource.ReloadableBiomeSource; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Holder; +import net.minecraft.core.QuartPos; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BiomeTags; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.biome.Climate; +import net.minecraft.world.level.levelgen.DensityFunction; + +import java.awt.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import org.jetbrains.annotations.NotNull; + +public class BCLibEndBiomeSource extends BCLBiomeSource implements BiomeSourceWithConfig, ReloadableBiomeSource { + public static Codec CODEC + = RecordCodecBuilder.create((instance) -> instance.group( + RegistryOps + .retrieveRegistry(Registry.BIOME_REGISTRY) + .forGetter((theEndBiomeSource) -> theEndBiomeSource.biomeRegistry), + Codec + .LONG + .fieldOf("seed") + .stable() + .forGetter(source -> source.currentSeed), + BCLEndBiomeSourceConfig + .CODEC + .fieldOf("config") + .orElse(BCLEndBiomeSourceConfig.DEFAULT) + .forGetter(o -> o.config) + ) + .apply( + instance, + instance.stable(BCLibEndBiomeSource::new) + ) + ); + private final Point pos; + private final BiFunction endLandFunction; + private BiomeMap mapLand; + private BiomeMap mapVoid; + private BiomeMap mapCenter; + private BiomeMap mapBarrens; + + private BiomePicker endLandBiomePicker; + private BiomePicker endVoidBiomePicker; + private BiomePicker endCenterBiomePicker; + private BiomePicker endBarrensBiomePicker; + private List deciders; + + private BCLEndBiomeSourceConfig config; + + public BCLibEndBiomeSource(Registry biomeRegistry, long seed, BCLEndBiomeSourceConfig config) { + this(biomeRegistry, seed, config, true); + } + + public BCLibEndBiomeSource(Registry biomeRegistry, BCLEndBiomeSourceConfig config) { + this(biomeRegistry, 0, config, false); + } + + private BCLibEndBiomeSource( + Registry biomeRegistry, + long seed, + BCLEndBiomeSourceConfig config, + boolean initMaps + ) { + this(biomeRegistry, getBiomes(biomeRegistry), seed, config, initMaps); + } + + private BCLibEndBiomeSource( + Registry biomeRegistry, + List> list, + long seed, + BCLEndBiomeSourceConfig config, + boolean initMaps + ) { + super(biomeRegistry, list, seed); + this.config = config; + rebuildBiomePickers(); + + this.endLandFunction = GeneratorOptions.getEndLandFunction(); + this.pos = new Point(); + + if (initMaps) { + initMap(seed); + } + } + + @NotNull + private void rebuildBiomePickers() { + var includeMap = Configs.BIOMES_CONFIG.getBiomeIncludeMap(); + var excludeList = Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.END); + + this.deciders = BiomeDecider.DECIDERS.stream() + .filter(d -> d.canProvideFor(this)) + .map(d -> d.createInstance(this)) + .toList(); + + this.endLandBiomePicker = new BiomePicker(biomeRegistry); + this.endVoidBiomePicker = new BiomePicker(biomeRegistry); + this.endCenterBiomePicker = new BiomePicker(biomeRegistry); + this.endBarrensBiomePicker = new BiomePicker(biomeRegistry); + Map pickerMap = new HashMap<>(); + pickerMap.put(BiomeAPI.BiomeType.END_LAND, endLandBiomePicker); + pickerMap.put(BiomeAPI.BiomeType.END_VOID, endVoidBiomePicker); + pickerMap.put(BiomeAPI.BiomeType.END_CENTER, endCenterBiomePicker); + pickerMap.put(BiomeAPI.BiomeType.END_BARRENS, endBarrensBiomePicker); + + + this.possibleBiomes().forEach(biome -> { + ResourceKey key = biome.unwrapKey().orElseThrow(); + ResourceLocation biomeID = key.location(); + String biomeStr = biomeID.toString(); + //exclude everything that was listed + if (excludeList != null && excludeList.contains(biomeStr)) return; + if (!biome.isBound()) { + BCLib.LOGGER.warning("Biome " + biomeStr + " is requested but not yet bound."); + return; + } + final BCLBiome bclBiome; + if (!BiomeAPI.hasBiome(biomeID)) { + bclBiome = new BCLBiome(biomeID, biome.value()); + } else { + bclBiome = BiomeAPI.getBiome(biomeID); + } + + + if (bclBiome != null || bclBiome != BCLBiomeRegistry.EMPTY_BIOME) { + if (bclBiome.getParentBiome() == null) { + //ignore small islands when void biomes are disabled + if (!config.withVoidBiomes) { + if (biomeID.equals(Biomes.SMALL_END_ISLANDS.location())) { + return; + } + } + + //force include biomes + boolean didForceAdd = false; + for (var entry : pickerMap.entrySet()) { + var includeList = includeMap == null ? null : includeMap.get(entry.getKey()); + if (includeList != null && includeList.contains(biomeStr)) { + entry.getValue().addBiome(bclBiome); + didForceAdd = true; + } + } + + if (!didForceAdd) { + if (biomeID.equals(BCLBiomeRegistry.EMPTY_BIOME.getID()) + || bclBiome.getIntendedType().is(BiomeAPI.BiomeType.END_IGNORE)) { + //we should not add this biome anywhere, so just ignore it + } else { + didForceAdd = false; + for (BiomeDecider decider : deciders) { + if (decider.addToPicker(bclBiome)) { + didForceAdd = true; + break; + } + } + if (!didForceAdd) { + if (bclBiome.getIntendedType().is(BiomeAPI.BiomeType.END_CENTER) + || TheEndBiomesHelper.canGenerateAsMainIslandBiome(key)) { + endCenterBiomePicker.addBiome(bclBiome); + } else if (bclBiome.getIntendedType().is(BiomeAPI.BiomeType.END_LAND) + || TheEndBiomesHelper.canGenerateAsHighlandsBiome(key)) { + if (!config.withVoidBiomes) endVoidBiomePicker.addBiome(bclBiome); + endLandBiomePicker.addBiome(bclBiome); + } else if (bclBiome.getIntendedType().is(BiomeAPI.BiomeType.END_BARRENS) + || TheEndBiomesHelper.canGenerateAsEndBarrens(key)) { + endBarrensBiomePicker.addBiome(bclBiome); + } else if (bclBiome.getIntendedType().is(BiomeAPI.BiomeType.END_VOID) + || TheEndBiomesHelper.canGenerateAsSmallIslandsBiome(key)) { + endVoidBiomePicker.addBiome(bclBiome); + } else { + BCLib.LOGGER.info("Found End Biome " + biomeStr + " that was not registers with fabric or bclib. Assuming end-land Biome..."); + endLandBiomePicker.addBiome(bclBiome); + } + } + } + } + } + } + }); + + endLandBiomePicker.rebuild(); + endVoidBiomePicker.rebuild(); + endBarrensBiomePicker.rebuild(); + endCenterBiomePicker.rebuild(); + + for (BiomeDecider decider : deciders) { + decider.rebuild(); + } + + if (endVoidBiomePicker.isEmpty()) { + BCLib.LOGGER.info("No Void Biomes found. Disabling by using barrens"); + endVoidBiomePicker = endBarrensBiomePicker; + } + if (endBarrensBiomePicker.isEmpty()) { + BCLib.LOGGER.info("No Barrens Biomes found. Disabling by using land Biomes"); + endBarrensBiomePicker = endLandBiomePicker; + endVoidBiomePicker = endLandBiomePicker; + } + if (endCenterBiomePicker.isEmpty()) { + BCLib.LOGGER.warning("No Center Island Biomes found. Forcing use of vanilla center."); + endCenterBiomePicker.addBiome(BiomeAPI.THE_END); + endCenterBiomePicker.rebuild(); + if (endCenterBiomePicker.isEmpty()) { + BCLib.LOGGER.error("Unable to force vanilla central Island. Falling back to land Biomes..."); + endCenterBiomePicker = endLandBiomePicker; + } + } + } + + protected BCLBiomeSource cloneForDatapack(Set> datapackBiomes) { + datapackBiomes.addAll(getBclBiomes(this.biomeRegistry)); + return new BCLibEndBiomeSource( + this.biomeRegistry, + datapackBiomes.stream() + .filter(b -> b.unwrapKey().orElse(null) != BCLBiomeRegistry.EMPTY_BIOME.getBiomeKey()) + .toList(), + this.currentSeed, + this.config, + true + ); + } + + private static List> getBclBiomes(Registry biomeRegistry) { + return getBiomes( + biomeRegistry, + Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.END), + Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.END), + BCLibEndBiomeSource::isValidNonVanillaEndBiome + ); + } + + private static List> getBiomes(Registry biomeRegistry) { + return getBiomes( + biomeRegistry, + Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.END), + Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.END), + BCLibEndBiomeSource::isValidEndBiome + ); + } + + + private static boolean isValidEndBiome(Holder biome, ResourceLocation location) { + if (BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.END_IGNORE)) return false; + + return biome.is(BiomeTags.IS_END) || + BiomeAPI.wasRegisteredAsEndBiome(location) || + TheEndBiomesHelper.canGenerateInEnd(biome.unwrapKey().orElse(null)); + } + + private static boolean isValidNonVanillaEndBiome(Holder biome, ResourceLocation location) { + if (BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.END_IGNORE)) return false; + + return biome.is(BiomeTags.IS_END) || + BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_LAND) || + BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_VOID) || + BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_CENTER) || + BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_BARRENS) || + TheEndBiomesHelper.canGenerateInEnd(biome.unwrapKey().orElse(null)); + } + + public static void register() { + Registry.register(Registry.BIOME_SOURCE, BCLib.makeID("end_biome_source"), CODEC); + } + + @Override + protected void onInitMap(long seed) { + for (BiomeDecider decider : deciders) { + decider.createMap((picker, size) -> config.mapVersion.mapBuilder.create( + seed, + size <= 0 ? config.landBiomesSize : size, + picker + )); + } + this.mapLand = config.mapVersion.mapBuilder.create( + seed, + config.landBiomesSize, + endLandBiomePicker + ); + + this.mapVoid = config.mapVersion.mapBuilder.create( + seed, + config.voidBiomesSize, + endVoidBiomePicker + ); + + this.mapCenter = config.mapVersion.mapBuilder.create( + seed, + config.centerBiomesSize, + endCenterBiomePicker + ); + + this.mapBarrens = config.mapVersion.mapBuilder.create( + seed, + config.barrensBiomesSize, + endBarrensBiomePicker + ); + } + + @Override + protected void onHeightChange(int newHeight) { + + } + + @Override + public Holder getNoiseBiome(int biomeX, int biomeY, int biomeZ, Climate.@NotNull Sampler sampler) { + if (mapLand == null || mapVoid == null || mapCenter == null || mapBarrens == null) + return this.possibleBiomes().stream().findFirst().orElseThrow(); + + int posX = QuartPos.toBlock(biomeX); + int posY = QuartPos.toBlock(biomeY); + int posZ = QuartPos.toBlock(biomeZ); + + long dist = Math.abs(posX) + Math.abs(posZ) > (long) config.innerVoidRadiusSquared + ? ((long) config.innerVoidRadiusSquared + 1) + : (long) posX * (long) posX + (long) posZ * (long) posZ; + + + if ((biomeX & 63) == 0 || (biomeZ & 63) == 0) { + mapLand.clearCache(); + mapVoid.clearCache(); + mapCenter.clearCache(); + mapVoid.clearCache(); + for (BiomeDecider decider : deciders) { + decider.clearMapCache(); + } + } + + BiomeAPI.BiomeType suggestedType; + + if (config.generatorVersion == BCLEndBiomeSourceConfig.EndBiomeGeneratorType.VANILLA) { + int x = (SectionPos.blockToSectionCoord(posX) * 2 + 1) * 8; + int z = (SectionPos.blockToSectionCoord(posZ) * 2 + 1) * 8; + double d = sampler.erosion().compute(new DensityFunction.SinglePointContext(x, posY, z)); + if (dist <= (long) config.innerVoidRadiusSquared) { + suggestedType = BiomeAPI.BiomeType.END_CENTER; + } else { + if (d > 0.25) { + suggestedType = BiomeAPI.BiomeType.END_LAND; //highlands + } else if (d >= -0.0625) { + suggestedType = BiomeAPI.BiomeType.END_LAND; //midlands + } else { + suggestedType = d < -0.21875 + ? BiomeAPI.BiomeType.END_VOID //small islands + : (config.withVoidBiomes + ? BiomeAPI.BiomeType.END_BARRENS + : BiomeAPI.BiomeType.END_LAND); //barrens + } + } + + final BiomeAPI.BiomeType originalType = suggestedType; + for (BiomeDecider decider : deciders) { + suggestedType = decider + .suggestType( + originalType, + suggestedType, + d, + maxHeight, + posX, + posY, + posZ, + biomeX, + biomeY, + biomeZ + ); + } + } else { + pos.setLocation(biomeX, biomeZ); + final BiomeAPI.BiomeType originalType = (dist <= (long) config.innerVoidRadiusSquared + ? BiomeAPI.BiomeType.END_CENTER + : BiomeAPI.BiomeType.END_LAND); + suggestedType = originalType; + + for (BiomeDecider decider : deciders) { + suggestedType = decider + .suggestType(originalType, suggestedType, maxHeight, posX, posY, posZ, biomeX, biomeY, biomeZ); + } + } + + BiomePicker.ActualBiome result; + for (BiomeDecider decider : deciders) { + if (decider.canProvideBiome(suggestedType)) { + result = decider.provideBiome(suggestedType, posX, posY, posZ); + if (result != null) return result.biome; + } + } + + if (suggestedType.is(BiomeAPI.BiomeType.END_CENTER)) return mapCenter.getBiome(posX, posY, posZ).biome; + if (suggestedType.is(BiomeAPI.BiomeType.END_VOID)) return mapVoid.getBiome(posX, posY, posZ).biome; + if (suggestedType.is(BiomeAPI.BiomeType.END_BARRENS)) return mapBarrens.getBiome(posX, posY, posZ).biome; + return mapLand.getBiome(posX, posY, posZ).biome; + } + + + @Override + protected Codec codec() { + return CODEC; + } + + @Override + public String toString() { + return "BCLib - The End BiomeSource (" + Integer.toHexString(hashCode()) + ", config=" + config + ", seed=" + currentSeed + ", height=" + maxHeight + ", customLand=" + (endLandFunction != null) + ", biomes=" + possibleBiomes().size() + ")"; + } + + @Override + public BCLEndBiomeSourceConfig getTogetherConfig() { + return config; + } + + @Override + public void setTogetherConfig(BCLEndBiomeSourceConfig newConfig) { + this.config = newConfig; + this.initMap(currentSeed); + } + + @Override + public void reloadBiomes() { + rebuildBiomePickers(); + this.initMap(currentSeed); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/BCLibNetherBiomeSource.java b/src/main/java/org/betterx/bclib/api/v2/generator/BCLibNetherBiomeSource.java new file mode 100644 index 00000000..60e87d0a --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/BCLibNetherBiomeSource.java @@ -0,0 +1,229 @@ +package org.betterx.bclib.api.v2.generator; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.generator.config.BCLNetherBiomeSourceConfig; +import org.betterx.bclib.api.v2.generator.config.MapBuilderFunction; +import org.betterx.bclib.api.v2.generator.map.MapStack; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.interfaces.BiomeMap; +import org.betterx.worlds.together.biomesource.BiomeSourceWithConfig; +import org.betterx.worlds.together.biomesource.ReloadableBiomeSource; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BiomeTags; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.Climate; + +import net.fabricmc.fabric.api.biome.v1.NetherBiomes; + +import java.util.List; +import java.util.Set; + +public class BCLibNetherBiomeSource extends BCLBiomeSource implements BiomeSourceWithConfig, ReloadableBiomeSource { + public static final Codec CODEC = RecordCodecBuilder + .create(instance -> instance + .group( + RegistryOps + .retrieveRegistry(Registry.BIOME_REGISTRY) + .forGetter(source -> source.biomeRegistry), + Codec + .LONG + .fieldOf("seed") + .stable() + .forGetter(source -> { + return source.currentSeed; + }), + BCLNetherBiomeSourceConfig + .CODEC + .fieldOf("config") + .orElse(BCLNetherBiomeSourceConfig.DEFAULT) + .forGetter(o -> o.config) + ) + .apply(instance, instance.stable(BCLibNetherBiomeSource::new)) + ); + private BiomeMap biomeMap; + private BiomePicker biomePicker; + private BCLNetherBiomeSourceConfig config; + + public BCLibNetherBiomeSource(Registry biomeRegistry, BCLNetherBiomeSourceConfig config) { + this(biomeRegistry, 0, config, false); + } + + public BCLibNetherBiomeSource(Registry biomeRegistry, long seed, BCLNetherBiomeSourceConfig config) { + this(biomeRegistry, seed, config, true); + } + + private BCLibNetherBiomeSource( + Registry biomeRegistry, + long seed, + BCLNetherBiomeSourceConfig config, + boolean initMaps + ) { + this(biomeRegistry, getBiomes(biomeRegistry), seed, config, initMaps); + } + + private BCLibNetherBiomeSource( + Registry biomeRegistry, + List> list, + long seed, + BCLNetherBiomeSourceConfig config, + boolean initMaps + ) { + super(biomeRegistry, list, seed); + this.config = config; + rebuildBiomePicker(); + if (initMaps) { + initMap(seed); + } + } + + private void rebuildBiomePicker() { + biomePicker = new BiomePicker(biomeRegistry); + + this.possibleBiomes().forEach(biome -> { + ResourceLocation biomeID = biome.unwrapKey().orElseThrow().location(); + if (!biome.isBound()) { + BCLib.LOGGER.warning("Biome " + biomeID.toString() + " is requested but not yet bound."); + return; + } + if (!BiomeAPI.hasBiome(biomeID)) { + + BCLBiome bclBiome = new BCLBiome(biomeID, biome.value()); + biomePicker.addBiome(bclBiome); + } else { + BCLBiome bclBiome = BiomeAPI.getBiome(biomeID); + + if (bclBiome != BCLBiomeRegistry.EMPTY_BIOME) { + if (bclBiome.getParentBiome() == null) { + biomePicker.addBiome(bclBiome); + } + } + } + }); + + biomePicker.rebuild(); + } + + protected BCLBiomeSource cloneForDatapack(Set> datapackBiomes) { + datapackBiomes.addAll(getBclBiomes(this.biomeRegistry)); + return new BCLibNetherBiomeSource( + this.biomeRegistry, + datapackBiomes.stream().toList(), + this.currentSeed, + config, + true + ); + } + + private static List> getBclBiomes(Registry biomeRegistry) { + List include = Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.NETHER); + List exclude = Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.NETHER); + + return getBiomes(biomeRegistry, exclude, include, BCLibNetherBiomeSource::isValidNonVanillaNetherBiome); + } + + + private static List> getBiomes(Registry biomeRegistry) { + List include = Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.NETHER); + List exclude = Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.NETHER); + + return getBiomes(biomeRegistry, exclude, include, BCLibNetherBiomeSource::isValidNetherBiome); + } + + + private static boolean isValidNetherBiome(Holder biome, ResourceLocation location) { + return NetherBiomes.canGenerateInNether(biome.unwrapKey().get()) || + biome.is(BiomeTags.IS_NETHER) || + BiomeAPI.wasRegisteredAsNetherBiome(location); + } + + private static boolean isValidNonVanillaNetherBiome(Holder biome, ResourceLocation location) { + return ( + !"minecraft".equals(location.getNamespace()) && + NetherBiomes.canGenerateInNether(biome.unwrapKey().get())) || + BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_NETHER); + } + + public static void debug(Object el, Registry reg) { + System.out.println("Unknown " + el + " in " + reg); + } + + public static void register() { + Registry.register(Registry.BIOME_SOURCE, BCLib.makeID("nether_biome_source"), CODEC); + } + + + @Override + public Holder getNoiseBiome(int biomeX, int biomeY, int biomeZ, Climate.Sampler var4) { + if (biomeMap == null) + return this.possibleBiomes().stream().findFirst().get(); + + if ((biomeX & 63) == 0 && (biomeZ & 63) == 0) { + biomeMap.clearCache(); + } + BiomePicker.ActualBiome bb = biomeMap.getBiome(biomeX << 2, biomeY << 2, biomeZ << 2); + return bb.biome; + } + + @Override + protected Codec codec() { + return CODEC; + } + + @Override + protected void onInitMap(long seed) { + MapBuilderFunction mapConstructor = config.mapVersion.mapBuilder; + if (maxHeight > config.biomeSizeVertical * 1.5 && config.useVerticalBiomes) { + this.biomeMap = new MapStack( + seed, + config.biomeSize, + biomePicker, + config.biomeSizeVertical, + maxHeight, + mapConstructor + ); + } else { + this.biomeMap = mapConstructor.create( + seed, + config.biomeSize, + biomePicker + ); + } + } + + @Override + protected void onHeightChange(int newHeight) { + initMap(currentSeed); + } + + @Override + public String toString() { + return "BCLib - Nether BiomeSource (" + Integer.toHexString(hashCode()) + ", config=" + config + ", seed=" + currentSeed + ", height=" + maxHeight + ", biomes=" + possibleBiomes().size() + ")"; + } + + @Override + public BCLNetherBiomeSourceConfig getTogetherConfig() { + return config; + } + + @Override + public void setTogetherConfig(BCLNetherBiomeSourceConfig newConfig) { + this.config = newConfig; + initMap(currentSeed); + } + + @Override + public void reloadBiomes() { + rebuildBiomePicker(); + initMap(currentSeed); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/BiomeDecider.java b/src/main/java/org/betterx/bclib/api/v2/generator/BiomeDecider.java new file mode 100644 index 00000000..37255815 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/BiomeDecider.java @@ -0,0 +1,264 @@ +package org.betterx.bclib.api.v2.generator; + +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; +import org.betterx.bclib.interfaces.BiomeMap; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; + +import java.util.LinkedList; +import java.util.List; + + +/** + * Used to extend the BiomePlacement in the {@link BCLBiomeSource} + */ +public abstract class BiomeDecider { + + /** + * used to create new {@link BiomeMap} instances + */ + @FunctionalInterface + public interface BiomeMapBuilderFunction { + /** + * Constructs a new {@link BiomeMap} + * + * @param picker The picker the BiomeMap should use + * @param biomeSize The biomeSize the map will use or -1 for the default size + * @return a new {@link BiomeMap} instance + */ + BiomeMap create(BiomePicker picker, int biomeSize); + } + + /** + * used to determine wether or not a decider can provide this biome + */ + @FunctionalInterface + public interface BiomePredicate { + boolean test(BCLBiome biome); + } + + protected BiomePicker picker; + protected BiomeMap map; + private final BiomePredicate predicate; + + static List DECIDERS = new LinkedList<>(); + + /** + * Register a high priority Decider for the {@link BCLibEndBiomeSource}. + * Normally you should not need to register a high priority decider and instead use + * {@link BiomeDecider#registerDecider(ResourceLocation, BiomeDecider)}. + * BetterEnd (for example) will add + * + * @param location The {@link ResourceLocation} for the decider + * @param decider The initial decider Instance. Each Instance of the {@link BCLibEndBiomeSource} + * will call {@link BiomeDecider#createInstance(BCLBiomeSource)} to build a + * new instance of this decider + */ + public static void registerHighPriorityDecider(ResourceLocation location, BiomeDecider decider) { + if (DECIDERS.size() == 0) DECIDERS.add(decider); + else DECIDERS.add(0, decider); + } + + /** + * Register a new Decider for the {@link BCLibEndBiomeSource} + * + * @param location The {@link ResourceLocation} for the decider + * @param decider The initial decider Instance. Each Instance of the {@link BCLibEndBiomeSource} + * will call {@link BiomeDecider#createInstance(BCLBiomeSource)} to build a + * new instance of this decider + */ + public static void registerDecider(ResourceLocation location, BiomeDecider decider) { + DECIDERS.add(decider); + } + + protected BiomeDecider(BiomePredicate predicate) { + this(null, predicate); + } + + /** + * @param biomeRegistry The biome registry assigned to the creating BiomeSource + * @param predicate A predicate that decides if a given Biome can be provided by this decider + */ + protected BiomeDecider( + Registry biomeRegistry, BiomePredicate predicate + ) { + this.predicate = predicate; + this.map = null; + if (biomeRegistry == null) { + this.picker = null; + } else { + this.picker = new BiomePicker(biomeRegistry); + } + } + + /** + * Called to test, if a decider is suitable for the given BiomeSource. + * + * @param source The BiomeSource that wants to use the decider + * @return true, if this decider is usable by that source + */ + public abstract boolean canProvideFor(BiomeSource source); + + /** + * Called from the BiomeSource whenever it needs to create a new instance of this decider. + *

+ * Inheriting classes should overwrite this method and return Instances of the class. For + * the base {@link BiomeDecider} you would return new BiomeDecider(biomeSource.biomeRegistry, this.predicate); + * + * @param biomeSource The biome source this decider is used from + * @return A new instance + */ + public abstract BiomeDecider createInstance(BCLBiomeSource biomeSource); + + /** + * Called when the BiomeSources needs to construct a new {@link BiomeMap} for the picker. + *

+ * The default implementation creates a new map with the instances picker and a default biome size + * + * @param mapBuilder A function you can use to create a new {@link BiomeMap} that conforms to the settings + * of the current BiomeSource. + */ + public void createMap(BiomeMapBuilderFunction mapBuilder) { + this.map = mapBuilder.create(picker, -1); + } + + /** + * called whenever the BiomeSource needs to clear caches + */ + public void clearMapCache() { + map.clearCache(); + } + + /** + * This method get's called whenever the BiomeSource populates the Biome Pickers. You need to + * determine if the passed Biome is valid for your picker. + *

+ * If this method returns false, the Biome wil not get added to any other Deciders/Pickers. + *

+ * The default implementation will use the instances {@link BiomeDecider#predicate} to determine if + * a biome should get added and return true if it was added. + * + * @param biome The biome that should get added if it matches the criteria of the picker + * @return false, if other pickers/deciders are allowed to use the biome as well + */ + public boolean addToPicker(BCLBiome biome) { + if (predicate.test(biome)) { + picker.addBiome(biome); + return true; + } + + return false; + } + + /** + * Called whenever the picker needs to rebuild it's contents + */ + public void rebuild() { + picker.rebuild(); + } + + /** + * Called from the BiomeSource to determine the type of Biome it needs to place. + * + * @param originalType The original biome type the source did select + * @param suggestedType The currently suggested type. This will differ from originalType if other + * {@link BiomeDecider} instances already had a new suggestion. You implementation should return the + * suggestedType if it does not want to provide the Biome for this location + * @param maxHeight The maximum terrain height for this world + * @param blockX The block coordinate where we are at + * @param blockY The block coordinate where we are at + * @param blockZ The block coordinate where we are at + * @param quarterX The quarter Block Coordinate (which is blockX/4) + * @param quarterY The quarter Block Coordinate (which is blockY/4) + * @param quarterZ The quarter Block Coordinate (which is blockZ/4) + * @return The suggestedType if this decider does not plan to provide a Biome, or a unique BiomeType. + * The Biome Source will call {@link BiomeDecider#canProvideBiome(BiomeAPI.BiomeType)} with the finally chosen type + * for all available Deciders. + */ + public BiomeAPI.BiomeType suggestType( + BiomeAPI.BiomeType originalType, + BiomeAPI.BiomeType suggestedType, + int maxHeight, + int blockX, + int blockY, + int blockZ, + int quarterX, + int quarterY, + int quarterZ + ) { + return suggestType( + originalType, + suggestedType, + 0, + maxHeight, + blockX, + blockY, + blockZ, + quarterX, + quarterY, + quarterZ + ); + } + + /** + * Called from the BiomeSource to determine the type of Biome it needs to place. + * + * @param originalType The original biome type the source did select + * @param suggestedType The currently suggested type. This will differ from originalType if other + * {@link BiomeDecider} instances already had a new suggestion. You implementation should return the + * suggestedType if it does not want to provide the Biome for this location + * @param density The terrain density at this location. Currently only valid if for {@link BCLibEndBiomeSource} + * that use the {@link org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig.EndBiomeGeneratorType#VANILLA} + * @param maxHeight The maximum terrain height for this world + * @param blockX The block coordinate where we are at + * @param blockY The block coordinate where we are at + * @param blockZ The block coordinate where we are at + * @param quarterX The quarter Block Coordinate (which is blockX/4) + * @param quarterY The quarter Block Coordinate (which is blockY/4) + * @param quarterZ The quarter Block Coordinate (which is blockZ/4) + * @param maxHeight + * @return The suggestedType if this decider does not plan to provide a Biome, or a unique BiomeType. + * The Biome Source will call {@link BiomeDecider#canProvideBiome(BiomeAPI.BiomeType)} with the finally chosen type + * for all available Deciders. + */ + public abstract BiomeAPI.BiomeType suggestType( + BiomeAPI.BiomeType originalType, + BiomeAPI.BiomeType suggestedType, + double density, + int maxHeight, + int blockX, + int blockY, + int blockZ, + int quarterX, + int quarterY, + int quarterZ + ); + + + /** + * Called to check if this decider can place a biome for the specified type + * + * @param suggestedType The type of biome we need to place + * @return true, if this type of biome can be provided by the current picker. If true + * is returned, the BiomeSource will call {@link BiomeDecider#provideBiome(BiomeAPI.BiomeType, int, int, int)} + * next + */ + public abstract boolean canProvideBiome(BiomeAPI.BiomeType suggestedType); + + /** + * Called to check if this decider can place a biome for the specified type + *

+ * The default implementation will return map.getBiome(posX, posY, posZ) + * + * @param suggestedType The type of biome we need to place + * @return The methode should return a Biome from its {@link BiomeMap}. If null is returned, the next + * decider (or the default map) will provide the biome + */ + public BiomePicker.ActualBiome provideBiome(BiomeAPI.BiomeType suggestedType, int posX, int posY, int posZ) { + return map.getBiome(posX, posY, posZ); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/BiomePicker.java b/src/main/java/org/betterx/bclib/api/v2/generator/BiomePicker.java new file mode 100644 index 00000000..550d467e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/BiomePicker.java @@ -0,0 +1,173 @@ +package org.betterx.bclib.api.v2.generator; + +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; +import org.betterx.bclib.util.WeighTree; +import org.betterx.bclib.util.WeightedList; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.WorldgenRandom; + +import com.google.common.collect.Lists; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class BiomePicker { + private final Map all = new HashMap<>(); + public final Registry biomeRegistry; + private final List biomes = Lists.newArrayList(); + private final List allowedBiomes; + public final ActualBiome fallbackBiome; + private WeighTree tree; + + public BiomePicker(Registry biomeRegistry) { + this(biomeRegistry, null); + } + + public BiomePicker(Registry biomeRegistry, List> allowedBiomes) { + this.biomeRegistry = biomeRegistry; + this.allowedBiomes = allowedBiomes != null ? allowedBiomes + .stream() + .map(h -> h.unwrapKey()) + .filter(o -> o.isPresent()) + .map(o -> o.get().location().toString()).toList() : null; + this.fallbackBiome = create(BCLBiomeRegistry.EMPTY_BIOME); + } + + private boolean isAllowed(BCLBiome b) { + if (allowedBiomes == null) return true; + return allowedBiomes.contains(b.getID().toString()); + } + + private ActualBiome create(BCLBiome bclBiome) { + ActualBiome e = all.get(bclBiome); + if (e != null) return e; + return new ActualBiome(bclBiome); + } + + public void addBiome(BCLBiome biome) { + biomes.add(create(biome)); + } + + public ActualBiome getBiome(WorldgenRandom random) { + return biomes.isEmpty() ? fallbackBiome : tree.get(random); + } + + public boolean isEmpty() { + return biomes.isEmpty(); + } + + public void rebuild() { + WeightedList list = new WeightedList<>(); + + biomes.forEach(biome -> { + if (biome.isValid) + list.add(biome, biome.bclBiome.getGenChance()); + }); + //only a single biome, we need to add the edges as well + if (list.size() == 1) { + ActualBiome biome = list.get(0); + + if (biome.getEdge() != null) { + float defaultBiomeSize = 128; + float edgeSize = (biome.bclBiome.getEdgeSize() * list.getWeight(0)) / defaultBiomeSize; + list.add(biome.getEdge(), edgeSize); + } + } + + //no Biome, make sure we add at least one, otherwise bad things will happen + if (list.isEmpty()) { + list.add(create(BCLBiomeRegistry.EMPTY_BIOME), 1); + } + + + tree = new WeighTree<>(list); + } + + public class ActualBiome { + public final BCLBiome bclBiome; + public final Holder biome; + public final ResourceKey key; + + private final WeightedList subbiomes = new WeightedList<>(); + private final ActualBiome edge; + private final ActualBiome parent; + public final boolean isValid; + + private ActualBiome(BCLBiome bclBiome) { + all.put(bclBiome, this); + this.bclBiome = bclBiome; + + this.key = biomeRegistry.getResourceKey(biomeRegistry.get(bclBiome.getID())).orElse(null); + this.biome = key != null ? biomeRegistry.getOrCreateHolderOrThrow(key) : null; + this.isValid = key != null && biome != null && biome.isBound(); + bclBiome.forEachSubBiome((b, w) -> { + if (isAllowed(b)) + subbiomes.add(create(b), w); + }); + + if (bclBiome.getEdge() != null && isAllowed(bclBiome.getEdge())) { + edge = create(bclBiome.getEdge()); + } else { + edge = null; + } + + parent = bclBiome.getParentBiome() != null ? create(bclBiome.getParentBiome()) : null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ActualBiome entry = (ActualBiome) o; + return bclBiome.equals(entry.bclBiome); + } + + @Override + public int hashCode() { + return Objects.hash(bclBiome); + } + + public ActualBiome getSubBiome(WorldgenRandom random) { + return subbiomes.get(random); + } + + public ActualBiome getEdge() { + return edge; + } + + public ActualBiome getParentBiome() { + return parent; + } + + public boolean isSame(ActualBiome e) { + return bclBiome.isSame(e.bclBiome); + } + + @Override + public String toString() { + return "ActualBiome{" + + "key=" + key.location() + + ", subbiomes=" + subbiomes.size() + + ", edge=" + (edge != null ? edge.key.location() : "null") + + ", parent=" + (parent != null ? parent.key.location() : "null") + + ", isValid=" + isValid + + '}'; + } + } + + @Override + public String toString() { + return "BiomePicker{" + + "biomes=" + biomes.size() + " (" + all.size() + ")" + + ", biomeRegistry=" + biomeRegistry + + ", type=" + super.toString() + + '}'; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/BiomeType.java b/src/main/java/org/betterx/bclib/api/v2/generator/BiomeType.java new file mode 100644 index 00000000..8bfb3789 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/BiomeType.java @@ -0,0 +1,5 @@ +package org.betterx.bclib.api.v2.generator; + +public enum BiomeType { + LAND, VOID +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/GeneratorOptions.java b/src/main/java/org/betterx/bclib/api/v2/generator/GeneratorOptions.java new file mode 100644 index 00000000..79679d8b --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/GeneratorOptions.java @@ -0,0 +1,105 @@ +package org.betterx.bclib.api.v2.generator; + +import org.betterx.bclib.config.Configs; + +import java.awt.*; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class GeneratorOptions { + //private static BiFunction endLandFunction; + private static boolean fixEndBiomeSource = true; + private static boolean fixNetherBiomeSource = true; + + public static void init() { + fixEndBiomeSource = Configs.GENERATOR_CONFIG.getBoolean("options.biomeSource", "fixEndBiomeSource", true); + fixNetherBiomeSource = Configs.GENERATOR_CONFIG.getBoolean("options.biomeSource", "fixNetherBiomeSource", true); + } + + @Deprecated(forRemoval = true) + public static int getBiomeSizeNether() { + return 256; + } + + @Deprecated(forRemoval = true) + public static int getVerticalBiomeSizeNether() { + return 86; + } + + @Deprecated(forRemoval = true) + public static int getBiomeSizeEndLand() { + return 256; + } + + @Deprecated(forRemoval = true) + public static int getBiomeSizeEndVoid() { + return 256; + } + + /** + * @param endLandFunction + * @deprecated use {@link #setEndLandFunction(BiFunction)} instead + */ + @Deprecated(forRemoval = true) + public static void setEndLandFunction(Function endLandFunction) { + //GeneratorOptions.endLandFunction = (p, h) -> endLandFunction.apply(p); + } + + @Deprecated(forRemoval = true) + public static void setEndLandFunction(BiFunction endLandFunction) { + ///GeneratorOptions.endLandFunction = endLandFunction; + } + + @Deprecated(forRemoval = true) + public static BiFunction getEndLandFunction() { + return (a, b) -> true;//endLandFunction; + } + + @Deprecated(forRemoval = true) + public static long getFarEndBiomes() { + return 1000000; + } + + /** + * Set distance of far End biomes generation, in blocks + * + * @param distance + */ + @Deprecated(forRemoval = true) + public static void setFarEndBiomes(int distance) { + } + + /** + * Set distance of far End biomes generation, in blocks^2 + * + * @param distanceSqr the distance squared + */ + @Deprecated(forRemoval = true) + public static void setFarEndBiomesSqr(long distanceSqr) { + + } + + @Deprecated(forRemoval = true) + public static boolean customNetherBiomeSource() { + return true; + } + + @Deprecated(forRemoval = true) + public static boolean customEndBiomeSource() { + return true; + } + + + @Deprecated(forRemoval = true) + public static boolean useVerticalBiomes() { + return true; + } + + public static boolean fixEndBiomeSource() { + return fixEndBiomeSource; + } + + public static boolean fixNetherBiomeSource() { + return fixNetherBiomeSource; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/TheEndBiomesHelper.java b/src/main/java/org/betterx/bclib/api/v2/generator/TheEndBiomesHelper.java new file mode 100644 index 00000000..93fe1d91 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/TheEndBiomesHelper.java @@ -0,0 +1,92 @@ +package org.betterx.bclib.api.v2.generator; + +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.jetbrains.annotations.ApiStatus; + + +/** + * Helper class until FAPI integrates this PR + */ +public class TheEndBiomesHelper { + @ApiStatus.Internal + private static Map>> END_BIOMES = new HashMap<>(); + + @ApiStatus.Internal + public static void add(BiomeAPI.BiomeType type, ResourceKey biome) { + if (biome == null) return; + END_BIOMES.computeIfAbsent(type, t -> new HashSet<>()).add(biome); + } + + private static boolean has(BiomeAPI.BiomeType type, ResourceKey biome) { + if (biome == null) return false; + Set> set = END_BIOMES.get(type); + if (set == null) return false; + return set.contains(biome); + } + + /** + * Returns true if the given biome was added as a main end Biome in the end, considering the Vanilla end biomes, + * and any biomes added to the End by mods. + * + * @param biome The biome to search for + */ + public static boolean canGenerateAsMainIslandBiome(ResourceKey biome) { + return has(BiomeAPI.BiomeType.END_CENTER, biome); + } + + /** + * Returns true if the given biome was added as a small end islands Biome in the end, considering the Vanilla end biomes, + * and any biomes added to the End by mods. + * + * @param biome The biome to search for + */ + public static boolean canGenerateAsSmallIslandsBiome(ResourceKey biome) { + return has(BiomeAPI.BiomeType.END_VOID, biome); + } + + /** + * Returns true if the given biome was added as a Highland Biome in the end, considering the Vanilla end biomes, + * and any biomes added to the End by mods. + * + * @param biome The biome to search for + */ + public static boolean canGenerateAsHighlandsBiome(ResourceKey biome) { + return has(BiomeAPI.BiomeType.END_LAND, biome); + } + + /** + * Returns true if the given biome was added as midland biome in the end, considering the Vanilla end biomes, + * and any biomes added to the End as midland biome by mods. + * + * @param biome The biome to search for + */ + public static boolean canGenerateAsEndMidlands(ResourceKey biome) { + return false; + } + + /** + * Returns true if the given biome was added as barrens biome in the end, considering the Vanilla end biomes, + * and any biomes added to the End as barrens biome by mods. + * + * @param biome The biome to search for + */ + public static boolean canGenerateAsEndBarrens(ResourceKey biome) { + return has(BiomeAPI.BiomeType.END_BARRENS, biome); + } + + public static boolean canGenerateInEnd(ResourceKey biome) { + return canGenerateAsHighlandsBiome(biome) + || canGenerateAsEndBarrens(biome) + || canGenerateAsEndMidlands(biome) + || canGenerateAsSmallIslandsBiome(biome) + || canGenerateAsMainIslandBiome(biome); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/TypeBiomeDecider.java b/src/main/java/org/betterx/bclib/api/v2/generator/TypeBiomeDecider.java new file mode 100644 index 00000000..559de7d2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/TypeBiomeDecider.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.api.v2.generator; + +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; + +import net.minecraft.core.Registry; +import net.minecraft.world.level.biome.Biome; + +public abstract class TypeBiomeDecider extends BiomeDecider { + protected final BiomeAPI.BiomeType assignedType; + + public TypeBiomeDecider(BiomeAPI.BiomeType assignedType) { + this(null, assignedType); + } + + protected TypeBiomeDecider(Registry biomeRegistry, BiomeAPI.BiomeType assignedType) { + super(biomeRegistry, (biome) -> biome.getIntendedType().is(assignedType)); + this.assignedType = assignedType; + } + + @Override + public boolean canProvideBiome(BiomeAPI.BiomeType suggestedType) { + return suggestedType.equals(assignedType); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/config/BCLEndBiomeSourceConfig.java b/src/main/java/org/betterx/bclib/api/v2/generator/config/BCLEndBiomeSourceConfig.java new file mode 100644 index 00000000..558773e6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/config/BCLEndBiomeSourceConfig.java @@ -0,0 +1,213 @@ +package org.betterx.bclib.api.v2.generator.config; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource; +import org.betterx.bclib.api.v2.generator.map.hex.HexBiomeMap; +import org.betterx.bclib.api.v2.generator.map.square.SquareBiomeMap; +import org.betterx.worlds.together.biomesource.config.BiomeSourceConfig; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.util.Mth; +import net.minecraft.util.StringRepresentable; + +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +public class BCLEndBiomeSourceConfig implements BiomeSourceConfig { + public static final BCLEndBiomeSourceConfig VANILLA = new BCLEndBiomeSourceConfig( + EndBiomeMapType.VANILLA, + EndBiomeGeneratorType.VANILLA, + true, + 4096, + 128, + 128, + 128, + 128 + ); + public static final BCLEndBiomeSourceConfig MINECRAFT_17 = new BCLEndBiomeSourceConfig( + EndBiomeMapType.SQUARE, + EndBiomeGeneratorType.PAULEVS, + true, + VANILLA.innerVoidRadiusSquared * 16 * 16, + 256, + 256, + 256, + 256 + ); + public static final BCLEndBiomeSourceConfig MINECRAFT_18 = new BCLEndBiomeSourceConfig( + EndBiomeMapType.HEX, + BCLib.RUNS_NULLSCAPE ? EndBiomeGeneratorType.VANILLA : EndBiomeGeneratorType.PAULEVS, + BCLib.RUNS_NULLSCAPE ? false : true, + MINECRAFT_17.innerVoidRadiusSquared, + MINECRAFT_17.centerBiomesSize, + MINECRAFT_17.voidBiomesSize, + MINECRAFT_17.landBiomesSize, + MINECRAFT_17.barrensBiomesSize + ); + public static final BCLEndBiomeSourceConfig DEFAULT = MINECRAFT_18; + + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + EndBiomeMapType.CODEC + .fieldOf("map_type") + .orElse(DEFAULT.mapVersion) + .forGetter(o -> o.mapVersion), + EndBiomeGeneratorType.CODEC + .fieldOf("generator_version") + .orElse(DEFAULT.generatorVersion) + .forGetter(o -> o.generatorVersion), + Codec.BOOL + .fieldOf("with_void_biomes") + .orElse(DEFAULT.withVoidBiomes) + .forGetter(o -> o.withVoidBiomes), + Codec.INT + .fieldOf("inner_void_radius_squared") + .orElse(DEFAULT.innerVoidRadiusSquared) + .forGetter(o -> o.innerVoidRadiusSquared), + Codec.INT + .fieldOf("center_biomes_size") + .orElse(DEFAULT.centerBiomesSize) + .forGetter(o -> o.centerBiomesSize), + Codec.INT + .fieldOf("void_biomes_size") + .orElse(DEFAULT.voidBiomesSize) + .forGetter(o -> o.voidBiomesSize), + Codec.INT + .fieldOf("land_biomes_size") + .orElse(DEFAULT.landBiomesSize) + .forGetter(o -> o.landBiomesSize), + Codec.INT + .fieldOf("barrens_biomes_size") + .orElse(DEFAULT.barrensBiomesSize) + .forGetter(o -> o.barrensBiomesSize) + ) + .apply(instance, BCLEndBiomeSourceConfig::new)); + + public BCLEndBiomeSourceConfig( + @NotNull EndBiomeMapType mapVersion, + @NotNull EndBiomeGeneratorType generatorVersion, + boolean withVoidBiomes, + int innerVoidRadiusSquared, + int centerBiomesSize, + int voidBiomesSize, + int landBiomesSize, + int barrensBiomesSize + ) { + this.mapVersion = mapVersion; + this.generatorVersion = generatorVersion; + this.withVoidBiomes = withVoidBiomes; + this.innerVoidRadiusSquared = innerVoidRadiusSquared; + this.barrensBiomesSize = Mth.clamp(barrensBiomesSize, 1, 8192); + this.voidBiomesSize = Mth.clamp(voidBiomesSize, 1, 8192); + this.centerBiomesSize = Mth.clamp(centerBiomesSize, 1, 8192); + this.landBiomesSize = Mth.clamp(landBiomesSize, 1, 8192); + } + + public enum EndBiomeMapType implements StringRepresentable { + VANILLA("vanilla", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker)), + SQUARE("square", (seed, biomeSize, picker) -> new SquareBiomeMap(seed, biomeSize, picker)), + HEX("hex", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker)); + + public static final Codec CODEC = StringRepresentable.fromEnum(EndBiomeMapType::values); + public final String name; + public final @NotNull MapBuilderFunction mapBuilder; + + EndBiomeMapType(String name, @NotNull MapBuilderFunction mapBuilder) { + this.name = name; + this.mapBuilder = mapBuilder; + } + + @Override + public String getSerializedName() { + return name; + } + + @Override + public String toString() { + return name; + } + } + + public enum EndBiomeGeneratorType implements StringRepresentable { + VANILLA("vanilla"), + PAULEVS("paulevs"); + + public static final Codec CODEC = StringRepresentable.fromEnum(EndBiomeGeneratorType::values); + public final String name; + + EndBiomeGeneratorType(String name) { + this.name = name; + } + + @Override + public String getSerializedName() { + return name; + } + + @Override + public String toString() { + return name; + } + } + + + public final @NotNull EndBiomeMapType mapVersion; + public final @NotNull EndBiomeGeneratorType generatorVersion; + public final boolean withVoidBiomes; + public final int innerVoidRadiusSquared; + + public final int voidBiomesSize; + public final int centerBiomesSize; + public final int landBiomesSize; + public final int barrensBiomesSize; + + @Override + public String toString() { + return "BCLEndBiomeSourceConfig{" + + "mapVersion=" + mapVersion + + ", generatorVersion=" + generatorVersion + + ", withVoidBiomes=" + withVoidBiomes + + ", innerVoidRadiusSquared=" + innerVoidRadiusSquared + + ", voidBiomesSize=" + voidBiomesSize + + ", centerBiomesSize=" + centerBiomesSize + + ", landBiomesSize=" + landBiomesSize + + ", barrensBiomesSize=" + barrensBiomesSize + + '}'; + } + + @Override + public boolean couldSetWithoutRepair(BiomeSourceConfig input) { + if (input instanceof BCLEndBiomeSourceConfig cfg) { + return withVoidBiomes == cfg.withVoidBiomes && mapVersion == cfg.mapVersion; + } + return false; + } + + @Override + public boolean sameConfig(BiomeSourceConfig input) { + return this.equals(input); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BCLEndBiomeSourceConfig that = (BCLEndBiomeSourceConfig) o; + return withVoidBiomes == that.withVoidBiomes && innerVoidRadiusSquared == that.innerVoidRadiusSquared && voidBiomesSize == that.voidBiomesSize && centerBiomesSize == that.centerBiomesSize && landBiomesSize == that.landBiomesSize && barrensBiomesSize == that.barrensBiomesSize && mapVersion == that.mapVersion && generatorVersion == that.generatorVersion; + } + + @Override + public int hashCode() { + return Objects.hash( + mapVersion, + generatorVersion, + withVoidBiomes, + innerVoidRadiusSquared, + voidBiomesSize, + centerBiomesSize, + landBiomesSize, + barrensBiomesSize + ); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/config/BCLNetherBiomeSourceConfig.java b/src/main/java/org/betterx/bclib/api/v2/generator/config/BCLNetherBiomeSourceConfig.java new file mode 100644 index 00000000..4d8c5249 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/config/BCLNetherBiomeSourceConfig.java @@ -0,0 +1,127 @@ +package org.betterx.bclib.api.v2.generator.config; + +import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource; +import org.betterx.bclib.api.v2.generator.map.hex.HexBiomeMap; +import org.betterx.bclib.api.v2.generator.map.square.SquareBiomeMap; +import org.betterx.worlds.together.biomesource.config.BiomeSourceConfig; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.util.Mth; +import net.minecraft.util.StringRepresentable; + +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +public class BCLNetherBiomeSourceConfig implements BiomeSourceConfig { + public static final BCLNetherBiomeSourceConfig VANILLA = new BCLNetherBiomeSourceConfig( + NetherBiomeMapType.VANILLA, + 256, + 86, + false + ); + public static final BCLNetherBiomeSourceConfig MINECRAFT_17 = new BCLNetherBiomeSourceConfig( + NetherBiomeMapType.SQUARE, + 256, + 86, + true + ); + public static final BCLNetherBiomeSourceConfig MINECRAFT_18 = new BCLNetherBiomeSourceConfig( + NetherBiomeMapType.HEX, + MINECRAFT_17.biomeSize, + MINECRAFT_17.biomeSizeVertical, + MINECRAFT_17.useVerticalBiomes + ); + public static final BCLNetherBiomeSourceConfig DEFAULT = MINECRAFT_18; + + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + BCLNetherBiomeSourceConfig.NetherBiomeMapType.CODEC + .fieldOf("map_type") + .orElse(DEFAULT.mapVersion) + .forGetter(o -> o.mapVersion), + Codec.INT.fieldOf("biome_size").orElse(DEFAULT.biomeSize).forGetter(o -> o.biomeSize), + Codec.INT.fieldOf("biome_size_vertical") + .orElse(DEFAULT.biomeSizeVertical) + .forGetter(o -> o.biomeSizeVertical), + Codec.BOOL.fieldOf("use_vertical_biomes") + .orElse(DEFAULT.useVerticalBiomes) + .forGetter(o -> o.useVerticalBiomes) + ) + .apply(instance, BCLNetherBiomeSourceConfig::new)); + public final @NotNull NetherBiomeMapType mapVersion; + public final int biomeSize; + public final int biomeSizeVertical; + + public final boolean useVerticalBiomes; + + public BCLNetherBiomeSourceConfig( + @NotNull NetherBiomeMapType mapVersion, + int biomeSize, + int biomeSizeVertical, + boolean useVerticalBiomes + ) { + this.mapVersion = mapVersion; + this.biomeSize = Mth.clamp(biomeSize, 1, 8192); + this.biomeSizeVertical = Mth.clamp(biomeSizeVertical, 1, 8192); + this.useVerticalBiomes = useVerticalBiomes; + } + + @Override + public String toString() { + return "BCLibNetherBiomeSourceConfig{" + + "mapVersion=" + mapVersion + + '}'; + } + + @Override + public boolean couldSetWithoutRepair(BiomeSourceConfig input) { + if (input instanceof BCLNetherBiomeSourceConfig cfg) { + return mapVersion == cfg.mapVersion; + } + return false; + } + + @Override + public boolean sameConfig(BiomeSourceConfig input) { + return this.equals(input); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BCLNetherBiomeSourceConfig)) return false; + BCLNetherBiomeSourceConfig that = (BCLNetherBiomeSourceConfig) o; + return mapVersion == that.mapVersion; + } + + @Override + public int hashCode() { + return Objects.hash(mapVersion); + } + + public enum NetherBiomeMapType implements StringRepresentable { + VANILLA("vanilla", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker)), + SQUARE("square", (seed, biomeSize, picker) -> new SquareBiomeMap(seed, biomeSize, picker)), + HEX("hex", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker)); + + public static final Codec CODEC = StringRepresentable.fromEnum(NetherBiomeMapType::values); + public final String name; + public final MapBuilderFunction mapBuilder; + + NetherBiomeMapType(String name, MapBuilderFunction mapBuilder) { + this.name = name; + this.mapBuilder = mapBuilder; + } + + @Override + public String getSerializedName() { + return name; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/config/MapBuilderFunction.java b/src/main/java/org/betterx/bclib/api/v2/generator/config/MapBuilderFunction.java new file mode 100644 index 00000000..cfb9e206 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/config/MapBuilderFunction.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.api.v2.generator.config; + +import org.betterx.bclib.api.v2.generator.BiomePicker; +import org.betterx.bclib.interfaces.BiomeMap; + +@FunctionalInterface +public interface MapBuilderFunction { + BiomeMap create(long seed, int biomeSize, BiomePicker picker); +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/map/MapStack.java b/src/main/java/org/betterx/bclib/api/v2/generator/map/MapStack.java new file mode 100644 index 00000000..74dbb00f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/map/MapStack.java @@ -0,0 +1,113 @@ +package org.betterx.bclib.api.v2.generator.map; + +import org.betterx.bclib.api.v2.generator.BiomePicker; +import org.betterx.bclib.api.v2.generator.config.MapBuilderFunction; +import org.betterx.bclib.interfaces.BiomeChunk; +import org.betterx.bclib.interfaces.BiomeMap; +import org.betterx.bclib.interfaces.TriConsumer; +import org.betterx.bclib.noise.OpenSimplexNoise; + +import net.minecraft.util.Mth; + +import java.util.Random; + +public class MapStack implements BiomeMap { + private final OpenSimplexNoise noise; + private final BiomeMap[] maps; + private final double layerDistortion; + private final int worldHeight; + private final int minValue; + private final int maxValue; + private final int maxIndex; + + public MapStack( + long seed, + int size, + BiomePicker picker, + int mapHeight, + int worldHeight, + MapBuilderFunction mapConstructor + ) { + final int mapCount = Mth.ceil((float) worldHeight / mapHeight); + this.maxIndex = mapCount - 1; + this.worldHeight = worldHeight; + this.layerDistortion = mapHeight * 0.1; + minValue = Mth.floor(mapHeight * 0.5F + 0.5F); + maxValue = Mth.floor(worldHeight - mapHeight * 0.5F + 0.5F); + maps = new BiomeMap[mapCount]; + Random random = new Random(seed); + for (int i = 0; i < mapCount; i++) { + maps[i] = mapConstructor.create(random.nextLong(), size, picker); + maps[i].setChunkProcessor(this::onChunkCreation); + } + noise = new OpenSimplexNoise(random.nextInt()); + } + + @Override + public void clearCache() { + for (BiomeMap map : maps) { + map.clearCache(); + } + } + + @Override + public void setChunkProcessor(TriConsumer processor) { + } + + @Override + public BiomeChunk getChunk(int cx, int cz, boolean update) { + return null; + } + + @Override + public BiomePicker.ActualBiome getBiome(double x, double y, double z) { + int mapIndex; + + if (y < minValue) { + mapIndex = 0; + } else if (y > maxValue) { + mapIndex = maxIndex; + } else { + mapIndex = Mth.floor((y + noise.eval( + x * 0.03, + z * 0.03 + ) * layerDistortion) / worldHeight * maxIndex + 0.5F); + mapIndex = Mth.clamp(mapIndex, 0, maxIndex); + } + + return maps[mapIndex].getBiome(x, y, z); + } + + private void onChunkCreation(int cx, int cz, int side) { + BiomePicker.ActualBiome[][] biomeMap = new BiomePicker.ActualBiome[side][side]; + BiomeChunk[] chunks = new BiomeChunk[maps.length]; + + boolean isNoEmpty = false; + for (int i = 0; i < maps.length; i++) { + chunks[i] = maps[i].getChunk(cx, cz, false); + for (int x = 0; x < side; x++) { + for (int z = 0; z < side; z++) { + if (biomeMap[x][z] == null) { + BiomePicker.ActualBiome biome = chunks[i].getBiome(x, z); + if (biome.bclBiome.isVertical()) { + biomeMap[x][z] = biome; + isNoEmpty = true; + } + } + } + } + } + + if (isNoEmpty) { + for (int i = 0; i < maps.length; i++) { + for (int x = 0; x < side; x++) { + for (int z = 0; z < side; z++) { + if (biomeMap[x][z] != null) { + chunks[i].setBiome(x, z, biomeMap[x][z]); + } + } + } + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/map/hex/HexBiomeChunk.java b/src/main/java/org/betterx/bclib/api/v2/generator/map/hex/HexBiomeChunk.java new file mode 100644 index 00000000..5332c128 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/map/hex/HexBiomeChunk.java @@ -0,0 +1,160 @@ +package org.betterx.bclib.api.v2.generator.map.hex; + +import org.betterx.bclib.api.v2.generator.BiomePicker; +import org.betterx.bclib.interfaces.BiomeChunk; + +import net.minecraft.world.level.levelgen.WorldgenRandom; + +import java.util.Arrays; + +public class HexBiomeChunk implements BiomeChunk { + private static final short SIDE = 32; + private static final byte SIDE_PRE = 4; + private static final short SIZE = SIDE * SIDE; + private static final short MAX_SIDE = SIZE - SIDE; + private static final byte SCALE_PRE = SIDE / SIDE_PRE; + private static final byte SIZE_PRE = SIDE_PRE * SIDE_PRE; + private static final byte SIDE_MASK = SIDE - 1; + private static final byte SIDE_PRE_MASK = SIDE_PRE - 1; + private static final byte SIDE_OFFSET = (byte) Math.round(Math.log(SIDE) / Math.log(2)); + private static final byte SIDE_PRE_OFFSET = (byte) Math.round(Math.log(SIDE_PRE) / Math.log(2)); + private static final short[][] NEIGHBOURS; + + private final BiomePicker.ActualBiome[] biomes = new BiomePicker.ActualBiome[SIZE]; + + public HexBiomeChunk(WorldgenRandom random, BiomePicker picker) { + BiomePicker.ActualBiome[][] buffers = new BiomePicker.ActualBiome[2][SIZE]; + + for (BiomePicker.ActualBiome[] buffer : buffers) { + Arrays.fill(buffer, null); + } + + for (byte index = 0; index < SIZE_PRE; index++) { + byte px = (byte) (index >> SIDE_PRE_OFFSET); + byte pz = (byte) (index & SIDE_PRE_MASK); + px = (byte) (px * SCALE_PRE + random.nextInt(SCALE_PRE)); + pz = (byte) (pz * SCALE_PRE + random.nextInt(SCALE_PRE)); + circle(buffers[0], getIndex(px, pz), picker.getBiome(random), null); + } + + boolean hasEmptyCells = true; + byte bufferIndex = 0; + while (hasEmptyCells) { + BiomePicker.ActualBiome[] inBuffer = buffers[bufferIndex]; + bufferIndex = (byte) ((bufferIndex + 1) & 1); + BiomePicker.ActualBiome[] outBuffer = buffers[bufferIndex]; + hasEmptyCells = false; + + for (short index = SIDE; index < MAX_SIDE; index++) { + byte z = (byte) (index & SIDE_MASK); + if (z == 0 || z == SIDE_MASK) { + continue; + } + if (inBuffer[index] != null) { + outBuffer[index] = inBuffer[index]; + short[] neighbours = getNeighbours(index & SIDE_MASK); + short indexSide = (short) (index + neighbours[random.nextInt(6)]); + if (indexSide >= 0 && indexSide < SIZE && outBuffer[indexSide] == null) { + outBuffer[indexSide] = inBuffer[index]; + } + } else { + hasEmptyCells = true; + } + } + } + + BiomePicker.ActualBiome[] outBuffer = buffers[bufferIndex]; + byte preN = (byte) (SIDE_MASK - 2); + for (byte index = 0; index < SIDE; index++) { + outBuffer[getIndex(index, (byte) 0)] = outBuffer[getIndex(index, (byte) 2)]; + outBuffer[getIndex((byte) 0, index)] = outBuffer[getIndex((byte) 2, index)]; + outBuffer[getIndex(index, SIDE_MASK)] = outBuffer[getIndex(index, preN)]; + outBuffer[getIndex(SIDE_MASK, index)] = outBuffer[getIndex(preN, index)]; + } + + for (short index = 0; index < SIZE; index++) { + if (outBuffer[index] == null) { + outBuffer[index] = picker.getBiome(random); + } else if (random.nextInt(4) == 0) { + circle(outBuffer, index, outBuffer[index].getSubBiome(random), outBuffer[index]); + } + } + + System.arraycopy(outBuffer, 0, this.biomes, 0, SIZE); + } + + private void circle( + BiomePicker.ActualBiome[] buffer, + short center, + BiomePicker.ActualBiome biome, + BiomePicker.ActualBiome mask + ) { + if (buffer[center] == mask) { + buffer[center] = biome; + } + short[] neighbours = getNeighbours(center & SIDE_MASK); + for (short i : neighbours) { + short index = (short) (center + i); + if (index >= 0 && index < SIZE && buffer[index] == mask) { + buffer[index] = biome; + } + } + } + + private static byte wrap(int value) { + return (byte) (value & SIDE_MASK); + } + + private short getIndex(byte x, byte z) { + return (short) ((short) x << SIDE_OFFSET | z); + } + + @Override + public BiomePicker.ActualBiome getBiome(int x, int z) { + return biomes[getIndex(wrap(x), wrap(z))]; + } + + @Override + public void setBiome(int x, int z, BiomePicker.ActualBiome biome) { + biomes[getIndex(wrap(x), wrap(z))] = biome; + } + + @Override + public int getSide() { + return SIDE; + } + + public static int scaleCoordinate(int value) { + return value >> SIDE_OFFSET; + } + + public static boolean isBorder(int value) { + return wrap(value) == SIDE_MASK; + } + + private short[] getNeighbours(int z) { + return NEIGHBOURS[z & 1]; + } + + public static float scaleMap(float size) { + return size / (SIDE >> 2); + } + + static { + NEIGHBOURS = new short[2][6]; + + NEIGHBOURS[0][0] = 1; + NEIGHBOURS[0][1] = -1; + NEIGHBOURS[0][2] = SIDE; + NEIGHBOURS[0][3] = -SIDE; + NEIGHBOURS[0][4] = SIDE + 1; + NEIGHBOURS[0][5] = SIDE - 1; + + NEIGHBOURS[1][0] = 1; + NEIGHBOURS[1][1] = -1; + NEIGHBOURS[1][2] = SIDE; + NEIGHBOURS[1][3] = -SIDE; + NEIGHBOURS[1][4] = -SIDE + 1; + NEIGHBOURS[1][5] = -SIDE - 1; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/map/hex/HexBiomeMap.java b/src/main/java/org/betterx/bclib/api/v2/generator/map/hex/HexBiomeMap.java new file mode 100644 index 00000000..d0f1095d --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/map/hex/HexBiomeMap.java @@ -0,0 +1,186 @@ +package org.betterx.bclib.api.v2.generator.map.hex; + +import org.betterx.bclib.api.v2.generator.BiomePicker; +import org.betterx.bclib.interfaces.BiomeChunk; +import org.betterx.bclib.interfaces.BiomeMap; +import org.betterx.bclib.interfaces.TriConsumer; +import org.betterx.bclib.noise.OpenSimplexNoise; +import org.betterx.bclib.util.MHelper; + +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.levelgen.WorldgenRandom; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Random; + +public class HexBiomeMap implements BiomeMap { + private static final float RAD_INNER = (float) Math.sqrt(3.0) * 0.5F; + private static final float COEF = 0.25F * (float) Math.sqrt(3.0); + private static final float COEF_HALF = COEF * 0.5F; + private static final float SIN = (float) Math.sin(0.4); + private static final float COS = (float) Math.cos(0.4); + private static final float[] EDGE_CIRCLE_X; + private static final float[] EDGE_CIRCLE_Z; + + private final Map chunks = Maps.newConcurrentMap(); + private final BiomePicker picker; + + private final OpenSimplexNoise[] noises = new OpenSimplexNoise[2]; + private TriConsumer processor; + private final byte noiseIterations; + private final float scale; + private final int seed; + + public HexBiomeMap(long seed, int size, BiomePicker picker) { + this.picker = picker; + this.scale = HexBiomeChunk.scaleMap(size); + Random random = new Random(seed); + + noises[0] = new OpenSimplexNoise(random.nextInt()); + noises[1] = new OpenSimplexNoise(random.nextInt()); + noiseIterations = (byte) Math.min(Math.ceil(Math.log(scale) / Math.log(2)), 5); + this.seed = random.nextInt(); + } + + @Override + public void clearCache() { + if (chunks.size() > 127) { + chunks.clear(); + } + } + + @Override + public BiomePicker.ActualBiome getBiome(double x, double y, double z) { + BiomePicker.ActualBiome biome = getRawBiome(x, z); + BiomePicker.ActualBiome edge = biome.getEdge(); + int size = biome.bclBiome.getEdgeSize(); + + if (edge == null && biome.getParentBiome() != null) { + edge = biome.getParentBiome().getEdge(); + size = biome.getParentBiome().bclBiome.getEdgeSize(); + } + + if (edge == null) { + return biome; + } + + for (byte i = 0; i < 8; i++) { + if (!getRawBiome(x + size * EDGE_CIRCLE_X[i], z + size * EDGE_CIRCLE_Z[i]).isSame(biome)) { + return edge; + } + } + + return biome; + } + + @Override + public BiomeChunk getChunk(final int cx, final int cz, final boolean update) { + final ChunkPos pos = new ChunkPos(cx, cz); + HexBiomeChunk chunk = chunks.get(pos); + if (chunk == null) { + WorldgenRandom random = new WorldgenRandom(Random.create(MHelper.getSeed(seed, cx, cz))); + chunk = new HexBiomeChunk(random, picker); + if (update && processor != null) { + processor.accept(cx, cz, chunk.getSide()); + } + chunks.put(pos, chunk); + } + return chunk; + } + + @Override + public void setChunkProcessor(TriConsumer processor) { + this.processor = processor; + } + + private BiomePicker.ActualBiome getRawBiome(double x, double z) { + double px = x / scale * RAD_INNER; + double pz = z / scale; + double dx = rotateX(px, pz); + double dz = rotateZ(px, pz); + px = dx; + pz = dz; + + dx = getNoise(px, pz, (byte) 0) * 0.2F; + dz = getNoise(pz, px, (byte) 1) * 0.2F; + px += dx; + pz += dz; + + int cellZ = (int) Math.floor(pz); + boolean offset = (cellZ & 1) == 1; + + if (offset) { + px += 0.5; + } + + int cellX = (int) Math.floor(px); + + float pointX = (float) (px - cellX - 0.5); + float pointZ = (float) (pz - cellZ - 0.5); + + if (Math.abs(pointZ) < 0.3333F) { + return getChunkBiome(cellX, cellZ); + } + + if (insideHexagon(0, 0, 1.1555F, pointZ * RAD_INNER, pointX)) { + return getChunkBiome(cellX, cellZ); + } + + cellX = pointX < 0 ? (offset ? cellX - 1 : cellX) : (offset ? cellX : cellX + 1); + cellZ = pointZ < 0 ? cellZ - 1 : cellZ + 1; + + return getChunkBiome(cellX, cellZ); + } + + private BiomePicker.ActualBiome getChunkBiome(int x, int z) { + int cx = HexBiomeChunk.scaleCoordinate(x); + int cz = HexBiomeChunk.scaleCoordinate(z); + + if (((z >> 2) & 1) == 0 && HexBiomeChunk.isBorder(x)) { + x = 0; + cx += 1; + } else if (((x >> 2) & 1) == 0 && HexBiomeChunk.isBorder(z)) { + z = 0; + cz += 1; + } + + return getChunk(cx, cz, true).getBiome(x, z); + } + + private boolean insideHexagon(float centerX, float centerZ, float radius, float x, float z) { + double dx = Math.abs(x - centerX) / radius; + double dy = Math.abs(z - centerZ) / radius; + return (dy <= COEF) && (COEF * dx + 0.25F * dy <= COEF_HALF); + } + + private double getNoise(double x, double z, byte state) { + double result = 0; + for (byte i = 1; i <= noiseIterations; i++) { + OpenSimplexNoise noise = noises[state]; + state = (byte) ((state + 1) & 1); + result += noise.eval(x * i, z * i) / i; + } + return result; + } + + private double rotateX(double x, double z) { + return x * COS - z * SIN; + } + + private double rotateZ(double x, double z) { + return x * SIN + z * COS; + } + + static { + EDGE_CIRCLE_X = new float[8]; + EDGE_CIRCLE_Z = new float[8]; + + for (byte i = 0; i < 8; i++) { + float angle = i / 4F * (float) Math.PI; + EDGE_CIRCLE_X[i] = (float) Math.sin(angle); + EDGE_CIRCLE_Z[i] = (float) Math.cos(angle); + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/map/square/SquareBiomeChunk.java b/src/main/java/org/betterx/bclib/api/v2/generator/map/square/SquareBiomeChunk.java new file mode 100644 index 00000000..a9df57a3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/map/square/SquareBiomeChunk.java @@ -0,0 +1,66 @@ +package org.betterx.bclib.api.v2.generator.map.square; + +import org.betterx.bclib.api.v2.generator.BiomePicker; +import org.betterx.bclib.interfaces.BiomeChunk; + +import net.minecraft.world.level.levelgen.WorldgenRandom; + +public class SquareBiomeChunk implements BiomeChunk { + private static final int BIT_OFFSET = 4; + protected static final int WIDTH = 1 << BIT_OFFSET; + private static final int SM_WIDTH = WIDTH >> 1; + private static final int SM_BIT_OFFSET = BIT_OFFSET >> 1; + private static final int MASK_OFFSET = SM_WIDTH - 1; + protected static final int MASK_WIDTH = WIDTH - 1; + + private static final int SM_CAPACITY = SM_WIDTH * SM_WIDTH; + private static final int CAPACITY = WIDTH * WIDTH; + + private final BiomePicker.ActualBiome[] biomes; + + public SquareBiomeChunk(WorldgenRandom random, BiomePicker picker) { + BiomePicker.ActualBiome[] PreBio = new BiomePicker.ActualBiome[SM_CAPACITY]; + biomes = new BiomePicker.ActualBiome[CAPACITY]; + + for (int x = 0; x < SM_WIDTH; x++) { + int offset = x << SM_BIT_OFFSET; + for (int z = 0; z < SM_WIDTH; z++) { + PreBio[offset | z] = picker.getBiome(random); + } + } + + for (int x = 0; x < WIDTH; x++) { + int offset = x << BIT_OFFSET; + for (int z = 0; z < WIDTH; z++) { + biomes[offset | z] = PreBio[getSmIndex(offsetXZ(x, random), offsetXZ(z, random))].getSubBiome(random); + } + } + } + + @Override + public BiomePicker.ActualBiome getBiome(int x, int z) { + return biomes[getIndex(x & MASK_WIDTH, z & MASK_WIDTH)]; + } + + @Override + public void setBiome(int x, int z, BiomePicker.ActualBiome biome) { + biomes[getIndex(x & MASK_WIDTH, z & MASK_WIDTH)] = biome; + } + + @Override + public int getSide() { + return WIDTH; + } + + private int offsetXZ(int x, WorldgenRandom random) { + return ((x + random.nextInt(2)) >> 1) & MASK_OFFSET; + } + + private int getIndex(int x, int z) { + return x << BIT_OFFSET | z; + } + + private int getSmIndex(int x, int z) { + return x << SM_BIT_OFFSET | z; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/generator/map/square/SquareBiomeMap.java b/src/main/java/org/betterx/bclib/api/v2/generator/map/square/SquareBiomeMap.java new file mode 100644 index 00000000..1ce1d9e0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/generator/map/square/SquareBiomeMap.java @@ -0,0 +1,143 @@ +package org.betterx.bclib.api.v2.generator.map.square; + +import org.betterx.bclib.api.v2.generator.BiomePicker; +import org.betterx.bclib.interfaces.BiomeChunk; +import org.betterx.bclib.interfaces.BiomeMap; +import org.betterx.bclib.interfaces.TriConsumer; +import org.betterx.bclib.noise.OpenSimplexNoise; +import org.betterx.bclib.util.MHelper; + +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.levelgen.WorldgenRandom; + +import com.google.common.collect.Maps; + +import java.util.Map; + +public class SquareBiomeMap implements BiomeMap { + private final Map maps = Maps.newHashMap(); + private final OpenSimplexNoise noiseX; + private final OpenSimplexNoise noiseZ; + private final WorldgenRandom random; + private final BiomePicker picker; + + private final int sizeXZ; + private final int depth; + private final int size; + + private TriConsumer processor; + + public SquareBiomeMap(long seed, int size, BiomePicker picker) { + random = new WorldgenRandom(new LegacyRandomSource(seed)); + noiseX = new OpenSimplexNoise(random.nextLong()); + noiseZ = new OpenSimplexNoise(random.nextLong()); + this.sizeXZ = size; + depth = (int) Math.ceil(Math.log(size) / Math.log(2)) - 2; + this.size = 1 << depth; + this.picker = picker; + } + + @Override + public void clearCache() { + if (maps.size() > 32) { + maps.clear(); + } + } + + @Override + public BiomePicker.ActualBiome getBiome(double x, double y, double z) { + BiomePicker.ActualBiome biome = getRawBiome(x, z); + + if (biome.getEdge() != null || (biome.getParentBiome() != null && biome.getParentBiome().getEdge() != null)) { + BiomePicker.ActualBiome search = biome; + if (biome.getParentBiome() != null) { + search = biome.getParentBiome(); + } + + int size = search.bclBiome.getEdgeSize(); + boolean edge = !search.isSame(getRawBiome(x + size, z)); + edge = edge || !search.isSame(getRawBiome(x - size, z)); + edge = edge || !search.isSame(getRawBiome(x, z + size)); + edge = edge || !search.isSame(getRawBiome(x, z - size)); + edge = edge || !search.isSame(getRawBiome(x - 1, z - 1)); + edge = edge || !search.isSame(getRawBiome(x - 1, z + 1)); + edge = edge || !search.isSame(getRawBiome(x + 1, z - 1)); + edge = edge || !search.isSame(getRawBiome(x + 1, z + 1)); + + if (edge) { + biome = search.getEdge(); + } + } + + return biome; + } + + @Override + public void setChunkProcessor(TriConsumer processor) { + this.processor = processor; + } + + @Override + public BiomeChunk getChunk(int cx, int cz, boolean update) { + ChunkPos cpos = new ChunkPos(cx, cz); + SquareBiomeChunk chunk = maps.get(cpos); + if (chunk == null) { + synchronized (random) { + random.setLargeFeatureWithSalt(0, cpos.x, cpos.z, 0); + chunk = new SquareBiomeChunk(random, picker); + } + maps.put(cpos, chunk); + + if (update && processor != null) { + processor.accept(cx, cz, chunk.getSide()); + } + } + + return chunk; + } + + private BiomePicker.ActualBiome getRawBiome(double bx, double bz) { + double x = bx * size / sizeXZ; + double z = bz * size / sizeXZ; + + double px = bx * 0.2; + double pz = bz * 0.2; + + for (int i = 0; i < depth; i++) { + double nx = (x + noiseX.eval(px, pz)) / 2F; + double nz = (z + noiseZ.eval(px, pz)) / 2F; + + x = nx; + z = nz; + + px = px / 2 + i; + pz = pz / 2 + i; + } + + int ix = MHelper.floor(x); + int iz = MHelper.floor(z); + + if ((ix & SquareBiomeChunk.MASK_WIDTH) == SquareBiomeChunk.MASK_WIDTH) { + x += (iz / 2) & 1; + } + if ((iz & SquareBiomeChunk.MASK_WIDTH) == SquareBiomeChunk.MASK_WIDTH) { + z += (ix / 2) & 1; + } + + ChunkPos cpos = new ChunkPos( + MHelper.floor(x / SquareBiomeChunk.WIDTH), + MHelper.floor(z / SquareBiomeChunk.WIDTH) + ); + SquareBiomeChunk chunk = maps.get(cpos); + if (chunk == null) { + synchronized (random) { + random.setLargeFeatureWithSalt(0, cpos.x, cpos.z, 0); + chunk = new SquareBiomeChunk(random, picker); + } + maps.put(cpos, chunk); + } + + return chunk.getBiome(MHelper.floor(x), MHelper.floor(z)); + } +} 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..b22db277 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/LevelGenEvents.java @@ -0,0 +1,161 @@ +package org.betterx.bclib.api.v2.levelgen; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.LifeCycleAPI; +import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI; +import org.betterx.bclib.api.v2.datafixer.DataFixerAPI; +import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource; +import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig; +import org.betterx.bclib.api.v2.levelgen.biomes.InternalBiomeAPI; +import org.betterx.bclib.api.v2.tag.TagAPI; +import org.betterx.bclib.registry.PresetsRegistry; +import org.betterx.worlds.together.tag.v3.TagManager; +import org.betterx.worlds.together.world.WorldConfig; +import org.betterx.worlds.together.world.event.WorldEvents; +import org.betterx.worlds.together.worldPreset.TogetherWorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPreset; + +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagLoader; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.storage.LevelStorageSource; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +public class LevelGenEvents { + public static void setupWorld() { + InternalBiomeAPI.prepareNewLevel(); + DataExchangeAPI.prepareServerside(); + } + + public static void register() { + WorldEvents.BEFORE_WORLD_LOAD.on(LevelGenEvents::prepareWorld); + WorldEvents.BEFORE_SERVER_WORLD_LOAD.on(LevelGenEvents::prepareServerWorld); + + WorldEvents.ON_WORLD_LOAD.on(LevelGenEvents::onWorldLoad); + WorldEvents.WORLD_REGISTRY_READY.on(LevelGenEvents::onRegistryReady); + WorldEvents.ON_FINALIZE_LEVEL_STEM.on(LevelGenEvents::finalizeStem); + + WorldEvents.PATCH_WORLD.on(LevelGenEvents::patchExistingWorld); + WorldEvents.ADAPT_WORLD_PRESET.on(LevelGenEvents::adaptWorldPresetSettings); + + WorldEvents.BEFORE_ADDING_TAGS.on(LevelGenEvents::appplyTags); + } + + private static void appplyTags( + String directory, + Map> tagsMap + ) { + //make sure we include Tags registered by the deprecated API + TagAPI.apply(directory, tagsMap); + + + if (directory.equals(TagManager.BIOMES.directory)) { + InternalBiomeAPI._runBiomeTagAdders(); + } + } + + + private static boolean patchExistingWorld( + LevelStorageSource.LevelStorageAccess storageAccess, + Consumer allDone + ) { + return DataFixerAPI.fixData(storageAccess, true, allDone); + } + + private static Optional> adaptWorldPresetSettings( + Optional> currentPreset, + WorldGenSettings worldGenSettings + ) { + LevelStem endStem = worldGenSettings.dimensions().get(LevelStem.END); + + //We probably loaded a Datapack for the End + if (!(endStem.generator().getBiomeSource() instanceof BCLibEndBiomeSource)) { + + + if (currentPreset.isPresent()) { + if (currentPreset.get().value() instanceof TogetherWorldPreset worldPreset) { + ResourceKey worldPresetKey = currentPreset.get().unwrapKey().orElse(null); + + //user did not configure/change the Preset! + if (PresetsRegistry.BCL_WORLD.equals(worldPresetKey) + || PresetsRegistry.BCL_WORLD_17.equals(worldPresetKey)) { + BCLib.LOGGER.info("Detected Datapack for END."); + + LevelStem configuredEndStem = worldPreset.getDimension(LevelStem.END); + if (configuredEndStem.generator().getBiomeSource() instanceof BCLibEndBiomeSource endSource) { + BCLib.LOGGER.info("Changing Default WorldPreset Settings for Datapack use."); + + BCLEndBiomeSourceConfig inputConfig = endSource.getTogetherConfig(); + endSource.setTogetherConfig(new BCLEndBiomeSourceConfig( + inputConfig.mapVersion, + BCLEndBiomeSourceConfig.EndBiomeGeneratorType.VANILLA, + false, + inputConfig.innerVoidRadiusSquared, + inputConfig.centerBiomesSize, + inputConfig.voidBiomesSize, + inputConfig.landBiomesSize, + inputConfig.barrensBiomesSize + )); + } + } + } + } + } + return currentPreset; + } + + private static void onRegistryReady(RegistryAccess a) { + InternalBiomeAPI.initRegistry(a); + } + + private static void prepareWorld( + LevelStorageSource.LevelStorageAccess storageAccess, + Map, ChunkGenerator> dimensions, + boolean isNewWorld + ) { + setupWorld(); + if (isNewWorld) { + WorldConfig.saveFile(BCLib.MOD_ID); + DataFixerAPI.initializePatchData(); + } else { + LevelGenUtil.migrateGeneratorSettings(); + } + } + + private static void prepareServerWorld( + LevelStorageSource.LevelStorageAccess storageAccess, + Map, ChunkGenerator> dimensions, + boolean isNewWorld + ) { + setupWorld(); + + if (isNewWorld) { + WorldConfig.saveFile(BCLib.MOD_ID); + DataFixerAPI.initializePatchData(); + } else { + LevelGenUtil.migrateGeneratorSettings(); + DataFixerAPI.fixData(storageAccess, false, (didFix) -> {/* not called when showUI==false */}); + } + } + + private static void onWorldLoad() { + LifeCycleAPI._runBeforeLevelLoad(); + } + + private static void finalizeStem( + WorldGenSettings settings, + ResourceKey dimension, + LevelStem levelStem + ) { + InternalBiomeAPI.applyModifications(levelStem.generator().getBiomeSource(), dimension); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/LevelGenUtil.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/LevelGenUtil.java new file mode 100644 index 00000000..30244772 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/LevelGenUtil.java @@ -0,0 +1,205 @@ +package org.betterx.bclib.api.v2.levelgen; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.generator.BCLChunkGenerator; +import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource; +import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource; +import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig; +import org.betterx.bclib.api.v2.generator.config.BCLNetherBiomeSourceConfig; +import org.betterx.bclib.registry.PresetsRegistry; +import org.betterx.worlds.together.levelgen.WorldGenUtil; +import org.betterx.worlds.together.util.ModUtil; +import org.betterx.worlds.together.world.WorldConfig; +import org.betterx.worlds.together.worldPreset.TogetherWorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPreset; + +import com.mojang.serialization.Lifecycle; +import net.minecraft.core.Holder; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldGenSettings; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; + +public class LevelGenUtil { + private static final String TAG_VERSION = "version"; + private static final String TAG_BN_GEN_VERSION = "generator_version"; + + @NotNull + public static LevelStem getBCLNetherLevelStem(WorldGenUtil.Context context, BCLNetherBiomeSourceConfig config) { + BCLibNetherBiomeSource netherSource = new BCLibNetherBiomeSource(context.biomes, config); + + return new LevelStem( + context.dimension, + new BCLChunkGenerator( + context.structureSets, + context.noiseParameters, + netherSource, + context.generatorSettings + ) + ); + } + + public static LevelStem getBCLEndLevelStem(WorldGenUtil.Context context, BCLEndBiomeSourceConfig config) { + BCLibEndBiomeSource endSource = new BCLibEndBiomeSource(context.biomes, config); + return new LevelStem( + context.dimension, + new BCLChunkGenerator( + context.structureSets, + context.noiseParameters, + endSource, + context.generatorSettings + ) + ); + } + + + public static WorldGenSettings replaceGenerator( + ResourceKey dimensionKey, + ResourceKey dimensionTypeKey, + RegistryAccess registryAccess, + WorldGenSettings worldGenSettings, + ChunkGenerator generator + ) { + Registry dimensionTypeRegistry = registryAccess.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY); + Registry newDimensions = withDimension( + dimensionKey, + dimensionTypeKey, + dimensionTypeRegistry, + worldGenSettings.dimensions(), + generator + ); + return new WorldGenSettings( + worldGenSettings.seed(), + worldGenSettings.generateStructures(), + worldGenSettings.generateBonusChest(), + newDimensions + ); + } + + public static Registry withDimension( + ResourceKey dimensionKey, + ResourceKey dimensionTypeKey, + Registry dimensionTypeRegistry, + Registry inputDimensions, + ChunkGenerator generator + ) { + + LevelStem levelStem = inputDimensions.get(dimensionKey); + Holder dimensionType = levelStem == null + ? dimensionTypeRegistry.getOrCreateHolderOrThrow(dimensionTypeKey) + : levelStem.typeHolder(); + return withDimension(dimensionKey, inputDimensions, new LevelStem(dimensionType, generator)); + } + + public static Registry withDimension( + ResourceKey dimensionKey, + Registry inputDimensions, + LevelStem levelStem + ) { + MappedRegistry writableRegistry = new MappedRegistry<>( + Registry.LEVEL_STEM_REGISTRY, + Lifecycle.experimental(), + null + ); + writableRegistry.register( + dimensionKey, + levelStem, + Lifecycle.stable() + ); + for (Map.Entry, LevelStem> entry : inputDimensions.entrySet()) { + ResourceKey resourceKey = entry.getKey(); + if (resourceKey == dimensionKey) continue; + writableRegistry.register( + resourceKey, + entry.getValue(), + inputDimensions.lifecycle(entry.getValue()) + ); + } + return writableRegistry; + } + + + public static void migrateGeneratorSettings() { + final CompoundTag settingsNbt = WorldGenUtil.getPresetsNbt(); + + if (settingsNbt.size() == 0) { + CompoundTag oldGen = WorldGenUtil.getGeneratorNbt(); + if (oldGen != null) { + if (oldGen.contains("type")) { + BCLib.LOGGER.info("Found World with beta generator Settings."); + if ("bclib:bcl_world_preset_settings".equals(oldGen.getString("type"))) { + int netherVersion = 18; + int endVersion = 18; + if (oldGen.contains("minecraft:the_nether")) + netherVersion = oldGen.getInt("minecraft:the_nether"); + if (oldGen.contains("minecraft:the_end")) + endVersion = oldGen.getInt("minecraft:the_end"); + + if (netherVersion == 18) netherVersion = 0; + else if (netherVersion == 17) netherVersion = 1; + else netherVersion = 2; + + if (endVersion == 18) endVersion = 0; + else if (endVersion == 17) endVersion = 1; + else endVersion = 2; + + var presets = List.of( + TogetherWorldPreset.getDimensionsMap(PresetsRegistry.BCL_WORLD), + TogetherWorldPreset.getDimensionsMap(PresetsRegistry.BCL_WORLD_17), + TogetherWorldPreset.getDimensionsMap(WorldPresets.NORMAL) + ); + Map, ChunkGenerator> dimensions = new HashMap<>(); + dimensions.put(LevelStem.OVERWORLD, presets.get(0).get(LevelStem.OVERWORLD)); + dimensions.put(LevelStem.NETHER, presets.get(netherVersion).get(LevelStem.NETHER)); + dimensions.put(LevelStem.END, presets.get(endVersion).get(LevelStem.END)); + + TogetherWorldPreset.writeWorldPresetSettingsDirect(dimensions); + } + return; + } + } + + BCLib.LOGGER.info("Found World without generator Settings. Setting up data..."); + ResourceKey biomeSourceVersion = PresetsRegistry.BCL_WORLD; + + final CompoundTag bclRoot = WorldConfig.getRootTag(BCLib.MOD_ID); + + String bclVersion = "0.0.0"; + if (bclRoot.contains(TAG_VERSION)) { + bclVersion = bclRoot.getString(TAG_VERSION); + } + boolean isPre18 = !ModUtil.isLargerOrEqualVersion(bclVersion, "1.0.0"); + + if (isPre18) { + BCLib.LOGGER.info("World was create pre 1.18!"); + biomeSourceVersion = PresetsRegistry.BCL_WORLD_17; + } + + if (WorldConfig.hasMod("betternether")) { + BCLib.LOGGER.info("Found Data from BetterNether, using for migration."); + final CompoundTag bnRoot = WorldConfig.getRootTag("betternether"); + biomeSourceVersion = "1.17".equals(bnRoot.getString(TAG_BN_GEN_VERSION)) + ? PresetsRegistry.BCL_WORLD_17 + : PresetsRegistry.BCL_WORLD; + } + + Registry dimensions = TogetherWorldPreset.getDimensions(biomeSourceVersion); + if (dimensions != null) { + BCLib.LOGGER.info("Set world to BiomeSource Version " + biomeSourceVersion); + TogetherWorldPreset.writeWorldPresetSettings(dimensions); + } else { + BCLib.LOGGER.error("Failed to set world to BiomeSource Version " + biomeSourceVersion); + } + } + } +} 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..fe7a699f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiome.java @@ -0,0 +1,539 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import org.betterx.bclib.util.WeightedList; + +import com.mojang.datafixers.Products; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Registry; +import net.minecraft.data.BuiltinRegistries; +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.Climate; +import net.minecraft.world.level.levelgen.WorldgenRandom; + +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.Nullable; + + +public class BCLBiome extends BCLBiomeSettings 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.terrainHeight); + + public RecordCodecBuilder t1 = Codec.FLOAT.fieldOf("fogDensity") + .orElse(1.0f) + .forGetter((T o1) -> o1.fogDensity); + public RecordCodecBuilder t2 = Codec.FLOAT.fieldOf("genChance") + .orElse(1.0f) + .forGetter((T o1) -> o1.genChance); + public RecordCodecBuilder t3 = Codec.INT.fieldOf("edgeSize") + .orElse(0) + .forGetter((T o1) -> o1.edgeSize); + public RecordCodecBuilder t4 = Codec.BOOL.fieldOf("vertical") + .orElse(false) + .forGetter((T o1) -> o1.vertical); + public RecordCodecBuilder> t5 = + ResourceLocation.CODEC + .optionalFieldOf("edge") + .orElse(Optional.empty()) + .forGetter((T o1) -> o1.edge == null + ? Optional.empty() + : Optional.of(o1.edge.biomeID)); + 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.biomeID)); + public RecordCodecBuilder>> t9 = + WeightedList.listCodec( + ResourceLocation.CODEC, + "biomes", + "biome" + ) + .optionalFieldOf("sub_biomes") + .forGetter( + (T o) -> { + if (o.subbiomes == null + || o.subbiomes.isEmpty() + || (o.subbiomes.size() == 1 && o.subbiomes.contains( + o))) { + return Optional.empty(); + } + return Optional.of( + o.subbiomes.map( + b -> b.biomeID)); + }); + 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.P12, Float, Float, Float, Integer, Boolean, Optional, ResourceLocation, Optional>, Optional, Optional>, Optional, P12> codecWithSettings( + RecordCodecBuilder.Instance instance, + 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.t9, a.t10, p12); + } + + public static Products.P13, Float, Float, Float, Integer, Boolean, Optional, ResourceLocation, Optional>, Optional, Optional>, Optional, P12, P13> codecWithSettings( + RecordCodecBuilder.Instance instance, + 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.t9, a.t10, p12, p13); + } + + public static Products.P14, Float, Float, Float, Integer, Boolean, Optional, ResourceLocation, Optional>, Optional, Optional>, Optional, P12, P13, P14> codecWithSettings( + RecordCodecBuilder.Instance instance, + 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.t9, a.t10, p12, p13, p14); + } + + public static Products.P11, Float, Float, Float, Integer, Boolean, Optional, ResourceLocation, Optional>, 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.t9, a.t10); + } + + protected final WeightedList subbiomes = new WeightedList<>(); + private final Map customData = Maps.newHashMap(); + private final ResourceLocation biomeID; + private final ResourceKey biomeKey; + final Biome biomeToRegister; + + protected final List parameterPoints = Lists.newArrayList(); + + private BCLBiome biomeParent; + + 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> subbiomes, + Optional intendedType + ) { + super(terrainHeight, fogDensity, genChance, edgeSize, vertical, edge.map(BiomeAPI::getBiome).orElse(null)); + biomeToRegister = null; + this.biomeID = biomeID; + this.biomeKey = ResourceKey.create(Registry.BIOME_REGISTRY, biomeID); + if (subbiomes.isEmpty() || subbiomes.get().size() == 0) { + this.subbiomes.add(this, 1); + } else { + this.subbiomes.addAll(subbiomes.get().map(BiomeAPI::getBiome)); + } + this.biomeParent = biomeParent.map(BiomeAPI::getBiome).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(Registry.BIOME_REGISTRY, biomeID), null); + } + + /** + * Create wrapper for existing biome using biome instance from {@link BuiltinRegistries}. + * + * @param biomeToRegister {@link Biome} to wrap. + */ + @Deprecated(forRemoval = true) + protected BCLBiome(Biome biomeToRegister) { + this(biomeToRegister, null); + } + + /** + * Create wrapper for existing biome using biome instance from {@link BuiltinRegistries}. + * + * @param biomeToRegister {@link Biome} to wrap. + * @param settings The Settings for this Biome or {@code null} if you want to apply default settings + */ + @Deprecated(forRemoval = true) + protected BCLBiome(Biome biomeToRegister, VanillaBiomeSettings settings) { + this(BiomeAPI.getBiomeID(biomeToRegister), biomeToRegister, settings); + } + + /** + * Create wrapper for existing biome using biome instance from {@link BuiltinRegistries}. + * + * @param biomeToRegister {@link Biome} to wrap. + * @param biomeID Teh ResoureLocation for this Biome + */ + @Deprecated(forRemoval = true) + //this constructor should become package private and not get removed + public BCLBiome(ResourceLocation biomeID, Biome biomeToRegister) { + this(biomeID, biomeToRegister, null); + } + + /** + * Create a new Biome + * + * @param biomeID {@link ResourceLocation} biome ID. + * @param biomeToRegister {@link Biome} to wrap. + * @param defaults The Settings for this Biome or null if you want to apply the defaults + */ + protected BCLBiome(ResourceLocation biomeID, Biome biomeToRegister, BCLBiomeSettings defaults) { + this(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID), biomeToRegister, defaults); + } + + /** + * Create a new Biome + * + * @param biomeKey {@link ResourceKey} 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(biomeKey, null, defaults); + } + + /** + * Create a new Biome + * + * @param biomeKey {@link ResourceKey} of the wrapped Biome + * @param biomeToRegister The biome you want to use when this instance gets registered through the {@link BiomeAPI} + * @param defaults The Settings for this Biome or null if you want to apply the defaults + */ + protected BCLBiome(ResourceKey biomeKey, Biome biomeToRegister, BCLBiomeSettings defaults) { + this.biomeToRegister = biomeToRegister; + this.subbiomes.add(this, 1.0F); + this.biomeID = biomeKey.location(); + this.biomeKey = biomeKey; + + if (defaults != null) { + defaults.applyWithDefaults(this); + } + } + + /** + * 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 edge; + } + + /** + * Set biome edge for this biome instance. + * + * @param edge {@link BCLBiome} as the edge biome. + * @return same {@link BCLBiome}. + */ + BCLBiome setEdge(BCLBiome edge) { + this.edge = edge; + edge.biomeParent = this; + return this; + } + + /** + * Set biome edge for this biome instance. If there is already an edge, the + * biome is added as subBiome to the current edge-biome + * + * @param edge The new edge + * @return same {@link BCLBiome}. + */ + public BCLBiome addEdge(BCLBiome edge) { + if (this.edge != null) { + this.edge.addSubBiome(edge); + } else { + this.setEdge(edge); + } + return this; + } + + /** + * Adds sub-biome into this biome instance. Biome chance will be interpreted as a sub-biome generation chance. + * Biome itself has chance 1.0 compared to all its sub-biomes. + * + * @param biome {@link Random} to be added. + * @return same {@link BCLBiome}. + */ + public BCLBiome addSubBiome(BCLBiome biome) { + biome.biomeParent = this; + subbiomes.add(biome, biome.getGenChance()); + return this; + } + + /** + * Checks if specified biome is a sub-biome of this one. + * + * @param biome {@link Random}. + * @return true if this instance contains specified biome as a sub-biome. + */ + public boolean containsSubBiome(BCLBiome biome) { + return subbiomes.contains(biome); + } + + /** + * Getter for a random sub-biome from all existing sub-biomes. Will return biome itself if there are no sub-biomes. + * + * @param random {@link Random}. + * @return {@link BCLBiome}. + */ + public BCLBiome getSubBiome(WorldgenRandom random) { + return subbiomes.get(random); + } + + public void forEachSubBiome(BiConsumer consumer) { + for (int i = 0; i < subbiomes.size(); i++) + consumer.accept(subbiomes.get(i), subbiomes.getWeight(i)); + } + + /** + * Getter for parent {@link BCLBiome} or null if there are no parent biome. + * + * @return {@link BCLBiome} or null. + */ + @Nullable + public BCLBiome getParentBiome() { + return this.biomeParent; + } + + /** + * Compares biome instances (directly) and their parents. Used in custom world generator. + * + * @param biome {@link BCLBiome} + * @return true if biome or its parent is same. + */ + public boolean isSame(BCLBiome biome) { + return biome == this || (biome.biomeParent != null && biome.biomeParent == this); + } + + /** + * Getter for biome identifier. + * + * @return {@link ResourceLocation} + */ + public ResourceLocation getID() { + return biomeID; + } + + + /** + * Getter for biome from buil-in registry. For datapack biomes will be same as actual biome. + * + * @return {@link Biome}. + */ + @Deprecated(forRemoval = true) + public Biome getBiome() { + if (biomeToRegister != null) return biomeToRegister; + return BiomeAPI.getFromBuiltinRegistry(biomeKey).value(); + } + + /** + * Getter for biomeKey + * + * @return {@link ResourceKey}. + */ + public ResourceKey getBiomeKey() { + return biomeKey; + } + + public ResourceKey getBCLBiomeKey() { + return ResourceKey.create(BCLBiomeRegistry.BCL_BIOMES_REGISTRY, biomeID); + } + + /** + * For internal use from BiomeAPI only + */ + void afterRegistration() { + + } + + + /** + * Getter for custom data. Will get custom data object or null if object doesn't exists. + * + * @param name {@link String} name of data object. + * @return object value or null. + */ + @Nullable + @SuppressWarnings("unchecked") + @Deprecated(forRemoval = true) + public T getCustomData(String name) { + return (T) customData.get(name); + } + + /** + * Getter for custom data. Will get custom data object or default value if object doesn't exists. + * + * @param name {@link String} name of data object. + * @param defaultValue object default value. + * @return object value or default value. + */ + @SuppressWarnings("unchecked") + @Deprecated(forRemoval = true) + public T getCustomData(String name, T defaultValue) { + return (T) customData.getOrDefault(name, defaultValue); + } + + /** + * Adds custom data object to this biome instance. + * + * @param name {@link String} name of data object. + * @param obj any data to add. + * @return same {@link BCLBiome}. + */ + @Deprecated(forRemoval = true) + public BCLBiome addCustomData(String name, Object obj) { + customData.put(name, obj); + return this; + } + + /** + * Adds custom data object to this biome instance. + * + * @param data a {@link Map} with custom data. + * @return same {@link BCLBiome}. + */ + @Deprecated(forRemoval = true) + public BCLBiome addCustomData(Map data) { + customData.putAll(data); + return this; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + BCLBiome biome = (BCLBiome) obj; + return biome != null && biomeID.equals(biome.biomeID); + } + + @Override + public int hashCode() { + return biomeID.hashCode(); + } + + @Override + public String toString() { + return biomeID.toString(); + } + + + /** + * Adds structures to this biome. For internal use only. + * Used inside {@link BCLBiomeBuilder}. + */ + void addClimateParameters(List 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() { + if (getParentBiome() == null) return false; + return getParentBiome().edge == this; + } + + boolean allowFabricRegistration() { + return !isEdgeBiome(); + } +} 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..a8bd1faf --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeBuilder.java @@ -0,0 +1,919 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import org.betterx.bclib.api.v2.levelgen.features.BCLFeature; +import org.betterx.bclib.api.v2.levelgen.structures.BCLStructure; +import org.betterx.bclib.api.v2.levelgen.surface.SurfaceRuleBuilder; +import org.betterx.bclib.entity.BCLEntityWrapper; +import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor; +import org.betterx.bclib.util.CollectionsUtil; +import org.betterx.bclib.util.ColorUtil; +import org.betterx.bclib.util.Pair; +import org.betterx.bclib.util.TriFunction; +import org.betterx.worlds.together.surfaceRules.SurfaceRuleRegistry; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.data.worldgen.BiomeDefaultFeatures; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.Music; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.tags.TagKey; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.level.biome.*; +import net.minecraft.world.level.biome.Biome.BiomeBuilder; +import net.minecraft.world.level.biome.Biome.Precipitation; +import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.GenerationStep.Decoration; +import net.minecraft.world.level.levelgen.Noises; +import net.minecraft.world.level.levelgen.SurfaceRules; +import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import net.fabricmc.fabric.api.biome.v1.BiomeModifications; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +public class BCLBiomeBuilder { + @FunctionalInterface + public interface BiomeSupplier extends TriFunction { + } + + + private static final BCLBiomeBuilder INSTANCE = new BCLBiomeBuilder(); + private static final SurfaceRules.ConditionSource SURFACE_NOISE = SurfaceRules.noiseCondition( + Noises.SOUL_SAND_LAYER, + -0.012 + ); + + private final List>>> carvers = new ArrayList<>( + 1); + private BiomeGenerationSettings.Builder generationSettings; + private BiomeSpecialEffects.Builder effectsBuilder; + private MobSpawnSettings.Builder spawnSettings; + private SurfaceRules.RuleSource surfaceRule; + private Precipitation precipitation; + private ResourceLocation biomeID; + + private final Set> 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 boolean vertical; + + private BiomeAPI.BiomeType biomeType; + + + /** + * Starts new biome building process. + * + * @param biomeID {@link ResourceLocation} biome identifier. + * @return prepared {@link BCLBiomeBuilder} instance. + */ + public static BCLBiomeBuilder start(ResourceLocation biomeID) { + INSTANCE.biomeID = biomeID; + INSTANCE.precipitation = Precipitation.NONE; + INSTANCE.generationSettings = null; + INSTANCE.effectsBuilder = null; + INSTANCE.spawnSettings = null; + INSTANCE.temperature = 1.0F; + INSTANCE.fogDensity = 1.0F; + INSTANCE.edgeSize = 0; + INSTANCE.downfall = 1.0F; + INSTANCE.genChance = 1.0F; + INSTANCE.height = 0.1F; + INSTANCE.vertical = false; + INSTANCE.edge = null; + INSTANCE.carvers.clear(); + INSTANCE.parameters.clear(); + INSTANCE.tags.clear(); + INSTANCE.biomeType = null; + return INSTANCE; + } + + public BCLBiomeBuilder addNetherClimateParamater(float temperature, float humidity) { + parameters.add(Climate.parameters(temperature, humidity, 0, 0, 0, 0, 0)); + return this; + } + + /** + * Set the type for this Biome. If the type was set, the Biome can be registered. + * + * @param type selected Type + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder type(BiomeAPI.BiomeType type) { + this.biomeType = type; + return this; + } + + /** + * Set biome {@link Precipitation}. Affect biome visual effects (rain, snow, none). + * + * @param precipitation {@link Precipitation} + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder precipitation(Precipitation precipitation) { + this.precipitation = precipitation; + return this; + } + + /** + * Set biome temperature, affect plant color, biome generation and ice formation. + * + * @param temperature biome temperature. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder temperature(float temperature) { + this.temperature = temperature; + return this; + } + + /** + * Set biome wetness (same as downfall). Affect plant color and biome generation. + * + * @param wetness biome wetness (downfall). + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder wetness(float wetness) { + this.downfall = wetness; + return this; + } + + /** + * Adds mob spawning to biome. + * + * @param entityType {@link EntityType} mob type. + * @param weight spawn weight. + * @param minGroupCount minimum mobs in group. + * @param maxGroupCount maximum mobs in group. + * @return same {@link BCLBiomeBuilder} instance. + */ + public 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(SoundEvent music) { + return music(new Music(music, 600, 2400, true)); + } + + /** + * Sets biome ambient loop sound. Can be used for biome environment. + * + * @param loopSound {@link SoundEvent} to use as a loop. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder loop(SoundEvent loopSound) { + getEffects().ambientLoopSound(loopSound); + return this; + } + + /** + * Sets biome mood sound. Can be used for biome environment. + * + * @param mood {@link SoundEvent} to use as a mood. + * @param tickDelay delay between sound events in ticks. + * @param blockSearchExtent block search radius (for area available for sound). + * @param soundPositionOffset offset in sound. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder mood(SoundEvent mood, int tickDelay, int blockSearchExtent, float soundPositionOffset) { + getEffects().ambientMoodSound(new AmbientMoodSettings(mood, tickDelay, blockSearchExtent, soundPositionOffset)); + return this; + } + + /** + * Sets biome mood sound. Can be used for biome environment. + * + * @param mood {@link SoundEvent} to use as a mood. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder mood(SoundEvent mood) { + return mood(mood, 6000, 8, 2.0F); + } + + /** + * Sets biome additionsl ambient sounds. + * + * @param additions {@link SoundEvent} to use. + * @param intensity sound intensity. Default is 0.0111F. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder additions(SoundEvent additions, float intensity) { + getEffects().ambientAdditionsSound(new AmbientAdditionsSettings(additions, intensity)); + return this; + } + + /** + * Sets biome additionsl ambient sounds. + * + * @param additions {@link SoundEvent} to use. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder additions(SoundEvent additions) { + return additions(additions, 0.0111F); + } + + /** + * Adds new feature to the biome. + * + * @param decoration {@link Decoration} feature step. + * @param feature {@link PlacedFeature}. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder feature(Decoration decoration, Holder feature) { + getGeneration().addFeature(decoration, feature); + return this; + } + + /** + * Adds vanilla Mushrooms. + * + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder defaultMushrooms() { + return feature(BiomeDefaultFeatures::addDefaultMushrooms); + } + + /** + * Adds vanilla Nether Ores. + * + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder netherDefaultOres() { + return feature(BiomeDefaultFeatures::addNetherDefaultOres); + } + + /** + * Will add features into biome, used for vanilla feature adding functions. + * + * @param featureAdd {@link Consumer} with {@link BiomeGenerationSettings.Builder}. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder feature(Consumer featureAdd) { + featureAdd.accept(getGeneration()); + return this; + } + + /** + * Adds new feature to the biome. + * + * @param feature {@link BCLFeature}. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder feature(BCLFeature feature) { + return feature(feature.getDecoration(), feature.getPlacedFeature()); + } + + /** + * Adds new feature to the biome. + * + * @param feature {@link BCLFeature}. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder feature(org.betterx.bclib.api.v3.levelgen.features.BCLFeature feature) { + return feature(feature.decoration, feature.placedFeature); + } + + /** + * Adds new structure feature into the biome. + * + * @param structureTag {@link TagKey} to add. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder structure(TagKey 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 ResourceLocation immutableID = biomeID; + var oKey = carver.unwrapKey(); + if (oKey.isPresent()) { + BiomeModifications.addCarver( + ctx -> ctx.getBiomeKey().location().equals(immutableID), + step, + (ResourceKey>) oKey.get() + ); + } + //carvers.add(new Pair<>(step, carver)); + return this; + } + + /** + * Adds new world surface rule for the given block + * + * @param surfaceBlock {@link Block} to use. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder surface(Block surfaceBlock) { + return surface(surfaceBlock.defaultBlockState()); + } + + /** + * Adds new world surface rule for the given block + * + * @param surfaceBlock {@link BlockState} to use. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder surface(BlockState surfaceBlock) { + return surface(SurfaceRuleBuilder.start().surface(surfaceBlock).build()); + } + + /** + * Adds blocks to the biome surface and below it (with specified depth). + * + * @param surfaceBlock {@link Block} that will cover biome. + * @param subterrainBlock {@link Block} below it with specified depth. + * @param depth thickness of bottom block layer. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder surface(Block surfaceBlock, Block subterrainBlock, int depth) { + return surface(SurfaceRuleBuilder + .start() + .surface(surfaceBlock.defaultBlockState()) + .subsurface(subterrainBlock.defaultBlockState(), depth) + .build()); + } + + /** + * Adds surface rule to this biome. + * + * @param newSurfaceRule {link SurfaceRules.RuleSource} surface rule. + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder surface(SurfaceRules.RuleSource newSurfaceRule) { + this.surfaceRule = newSurfaceRule; + return this; + } + + /** + * 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 = vertical; + return this; + } + + + private static BiomeGenerationSettings fixGenerationSettings(BiomeGenerationSettings settings) { + //Fabric Biome Modification API can not handle an empty carver map, thus we will create one with + //an empty HolderSet for every possible step: + //https://github.com/FabricMC/fabric/issues/2079 + //TODO: Remove, once fabric gets fixed + if (settings instanceof BiomeGenerationSettingsAccessor acc) { + Map>> carvers = CollectionsUtil.getMutable(acc.bclib_getCarvers()); + for (GenerationStep.Carving step : GenerationStep.Carving.values()) { + carvers.computeIfAbsent(step, __ -> HolderSet.direct(Lists.newArrayList())); + } + acc.bclib_setCarvers(carvers); + } + return settings; + } + + + /** + * Get or create {@link BiomeSpecialEffects.Builder} for biome visual effects. + * For internal usage only. + * For internal usage only. + * + * @return new or same {@link BiomeSpecialEffects.Builder} instance. + */ + private BiomeSpecialEffects.Builder getEffects() { + if (effectsBuilder == null) { + effectsBuilder = new BiomeSpecialEffects.Builder(); + } + return effectsBuilder; + } + + /** + * Get or create {@link MobSpawnSettings.Builder} for biome mob spawning. + * For internal usage only. + * + * @return new or same {@link MobSpawnSettings.Builder} instance. + */ + private MobSpawnSettings.Builder getSpawns() { + if (spawnSettings == null) { + spawnSettings = new MobSpawnSettings.Builder(); + } + return spawnSettings; + } + + /** + * Get or create {@link BiomeGenerationSettings.Builder} for biome features and generation. + * For internal usage only. + * + * @return new or same {@link BiomeGenerationSettings.Builder} instance. + */ + private BiomeGenerationSettings.Builder getGeneration() { + if (generationSettings == null) { + generationSettings = new BiomeGenerationSettings.Builder(); + } + return generationSettings; + } + + /** + * Finalize biome creation. + * + * @return created {@link BCLBiome} instance. + */ + public BCLBiome build() { + return build((BiomeSupplier) BCLBiome::new); + } + + /** + * Finalize biome creation. + * + * @param biomeConstructor {@link BiFunction} biome constructor. + * @return created {@link BCLBiome} instance. + * @deprecated Replaced with {@link #build(BiomeSupplier)} + */ + @Deprecated(forRemoval = true) + public T build(BiFunction biomeConstructor) { + return build((id, biome, settings) -> biomeConstructor.apply(id, biome)); + } + + /** + * Finalize biome creation. + * + * @param biomeConstructor {@link BiomeSupplier} biome constructor. + * @return created {@link BCLBiome} instance. + */ + public T build(BiomeSupplier biomeConstructor) { + BiomeBuilder builder = new BiomeBuilder() + .precipitation(precipitation) + .temperature(temperature) + .downfall(downfall); + + builder.mobSpawnSettings(getSpawns().build()); + builder.specialEffects(getEffects().build()); + + builder.generationSettings(fixGenerationSettings(getGeneration().build())); + + BCLBiomeSettings settings = BCLBiomeSettings.createBCL() + .setTerrainHeight(height) + .setFogDensity(fogDensity) + .setGenChance(genChance) + .setEdgeSize(edgeSize) + .setEdge(edge) + .setVertical(vertical) + .build(); + + final Biome biome = builder.build(); + final T res = biomeConstructor.apply(biomeID, biome, settings); + tags.forEach(tagKey -> TagManager.BIOMES.add(tagKey, res.getBiomeKey())); + + //res.addBiomeTags(tags); + //res.setSurface(surfaceRule); + SurfaceRuleRegistry.registerRule(biomeID, surfaceRule, biomeID); + res.addClimateParameters(parameters); + if (biomeType != null) + res._setIntendedType(biomeType); + + + //carvers.forEach(cfg -> BiomeAPI.addBiomeCarver(biome, cfg.second, cfg.first)); + return res; + } +} 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..bf7e9c72 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeRegistry.java @@ -0,0 +1,145 @@ +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.BuiltinRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.biome.Biomes; + +import java.util.Optional; +import java.util.stream.Stream; +import org.jetbrains.annotations.ApiStatus; + +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 = Registry.registerSimple( + BCL_BIOME_CODEC_REGISTRY, + BCLBiomeRegistry::bootstrapCodecs + ); + public static Registry BUILTIN_BCL_BIOMES = new MappedRegistry<>( + BCL_BIOMES_REGISTRY, + Lifecycle.stable(), null + ); + + /** + * 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()); + + public static Codec registerBiomeCodec( + ResourceLocation location, + KeyDispatchDataCodec codec + ) { + Registry.register(BIOME_CODECS, location, codec.codec()); + return codec.codec(); + } + + public static ResourceKey register(BCLBiome biome) { + Registry.register(BUILTIN_BCL_BIOMES, biome.getBCLBiomeKey(), biome); + return biome.getBCLBiomeKey(); + } + + 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()); + } + + + @ApiStatus.Internal + public static Holder bootstrap(Registry registry) { + BuiltinRegistries.register(registry, BiomeAPI.SMALL_END_ISLANDS.getBCLBiomeKey(), BiomeAPI.SMALL_END_ISLANDS); + BuiltinRegistries.register(registry, BiomeAPI.END_BARRENS.getBCLBiomeKey(), BiomeAPI.END_BARRENS); + BuiltinRegistries.register(registry, BiomeAPI.END_HIGHLANDS.getBCLBiomeKey(), BiomeAPI.END_HIGHLANDS); + BuiltinRegistries.register(registry, BiomeAPI.END_MIDLANDS.getBCLBiomeKey(), BiomeAPI.END_MIDLANDS); + BuiltinRegistries.register(registry, BiomeAPI.THE_END.getBCLBiomeKey(), BiomeAPI.THE_END); + BuiltinRegistries.register( + registry, + BiomeAPI.BASALT_DELTAS_BIOME.getBCLBiomeKey(), + BiomeAPI.BASALT_DELTAS_BIOME + ); + BuiltinRegistries.register( + registry, + BiomeAPI.SOUL_SAND_VALLEY_BIOME.getBCLBiomeKey(), + BiomeAPI.SOUL_SAND_VALLEY_BIOME + ); + BuiltinRegistries.register( + registry, + BiomeAPI.WARPED_FOREST_BIOME.getBCLBiomeKey(), + BiomeAPI.WARPED_FOREST_BIOME + ); + BuiltinRegistries.register( + registry, + BiomeAPI.CRIMSON_FOREST_BIOME.getBCLBiomeKey(), + BiomeAPI.CRIMSON_FOREST_BIOME + ); + BuiltinRegistries.register( + registry, + BiomeAPI.NETHER_WASTES_BIOME.getBCLBiomeKey(), + BiomeAPI.NETHER_WASTES_BIOME + ); + return BuiltinRegistries.register(registry, EMPTY_BIOME.getBCLBiomeKey(), EMPTY_BIOME); + } + + public static BCLBiome get(ResourceLocation loc) { + return get(WorldBootstrap.getLastRegistryAccessOrElseBuiltin(), loc); + } + + public static BCLBiome get(RegistryAccess access, ResourceLocation loc) { + return getBclBiomesRegistry(access).get(loc); + } + + public static BCLBiome getOrElseEmpty(ResourceLocation loc) { + return getOrElseEmpty(WorldBootstrap.getLastRegistryAccessOrElseBuiltin(), loc); + } + + public static BCLBiome getOrElseEmpty(RegistryAccess access, ResourceLocation loc) { + BCLBiome res = get(access, loc); + if (res == null) return EMPTY_BIOME; + return res; + } + + public static Stream> getAll(BiomeAPI.BiomeType dim) { + return getAll(WorldBootstrap.getLastRegistryAccessOrElseBuiltin(), dim); + } + + public static Stream> getAll(RegistryAccess access, BiomeAPI.BiomeType dim) { + return getBclBiomesRegistry(access) + .entrySet() + .stream() + .filter(e -> e.getValue().getIntendedType().is(BiomeAPI.BiomeType.END)) + .map(e -> e.getKey()); + } + + private static Registry getBclBiomesRegistry(RegistryAccess access) { + if (access != null) { + return ((Optional>) access + .registry(BCLBiomeRegistry.BCL_BIOMES_REGISTRY)) + .orElse(BUILTIN_BCL_BIOMES); + } else { + return BUILTIN_BCL_BIOMES; + } + } + + public static void ensureStaticallyLoaded() { + + } +} 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..948702c6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeSettings.java @@ -0,0 +1,218 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import org.betterx.bclib.api.v2.generator.BiomePicker; +import org.betterx.bclib.config.Configs; + +import net.minecraft.world.level.biome.Biome; + +public class BCLBiomeSettings { + public static Builder createBCL() { + return new Builder(); + } + + public static class Builder extends CommonBuilder { + 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; + } + + /** + * Set edges:biome for this biome. + * + * @param edge The {@link Biome}. + * @return same {@link Builder}. + */ + public R setEdge(BCLBiome edge) { + storage.edge = edge; + return (R) this; + } + + /** + * Sets fog density for this biome. + * + * @param fogDensity + * @return same {@link Builder}. + */ + public R setFogDensity(float fogDensity) { + storage.fogDensity = fogDensity; + return (R) this; + } + } + + BCLBiomeSettings( + float terrainHeight, + float fogDensity, + float genChance, + int edgeSize, + boolean vertical, + BCLBiome edge + ) { + this.terrainHeight = terrainHeight; + this.fogDensity = fogDensity; + this.genChance = genChance; + this.edgeSize = edgeSize; + this.vertical = vertical; + this.edge = edge; + } + + protected BCLBiomeSettings() { + this.terrainHeight = 0.1F; + this.fogDensity = 1.0F; + this.genChance = 1.0F; + this.edgeSize = 0; + this.vertical = false; + this.edge = null; + } + + float terrainHeight; + float fogDensity; + float genChance; + int edgeSize; + boolean vertical; + BCLBiome edge; + + + /** + * Getter for biome generation chance, used in {@link BiomePicker} and in custom generators. + * + * @return biome generation chance as float. + */ + public float getGenChance() { + return this.genChance; + } + + /** + * Checks if biome is vertical, for tall Nether only (or for custom generators). + * + * @return is biome vertical or not. + */ + public boolean isVertical() { + return vertical; + } + + /** + * Getter for terrain height, can be used in custom terrain generator. + * + * @return terrain height. + */ + public float getTerrainHeight() { + return terrainHeight; + } + + /** + * Getter for fog density, used in custom for renderer. + * + * @return fog density as a float. + */ + public float getFogDensity() { + return fogDensity; + } + + /** + * Getter for biome edge size. + * + * @return edge size in blocks. + */ + public int getEdgeSize() { + return edgeSize; + } + + /** + * Getter for edge-biome. + * + * @return The assigned edge biome. + */ + public BCLBiome getEdge() { + return edge; + } + + /** + * Load values from Config and apply to the passed Biome. The Default values for the loaded settings + * are derifed from the values store in this object + * + * @param biome {@link BCLBiome} to assign values to + */ + public void applyWithDefaults(BCLBiome biome) { + final String group = biome.configGroup(); + biome.genChance = Configs.BIOMES_CONFIG.getFloat(group, "generation_chance", this.genChance); + + if (edge != null) { + biome.edgeSize = Configs.BIOMES_CONFIG.getInt(group, "edge_size", this.edgeSize); + if (edgeSize > 0) { + biome.setEdge(edge); + } + } + + if (!(this instanceof VanillaBiomeSettings)) { + biome.fogDensity = Configs.BIOMES_CONFIG.getFloat(group, "fog_density", this.fogDensity); + biome.vertical = Configs.BIOMES_CONFIG.getBoolean(group, "vertical", this.vertical); + biome.terrainHeight = Configs.BIOMES_CONFIG.getFloat(group, "terrain_height", this.terrainHeight); + } + + Configs.BIOMES_CONFIG.saveChanges(); + } +} 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..efcf846a --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BiomeAPI.java @@ -0,0 +1,1093 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.features.BCLFeature; +import org.betterx.bclib.interfaces.SurfaceMaterialProvider; +import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor; +import org.betterx.bclib.mixin.common.MobSpawnSettingsAccessor; +import org.betterx.bclib.util.CollectionsUtil; +import org.betterx.worlds.together.tag.v3.CommonBiomeTags; +import org.betterx.worlds.together.tag.v3.TagManager; +import org.betterx.worlds.together.world.event.WorldBootstrap; + +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.HolderSet; +import net.minecraft.core.Registry; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BiomeTags; +import net.minecraft.util.random.WeightedRandomList; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.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 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.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.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.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.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; + + @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); + } + } + + /** + * 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. + * + * @deprecated use {@link BCLBiomeRegistry#EMPTY_BIOME} instead + */ + public static final BCLBiome EMPTY_BIOME = BCLBiomeRegistry.EMPTY_BIOME; + + public static final BCLBiome NETHER_WASTES_BIOME = InternalBiomeAPI.wrapNativeBiome( + Biomes.NETHER_WASTES, + InternalBiomeAPI.OTHER_NETHER + ); + public static final BCLBiome CRIMSON_FOREST_BIOME = InternalBiomeAPI.wrapNativeBiome( + Biomes.CRIMSON_FOREST, + InternalBiomeAPI.OTHER_NETHER + ); + public static final BCLBiome WARPED_FOREST_BIOME = InternalBiomeAPI.wrapNativeBiome( + Biomes.WARPED_FOREST, + InternalBiomeAPI.OTHER_NETHER + ); + public static final BCLBiome SOUL_SAND_VALLEY_BIOME = InternalBiomeAPI.wrapNativeBiome( + Biomes.SOUL_SAND_VALLEY, + InternalBiomeAPI.OTHER_NETHER + ); + public static final BCLBiome BASALT_DELTAS_BIOME = InternalBiomeAPI.wrapNativeBiome( + Biomes.BASALT_DELTAS, + InternalBiomeAPI.OTHER_NETHER + ); + + + public static final BCLBiome THE_END = InternalBiomeAPI.wrapNativeBiome( + Biomes.THE_END, + 0.5F, + InternalBiomeAPI.OTHER_END_CENTER + ); + + public static final BCLBiome END_MIDLANDS = InternalBiomeAPI.wrapNativeBiome( + Biomes.END_MIDLANDS, + 0.5F, + InternalBiomeAPI.OTHER_END_LAND + ); + + public static final BCLBiome END_HIGHLANDS = InternalBiomeAPI.wrapNativeBiome( + Biomes.END_HIGHLANDS, + END_MIDLANDS, + 8, + 0.5F, + InternalBiomeAPI.OTHER_END_LAND + ); + + + public static final BCLBiome END_BARRENS = InternalBiomeAPI.wrapNativeBiome( + Biomes.END_BARRENS, + InternalBiomeAPI.OTHER_END_BARRENS + ); + + public static final BCLBiome SMALL_END_ISLANDS = InternalBiomeAPI.wrapNativeBiome( + Biomes.SMALL_END_ISLANDS, + InternalBiomeAPI.OTHER_END_VOID + ); + + /** + * Register {@link BCLBiome} instance and its {@link Biome} if necessary. + * + * @param bclbiome {@link BCLBiome} + * @return {@link BCLBiome} + */ + public static BCLBiome registerBiome(BCLBiome bclbiome) { + return registerBiome(bclbiome, BuiltinRegistries.BIOME); + } + + /** + * Register {@link BCLBiome} instance and its {@link Biome} if necessary. + * + * @param bclbiome {@link BCLBiome} + * @param dim The Dimension fo rthis Biome + * @return {@link BCLBiome} + */ + @Deprecated(forRemoval = true) + public static BCLBiome registerBiome(BCLBiome bclbiome, BiomeType dim) { + return registerBiome(bclbiome, dim, BuiltinRegistries.BIOME); + } + + /** + * Register {@link BCLBiome} instance and its {@link Biome} if necessary. + * + * @param bclbiome {@link BCLBiome} + * @param dim The Dimension fo rthis Biome + * @return {@link BCLBiome} + */ + @Deprecated(forRemoval = true) + static BCLBiome registerBiome(BCLBiome bclbiome, BiomeType dim, Registry registryOrNull) { + bclbiome._setIntendedType(dim); + return registerBiome(bclbiome, registryOrNull); + } + + /** + * Register {@link BCLBiome} instance and its {@link Biome} if necessary. + * + * @param bclbiome {@link BCLBiome} + * @return {@link BCLBiome} + */ + static BCLBiome registerBiome(BCLBiome bclbiome, Registry registryOrNull) { + BiomeType dim = bclbiome.getIntendedType(); + if (registryOrNull != null + && bclbiome.biomeToRegister != null + && registryOrNull.get(bclbiome.getID()) == null) { + Registry.register(registryOrNull, bclbiome.getBiomeKey(), bclbiome.biomeToRegister); + + BCLBiomeRegistry.register(bclbiome); + } + + if (dim != null && dim.is(BiomeType.NETHER)) { + TagManager.BIOMES.add(BiomeTags.IS_NETHER, bclbiome.getBiomeKey()); + TagManager.BIOMES.add(CommonBiomeTags.IN_NETHER, bclbiome.getBiomeKey()); + } else if (dim != null && dim.is(BiomeType.END)) { + TagManager.BIOMES.add(CommonBiomeTags.IN_END, bclbiome.getBiomeKey()); + TagManager.BIOMES.add(CommonBiomeTags.IN_END, bclbiome.getBiomeKey()); + } + + bclbiome.afterRegistration(); + + return bclbiome; + } + + public static BCLBiome registerSubBiome(BCLBiome parent, BCLBiome subBiome) { + return registerSubBiome( + parent, + subBiome, + parent.getIntendedType() + ); + } + + public static BCLBiome registerSubBiome(BCLBiome parent, Biome subBiome, float genChance) { + return registerSubBiome( + parent, + subBiome, + genChance, + parent.getIntendedType() + ); + } + + public static BCLBiome registerSubBiome(BCLBiome parent, BCLBiome subBiome, BiomeType dim) { + registerBiome(subBiome, dim); + parent.addSubBiome(subBiome); + + return subBiome; + } + + public static BCLBiome registerSubBiome(BCLBiome parent, Biome biome, float genChance, BiomeType dim) { + BCLBiome subBiome = new BCLBiome(biome, VanillaBiomeSettings.createVanilla().setGenChance(genChance).build()); + return registerSubBiome(parent, subBiome, dim); + } + + /** + * Register {@link BCLBiome} instance and its {@link Biome} if necessary. + * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands). + * + * @param biome {@link BCLBiome} + * @return {@link BCLBiome} + */ + public static BCLBiome registerEndLandBiome(BCLBiome biome) { + registerBiome(biome, BiomeType.BCL_END_LAND); + + float weight = biome.getGenChance(); + ResourceKey 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} + */ + public static BCLBiome registerEndVoidBiome(BCLBiome biome) { + registerBiome(biome, BiomeType.BCL_END_VOID); + + float weight = biome.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} + */ + public static BCLBiome registerEndCenterBiome(BCLBiome biome) { + registerBiome(biome, BiomeType.BCL_END_CENTER); + + float weight = biome.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} + */ + public static BCLBiome registerEndBarrensBiome(BCLBiome highlandBiome, BCLBiome biome) { + registerBiome(biome, BiomeType.BCL_END_BARRENS); + + float weight = biome.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} + */ + public static BCLBiome registerNetherBiome(BCLBiome bclBiome) { + registerBiome(bclBiome, BiomeType.BCL_NETHER); + + ResourceKey key = bclBiome.getBiomeKey(); + if (!bclBiome.isEdgeBiome()) { + bclBiome.forEachClimateParameter(p -> NetherBiomes.addNetherBiome(key, p)); + } + return bclBiome; + } + + + /** + * Get {@link BCLBiome} from {@link Biome} instance on server. Used to convert world biomes to BCLBiomes. + * + * @param biome - {@link Holder} from world. + * @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}. + */ + @Deprecated(forRemoval = true) + public static BCLBiome getFromBiome(Holder biome) { + if (InternalBiomeAPI.biomeRegistry == null) { + return BCLBiomeRegistry.EMPTY_BIOME; + } + return BCLBiomeRegistry + .getOrElseEmpty( + InternalBiomeAPI.registryAccess, + biome.unwrapKey().orElseThrow().location() + ); + } + + /** + * 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) { + ResourceLocation id = WorldBootstrap.getLastRegistryAccessOrElseBuiltin() + .registryOrThrow(Registry.BIOME_REGISTRY) + .getKey(biome); + endBiome = id == null + ? BCLBiomeRegistry.EMPTY_BIOME + : BCLBiomeRegistry.getOrElseEmpty(id); + 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 BuiltinRegistries.BIOME + .getResourceKey(biome) + .orElseGet(null); + } + + /** + * Get biome {@link ResourceLocation} from given {@link Biome}. + * + * @param biome - {@link Biome} from server world. + * @return biome {@link ResourceLocation}. + */ + public static ResourceLocation getBiomeID(Biome biome) { + ResourceLocation id = null; + if (InternalBiomeAPI.biomeRegistry != null) { + id = InternalBiomeAPI.biomeRegistry.getKey(biome); + } + if (id == null) { + id = BuiltinRegistries.BIOME.getKey(biome); + } + + if (id == null) { + BCLib.LOGGER.error("Unable to get ID for " + biome + ". Falling back to empty Biome..."); + id = 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); + } + + public static ResourceKey getBiomeKey(Holder biome) { + return biome.unwrapKey().orElse(null); + } + + public static ResourceKey getBiomeKeyOrThrow(Holder biome) { + return biome.unwrapKey().orElseThrow(); + } + + public static Holder getBiomeHolder(BCLBiome biome) { + return getBiomeHolder(biome.getBiomeKey()); + } + + public static Holder getBiomeHolder(Biome biome) { + Optional> key = Optional.empty(); + if (InternalBiomeAPI.biomeRegistry != null) { + key = InternalBiomeAPI.biomeRegistry.getResourceKey(biome); + } else { + key = BuiltinRegistries.BIOME.getResourceKey(biome); + } + + return getBiomeHolder(key.orElseThrow()); + } + + public static Holder getBiomeHolder(ResourceKey biomeKey) { + if (InternalBiomeAPI.biomeRegistry != null) { + return InternalBiomeAPI.biomeRegistry.getOrCreateHolderOrThrow(biomeKey); + } + return BuiltinRegistries.BIOME.getOrCreateHolderOrThrow(biomeKey); + } + + public static Holder getBiomeHolder(ResourceLocation biome) { + return getBiomeHolder(ResourceKey.create(Registry.BIOME_REGISTRY, biome)); + } + + /** + * Get {@link BCLBiome} from given {@link ResourceLocation}. + * + * @param biomeID - biome {@link ResourceLocation}. + * @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}. + */ + public static BCLBiome getBiome(ResourceLocation biomeID) { + if (biomeID == null) return BCLBiomeRegistry.EMPTY_BIOME; + return BCLBiomeRegistry.getOrElseEmpty(biomeID); + } + + /** + * Get {@link BCLBiome} from given {@link Biome}. + * + * @param biome - biome {@link Biome}. + * @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}. + */ + public static BCLBiome getBiome(Biome biome) { + return getBiome(BiomeAPI.getBiomeID(biome)); + } + + /** + * Get {@link BCLBiome} from given {@link Biome}. + * + * @param biome - biome {@link Biome}. + * @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}. + */ + public static BCLBiome getBiome(Holder biome) { + return getBiome(BiomeAPI.getBiomeID(biome)); + } + + /** + * Check if biome with {@link ResourceLocation} exists in API registry. + * + * @param biomeID - biome {@link ResourceLocation}. + * @return {@code true} if biome exists in API registry and {@code false} if not. + */ + public static boolean hasBiome(ResourceLocation biomeID) { + return BCLBiomeRegistry.get(biomeID) != null; + } + + public static Holder getFromRegistry(ResourceLocation biomeID) { + if (InternalBiomeAPI.biomeRegistry != null) + return InternalBiomeAPI.biomeRegistry.getHolder(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID)) + .orElseThrow(); + return getFromBuiltinRegistry(biomeID); + } + + @Nullable + public static Holder getFromRegistry(ResourceKey key) { + if (InternalBiomeAPI.biomeRegistry != null) + return InternalBiomeAPI.biomeRegistry.getHolder(key).orElseThrow(); + return getFromBuiltinRegistry(key); + } + + @Nullable + public static Holder getFromBuiltinRegistry(ResourceLocation biomeID) { + return BuiltinRegistries.BIOME.getHolder(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID)).orElse(null); + } + + @Nullable + public static Holder getFromBuiltinRegistry(ResourceKey key) { + return BuiltinRegistries.BIOME.getHolder(key).orElse(null); + } + + @Deprecated(forRemoval = true) + public static boolean registryContains(ResourceKey key) { + if (InternalBiomeAPI.biomeRegistry != null) + return InternalBiomeAPI.biomeRegistry.containsKey(key); + return builtinRegistryContains(key); + } + + @Nullable + @Deprecated(forRemoval = true) + public static boolean builtinRegistryContains(ResourceKey key) { + return BuiltinRegistries.BIOME.containsKey(key); + } + + public static boolean isDatapackBiome(ResourceLocation biomeID) { + return getFromBuiltinRegistry(biomeID) == null; + } + + public static boolean wasRegisteredAs(ResourceLocation biomeID, BiomeType dim) { + if (BCLBiomeRegistry.EMPTY_BIOME.getID().equals(biomeID)) + return false; + return BCLBiomeRegistry.getOrElseEmpty(biomeID).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); + } + + + /** + * Create a unique sort order for all Features of the Biome. This method is automatically called for each Biome + * after all biome Modifications were executed. + * + * @param biome The {@link Biome} to sort the features for + */ + public static void sortBiomeFeatures(Holder biome) { + sortBiomeFeatures(biome.value()); + } + + static void sortBiomeFeatures(Biome biome) { +// BiomeGenerationSettings settings = biome.getGenerationSettings(); +// BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) settings; +// List> featureList = CollectionsUtil.getMutable(accessor.bclib_getFeatures()); +// final int size = featureList.size(); +// for (int i = 0; i < size; i++) { +// List> features = getFeaturesListCopy(featureList, i); +// sortFeatures(features); +// featureList.set(i, HolderSet.direct(features)); +// } +// accessor.bclib_setFeatures(featureList); + } + + /** + * Adds new features to existing biome. + * + * @param biome {@link Biome} to add features in. + * @param feature {@link ConfiguredFeature} to add. + */ + public static void addBiomeFeature(Holder biome, BCLFeature feature) { + addBiomeFeature(biome, feature.getDecoration(), feature.getPlacedFeature()); + } + + /** + * Adds new features to existing biome. + * + * @param biome {@link Biome} to add features in. + * @param feature {@link ConfiguredFeature} to add. + */ + public static void addBiomeFeature( + Holder biome, + org.betterx.bclib.api.v3.levelgen.features.BCLFeature feature + ) { + addBiomeFeature(biome, feature.getDecoration(), feature.getPlacedFeature()); + } + + /** + * Adds new features to existing biome. + * + * @param biome {@link Biome} to add features in. + * @param step a {@link Decoration} step for the feature. + * @param featureList {@link ConfiguredFeature} to add. + */ + public static void addBiomeFeature(Holder biome, 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; + PalettedContainer> palette = chunk.getSection(sectionY).getBiomes(); + palette.set((pos.getX() & 15) >> 2, (pos.getY() & 15) >> 2, (pos.getZ() & 15) >> 2, biome); + } + + /** + * 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 void sortFeatures(List> features) { +// InternalBiomeAPI.initFeatureOrder(); +// +// Set> featuresWithoutDuplicates = Sets.newHashSet(); +// features.forEach(holder -> featuresWithoutDuplicates.add(holder)); +// +// if (featuresWithoutDuplicates.size() != features.size()) { +// features.clear(); +// featuresWithoutDuplicates.forEach(feature -> features.add(feature)); +// } +// +// features.forEach(feature -> { +// InternalBiomeAPI.FEATURE_ORDER.computeIfAbsent( +// feature, +// f -> InternalBiomeAPI.FEATURE_ORDER_ID.getAndIncrement() +// ); +// }); +// +// features.sort((f1, f2) -> { +// int v1 = InternalBiomeAPI.FEATURE_ORDER.getOrDefault(f1, 70000); +// int v2 = InternalBiomeAPI.FEATURE_ORDER.getOrDefault(f2, 70000); +// return Integer.compare(v1, v2); +// }); + } + + + private static List> 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()); + } + + + /** + * Register {@link BCLBiome} instance and its {@link Biome} if necessary. + * After that biome will be added to BCLib Nether Biome Generator and into Fabric Biome API. + * + * @param biome {@link BCLBiome} + * @return {@link BCLBiome} + */ + @Deprecated(forRemoval = true) + public static BCLBiome registerNetherBiome(Biome biome) { + return InternalBiomeAPI.wrapNativeBiome(biome, -1, InternalBiomeAPI.OTHER_NETHER); + } + + @Deprecated(forRemoval = true) + public static BCLBiome registerEndLandBiome(Holder biome) { + return InternalBiomeAPI.wrapNativeBiome(biome.unwrapKey().orElseThrow(), InternalBiomeAPI.OTHER_END_LAND); + } + + /** + * Register {@link BCLBiome} instance and its {@link Biome} if necessary. + * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands). + * + * @param biome {@link BCLBiome} + * @return {@link BCLBiome} + */ + @Deprecated(forRemoval = true) + public static BCLBiome registerEndVoidBiome(Holder biome) { + return InternalBiomeAPI.wrapNativeBiome(biome.unwrapKey().orElseThrow(), InternalBiomeAPI.OTHER_END_VOID); + } + + /** + * Register {@link BCLBiome} wrapper for {@link Biome}. + * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands). + * + * @param biome {@link BCLBiome}; + * @param genChance float generation chance. + * @return {@link BCLBiome} + */ + @Deprecated(forRemoval = true) + public static BCLBiome registerEndLandBiome(Holder biome, float genChance) { + return InternalBiomeAPI.wrapNativeBiome( + biome.unwrapKey().orElseThrow(), + genChance, + InternalBiomeAPI.OTHER_END_LAND + ); + } + + /** + * Register {@link BCLBiome} instance and its {@link Biome} if necessary. + * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands). + * + * @param biome {@link BCLBiome}. + * @param genChance float generation chance. + * @return {@link BCLBiome} + */ + @Deprecated(forRemoval = true) + public static BCLBiome registerEndVoidBiome(Holder biome, float genChance) { + return InternalBiomeAPI.wrapNativeBiome( + biome.unwrapKey().orElseThrow(), + genChance, + InternalBiomeAPI.OTHER_END_VOID + ); + } + + + @Deprecated(forRemoval = true) + public static BCLBiome registerEndBiome(Holder biome) { + BCLBiome bclBiome = new BCLBiome(biome.value(), null); + + registerBiome(bclBiome, BiomeType.END); + return bclBiome; + } + + + @Deprecated(forRemoval = true) + public static BCLBiome registerCenterBiome(Holder biome) { + BCLBiome bclBiome = new BCLBiome(biome.value(), null); + + registerBiome(bclBiome, BiomeType.END_CENTER); + return bclBiome; + } +} 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..0b0cce96 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/InternalBiomeAPI.java @@ -0,0 +1,378 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.interfaces.BiomeSourceAccessor; +import org.betterx.bclib.interfaces.NoiseGeneratorSettingsProvider; +import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor; +import org.betterx.worlds.together.surfaceRules.SurfaceRuleProvider; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback; +import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.commons.lang3.mutable.MutableInt; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Stream; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class InternalBiomeAPI { + public static final BiomeAPI.BiomeType OTHER_NETHER = new BiomeAPI.BiomeType( + "OTHER_NETHER", + BiomeAPI.BiomeType.NETHER + ); + public static final BiomeAPI.BiomeType OTHER_END_LAND = new BiomeAPI.BiomeType( + "OTHER_END_LAND", + BiomeAPI.BiomeType.END_LAND + ); + public static final BiomeAPI.BiomeType OTHER_END_VOID = new BiomeAPI.BiomeType( + "OTHER_END_VOID", + BiomeAPI.BiomeType.END_VOID + ); + public static final BiomeAPI.BiomeType OTHER_END_CENTER = new BiomeAPI.BiomeType( + "OTHER_END_CENTER", + BiomeAPI.BiomeType.END_CENTER + ); + public static final BiomeAPI.BiomeType OTHER_END_BARRENS = new BiomeAPI.BiomeType( + "OTHER_END_BARRENS", + BiomeAPI.BiomeType.END_BARRENS + ); + static final Map CLIENT = Maps.newHashMap(); + static final Map, Integer> FEATURE_ORDER = 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; + + static void initFeatureOrder() { + if (!FEATURE_ORDER.isEmpty()) { + return; + } + + BuiltinRegistries.BIOME + .entrySet() + .stream() + .filter(entry -> entry + .getKey() + .location() + .getNamespace() + .equals("minecraft")) + .map(Map.Entry::getValue) + .map(biome -> (BiomeGenerationSettingsAccessor) biome.getGenerationSettings()) + .map(BiomeGenerationSettingsAccessor::bclib_getFeatures) + .forEach(stepFeatureSuppliers -> stepFeatureSuppliers.forEach(step -> step.forEach(feature -> { + FEATURE_ORDER.computeIfAbsent(feature, f -> FEATURE_ORDER_ID.getAndIncrement()); + }))); + } + + public static RegistryAccess worldRegistryAccess() { + return registryAccess; + } + + /** + * Initialize registry for current server. + * + * @param access - The new, active {@link RegistryAccess} for the current session. + */ + public static void initRegistry(RegistryAccess access) { + if (access != registryAccess) { + registryAccess = access; + Registry biomeRegistry = access.registry(Registry.BIOME_REGISTRY).orElse(null); + + if (biomeRegistry != InternalBiomeAPI.biomeRegistry) { + InternalBiomeAPI.biomeRegistry = biomeRegistry; + CLIENT.clear(); + + BIOMES_TO_SORT.forEach(id -> { + Biome b = biomeRegistry.get(id); + if (b != null) { + BCLib.LOGGER.info("Found non fabric/bclib Biome: " + id + "(" + b + ")"); + BiomeAPI.sortBiomeFeatures(b); + } else { + BCLib.LOGGER.info("Unknown Biome: " + id); + } + }); + } + } + } + + /** + * For internal use only. + *

+ * 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); + } + }); + } + } + } + + @Deprecated(forRemoval = true) + public static void applyModificationsDeprecated(ServerLevel level) { + //TODO: Now Disabled, because we fix the settings when everything gets loaded + if (level != null) return; + + NoiseGeneratorSettings noiseGeneratorSettings = null; + final ChunkGenerator chunkGenerator = level.getChunkSource().getGenerator(); + final BiomeSource source = chunkGenerator.getBiomeSource(); + final Set> biomes = source.possibleBiomes(); + + if (chunkGenerator instanceof NoiseGeneratorSettingsProvider gen) + noiseGeneratorSettings = gen.bclib_getNoiseGeneratorSettings(); + + // Datapacks (like Amplified Nether)will change the GeneratorSettings upon load, so we will + // only use the default Setting for Nether/End if we were unable to find a settings object + if (noiseGeneratorSettings == null) { + if (level.dimension() == Level.NETHER) { + noiseGeneratorSettings = BuiltinRegistries.NOISE_GENERATOR_SETTINGS.get(NoiseGeneratorSettings.NETHER); + } else if (level.dimension() == Level.END) { + noiseGeneratorSettings = BuiltinRegistries.NOISE_GENERATOR_SETTINGS.get(NoiseGeneratorSettings.END); + } + } + + List>> modifications = MODIFICATIONS.get(level + .dimensionTypeRegistration() + .unwrapKey() + .orElseThrow()); + for (Holder biomeHolder : biomes) { + if (biomeHolder.isBound()) { + applyModificationsAndUpdateFeatures(modifications, biomeHolder); + } + } + + + if (noiseGeneratorSettings != null) { + final SurfaceRuleProvider provider = SurfaceRuleProvider.class.cast(noiseGeneratorSettings); + // Multiple Biomes can use the same generator. So we need to keep track of all Biomes that are + // Provided by all the BiomeSources that use the same generator. + // This happens for example when using the MiningDimensions, which reuses the generator for the + // Nethering Dimension + //MODIFIED_SURFACE_PROVIDERS.add(provider); + + //provider.bclib_addBiomeSource(source); + } else { + BCLib.LOGGER.warning("No generator for " + source); + } + + ((BiomeSourceAccessor) source).bclRebuildFeatures(); + } + + public static void applyModifications(BiomeSource source, ResourceKey dimension) { + BCLib.LOGGER.info("Apply Modifications for " + dimension.location() + " BiomeSource " + source); + /*if (dimension.location().equals(LevelStem.NETHER)){ + if (source instanceof BCLBiomeSource s) { + NetherBiomes.useLegacyGeneration = s.biomeSourceVersion==BCLBiomeSource.BIOME_SOURCE_VERSION_SQUARE; + } + }*/ + final Set> 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); + }); + } + + BiomeAPI.sortBiomeFeatures(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 wrapNativeBiome(ResourceKey biomeKey, BiomeAPI.BiomeType type) { + return wrapNativeBiome(biomeKey, -1, type); + } + + /** + * Register {@link BCLBiome} wrapper for {@link Biome}. + * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands). + * + * @param biomeKey The source biome to wrap + * @param genChance generation chance. If <0 the default genChance is used + * @return {@link BCLBiome} + */ + public static BCLBiome wrapNativeBiome(ResourceKey biomeKey, float genChance, BiomeAPI.BiomeType type) { + return wrapNativeBiome( + biomeKey, + genChance < 0 ? null : VanillaBiomeSettings.createVanilla().setGenChance(genChance).build(), + type + ); + } + + public static BCLBiome wrapNativeBiome( + ResourceKey biomeKey, + BCLBiome edgeBiome, + int edgeBiomeSize, + float genChance, + BiomeAPI.BiomeType type + ) { + VanillaBiomeSettings.Builder settings = VanillaBiomeSettings.createVanilla(); + if (genChance >= 0) settings.setGenChance(genChance); + settings.setEdge(edgeBiome); + settings.setEdgeSize(edgeBiomeSize); + return wrapNativeBiome(biomeKey, settings.build(), type); + } + + /** + * Register {@link BCLBiome} wrapper for {@link Biome}. + * After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands). + * + * @param biomeKey The source biome to wrap + * @param setings the {@link VanillaBiomeSettings} to use + * @return {@link BCLBiome} + */ + private static BCLBiome wrapNativeBiome( + ResourceKey biomeKey, + VanillaBiomeSettings setings, + BiomeAPI.BiomeType type + ) { + BCLBiome bclBiome = BiomeAPI.getBiome(biomeKey.location()); + if (bclBiome == BCLBiomeRegistry.EMPTY_BIOME) { + bclBiome = new BCLBiome(biomeKey, setings); + bclBiome._setIntendedType(type); + } + + BiomeAPI.registerBiome(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 biome The source biome to wrap + * @param genChance generation chance. + * @return {@link BCLBiome} + */ + @Deprecated(forRemoval = true) + static BCLBiome wrapNativeBiome(Biome biome, float genChance, BiomeAPI.BiomeType type) { + BCLBiome bclBiome = BiomeAPI.getBiome(biome); + if (bclBiome == BCLBiomeRegistry.EMPTY_BIOME) { + bclBiome = new BCLBiome( + biome, + genChance < 0 ? null : VanillaBiomeSettings.createVanilla().setGenChance(genChance).build() + ); + } + + BiomeAPI.registerBiome(bclBiome, type, null); + return bclBiome; + } + + static { + DynamicRegistrySetupCallback.EVENT.register(registryManager -> { + Optional> oBiomeRegistry = registryManager.registry(Registry.BIOME_REGISTRY); + RegistryEntryAddedCallback + .event(oBiomeRegistry.get()) + .register((rawId, id, biome) -> { + BCLBiome b = BiomeAPI.getBiome(id); + if (!"minecraft".equals(id.getNamespace()) && (b == null || b == BCLBiomeRegistry.EMPTY_BIOME)) { + //BCLib.LOGGER.info(" #### " + rawId + ", " + biome + ", " + id); + BIOMES_TO_SORT.add(id); + } + }); + }); + } + + public static boolean registryContainsBound(ResourceKey key) { + Registry reg = biomeRegistry; + if (reg == null) reg = BuiltinRegistries.BIOME; + + if (reg.containsKey(key)) { + return reg.getOrCreateHolderOrThrow(key).isBound(); + } + return false; + } +} 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/BCLCommonFeatures.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/BCLCommonFeatures.java new file mode 100644 index 00000000..a3983e06 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/BCLCommonFeatures.java @@ -0,0 +1,166 @@ +package org.betterx.bclib.api.v2.levelgen.features; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.levelgen.GenerationStep.Decoration; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; +import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.structure.templatesystem.BlockMatchTest; + +@Deprecated(forRemoval = true) +public class BCLCommonFeatures { + /** + * Will create a basic plant feature. + * + * @param id {@link ResourceLocation} feature ID. + * @param feature {@link Feature} with {@link NoneFeatureConfiguration} config. + * @param density iterations per chunk. + * @return new BCLFeature instance. + */ + public static BCLFeature makeVegetationFeature( + ResourceLocation id, + Feature feature, + int density + ) { + return makeVegetationFeature(id, feature, density, false); + } + + /** + * Will create a basic plant feature. + * + * @param id {@link ResourceLocation} feature ID. + * @param feature {@link Feature} with {@link NoneFeatureConfiguration} config. + * @param density iterations per chunk. + * @param allHeight if {@code true} will generate plant on all layers, if {@code false} - only on surface. + * @return new BCLFeature instance. + */ + public static BCLFeature makeVegetationFeature( + ResourceLocation id, + Feature feature, + int density, + boolean allHeight + ) { + if (allHeight) { + return BCLFeatureBuilder + .start(id, feature) + .countLayers(density) + .squarePlacement() + .onlyInBiome() + .buildAndRegister(); + } else { + return BCLFeatureBuilder + .start(id, feature) + .countMax(density) + .squarePlacement() + .heightmap() + .onlyInBiome() + .buildAndRegister(); + } + } + + /** + * Will create feature which will be generated once in each chunk. + * + * @param id {@link ResourceLocation} feature ID. + * @param decoration {@link Decoration} feature step. + * @param feature {@link Feature} with {@link NoneFeatureConfiguration} config. + * @return new BCLFeature instance. + */ + public static BCLFeature makeChunkFeature( + ResourceLocation id, + Decoration decoration, + Feature feature + ) { + return BCLFeatureBuilder.start(id, feature).decoration(decoration).count(1).onlyInBiome().buildAndRegister(); + } + + /** + * Will create feature with chanced decoration, chance for feature to generate per chunk is 1 / chance. + * + * @param id {@link ResourceLocation} feature ID. + * @param decoration {@link Decoration} feature step. + * @param feature {@link Feature} with {@link NoneFeatureConfiguration} config. + * @param chance chance for feature to be generated in. + * @return new BCLFeature instance. + */ + public static BCLFeature makeChancedFeature( + ResourceLocation id, + Decoration decoration, + Feature feature, + int chance + ) { + return BCLFeatureBuilder.start(id, feature) + .decoration(decoration) + .onceEvery(chance) + .squarePlacement() + .onlyInBiome() + .buildAndRegister(); + } + + /** + * Will create feature with specified generation iterations per chunk. + * + * @param id {@link ResourceLocation} feature ID. + * @param decoration {@link Decoration} feature step. + * @param feature {@link Feature} with {@link NoneFeatureConfiguration} config. + * @param count iterations steps. + * @return new BCLFeature instance. + */ + public static BCLFeature makeCountFeature( + ResourceLocation id, + Decoration decoration, + Feature feature, + int count + ) { + return BCLFeatureBuilder.start(id, feature) + .decoration(decoration) + .count(count) + .squarePlacement() + .onlyInBiome() + .buildAndRegister(); + } + + /** + * Will create a basic ore feature. + * + * @param id {@link ResourceLocation} feature ID. + * @param blockOre {@link Decoration} feature step. + * @param hostBlock {@link Block} to generate feature in. + * @param veins iterations per chunk. + * @param veinSize size of ore vein. + * @param airDiscardChance chance that this orge gets discarded when it is exposed to air + * @param placement {@link net.minecraft.world.level.levelgen.placement.PlacementModifier} for the ore distribution, + * for example {@code PlacementUtils.FULL_RANGE}, {@code PlacementUtils.RANGE_10_10} + * @param rare when true, this is placed as a rare resource + * @return new BCLFeature instance. + */ + public static BCLFeature makeOreFeature( + ResourceLocation id, + Block blockOre, + Block hostBlock, + int veins, + int veinSize, + float airDiscardChance, + PlacementModifier placement, + boolean rare + ) { + BCLFeatureBuilder builder = BCLFeatureBuilder.start(id, Feature.ORE).decoration(Decoration.UNDERGROUND_ORES); + + if (rare) { + builder.onceEvery(veins); + } else { + builder.count(veins); + } + + builder.modifier(placement).squarePlacement().onlyInBiome(); + + return builder.buildAndRegister(new OreConfiguration( + new BlockMatchTest(hostBlock), + blockOre.defaultBlockState(), + veinSize, + airDiscardChance + )); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/BCLFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/BCLFeature.java new file mode 100644 index 00000000..74ef073b --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/BCLFeature.java @@ -0,0 +1,290 @@ +package org.betterx.bclib.api.v2.levelgen.features; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.features.config.ScatterFeatureConfig; +import org.betterx.bclib.api.v2.levelgen.features.features.ScatterFeature; +import org.betterx.bclib.api.v2.levelgen.features.features.WeightedRandomSelectorFeature; +import org.betterx.bclib.api.v3.levelgen.features.BCLConfigureFeature; +import org.betterx.bclib.api.v3.levelgen.features.BCLFeatureBuilder; +import org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature; +import org.betterx.bclib.api.v3.levelgen.features.config.ConditionFeatureConfig; +import org.betterx.bclib.api.v3.levelgen.features.config.PlaceFacingBlockConfig; +import org.betterx.bclib.api.v3.levelgen.features.config.SequenceFeatureConfig; +import org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.data.worldgen.features.FeatureUtils; +import net.minecraft.data.worldgen.placement.PlacementUtils; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.levelgen.GenerationStep.Decoration; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; +import net.minecraft.world.level.levelgen.feature.configurations.RandomFeatureConfiguration; +import net.minecraft.world.level.levelgen.feature.configurations.RandomPatchConfiguration; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; + +import java.util.Map; +import java.util.Optional; +import java.util.Random; + +/** + * @param + * @param + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature} instead + */ +@Deprecated(forRemoval = true) +public class BCLFeature, FC extends FeatureConfiguration> { + /** + * @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#PLACE_BLOCK} + */ + @Deprecated(forRemoval = true) + public static final Feature PLACE_BLOCK = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.PLACE_BLOCK; + + @Deprecated(forRemoval = true) + public static final Feature SCATTER_ON_SOLID = register( + BCLib.makeID("scatter_on_solid"), + new ScatterFeature<>(ScatterFeatureConfig.OnSolid.CODEC) + ); + + @Deprecated(forRemoval = true) + public static final Feature SCATTER_EXTEND_TOP = register( + BCLib.makeID("scatter_extend_top"), + new ScatterFeature<>(ScatterFeatureConfig.ExtendTop.CODEC) + ); + + @Deprecated(forRemoval = true) + public static final Feature SCATTER_EXTEND_BOTTOM = register( + BCLib.makeID("scatter_extend_bottom"), + new ScatterFeature<>(ScatterFeatureConfig.ExtendBottom.CODEC) + ); + + @Deprecated(forRemoval = true) + public static final Feature RANDOM_SELECTOR = register( + BCLib.makeID("random_select"), + new WeightedRandomSelectorFeature() + ); + + /** + * @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#TEMPLATE} + */ + @Deprecated(forRemoval = true) + public static final Feature TEMPLATE = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.TEMPLATE; + /** + * @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#MARK_POSTPROCESSING} + */ + @Deprecated(forRemoval = true) + public static final Feature MARK_POSTPROCESSING = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.MARK_POSTPROCESSING; + /** + * @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#SEQUENCE} + */ + @Deprecated(forRemoval = true) + public static final Feature SEQUENCE = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.SEQUENCE; + /** + * @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#CONDITION} + */ + @Deprecated(forRemoval = true) + public static final Feature CONDITION = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.CONDITION; + + public final ResourceLocation id; + + org.betterx.bclib.api.v3.levelgen.features.BCLFeature proxy; + + @Deprecated(forRemoval = true) + public BCLFeature( + ResourceLocation id, + F feature, + Decoration featureStep, + FC configuration, + PlacementModifier[] modifiers + ) { + this(id, feature, featureStep, configuration, buildPlacedFeature(id, feature, configuration, modifiers)); + } + + private static boolean containsObj(Registry registry, E obj) { + Optional, E>> optional = registry + .entrySet() + .stream() + .filter(entry -> entry.getValue() == obj) + .findAny(); + return optional.isPresent(); + } + + private static , FC extends FeatureConfiguration> org.betterx.bclib.api.v3.levelgen.features.BCLFeature build( + ResourceLocation id, + F feature, + Decoration featureStep, + FC configuration, + Holder placedFeature + ) { + BCLConfigureFeature cfg = BCLFeatureBuilder.start(id, feature) + .configuration(configuration) + .build(); + if (!BuiltinRegistries.PLACED_FEATURE.containsKey(id)) { + Registry.register(BuiltinRegistries.PLACED_FEATURE, id, placedFeature.value()); + } + if (!Registry.FEATURE.containsKey(id) && !containsObj(Registry.FEATURE, feature)) { + Registry.register(Registry.FEATURE, id, feature); + } + return new org.betterx.bclib.api.v3.levelgen.features.BCLFeature<>(cfg, placedFeature, featureStep); + } + + @Deprecated(forRemoval = true) + public BCLFeature( + ResourceLocation id, + F feature, + Decoration featureStep, + FC configuration, + Holder placedFeature + ) { + this(build(id, feature, featureStep, configuration, placedFeature)); + } + + @Deprecated(forRemoval = true) + public BCLFeature(org.betterx.bclib.api.v3.levelgen.features.BCLFeature proxy) { + this.proxy = proxy; + this.id = proxy.configuredFeature.id; + } + + private static > Holder buildPlacedFeature( + ResourceLocation id, + F feature, + FC configuration, + PlacementModifier[] modifiers + ) { + Holder> configuredFeature; + if (!BuiltinRegistries.CONFIGURED_FEATURE.containsKey(id)) { + configuredFeature = (Holder>) (Object) FeatureUtils.register( + id.toString(), + feature, + configuration + ); + } else { + configuredFeature = BuiltinRegistries.CONFIGURED_FEATURE + .getHolder(ResourceKey.create( + BuiltinRegistries.CONFIGURED_FEATURE.key(), + id + )) + .orElseThrow(); + } + + if (!BuiltinRegistries.PLACED_FEATURE.containsKey(id)) { + return PlacementUtils.register(id.toString(), configuredFeature, modifiers); + } else { + return BuiltinRegistries.PLACED_FEATURE.getHolder(ResourceKey.create( + BuiltinRegistries.PLACED_FEATURE.key(), + id + )).orElseThrow(); + } + } + + /** + * @param string + * @param feature + * @param + * @param + * @return + * @deprecated Use {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#register(ResourceLocation, Feature)} instead + */ + @Deprecated(forRemoval = true) + public static > F register( + ResourceLocation string, + F feature + ) { + return org.betterx.bclib.api.v3.levelgen.features.BCLFeature.register(string, feature); + } + + /** + * Get raw feature. + * + * @return {@link Feature}. + */ + public F getFeature() { + return proxy.getFeature(); + } + + public BCLConfigureFeature getConfFeature() { + return proxy.configuredFeature; + } + + /** + * Get configured feature. + * + * @return {@link PlacedFeature}. + */ + public Holder getPlacedFeature() { + return proxy.getPlacedFeature(); + } + + /** + * Get feature decoration step. + * + * @return {@link Decoration}. + */ + public Decoration getDecoration() { + return proxy.getDecoration(); + } + + public FC getConfiguration() { + return proxy.getConfiguration(); + } + + public boolean place(ServerLevel level, BlockPos pos, Random random) { + return place(this.getFeature(), this.getConfiguration(), level, pos, random); + } + + private static boolean placeUnbound( + Feature feature, + FeatureConfiguration config, + ServerLevel level, + BlockPos pos, + Random random + ) { + if (config instanceof RandomPatchConfiguration rnd) { + var configured = rnd.feature().value().feature().value(); + feature = configured.feature(); + config = configured.config(); + } + + if (feature instanceof UserGrowableFeature growable) { + return growable.grow(level, pos, random, config); + } + + FeaturePlaceContext context = new FeaturePlaceContext( + Optional.empty(), + level, + level.getChunkSource().getGenerator(), + random, + pos, + config + ); + return feature.place(context); + } + + public static boolean place( + Feature feature, + ServerLevel level, + BlockPos pos, + Random random + ) { + return placeUnbound(feature, FeatureConfiguration.NONE, level, pos, random); + } + + public static boolean place( + Feature feature, + FC config, + ServerLevel level, + BlockPos pos, + Random random + ) { + return placeUnbound(feature, config, level, pos, random); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/BCLFeatureBuilder.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/BCLFeatureBuilder.java new file mode 100644 index 00000000..c34eb278 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/BCLFeatureBuilder.java @@ -0,0 +1,402 @@ +package org.betterx.bclib.api.v2.levelgen.features; + +import org.betterx.bclib.api.v2.levelgen.features.placement.IsEmptyAboveSampledFilter; +import org.betterx.bclib.api.v2.levelgen.features.placement.MinEmptyFilter; +import org.betterx.bclib.api.v2.levelgen.features.placement.Stencil; +import org.betterx.bclib.api.v3.levelgen.features.placement.*; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import net.minecraft.core.Direction; +import net.minecraft.core.Vec3i; +import net.minecraft.data.worldgen.placement.PlacementUtils; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.valueproviders.IntProvider; +import net.minecraft.util.valueproviders.UniformInt; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.GenerationStep.Decoration; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.SimpleBlockFeature; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.level.levelgen.feature.configurations.SimpleBlockConfiguration; +import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider; +import net.minecraft.world.level.levelgen.placement.*; +import net.minecraft.world.level.material.Material; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * @param + * @param + * @deprecated please use {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeatureBuilder} instead + */ +@Deprecated(forRemoval = true) +public class BCLFeatureBuilder> { + private final List modifications = new ArrayList<>(5); + private final ResourceLocation featureID; + private Decoration decoration = Decoration.VEGETAL_DECORATION; + private final F feature; + private BlockStateProvider provider; + + private BCLFeatureBuilder(ResourceLocation featureID, F feature) { + this.featureID = featureID; + this.feature = feature; + } + + /** + * Starts a new {@link BCLFeature} builder. + * + * @param featureID {@link ResourceLocation} feature identifier. + * @param feature {@link Feature} to construct. + * @return {@link BCLFeatureBuilder} instance. + */ + public static BCLFeatureBuilder start(ResourceLocation featureID, Feature feature) { + return new BCLFeatureBuilder(featureID, feature); + } + + public static BCLFeatureBuilder start( + ResourceLocation featureID, + Block block + ) { + return start(featureID, BlockStateProvider.simple(block)); + } + + public static BCLFeatureBuilder start( + ResourceLocation featureID, + BlockState state + ) { + return start(featureID, BlockStateProvider.simple(state)); + } + + public static BCLFeatureBuilder start( + ResourceLocation featureID, + BlockStateProvider provider + ) { + BCLFeatureBuilder builder = new BCLFeatureBuilder( + featureID, + Feature.SIMPLE_BLOCK + ); + builder.provider = provider; + return builder; + } + + /** + * Set generation step for the feature. Default is {@code VEGETAL_DECORATION}. + * + * @param decoration {@link Decoration} step. + * @return same {@link BCLFeatureBuilder} instance. + */ + public BCLFeatureBuilder decoration(Decoration decoration) { + this.decoration = decoration; + return this; + } + + /** + * Add feature placement modifier. Used as a condition for feature how to generate. + * + * @param modifier {@link PlacementModifier}. + * @return same {@link BCLFeatureBuilder} instance. + */ + public BCLFeatureBuilder modifier(PlacementModifier modifier) { + modifications.add(modifier); + return this; + } + + public BCLFeatureBuilder modifier(List modifiers) { + modifications.addAll(modifiers); + return this; + } + + /** + * Generate feature in certain iterations (per chunk). + * + * @param count how many times feature will be generated in chunk. + * @return same {@link BCLFeatureBuilder} instance. + */ + public BCLFeatureBuilder count(int count) { + return modifier(CountPlacement.of(count)); + } + + /** + * Generate feature in certain iterations (per chunk). Count can be between 0 and max value. + * + * @param count maximum amount of iterations per chunk. + * @return same {@link BCLFeatureBuilder} instance. + */ + public BCLFeatureBuilder countMax(int count) { + return modifier(CountPlacement.of(UniformInt.of(0, count))); + } + + public BCLFeatureBuilder countRange(int min, int max) { + return modifier(CountPlacement.of(UniformInt.of(min, max))); + } + + /** + * Generate feature in certain iterations (per chunk). + * Feature will be generated on all layers (example - Nether plants). + * + * @param count how many times feature will be generated in chunk layers. + * @return same {@link BCLFeatureBuilder} instance. + */ + @SuppressWarnings("deprecation") + public BCLFeatureBuilder countLayers(int count) { + return modifier(CountOnEveryLayerPlacement.of(count)); + } + + /** + * Generate feature in certain iterations (per chunk). Count can be between 0 and max value. + * Feature will be generated on all layers (example - Nether plants). + * + * @param count maximum amount of iterations per chunk layers. + * @return same {@link BCLFeatureBuilder} instance. + */ + @SuppressWarnings("deprecation") + public BCLFeatureBuilder countLayersMax(int count) { + return modifier(CountOnEveryLayerPlacement.of(UniformInt.of(0, count))); + } + + /** + * Will place feature once every n-th attempts (in average). + * + * @param n amount of attempts. + * @return same {@link BCLFeatureBuilder} instance. + */ + public BCLFeatureBuilder onceEvery(int n) { + return modifier(RarityFilter.onAverageOnceEvery(n)); + } + + /** + * Restricts feature generation only to biome where feature was added. + * + * @return same {@link BCLFeatureBuilder} instance. + */ + public BCLFeatureBuilder onlyInBiome() { + return modifier(BiomeFilter.biome()); + } + + public BCLFeatureBuilder squarePlacement() { + return modifier(InSquarePlacement.spread()); + } + + public BCLFeatureBuilder stencil() { + return modifier(Stencil.all()); + } + + public BCLFeatureBuilder all() { + return modifier(All.simple()); + } + + public BCLFeatureBuilder stencilOneIn4() { + return modifier(Stencil.oneIn4()); + } + + /** + * Select random height that is 10 above min Build height and 10 below max generation height + * + * @return The instance it was called on + */ + public BCLFeatureBuilder randomHeight10FromFloorCeil() { + return modifier(PlacementUtils.RANGE_10_10); + } + + /** + * Select random height that is 4 above min Build height and 10 below max generation height + * + * @return The instance it was called on + */ + public BCLFeatureBuilder randomHeight4FromFloorCeil() { + return modifier(PlacementUtils.RANGE_4_4); + } + + /** + * Select random height that is 8 above min Build height and 10 below max generation height + * + * @return The instance it was called on + */ + public BCLFeatureBuilder randomHeight8FromFloorCeil() { + return modifier(PlacementUtils.RANGE_8_8); + } + + /** + * Select random height that is above min Build height and 10 below max generation height + * + * @return The instance it was called on + */ + public BCLFeatureBuilder randomHeight() { + return modifier(PlacementUtils.FULL_RANGE); + } + + public BCLFeatureBuilder isEmptyAbove4() { + return modifier(IsEmptyAboveSampledFilter.emptyAbove4()); + } + + public BCLFeatureBuilder isEmptyAbove2() { + return modifier(IsEmptyAboveSampledFilter.emptyAbove2()); + } + + public BCLFeatureBuilder isEmptyAbove() { + return modifier(IsEmptyAboveSampledFilter.emptyAbove()); + } + + public BCLFeatureBuilder isEmptyBelow4() { + return modifier(IsEmptyAboveSampledFilter.emptyBelow4()); + } + + public BCLFeatureBuilder isEmptyBelow2() { + return modifier(IsEmptyAboveSampledFilter.emptyBelow2()); + } + + public BCLFeatureBuilder isEmptyBelow() { + return modifier(IsEmptyAboveSampledFilter.emptyBelow()); + } + + public BCLFeatureBuilder isEmptyAbove(int d1, int d2) { + return modifier(new IsEmptyAboveSampledFilter(d1, d2)); + } + + public BCLFeatureBuilder onEveryLayer() { + return modifier(OnEveryLayer.simple()); + } + + public BCLFeatureBuilder underEveryLayer() { + return modifier(UnderEveryLayer.simple()); + } + + public BCLFeatureBuilder spreadHorizontal(IntProvider p) { + return modifier(RandomOffsetPlacement.horizontal(p)); + } + + public BCLFeatureBuilder spreadVertical(IntProvider p) { + return modifier(RandomOffsetPlacement.horizontal(p)); + } + + public BCLFeatureBuilder spread(IntProvider horizontal, IntProvider vertical) { + return modifier(RandomOffsetPlacement.of(horizontal, vertical)); + } + + public BCLFeatureBuilder offset(Direction dir) { + return modifier(Offset.inDirection(dir)); + } + + public BCLFeatureBuilder offset(Vec3i dir) { + return modifier(new Offset(dir)); + } + + /** + * Cast a downward ray with max {@code distance} length to find the next solid Block. + * + * @param distance The maximum search Distance + * @return The instance it was called on + * @see #findSolidSurface(Direction, int) for Details + */ + public BCLFeatureBuilder findSolidFloor(int distance) { + return modifier(FindSolidInDirection.down(distance)); + } + + public BCLFeatureBuilder noiseBasedCount(float noiseLevel, int belowNoiseCount, int aboveNoiseCount) { + return modifier(NoiseThresholdCountPlacement.of(noiseLevel, belowNoiseCount, aboveNoiseCount)); + } + + public BCLFeatureBuilder extendDown(int min, int max) { + return modifier(new Extend(Direction.DOWN, UniformInt.of(min, max))); + } + + public BCLFeatureBuilder inBasinOf(BlockPredicate... predicates) { + return modifier(new IsBasin(BlockPredicate.anyOf(predicates))); + } + + public BCLFeatureBuilder inOpenBasinOf(BlockPredicate... predicates) { + return modifier(IsBasin.openTop(BlockPredicate.anyOf(predicates))); + } + + public BCLFeatureBuilder is(BlockPredicate... predicates) { + return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.empty())); + } + + public BCLFeatureBuilder isAbove(BlockPredicate... predicates) { + return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.of(Direction.DOWN.getNormal()))); + } + + public BCLFeatureBuilder isUnder(BlockPredicate... predicates) { + return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.of(Direction.UP.getNormal()))); + } + + public BCLFeatureBuilder findSolidCeil(int distance) { + return modifier(FindSolidInDirection.up(distance)); + } + + public BCLFeatureBuilder hasMinimumDownwardSpace() { + return modifier(MinEmptyFilter.down()); + } + + public BCLFeatureBuilder hasMinimumUpwardSpace() { + return modifier(MinEmptyFilter.up()); + } + + + /** + * Cast a ray with max {@code distance} length to find the next solid Block. The ray will travel through replaceable + * Blocks (see {@link Material#isReplaceable()}) and will be accepted if it hits a block with the + * {@link CommonBlockTags#TERRAIN}-tag + * + * @param dir The direction the ray is cast + * @param distance The maximum search Distance + * @return The instance it was called on + * @see #findSolidSurface(Direction, int) for Details + */ + public BCLFeatureBuilder findSolidSurface(Direction dir, int distance) { + return modifier(new FindSolidInDirection(dir, distance, 0)); + } + + public BCLFeatureBuilder findSolidSurface(List dir, int distance, boolean randomSelect) { + return modifier(new FindSolidInDirection(dir, distance, randomSelect, 0)); + } + + public BCLFeatureBuilder heightmap() { + return modifier(PlacementUtils.HEIGHTMAP); + } + + public BCLFeatureBuilder heightmapTopSolid() { + return modifier(PlacementUtils.HEIGHTMAP_TOP_SOLID); + } + + public BCLFeatureBuilder heightmapWorldSurface() { + return modifier(PlacementUtils.HEIGHTMAP_WORLD_SURFACE); + } + + /** + * Builds a new {@link BCLFeature} instance. Features will be registered during this process. + * + * @param configuration any {@link FeatureConfiguration} for provided {@link Feature}. + * @return created {@link BCLFeature} instance. + */ + public BCLFeature buildAndRegister(FC configuration) { + PlacementModifier[] modifiers = modifications.toArray(new PlacementModifier[modifications.size()]); + return new BCLFeature(featureID, feature, decoration, configuration, modifiers); + } + + /** + * Builds a new {@link BCLFeature} instance with {@code NONE} {@link FeatureConfiguration}. + * Features will be registered during this process. + * + * @return created {@link BCLFeature} instance. + */ + public BCLFeature buildAndRegister() { + if (this.feature == Feature.SIMPLE_BLOCK && provider != null) + return buildAndRegister((FC) new SimpleBlockConfiguration(provider)); + return buildAndRegister((FC) FeatureConfiguration.NONE); + } + + @Deprecated(forRemoval = true) + public BCLFeature build(FC configuration) { + return buildAndRegister(configuration); + } + + @Deprecated(forRemoval = true) + public BCLFeature build() { + return buildAndRegister(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/FastFeatures.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/FastFeatures.java new file mode 100644 index 00000000..dd994fed --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/FastFeatures.java @@ -0,0 +1,255 @@ +package org.betterx.bclib.api.v2.levelgen.features; + +@Deprecated(forRemoval = true) +public class FastFeatures { +// @Deprecated(forRemoval = true) +// public static RandomPatchConfiguration grassPatch(BlockStateProvider stateProvider, int tries) { +// return FeatureUtils.simpleRandomPatchConfiguration( +// tries, +// PlacementUtils.onlyWhenEmpty(Feature.SIMPLE_BLOCK, new SimpleBlockConfiguration(stateProvider)) +// ); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature, ScatterFeatureConfig.OnSolid> vine( +// ResourceLocation location, +// boolean onFloor, +// boolean sparse, +// ScatterFeatureConfig.Builder builder +// ) { +// return scatter(location, onFloor, sparse, builder, +// org.betterx.bclib.api.v3.levelgen.features.BCLFeature.SCATTER_ON_SOLID +// ); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature scatter( +// ResourceLocation location, +// boolean onFloor, +// boolean sparse, +// ScatterFeatureConfig.Builder builder, +// Feature scatterFeature +// ) { +// BCLFeatureBuilder fBuilder = BCLFeatureBuilder.start(location, scatterFeature); +// if (onFloor) { +// fBuilder.findSolidFloor(3).isEmptyAbove2(); +// builder.onFloor(); +// } else { +// fBuilder.findSolidCeil(3).isEmptyBelow2(); +// builder.onCeil(); +// } +// if (sparse) { +// fBuilder.onceEvery(3); +// } +// +// return fBuilder +// .is(BlockPredicate.ONLY_IN_AIR_PREDICATE) +// .buildAndRegister(builder.build()); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature patch(ResourceLocation location, Block block) { +// return patch(location, block, 96, 7, 3); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// patch(ResourceLocation location, Block block, int attempts, int xzSpread, int ySpread) { +// return patch( +// location, +// attempts, +// xzSpread, +// ySpread, +// Feature.SIMPLE_BLOCK, +// new SimpleBlockConfiguration(BlockStateProvider.simple(block)) +// ); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// patch(ResourceLocation location, BlockStateProvider provider, int attempts, int xzSpread, int ySpread) { +// return patch( +// location, +// attempts, +// xzSpread, +// ySpread, +// Feature.SIMPLE_BLOCK, +// new SimpleBlockConfiguration(provider) +// ); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature patchWitRandomInt(ResourceLocation location, Block block, IntegerProperty prop) { +// return patchWitRandomInt(location, block, prop, 96, 7, 3); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// patchWitRandomInt( +// ResourceLocation location, +// Block block, +// IntegerProperty prop, +// int attempts, +// int xzSpread, +// int ySpread +// ) { +// return patch( +// location, +// attempts, +// xzSpread, +// ySpread, +// simple(location, ySpread, false, block.defaultBlockState(), prop) +// ); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// simple( +// ResourceLocation location, +// int searchDist, +// boolean rare, +// Feature feature +// ) { +// return simple(location, searchDist, rare, feature, NoneFeatureConfiguration.NONE); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// single(ResourceLocation location, Block block) { +// return single(location, BlockStateProvider.simple(block)); +// +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// single(ResourceLocation location, BlockStateProvider provider) { +// return BCLFeatureBuilder +// .start(location, provider) +// .buildAndRegister(); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// simple(ResourceLocation location, Feature feature) { +// return BCLFeatureBuilder +// .start(location, feature) +// .buildAndRegister(); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// simple( +// ResourceLocation location, +// int searchDist, +// boolean rare, +// BlockState baseState, +// IntegerProperty property +// ) { +// int min = Integer.MAX_VALUE; +// int max = Integer.MIN_VALUE; +// +// for (Integer i : property.getPossibleValues()) { +// if (i < min) min = i; +// if (i > max) max = i; +// } +// +// return simple( +// location, +// searchDist, +// rare, +// Feature.SIMPLE_BLOCK, +// new SimpleBlockConfiguration(new RandomizedIntStateProvider( +// BlockStateProvider.simple(baseState), +// property, +// UniformInt.of(min, max) +// )) +// ); +// } +// +// @Deprecated(forRemoval = true) +// +// public static BCLFeature, FC> +// simple( +// ResourceLocation location, +// int searchDist, +// boolean rare, +// Feature feature, +// FC config +// ) { +// BCLFeatureBuilder builder = BCLFeatureBuilder +// .start(location, feature) +// .findSolidFloor(Math.min(12, searchDist)) +// .is(BlockPredicate.ONLY_IN_AIR_PREDICATE); +// if (rare) { +// builder.onceEvery(4); +// } +// return builder.buildAndRegister(config); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// patch(ResourceLocation location, Feature feature) { +// return patch(location, 96, 7, 3, feature, FeatureConfiguration.NONE); +// } +// +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// patch( +// ResourceLocation location, +// int attempts, +// int xzSpread, +// int ySpread, +// Feature feature +// ) { +// return patch(location, attempts, xzSpread, ySpread, feature, FeatureConfiguration.NONE); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// patch( +// ResourceLocation location, +// int attempts, +// int xzSpread, +// int ySpread, +// Feature feature, +// FC config +// ) { +// final BCLFeature SINGLE = simple(location, ySpread, false, feature, config); +// return patch(location, attempts, xzSpread, ySpread, SINGLE); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// wallPatch( +// ResourceLocation location, +// Block block, +// int attempts, +// int xzSpread, +// int ySpread +// ) { +// final BCLFeature SINGLE = simple(location, ySpread, false, +// org.betterx.bclib.api.v3.levelgen.features.BCLFeature.PLACE_BLOCK, +// new PlaceFacingBlockConfig(block, PlaceFacingBlockConfig.HORIZONTAL) +// ); +// return patch(location, attempts, xzSpread, ySpread, SINGLE); +// } +// +// @Deprecated(forRemoval = true) +// public static BCLFeature +// patch( +// ResourceLocation location, +// int attempts, +// int xzSpread, +// int ySpread, +// BCLFeature single +// ) { +// ResourceLocation patchLocation = new ResourceLocation(location.getNamespace(), location.getPath() + "_patch"); +// +// return BCLFeatureBuilder +// .start(patchLocation, Feature.RANDOM_PATCH) +// .buildAndRegister(new RandomPatchConfiguration(attempts, xzSpread, ySpread, single.getPlacedFeature())); +// } +// + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/UserGrowableFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/UserGrowableFeature.java new file mode 100644 index 00000000..0935ab8d --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/UserGrowableFeature.java @@ -0,0 +1,11 @@ +package org.betterx.bclib.api.v2.levelgen.features; + +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; + +/** + * @param + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature} instead + */ +@Deprecated(forRemoval = true) +public interface UserGrowableFeature extends org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature { +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/blockpredicates/Types.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/blockpredicates/Types.java new file mode 100644 index 00000000..c4f3f704 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/blockpredicates/Types.java @@ -0,0 +1,37 @@ +package org.betterx.bclib.api.v2.levelgen.features.blockpredicates; + +import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates; +import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.IsFullShape; + +import com.mojang.serialization.Codec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicateType; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates} instead + */ +@Deprecated(forRemoval = true) +public class Types { + /** + * @deprecated Please use {@link BlockPredicates#FULL_SHAPE} instead + */ + @Deprecated(forRemoval = true) + public static final BlockPredicateType FULL_SHAPE = BlockPredicates.FULL_SHAPE; + + /** + * @param location + * @param codec + * @param

+ * @return + * @deprecated Please use {@link BlockPredicates#register(ResourceLocation, Codec)} instead + */ + @Deprecated(forRemoval = true) + public static

BlockPredicateType

register(ResourceLocation location, Codec

codec) { + return BlockPredicates.register(location, codec); + } + + public static void ensureStaticInitialization() { + + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/ConditionFeatureConfig.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/ConditionFeatureConfig.java new file mode 100644 index 00000000..1161cb3a --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/ConditionFeatureConfig.java @@ -0,0 +1,50 @@ +package org.betterx.bclib.api.v2.levelgen.features.config; + +import org.betterx.bclib.api.v2.levelgen.features.BCLFeature; + +import net.minecraft.core.Holder; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.placement.PlacementFilter; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; + +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.ConditionFeatureConfig} instead + */ +@Deprecated(forRemoval = true) +public class ConditionFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.ConditionFeatureConfig { + + public ConditionFeatureConfig(@NotNull PlacementFilter filter, @NotNull BCLFeature okFeature) { + super(filter, okFeature); + } + + public ConditionFeatureConfig( + @NotNull PlacementFilter filter, + @NotNull BCLFeature okFeature, + @NotNull BCLFeature failFeature + ) { + super(filter, okFeature, failFeature); + } + + public ConditionFeatureConfig(@NotNull PlacementFilter filter, @NotNull Holder okFeature) { + super(filter, okFeature); + } + + public ConditionFeatureConfig( + @NotNull PlacementFilter filter, + @NotNull Holder okFeature, + @NotNull Holder failFeature + ) { + super(filter, okFeature, failFeature); + } + + protected ConditionFeatureConfig( + @NotNull PlacementModifier filter, + @NotNull Holder okFeature, + @NotNull Optional> failFeature + ) { + super(filter, okFeature, failFeature); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/PlaceBlockFeatureConfig.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/PlaceBlockFeatureConfig.java new file mode 100644 index 00000000..8593750f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/PlaceBlockFeatureConfig.java @@ -0,0 +1,32 @@ +package org.betterx.bclib.api.v2.levelgen.features.config; + +import net.minecraft.util.random.SimpleWeightedRandomList; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider; + +import java.util.List; + +@Deprecated(forRemoval = true) +public abstract class PlaceBlockFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.PlaceBlockFeatureConfig { + + public PlaceBlockFeatureConfig(Block block) { + super(block); + } + + public PlaceBlockFeatureConfig(BlockState state) { + super(state); + } + + public PlaceBlockFeatureConfig(List states) { + super(states); + } + + public PlaceBlockFeatureConfig(SimpleWeightedRandomList blocks) { + super(blocks); + } + + public PlaceBlockFeatureConfig(BlockStateProvider blocks) { + super(blocks); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/PlaceFacingBlockConfig.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/PlaceFacingBlockConfig.java new file mode 100644 index 00000000..d4790e59 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/PlaceFacingBlockConfig.java @@ -0,0 +1,37 @@ +package org.betterx.bclib.api.v2.levelgen.features.config; + +import net.minecraft.core.Direction; +import net.minecraft.util.random.SimpleWeightedRandomList; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider; + +import java.util.List; + + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.PlaceFacingBlockConfig} instead + */ +@Deprecated(forRemoval = true) +public class PlaceFacingBlockConfig extends org.betterx.bclib.api.v3.levelgen.features.config.PlaceFacingBlockConfig { + + public PlaceFacingBlockConfig(Block block, List dir) { + super(block, dir); + } + + public PlaceFacingBlockConfig(BlockState state, List dir) { + super(state, dir); + } + + public PlaceFacingBlockConfig(List states, List dir) { + super(states, dir); + } + + public PlaceFacingBlockConfig(SimpleWeightedRandomList blocks, List dir) { + super(blocks, dir); + } + + public PlaceFacingBlockConfig(BlockStateProvider blocks, List dir) { + super(blocks, dir); + } +} 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..2246dc8b --- /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.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; +import java.util.Random; + +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(Random random) { + return random.nextFloat() < floorChance; + } + + public abstract boolean isValidBase(BlockState state); + + public abstract BlockState createBlock(int height, int maxHeight, Random 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, Random 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, Random 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, Random 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/config/SequenceFeatureConfig.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/SequenceFeatureConfig.java new file mode 100644 index 00000000..6487cd77 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/SequenceFeatureConfig.java @@ -0,0 +1,17 @@ +package org.betterx.bclib.api.v2.levelgen.features.config; + +import net.minecraft.core.Holder; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import java.util.List; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.SequenceFeatureConfig instead} + */ +@Deprecated(forRemoval = true) +public class SequenceFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.SequenceFeatureConfig { + + public SequenceFeatureConfig(List> features) { + super(features); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/TemplateFeatureConfig.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/TemplateFeatureConfig.java new file mode 100644 index 00000000..e414a37c --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/TemplateFeatureConfig.java @@ -0,0 +1,23 @@ +package org.betterx.bclib.api.v2.levelgen.features.config; + +import org.betterx.bclib.api.v2.levelgen.structures.StructurePlacementType; +import org.betterx.bclib.api.v2.levelgen.structures.StructureWorldNBT; + +import net.minecraft.resources.ResourceLocation; + +import java.util.List; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig} instead + */ +@Deprecated(forRemoval = true) +public class TemplateFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig { + + public TemplateFeatureConfig(ResourceLocation location, int offsetY, StructurePlacementType type) { + super(location, offsetY, type); + } + + public TemplateFeatureConfig(List structures) { + super(structures); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/ConditionFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/ConditionFeature.java new file mode 100644 index 00000000..cefc2c70 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/ConditionFeature.java @@ -0,0 +1,8 @@ +package org.betterx.bclib.api.v2.levelgen.features.features; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.ConditionFeature} instead. + */ +@Deprecated(forRemoval = true) +public class ConditionFeature extends org.betterx.bclib.api.v3.levelgen.features.features.ConditionFeature { +} 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/features/features/MarkPostProcessingFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/MarkPostProcessingFeature.java new file mode 100644 index 00000000..182c32c0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/MarkPostProcessingFeature.java @@ -0,0 +1,8 @@ +package org.betterx.bclib.api.v2.levelgen.features.features; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.MarkPostProcessingFeature} instead. + */ +@Deprecated(forRemoval = true) +public class MarkPostProcessingFeature extends org.betterx.bclib.api.v3.levelgen.features.features.MarkPostProcessingFeature { +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/PlaceBlockFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/PlaceBlockFeature.java new file mode 100644 index 00000000..a7073933 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/PlaceBlockFeature.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.api.v2.levelgen.features.features; + +import org.betterx.bclib.api.v3.levelgen.features.config.PlaceBlockFeatureConfig; + +import com.mojang.serialization.Codec; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.PlaceBlockFeature} instead. + */ +@Deprecated(forRemoval = true) +public class PlaceBlockFeature extends org.betterx.bclib.api.v3.levelgen.features.features.PlaceBlockFeature { + + public PlaceBlockFeature(Codec codec) { + super(codec); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/ScatterFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/ScatterFeature.java new file mode 100644 index 00000000..00741702 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/ScatterFeature.java @@ -0,0 +1,249 @@ +package org.betterx.bclib.api.v2.levelgen.features.features; + +import org.betterx.bclib.api.v2.levelgen.features.config.ScatterFeatureConfig; +import org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature; +import org.betterx.bclib.util.BlocksHelper; + +import com.mojang.serialization.Codec; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; + +import java.util.Optional; +import java.util.Random; + +@Deprecated(forRemoval = true) +public class ScatterFeature + extends Feature implements UserGrowableFeature { + + public ScatterFeature(Codec configCodec) { + super(configCodec); + } + + @Override + public boolean place(FeaturePlaceContext featurePlaceContext) { + final WorldGenLevel level = featurePlaceContext.level(); + final BlockPos origin = featurePlaceContext.origin(); + final Random random = featurePlaceContext.random(); + + ScatterFeatureConfig config = featurePlaceContext.config(); + Optional direction = getTipDirection(level, origin, random, config); + if (direction.isEmpty()) { + return false; + } + BlockPos basePos = origin.relative(direction.get(), -1); + + + int i = (int) (random.nextFloat() * (1 + config.maxHeight - config.minHeight) + config.minHeight); + growCenterPillar(level, origin, basePos, direction.get(), i, config, random); + return true; + } + + + protected void growCenterPillar( + LevelAccessor level, + BlockPos origin, + BlockPos basePos, + Direction direction, + int centerHeight, + ScatterFeatureConfig config, + Random random + ) { + if (config.isValidBase(level.getBlockState(basePos))) { + final Direction surfaceDirection = direction.getOpposite(); + BlockPos.MutableBlockPos POS = new BlockPos.MutableBlockPos(); + int adaptedHeight = freeHeight(level, direction, centerHeight, config, origin); + buildPillarWithBase(level, origin, basePos, direction, adaptedHeight, config, random, false); + + final double distNormalizer = (config.maxSpread * Math.sqrt(2)); + final int tryCount = config.spreadCount.sample(random); + for (int i = 0; i < tryCount; i++) { + int x = origin.getX() + (int) (random.nextGaussian() * config.maxSpread); + int z = origin.getZ() + (int) (random.nextGaussian() * config.maxSpread); + POS.set(x, basePos.getY(), z); + + if (BlocksHelper.findSurroundingSurface(level, POS, surfaceDirection, 4, config::isValidBase)) { + int myHeight = freeHeight( + level, + direction, + centerHeight, + config, + POS + ); + + int dx = x - POS.getX(); + int dz = z - POS.getZ(); + float sizeFactor = (1 - (float) (Math.sqrt(dx * dx + dz * dz) / distNormalizer)); + sizeFactor = (1 - (random.nextFloat() * config.sizeVariation)) * sizeFactor; + myHeight = (int) Math.min(Math.max( + config.minHeight, + config.minHeight + sizeFactor * (myHeight - config.minHeight) + ), config.maxHeight); + + BlockState baseState = level.getBlockState(POS.relative(direction.getOpposite())); + if (!config.isValidBase(baseState)) { + System.out.println("Starting from " + baseState + " at " + POS.relative(direction.getOpposite())); + } + buildPillarWithBase(level, + POS, + POS.relative(direction.getOpposite()), + direction, + myHeight, + config, + random, false + ); + } + } + } + } + + private int freeHeight( + LevelAccessor level, + Direction direction, + int defaultHeight, + ScatterFeatureConfig config, + BlockPos POS + ) { + int myHeight; + if (config.growWhileFree) { + myHeight = BlocksHelper.blockCount( + level, + POS, + direction, + config.maxHeight, + BlocksHelper::isFree + ); + } else { + myHeight = defaultHeight; + } + return Math.max(config.minHeight, myHeight); + } + + private void buildPillarWithBase( + LevelAccessor level, + BlockPos origin, + BlockPos basePos, + Direction direction, + int height, + ScatterFeatureConfig config, + Random random, + boolean force + ) { + if (force || BlocksHelper.isFreeSpace(level, origin, direction, height, BlocksHelper::isFree)) { + createPatchOfBaseBlocks(level, random, basePos, config); + BlockState bottom = config.bottomBlock.getState(random, origin); + if (bottom.canSurvive(level, origin)) { + buildPillar(level, origin, direction, height, config, random); + } + } + } + + private void buildPillar( + LevelAccessor level, + BlockPos origin, + Direction direction, + int height, + ScatterFeatureConfig config, + Random random + ) { + + final BlockPos.MutableBlockPos POS = origin.mutable(); + for (int size = 0; size < height; size++) { + BlockState state = config.createBlock(size, height - 1, random, POS); + BlocksHelper.setWithoutUpdate(level, POS, state); + POS.move(direction); + } + } + + private Optional getTipDirection( + LevelAccessor levelAccessor, + BlockPos blockPos, + Random Random, + ScatterFeatureConfig config + ) { + boolean onCeil = config.floorChance < 1 && config.isValidBase(levelAccessor.getBlockState(blockPos.above())); + boolean onFloor = config.floorChance > 0 && config.isValidBase(levelAccessor.getBlockState(blockPos.below())); + + if (onCeil && onFloor) { + return Optional.of(config.isFloor(Random) ? Direction.DOWN : Direction.UP); + } + if (onCeil) { + return Optional.of(Direction.DOWN); + } + if (onFloor) { + return Optional.of(Direction.UP); + } + return Optional.empty(); + } + + private void createPatchOfBaseBlocks( + LevelAccessor levelAccessor, + Random Random, + BlockPos blockPos, + ScatterFeatureConfig config + ) { + if (config.baseState.isPresent() && config.baseReplaceChance > 0 && Random.nextFloat() < config.baseReplaceChance) { + final BlockState baseState = config.baseState.get(); + BlockPos pos; + for (Direction direction : Direction.Plane.HORIZONTAL) { + if (Random.nextFloat() > config.chanceOfDirectionalSpread) continue; + pos = blockPos.relative(direction); + placeBaseBlockIfPossible(levelAccessor, pos, baseState); + + if (Random.nextFloat() > config.chanceOfSpreadRadius2) continue; + pos = pos.relative(Direction.getRandom(Random)); + placeBaseBlockIfPossible(levelAccessor, pos, baseState); + + if (Random.nextFloat() > config.chanceOfSpreadRadius3) continue; + pos = pos.relative(Direction.getRandom(Random)); + placeBaseBlockIfPossible(levelAccessor, pos, baseState); + } + placeBaseBlockIfPossible(levelAccessor, blockPos, baseState); + } + } + + protected void placeBaseBlockIfPossible( + LevelAccessor levelAccessor, + BlockPos blockPos, + BlockState baseState + ) { + BlockState blockState = levelAccessor.getBlockState(blockPos); + if (BlocksHelper.isTerrain(blockState)) { + levelAccessor.setBlock(blockPos, baseState, 2); + } + } + + @Override + public boolean grow( + ServerLevelAccessor level, + BlockPos origin, + Random random, + FC config + ) { + Optional oDirection = getTipDirection(level, origin, random, config); + if (oDirection.isEmpty()) { + return false; + } + Direction direction = oDirection.get(); + BlockPos basePos = origin.relative(direction, -1); + + if (config.isValidBase(level.getBlockState(basePos))) { + int centerHeight = (int) (random.nextFloat() * (1 + config.maxHeight - config.minHeight) + config.minHeight); + centerHeight = freeHeight( + level, + direction, + centerHeight, + config, + origin.relative(direction, 1) + ) + 1; + buildPillarWithBase(level, origin, basePos, direction, centerHeight, config, random, true); + } + return false; + } + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/SequenceFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/SequenceFeature.java new file mode 100644 index 00000000..908062aa --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/SequenceFeature.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.api.v2.levelgen.features.features; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.SequenceFeature} instead. + */ +@Deprecated(forRemoval = true) +public class SequenceFeature extends org.betterx.bclib.api.v3.levelgen.features.features.SequenceFeature { + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/SurfaceFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/SurfaceFeature.java new file mode 100644 index 00000000..df6117b0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/SurfaceFeature.java @@ -0,0 +1,51 @@ +package org.betterx.bclib.api.v2.levelgen.features.features; + +import org.betterx.bclib.util.BlocksHelper; + +import com.mojang.serialization.Codec; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; + +import java.util.Optional; + +@Deprecated(forRemoval = true) +public abstract class SurfaceFeature extends Feature { + public static abstract class DefaultConfiguration extends SurfaceFeature { + protected DefaultConfiguration() { + super(NoneFeatureConfiguration.CODEC); + } + } + + protected SurfaceFeature(Codec codec) { + super(codec); + } + + protected abstract boolean isValidSurface(BlockState state); + + protected int minHeight(FeaturePlaceContext ctx) { + return ctx.chunkGenerator().getSeaLevel(); + } + + @Override + public boolean place(FeaturePlaceContext ctx) { + Optional pos = BlocksHelper.findSurfaceBelow( + ctx.level(), + ctx.origin(), + minHeight(ctx), + this::isValidSurface + ); + if (pos.isPresent()) { + generate(pos.get(), ctx); + return true; + } + + + return false; + } + + protected abstract void generate(BlockPos centerPos, FeaturePlaceContext ctx); +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/TemplateFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/TemplateFeature.java new file mode 100644 index 00000000..e7a86062 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/TemplateFeature.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.api.v2.levelgen.features.features; + +import org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig; + +import com.mojang.serialization.Codec; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.TemplateFeature} instead. + */ +@Deprecated(forRemoval = true) +public class TemplateFeature extends org.betterx.bclib.api.v3.levelgen.features.features.TemplateFeature { + + public TemplateFeature(Codec codec) { + super(codec); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/WeightedRandomSelectorFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/WeightedRandomSelectorFeature.java new file mode 100644 index 00000000..79da6dec --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/WeightedRandomSelectorFeature.java @@ -0,0 +1,41 @@ +package org.betterx.bclib.api.v2.levelgen.features.features; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.WeightedPlacedFeature; +import net.minecraft.world.level.levelgen.feature.configurations.RandomFeatureConfiguration; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import java.util.Random; + + +@Deprecated(forRemoval = true) +public class WeightedRandomSelectorFeature extends Feature { + public WeightedRandomSelectorFeature() { + super(RandomFeatureConfiguration.CODEC); + } + + public boolean place(FeaturePlaceContext ctx) { + final WorldGenLevel level = ctx.level(); + final ChunkGenerator generator = ctx.chunkGenerator(); + final RandomFeatureConfiguration cfg = ctx.config(); + final Random random = ctx.random(); + final BlockPos pos = ctx.origin(); + + PlacedFeature selected = cfg.defaultFeature.value(); + if (!cfg.features.isEmpty()) { + final float totalWeight = cfg.features.stream().map(w -> w.chance).reduce(0.0f, (p, c) -> p + c); + float bar = random.nextFloat() * totalWeight; + + for (WeightedPlacedFeature f : cfg.features) { + selected = f.feature.value(); + bar -= f.chance; + if (bar < 0) break; + } + } + return selected.place(level, generator, random, pos); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/All.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/All.java new file mode 100644 index 00000000..0d89537d --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/All.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.All} instead. + */ +@Deprecated(forRemoval = true) +public class All extends org.betterx.bclib.api.v3.levelgen.features.placement.All { + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Debug.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Debug.java new file mode 100644 index 00000000..d81a8c52 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Debug.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.Debug} instead. + */ +@Deprecated(forRemoval = true) +public class Debug extends org.betterx.bclib.api.v3.levelgen.features.placement.Debug { + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Extend.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Extend.java new file mode 100644 index 00000000..c431d980 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Extend.java @@ -0,0 +1,14 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import net.minecraft.core.Direction; +import net.minecraft.util.valueproviders.IntProvider; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.Extend} instead. + */ +@Deprecated(forRemoval = true) +public class Extend extends org.betterx.bclib.api.v3.levelgen.features.placement.Extend { + public Extend(Direction direction, IntProvider length) { + super(direction, length); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/FindSolidInDirection.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/FindSolidInDirection.java new file mode 100644 index 00000000..12bcbdcc --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/FindSolidInDirection.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import net.minecraft.core.Direction; + +import java.util.List; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.FindSolidInDirection} instead. + */ +@Deprecated(forRemoval = true) +public class FindSolidInDirection extends org.betterx.bclib.api.v3.levelgen.features.placement.FindSolidInDirection { + + + public FindSolidInDirection(Direction direction, int maxSearchDistance) { + super(direction, maxSearchDistance, 0); + } + + public FindSolidInDirection(List direction, int maxSearchDistance) { + super(direction, maxSearchDistance, 0); + } + + public FindSolidInDirection(List direction, int maxSearchDistance, boolean randomSelect) { + super(direction, maxSearchDistance, randomSelect, 0); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/ForAll.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/ForAll.java new file mode 100644 index 00000000..5f6ac277 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/ForAll.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import net.minecraft.world.level.levelgen.placement.PlacementModifier; + +import java.util.List; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.ForAll} instead. + */ +@Deprecated(forRemoval = true) +public class ForAll extends org.betterx.bclib.api.v3.levelgen.features.placement.ForAll { + + public ForAll(List modifiers) { + super(modifiers); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Is.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Is.java new file mode 100644 index 00000000..360dadf2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Is.java @@ -0,0 +1,17 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import net.minecraft.core.Vec3i; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; + +import java.util.Optional; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.Is} instead. + */ +@Deprecated(forRemoval = true) +public class Is extends org.betterx.bclib.api.v3.levelgen.features.placement.Is { + + public Is(BlockPredicate predicate, Optional offset) { + super(predicate, offset); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/IsBasin.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/IsBasin.java new file mode 100644 index 00000000..c96d3506 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/IsBasin.java @@ -0,0 +1,21 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; + +import java.util.Optional; + + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.IsBasin} instead. + */ +@Deprecated(forRemoval = true) +public class IsBasin extends org.betterx.bclib.api.v3.levelgen.features.placement.IsBasin { + + public IsBasin(BlockPredicate predicate) { + super(predicate); + } + + public IsBasin(BlockPredicate predicate, Optional topPredicate) { + super(predicate, topPredicate); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/IsEmptyAboveSampledFilter.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/IsEmptyAboveSampledFilter.java new file mode 100644 index 00000000..6edadfbd --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/IsEmptyAboveSampledFilter.java @@ -0,0 +1,76 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.WorldGenLevel; +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.Random; + +/** + * Tests if there is air at two locations above the tested block position + */ +@Deprecated(forRemoval = true) +public class IsEmptyAboveSampledFilter extends PlacementFilter { + private static final IsEmptyAboveSampledFilter DEFAULT = new IsEmptyAboveSampledFilter(4, 2); + private static final IsEmptyAboveSampledFilter DEFAULT1 = new IsEmptyAboveSampledFilter(1, 1); + private static final IsEmptyAboveSampledFilter DEFAULT2 = new IsEmptyAboveSampledFilter(1, 2); + + private static final IsEmptyAboveSampledFilter BELOW_DEFAULT = new IsEmptyAboveSampledFilter(-4, -2); + private static final IsEmptyAboveSampledFilter BELOW_DEFAULT1 = new IsEmptyAboveSampledFilter(-1, -1); + private static final IsEmptyAboveSampledFilter BELOW_DEFAULT2 = new IsEmptyAboveSampledFilter(-1, -2); + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + Codec.intRange(-32, 32).fieldOf("d1").orElse(4).forGetter((p) -> p.distance1), + Codec.intRange(-32, 32).fieldOf("d2").orElse(2).forGetter((p) -> p.distance1) + ) + .apply(instance, IsEmptyAboveSampledFilter::new)); + + public static PlacementFilter emptyAbove4() { + return DEFAULT; + } + + public static PlacementFilter emptyAbove2() { + return DEFAULT2; + } + + public static PlacementFilter emptyAbove() { + return DEFAULT1; + } + + public static PlacementFilter emptyBelow4() { + return BELOW_DEFAULT; + } + + public static PlacementFilter emptyBelow2() { + return BELOW_DEFAULT2; + } + + public static PlacementFilter emptyBelow() { + return BELOW_DEFAULT1; + } + + public IsEmptyAboveSampledFilter(int d1, int d2) { + this.distance1 = d1; + this.distance2 = d2; + } + + private final int distance1; + private final int distance2; + + + @Override + protected boolean shouldPlace(PlacementContext ctx, Random random, BlockPos pos) { + WorldGenLevel level = ctx.getLevel(); + return level.isEmptyBlock(pos.above(distance1)) + && (distance1 == distance2 || level.isEmptyBlock(pos.above(distance2))); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.IS_EMPTY_ABOVE_SAMPLED_FILTER; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/MinEmptyFilter.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/MinEmptyFilter.java new file mode 100644 index 00000000..cc1a9244 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/MinEmptyFilter.java @@ -0,0 +1,66 @@ +package org.betterx.bclib.api.v2.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.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementFilter; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.Random; + +@Deprecated(forRemoval = true) +public class MinEmptyFilter extends PlacementFilter { + private static final MinEmptyFilter DOWN = new MinEmptyFilter(Direction.DOWN, 2); + private static final MinEmptyFilter UP = new MinEmptyFilter(Direction.UP, 2); + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + Direction.CODEC.fieldOf("dir").orElse(Direction.DOWN).forGetter((p) -> p.direction), + Codec.intRange(1, 32).fieldOf("dist").orElse(12).forGetter((p) -> p.distance) + ) + .apply(instance, MinEmptyFilter::new)); + + private final Direction direction; + private final int distance; + + protected MinEmptyFilter(Direction direction, int distance) { + this.direction = direction; + this.distance = distance; + } + + public static PlacementModifier down() { + return DOWN; + } + + public static PlacementModifier down(int dist) { + return new MinEmptyFilter(Direction.DOWN, dist); + } + + public static PlacementModifier up() { + return UP; + } + + public static PlacementModifier up(int dist) { + return new MinEmptyFilter(Direction.UP, dist); + } + + @Override + protected boolean shouldPlace(PlacementContext ctx, Random Random, BlockPos pos) { + return BlocksHelper.isFreeSpace( + ctx.getLevel(), + pos.relative(direction), + direction, + distance - 1, + BlocksHelper::isFree + ); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.MIN_EMPTY_FILTER; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Offset.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Offset.java new file mode 100644 index 00000000..388515c2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Offset.java @@ -0,0 +1,14 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import net.minecraft.core.Vec3i; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.Offset} instead. + */ +@Deprecated(forRemoval = true) +public class Offset extends org.betterx.bclib.api.v3.levelgen.features.placement.Offset { + + public Offset(Vec3i offset) { + super(offset); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/OnEveryLayer.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/OnEveryLayer.java new file mode 100644 index 00000000..b4d89677 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/OnEveryLayer.java @@ -0,0 +1,14 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import java.util.Optional; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.OnEveryLayer} instead + */ +@Deprecated(forRemoval = true) +public class OnEveryLayer extends org.betterx.bclib.api.v3.levelgen.features.placement.OnEveryLayer { + + protected OnEveryLayer(Optional minHeight, Optional maxHeight) { + super(minHeight, maxHeight); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/PlacementModifiers.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/PlacementModifiers.java new file mode 100644 index 00000000..7977cfbd --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/PlacementModifiers.java @@ -0,0 +1,133 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.levelgen.features.placement.All; +import org.betterx.bclib.api.v3.levelgen.features.placement.Debug; +import org.betterx.bclib.api.v3.levelgen.features.placement.Extend; +import org.betterx.bclib.api.v3.levelgen.features.placement.FindSolidInDirection; +import org.betterx.bclib.api.v3.levelgen.features.placement.ForAll; +import org.betterx.bclib.api.v3.levelgen.features.placement.Is; +import org.betterx.bclib.api.v3.levelgen.features.placement.IsBasin; +import org.betterx.bclib.api.v3.levelgen.features.placement.Offset; +import org.betterx.bclib.api.v3.levelgen.features.placement.OnEveryLayer; +import org.betterx.bclib.api.v3.levelgen.features.placement.Stencil; +import org.betterx.bclib.api.v3.levelgen.features.placement.UnderEveryLayer; +import org.betterx.bclib.api.v3.levelgen.features.placement.*; + +import com.mojang.serialization.Codec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers} instead + */ +@Deprecated(forRemoval = true) +public class PlacementModifiers { + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#NOISE_FILTER} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType NOISE_FILTER = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.NOISE_FILTER; + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#DEBUG} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType DEBUG = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.DEBUG; + @Deprecated(forRemoval = true) + public static final PlacementModifierType IS_EMPTY_ABOVE_SAMPLED_FILTER = register( + "is_empty_above_sampled_filter", + IsEmptyAboveSampledFilter.CODEC + ); + + public static final PlacementModifierType MIN_EMPTY_FILTER = register( + "min_empty_filter", + MinEmptyFilter.CODEC + ); + + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#FOR_ALL} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType FOR_ALL = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.FOR_ALL; + + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#SOLID_IN_DIR} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType SOLID_IN_DIR = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.SOLID_IN_DIR; + + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#STENCIL} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType STENCIL = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.STENCIL; + + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#ALL} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType ALL = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.ALL; + + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#IS_BASIN} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType IS_BASIN = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.IS_BASIN; + + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#IS} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType IS = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.IS; + + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#OFFSET} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType OFFSET = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.OFFSET; + + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#EXTEND} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType EXTEND = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.EXTEND; + + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#ON_EVERY_LAYER} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType ON_EVERY_LAYER = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.ON_EVERY_LAYER; + + /** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#UNDER_EVERY_LAYER} instead + */ + @Deprecated(forRemoval = true) + public static final PlacementModifierType UNDER_EVERY_LAYER = org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.UNDER_EVERY_LAYER; + + private static

PlacementModifierType

register(String path, Codec

codec) { + return register(BCLib.makeID(path), codec); + } + + + /** + * @param location + * @param codec + * @param

+ * @return + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers#register(ResourceLocation, Codec)} instead + */ + @Deprecated(forRemoval = true) + public static

PlacementModifierType

register( + ResourceLocation location, + Codec

codec + ) { + return org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers.register(location, codec); + } + + @Deprecated(forRemoval = true) + public static void ensureStaticInitialization() { + + } +} + diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Stencil.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Stencil.java new file mode 100644 index 00000000..c6f91c4d --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/Stencil.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import java.util.List; + + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.Stencil} instead + */ +@Deprecated(forRemoval = true) +public class Stencil extends org.betterx.bclib.api.v3.levelgen.features.placement.Stencil { + + public Stencil(Boolean[] stencil, int selectOneIn) { + super(stencil, selectOneIn); + } + + public Stencil(List stencil, int selectOneIn) { + super(stencil, selectOneIn); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/UnderEveryLayer.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/UnderEveryLayer.java new file mode 100644 index 00000000..36259571 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/placement/UnderEveryLayer.java @@ -0,0 +1,15 @@ +package org.betterx.bclib.api.v2.levelgen.features.placement; + +import java.util.Optional; + +/** + * @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.UnderEveryLayer} instead + */ +@Deprecated(forRemoval = true) +public class UnderEveryLayer + extends org.betterx.bclib.api.v3.levelgen.features.placement.UnderEveryLayer { + + protected UnderEveryLayer(Optional minHeight, Optional maxHeight) { + super(minHeight, maxHeight); + } +} 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..915c067e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructure.java @@ -0,0 +1,163 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeBuilder; +import org.betterx.bclib.mixin.common.StructuresAccessor; + +import com.mojang.serialization.Codec; +import net.minecraft.core.*; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.data.worldgen.StructureSets; +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 java.util.function.Function; +import org.jetbrains.annotations.NotNull; + +public class BCLStructure { + private final Holder structure; + private final GenerationStep.Decoration featureStep; + private final List biomes = Lists.newArrayList(); + private final ResourceLocation id; + public final TagKey biomeTag; + public final ResourceKey structureKey; + public final S baseStructure; + public final ResourceKey structureSetKey; + public final StructurePlacement spreadConfig; + + public final StructureType structureType; + + public final Codec STRUCTURE_CODEC; + + + private static HolderSet biomes(TagKey tagKey) { + return BuiltinRegistries.BIOME.getOrCreateTag(tagKey); + } + + private static Structure.StructureSettings structure( + TagKey tagKey, + Map map, + GenerationStep.Decoration decoration, + TerrainAdjustment terrainAdjustment + ) { + return new Structure.StructureSettings(biomes(tagKey), map, decoration, terrainAdjustment); + } + + private static Structure.StructureSettings structure( + TagKey tagKey, + GenerationStep.Decoration decoration, + TerrainAdjustment terrainAdjustment + ) { + return structure(tagKey, Map.of(), decoration, terrainAdjustment); + } + + private static StructureType registerStructureType( + ResourceLocation id, + Codec codec + ) { + return Registry.register(Registry.STRUCTURE_TYPES, id, () -> codec); + } + + protected BCLStructure( + @NotNull ResourceLocation id, + @NotNull Function structureBuilder, + GenerationStep.Decoration step, + @NotNull StructurePlacement placement, + @NotNull Codec codec, + @NotNull TagKey biomeTag, + @NotNull TerrainAdjustment terrainAdjustment + ) { + this.id = id; + this.featureStep = step; + this.STRUCTURE_CODEC = codec; + this.spreadConfig = placement; + this.structureKey = ResourceKey.create(Registry.STRUCTURE_REGISTRY, id); + this.structureSetKey = ResourceKey.create(Registry.STRUCTURE_SET_REGISTRY, id); + + this.structureType = registerStructureType(id, STRUCTURE_CODEC); + + this.biomeTag = biomeTag; + this.baseStructure = structureBuilder.apply(structure(this.biomeTag, featureStep, terrainAdjustment)); + this.structure = StructuresAccessor.callRegister(structureKey, this.baseStructure); + StructureSets.register(structureSetKey, this.structure, spreadConfig); + } + + /** + * 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 Holder getStructure() { + return structure; + } + + 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; + } +} 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..89698616 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructureBuilder.java @@ -0,0 +1,109 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import org.betterx.worlds.together.tag.v3.TagManager; + +import com.mojang.serialization.Codec; +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.feature.StructureFeature; +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.function.Function; + +public class BCLStructureBuilder { + private static final BCLStructureBuilder INSTANCE = new BCLStructureBuilder(); + + private ResourceLocation structureID; + private Function structureBuilder; + + private GenerationStep.Decoration step; + + private Codec codec; + + private StructurePlacement placement; + + private TagKey biomeTag; + + private TerrainAdjustment terrainAdjustment; + + private BCLStructureBuilder() { + } + + public static BCLStructureBuilder start( + ResourceLocation structureID, + Function structureBuilder + ) { + INSTANCE.structureID = structureID; + INSTANCE.structureBuilder = structureBuilder; + + INSTANCE.step = GenerationStep.Decoration.SURFACE_STRUCTURES; + INSTANCE.terrainAdjustment = TerrainAdjustment.NONE; + INSTANCE.codec = null; + INSTANCE.placement = null; + INSTANCE.biomeTag = null; + + return INSTANCE; + } + + public BCLStructureBuilder adjustment(TerrainAdjustment value) { + this.terrainAdjustment = value; + return this; + } + + public BCLStructureBuilder step(GenerationStep.Decoration value) { + this.step = value; + return this; + } + + public BCLStructureBuilder codec(Codec value) { + this.codec = value; + return this; + } + + public BCLStructureBuilder placement(StructurePlacement value) { + this.placement = value; + return this; + } + + public BCLStructureBuilder randomPlacement(int spacing, int separation) { + this.placement = new RandomSpreadStructurePlacement( + spacing, + separation, + RandomSpreadType.LINEAR, + 13323129 + spacing + separation + structureID.toString().hashCode() % 10000 + ); + return this; + } + + public BCLStructureBuilder biomeTag(String modID, String path) { + this.biomeTag = TagManager.BIOMES.makeStructureTag(modID, path); + return this; + } + + public BCLStructureBuilder biomeTag(TagKey tag) { + this.biomeTag = tag; + return this; + } + + public BCLStructure build() { + if (placement == null) { + throw new IllegalStateException("Placement needs to be defined for " + this.structureID); + } + if (codec == null) codec(Structure.simpleCodec(structureBuilder)); + if (biomeTag == null) biomeTag(structureID.getNamespace(), structureID.getPath()); + + return new BCLStructure<>( + structureID, + structureBuilder, + step, + placement, + codec, + biomeTag, + terrainAdjustment + ); + } +} 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..4bd22cb7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureNBT.java @@ -0,0 +1,128 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import org.betterx.bclib.BCLib; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.core.Vec3i; +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.ServerLevelAccessor; +import net.minecraft.world.level.block.Block; +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.util.Map; +import java.util.Random; + +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(Random random) { + return Rotation.getRandom(random); + } + + public static Mirror getRandomMirror(Random 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) { + if (structure == null) { + BCLib.LOGGER.error("No structure: " + location.toString()); + return false; + } + + 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)); + StructurePlaceSettings data = new StructurePlaceSettings().setRotation(rotation).setMirror(mirror); + BlockPos newPos = pos.offset(-blockpos2.getX() >> 1, 0, -blockpos2.getZ() >> 1); + structure.placeInWorld( + world, + newPos, + newPos, + data, + world.getRandom(), + Block.UPDATE_CLIENTS + ); + return true; + } + + private static final Map READER_CACHE = Maps.newHashMap(); + + private static StructureTemplate readStructureFromJar(ResourceLocation resource) { + return READER_CACHE.computeIfAbsent(resource, r -> _readStructureFromJar(r)); + } + + private static StructureTemplate _readStructureFromJar(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 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); + } +} 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/StructureWorld.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureWorld.java new file mode 100644 index 00000000..002b37d8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureWorld.java @@ -0,0 +1,172 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +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; + +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((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..3c2f8056 --- /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.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; +import java.util.Random; + +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, + Random 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).getMaterial().isReplaceable()) + 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), y1, -(size.getZ() >> 1)); + BlockPos end = pos.offset(size.getX() >> 1, 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).getMaterial().isReplaceable()) + 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..6d4cd3e0 --- /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.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.StructureFeatureManager; +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.StructureManager; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; + +import java.util.Random; + +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(Registry.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( + StructureManager structureTemplateManager, + ResourceLocation resourceLocation, + BlockPos centerPos, + Rotation rotation, + Mirror mirror, + BlockPos halfSize + ) { + this(structureTemplateManager, resourceLocation, centerPos, rotation, mirror, halfSize, 0, false); + } + + public TemplatePiece( + StructureManager 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(StructureManager 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, + Random Random, + BoundingBox boundingBox + ) { + + } + + @Override + public void postProcess( + WorldGenLevel world, + StructureFeatureManager structureManager, + ChunkGenerator chunkGenerator, + Random 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..58b9d92d --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/TemplateStructure.java @@ -0,0 +1,277 @@ +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.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.NoiseColumn; +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.feature.StructureFeature; +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.Random; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; + +public abstract class TemplateStructure extends StructureFeature { + 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(Random 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.getMaterial().isSolid(); + } + + protected int erosion(RandomSource rnd) { + return 0; + } + + protected boolean cover(RandomSource rnd) { + return false; + } + + @Override + public Optional findGenerationPoint(GenerationContext ctx) { + WorldGenerationContext worldGenerationContext = new WorldGenerationContext( + ctx.chunkGenerator(), + ctx.heightAccessor() + ); + 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(); + 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 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.getMaterial().isReplaceable()) { + 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..46f3b172 --- /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..e849af3a --- /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..63920e1a --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/Conditions.java @@ -0,0 +1,88 @@ +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.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(Registry.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); + } +} 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..d691aa92 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/RoughNoiseCondition.java @@ -0,0 +1,114 @@ +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.Registry; +import net.minecraft.resources.ResourceKey; +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; + +import java.util.Random; + +public class RoughNoiseCondition implements SurfaceRules.ConditionSource { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + ResourceKey.codec(Registry.NOISE_REGISTRY).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 + ) + )); + + 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 Codec codec() { + return 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 Random 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..c5ce2fe9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/SwitchRuleSource.java @@ -0,0 +1,54 @@ +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.core.Registry; +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 { + Registry.register(Registry.RULE, "bclib_switch_rule", SwitchRuleSource.CODEC); + } +} 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..2e71c240 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/ThresholdCondition.java @@ -0,0 +1,78 @@ +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.valueproviders.ConstantFloat; +import net.minecraft.util.valueproviders.FloatProvider; +import net.minecraft.world.level.levelgen.SurfaceRules; +import net.minecraft.world.level.levelgen.ThreadSafeLegacyRandom; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Random; + +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)); + 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 Codec codec() { + return CODEC; + } + + static class Context { + public final OpenSimplexNoise noise; + public final Random 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 ThreadSafeLegacyRandom(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..edf9e61f --- /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.valueproviders.ConstantFloat; +import net.minecraft.util.valueproviders.FloatProvider; +import net.minecraft.world.level.levelgen.SurfaceRules; +import net.minecraft.world.level.levelgen.ThreadSafeLegacyRandom; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Random; + +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 Random 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 ThreadSafeLegacyRandom(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..c3a20620 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/poi/BCLPoiType.java @@ -0,0 +1,108 @@ +package org.betterx.bclib.api.v2.poi; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +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.entity.ai.village.poi.PoiTypes; +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 BCLPoiType register( + ResourceLocation location, + Set matchingStates, + int maxTickets, + int validRanges + ) { + ResourceKey key = ResourceKey.create(Registry.POINT_OF_INTEREST_TYPE_REGISTRY, location); + PoiType type = PoiTypes.register(Registry.POINT_OF_INTEREST_TYPE, key, matchingStates, maxTickets, validRanges); + return new BCLPoiType(key, type, matchingStates, maxTickets, validRanges); + } + + public static Set getBlockStates(Block block) { + return ImmutableSet.copyOf(block.getStateDefinition().getPossibleStates()); + } + + public Optional findPoiAround( + ServerLevel level, + BlockPos center, + boolean wideSearch, + WorldBorder worldBorder + ) { + return findPoiAround(key, level, center, wideSearch, worldBorder); + } + + 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/spawning/SpawnRuleBuilder.java b/src/main/java/org/betterx/bclib/api/v2/spawning/SpawnRuleBuilder.java new file mode 100644 index 00000000..a00478f6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/spawning/SpawnRuleBuilder.java @@ -0,0 +1,370 @@ +package org.betterx.bclib.api.v2.spawning; + +import org.betterx.bclib.entity.BCLEntityWrapper; +import org.betterx.bclib.interfaces.SpawnRule; + +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.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 net.fabricmc.fabric.mixin.object.builder.SpawnRestrictionAccessor; + +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); + }); + 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 + ); + }); + 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 pos.getY() > world.getHeight(Types.WORLD_SURFACE, pos.getX(), pos.getZ()) + minHeight; + }); + }); + 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() + ); + }); + 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); + }); + }); + 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; + }); + }); + + 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); + }); + 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 + ); + }); + 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 + ); + }); + 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; + } + }); + }); + 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)); + 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)) { + return false; + } + } + return true; + }; + + SpawnRestrictionAccessor.callRegister(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) { + 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..94d7092a --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/spawning/SpawnRuleEntry.java @@ -0,0 +1,37 @@ +package org.betterx.bclib.api.v2.spawning; + +import org.betterx.bclib.interfaces.SpawnRule; + +import net.minecraft.core.BlockPos; +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 java.util.Random; +import org.jetbrains.annotations.NotNull; + +public class SpawnRuleEntry implements Comparable { + private final SpawnRule rule; + private final byte priority; + + public SpawnRuleEntry(int priority, SpawnRule rule) { + this.priority = (byte) priority; + this.rule = rule; + } + + protected boolean canSpawn( + EntityType type, + LevelAccessor world, + MobSpawnType spawnReason, + BlockPos pos, + Random 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/v2/tag/CommonBiomeTags.java b/src/main/java/org/betterx/bclib/api/v2/tag/CommonBiomeTags.java new file mode 100644 index 00000000..df8402d5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/tag/CommonBiomeTags.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.api.v2.tag; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.biome.Biome; + +/** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBiomeTags} + */ +@Deprecated(forRemoval = true) +public class CommonBiomeTags { + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBiomeTags#IN_NETHER} + **/ + @Deprecated(forRemoval = true) + public static final TagKey IN_NETHER = org.betterx.worlds.together.tag.v3.CommonBiomeTags.IN_NETHER; +} diff --git a/src/main/java/org/betterx/bclib/api/v2/tag/CommonBlockTags.java b/src/main/java/org/betterx/bclib/api/v2/tag/CommonBlockTags.java new file mode 100644 index 00000000..38d1d7cf --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/tag/CommonBlockTags.java @@ -0,0 +1,131 @@ +package org.betterx.bclib.api.v2.tag; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.Block; + +/** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags} + */ +@Deprecated(forRemoval = true) +public class CommonBlockTags { + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#BARREL} + **/ + @Deprecated(forRemoval = true) + public static final TagKey BARREL = org.betterx.worlds.together.tag.v3.CommonBlockTags.BARREL; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#BOOKSHELVES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey BOOKSHELVES = org.betterx.worlds.together.tag.v3.CommonBlockTags.BOOKSHELVES; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#CHEST} + **/ + @Deprecated(forRemoval = true) + public static final TagKey CHEST = org.betterx.worlds.together.tag.v3.CommonBlockTags.CHEST; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#END_STONES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey END_STONES = org.betterx.worlds.together.tag.v3.CommonBlockTags.END_STONES; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#GEN_END_STONES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey GEN_END_STONES = org.betterx.worlds.together.tag.v3.CommonBlockTags.GEN_END_STONES; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#IMMOBILE} + **/ + @Deprecated(forRemoval = true) + public static final TagKey IMMOBILE = org.betterx.worlds.together.tag.v3.CommonBlockTags.IMMOBILE; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#LEAVES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey LEAVES = org.betterx.worlds.together.tag.v3.CommonBlockTags.LEAVES; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#NETHERRACK} + **/ + @Deprecated(forRemoval = true) + public static final TagKey NETHERRACK = org.betterx.worlds.together.tag.v3.CommonBlockTags.NETHERRACK; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#MYCELIUM} + **/ + @Deprecated(forRemoval = true) + public static final TagKey MYCELIUM = org.betterx.worlds.together.tag.v3.CommonBlockTags.MYCELIUM; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#NETHER_MYCELIUM} + **/ + @Deprecated(forRemoval = true) + public static final TagKey NETHER_MYCELIUM = org.betterx.worlds.together.tag.v3.CommonBlockTags.NETHER_MYCELIUM; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#NETHER_PORTAL_FRAME} + **/ + @Deprecated(forRemoval = true) + public static final TagKey NETHER_PORTAL_FRAME = org.betterx.worlds.together.tag.v3.CommonBlockTags.NETHER_PORTAL_FRAME; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#NETHER_STONES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey NETHER_STONES = org.betterx.worlds.together.tag.v3.CommonBlockTags.NETHER_STONES; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#NETHER_ORES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey NETHER_ORES = org.betterx.worlds.together.tag.v3.CommonBlockTags.NETHER_ORES; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#END_ORES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey END_ORES = org.betterx.worlds.together.tag.v3.CommonBlockTags.END_ORES; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#SAPLINGS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SAPLINGS = org.betterx.worlds.together.tag.v3.CommonBlockTags.SAPLINGS; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#SOUL_GROUND} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SOUL_GROUND = org.betterx.worlds.together.tag.v3.CommonBlockTags.SOUL_GROUND; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#WOODEN_BARREL} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_BARREL = org.betterx.worlds.together.tag.v3.CommonBlockTags.WOODEN_BARREL; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#WOODEN_CHEST} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_CHEST = org.betterx.worlds.together.tag.v3.CommonBlockTags.WOODEN_CHEST; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#WORKBENCHES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WORKBENCHES = org.betterx.worlds.together.tag.v3.CommonBlockTags.WORKBENCHES; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#DRAGON_IMMUNE} + **/ + @Deprecated(forRemoval = true) + public static final TagKey DRAGON_IMMUNE = org.betterx.worlds.together.tag.v3.CommonBlockTags.DRAGON_IMMUNE; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#MINABLE_WITH_HAMMER} + **/ + @Deprecated(forRemoval = true) + public static final TagKey MINABLE_WITH_HAMMER = org.betterx.worlds.together.tag.v3.CommonBlockTags.MINABLE_WITH_HAMMER; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#IS_OBSIDIAN} + **/ + @Deprecated(forRemoval = true) + public static final TagKey IS_OBSIDIAN = org.betterx.worlds.together.tag.v3.CommonBlockTags.IS_OBSIDIAN; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#TERRAIN} + **/ + @Deprecated(forRemoval = true) + public static final TagKey TERRAIN = org.betterx.worlds.together.tag.v3.CommonBlockTags.TERRAIN; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonBlockTags#NETHER_TERRAIN} + **/ + @Deprecated(forRemoval = true) + public static final TagKey NETHER_TERRAIN = org.betterx.worlds.together.tag.v3.CommonBlockTags.NETHER_TERRAIN; +} diff --git a/src/main/java/org/betterx/bclib/api/v2/tag/CommonItemTags.java b/src/main/java/org/betterx/bclib/api/v2/tag/CommonItemTags.java new file mode 100644 index 00000000..543c7fd9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/tag/CommonItemTags.java @@ -0,0 +1,73 @@ +package org.betterx.bclib.api.v2.tag; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; + +/** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags} + */ +@Deprecated(forRemoval = true) +public class CommonItemTags { + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#HAMMERS} + **/ + @Deprecated(forRemoval = true) + public final static TagKey HAMMERS = org.betterx.worlds.together.tag.v3.CommonItemTags.HAMMERS; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#BARREL} + **/ + @Deprecated(forRemoval = true) + public static final TagKey BARREL = org.betterx.worlds.together.tag.v3.CommonItemTags.BARREL; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#CHEST} + **/ + @Deprecated(forRemoval = true) + public static final TagKey CHEST = org.betterx.worlds.together.tag.v3.CommonItemTags.CHEST; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#SHEARS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SHEARS = org.betterx.worlds.together.tag.v3.CommonItemTags.SHEARS; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#FURNACES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey FURNACES = org.betterx.worlds.together.tag.v3.CommonItemTags.FURNACES; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#IRON_INGOTS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey IRON_INGOTS = org.betterx.worlds.together.tag.v3.CommonItemTags.IRON_INGOTS; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#LEAVES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey LEAVES = org.betterx.worlds.together.tag.v3.CommonItemTags.LEAVES; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#SAPLINGS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SAPLINGS = org.betterx.worlds.together.tag.v3.CommonItemTags.SAPLINGS; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#SOUL_GROUND} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SOUL_GROUND = org.betterx.worlds.together.tag.v3.CommonItemTags.SOUL_GROUND; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#WOODEN_BARREL} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_BARREL = org.betterx.worlds.together.tag.v3.CommonItemTags.WOODEN_BARREL; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#WOODEN_CHEST} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_CHEST = org.betterx.worlds.together.tag.v3.CommonItemTags.WOODEN_CHEST; + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.tag.v3.CommonItemTags#WORKBENCHES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WORKBENCHES = org.betterx.worlds.together.tag.v3.CommonItemTags.WORKBENCHES; + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/tag/NamedBlockTags.java b/src/main/java/org/betterx/bclib/api/v2/tag/NamedBlockTags.java new file mode 100644 index 00000000..5e423797 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/tag/NamedBlockTags.java @@ -0,0 +1,162 @@ +package org.betterx.bclib.api.v2.tag; + +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.Block; + +/** + * @deprecated Replaced by {@link BlockTags} + */ +@Deprecated(forRemoval = true) +public class NamedBlockTags { + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#ANVIL} + **/ + @Deprecated(forRemoval = true) + public static final TagKey ANVIL = BlockTags.ANVIL; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#BUTTONS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey BUTTONS = BlockTags.BUTTONS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#CLIMBABLE} + **/ + @Deprecated(forRemoval = true) + public static final TagKey CLIMBABLE = BlockTags.CLIMBABLE; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#DOORS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey DOORS = BlockTags.DOORS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#FENCES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey FENCES = BlockTags.FENCES; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#FENCE_GATES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey FENCE_GATES = BlockTags.FENCE_GATES; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#LEAVES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey LEAVES = BlockTags.LEAVES; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#LOGS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey LOGS = BlockTags.LOGS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#LOGS_THAT_BURN} + **/ + @Deprecated(forRemoval = true) + public static final TagKey LOGS_THAT_BURN = BlockTags.LOGS_THAT_BURN; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#NYLIUM} + **/ + @Deprecated(forRemoval = true) + public static final TagKey NYLIUM = BlockTags.NYLIUM; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#PLANKS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey PLANKS = BlockTags.PLANKS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#PRESSURE_PLATES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey PRESSURE_PLATES = BlockTags.PRESSURE_PLATES; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#SAPLINGS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SAPLINGS = BlockTags.SAPLINGS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#SIGNS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SIGNS = BlockTags.SIGNS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#SLABS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SLABS = BlockTags.SLABS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#STAIRS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey STAIRS = BlockTags.STAIRS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#STONE_PRESSURE_PLATES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey STONE_PRESSURE_PLATES = BlockTags.STONE_PRESSURE_PLATES; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#TRAPDOORS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey TRAPDOORS = BlockTags.TRAPDOORS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#WALLS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WALLS = BlockTags.WALLS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#WOODEN_BUTTONS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_BUTTONS = BlockTags.WOODEN_BUTTONS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#WOODEN_DOORS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_DOORS = BlockTags.WOODEN_DOORS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#WOODEN_FENCES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_FENCES = BlockTags.WOODEN_FENCES; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#WOODEN_PRESSURE_PLATES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_PRESSURE_PLATES = BlockTags.WOODEN_PRESSURE_PLATES; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#WOODEN_SLABS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_SLABS = BlockTags.WOODEN_SLABS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#WOODEN_STAIRS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_STAIRS = BlockTags.WOODEN_STAIRS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#WOODEN_TRAPDOORS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_TRAPDOORS = BlockTags.WOODEN_TRAPDOORS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#SOUL_FIRE_BASE_BLOCKS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SOUL_FIRE_BASE_BLOCKS = BlockTags.SOUL_FIRE_BASE_BLOCKS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#SOUL_SPEED_BLOCKS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SOUL_SPEED_BLOCKS = BlockTags.SOUL_SPEED_BLOCKS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#BEACON_BASE_BLOCKS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey BEACON_BASE_BLOCKS = BlockTags.BEACON_BASE_BLOCKS; + /** + * @deprecated replaced by {@link net.minecraft.tags.BlockTags#STONE_BRICKS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey STONE_BRICKS = BlockTags.STONE_BRICKS; +} diff --git a/src/main/java/org/betterx/bclib/api/v2/tag/NamedItemTags.java b/src/main/java/org/betterx/bclib/api/v2/tag/NamedItemTags.java new file mode 100644 index 00000000..68165e05 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/tag/NamedItemTags.java @@ -0,0 +1,128 @@ +package org.betterx.bclib.api.v2.tag; + +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; + + +/** + * @deprecated Replaced by {@link ItemTags} + */ +@Deprecated(forRemoval = true) +public class NamedItemTags { + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#BUTTONS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey BUTTONS = ItemTags.BUTTONS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#DOORS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey DOORS = ItemTags.DOORS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#FENCES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey FENCES = ItemTags.FENCES; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#LEAVES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey LEAVES = ItemTags.LEAVES; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#LOGS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey LOGS = ItemTags.LOGS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#LOGS_THAT_BURN} + **/ + @Deprecated(forRemoval = true) + public static final TagKey LOGS_THAT_BURN = ItemTags.LOGS_THAT_BURN; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#PLANKS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey PLANKS = ItemTags.PLANKS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#SAPLINGS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SAPLINGS = ItemTags.SAPLINGS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#SIGNS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SIGNS = ItemTags.SIGNS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#SLABS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey SLABS = ItemTags.SLABS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#STAIRS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey STAIRS = ItemTags.STAIRS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#TRAPDOORS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey TRAPDOORS = ItemTags.TRAPDOORS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#WOODEN_BUTTONS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_BUTTONS = ItemTags.WOODEN_BUTTONS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#WOODEN_DOORS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_DOORS = ItemTags.WOODEN_DOORS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#WOODEN_FENCES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_FENCES = ItemTags.WOODEN_FENCES; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#WOODEN_PRESSURE_PLATES} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_PRESSURE_PLATES = ItemTags.WOODEN_PRESSURE_PLATES; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#WOODEN_SLABS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_SLABS = ItemTags.WOODEN_SLABS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#WOODEN_STAIRS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_STAIRS = ItemTags.WOODEN_STAIRS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#WOODEN_TRAPDOORS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey WOODEN_TRAPDOORS = ItemTags.WOODEN_TRAPDOORS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#BEACON_PAYMENT_ITEMS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey BEACON_PAYMENT_ITEMS = ItemTags.BEACON_PAYMENT_ITEMS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#STONE_BRICKS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey STONE_BRICKS = ItemTags.STONE_BRICKS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#STONE_CRAFTING_MATERIALS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey STONE_CRAFTING_MATERIALS = ItemTags.STONE_CRAFTING_MATERIALS; + /** + * @deprecated replaced by {@link net.minecraft.tags.ItemTags#STONE_TOOL_MATERIALS} + **/ + @Deprecated(forRemoval = true) + public static final TagKey STONE_TOOL_MATERIALS = ItemTags.STONE_TOOL_MATERIALS; +} diff --git a/src/main/java/org/betterx/bclib/api/v2/tag/NamedMineableTags.java b/src/main/java/org/betterx/bclib/api/v2/tag/NamedMineableTags.java new file mode 100644 index 00000000..29f16d16 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/tag/NamedMineableTags.java @@ -0,0 +1,48 @@ +package org.betterx.bclib.api.v2.tag; + +import org.betterx.worlds.together.tag.v3.MineableTags; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.Block; + +/** + * @deprecated Replaced by {@link MineableTags} + */ +@Deprecated(forRemoval = true) +public class NamedMineableTags { + /** + * @deprecated use {@link MineableTags#AXE} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey AXE = MineableTags.AXE; + /** + * @deprecated use {@link MineableTags#HOE} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey HOE = MineableTags.HOE; + /** + * @deprecated use {@link MineableTags#PICKAXE} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey PICKAXE = MineableTags.PICKAXE; + /** + * @deprecated use {@link MineableTags#SHEARS} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey SHEARS = MineableTags.SHEARS; + /** + * @deprecated use {@link MineableTags#SHOVEL} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey SHOVEL = MineableTags.SHOVEL; + /** + * @deprecated use {@link MineableTags#SWORD} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey SWORD = MineableTags.SWORD; + /** + * @deprecated use {@link MineableTags#HAMMER} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey HAMMER = MineableTags.HAMMER; +} diff --git a/src/main/java/org/betterx/bclib/api/v2/tag/NamedToolTags.java b/src/main/java/org/betterx/bclib/api/v2/tag/NamedToolTags.java new file mode 100644 index 00000000..72a197cf --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/tag/NamedToolTags.java @@ -0,0 +1,45 @@ +package org.betterx.bclib.api.v2.tag; + +import org.betterx.worlds.together.tag.v3.ToolTags; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; + +/** + * @deprecated Replaced by {@link ToolTags} + */ +@Deprecated(forRemoval = true) +public class NamedToolTags { + /** + * @deprecated use {@link ToolTags#FABRIC_AXES} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey FABRIC_AXES = ToolTags.FABRIC_AXES; + /** + * @deprecated use {@link ToolTags#FABRIC_HOES} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey FABRIC_HOES = ToolTags.FABRIC_HOES; + /** + * @deprecated use {@link ToolTags#FABRIC_PICKAXES} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey FABRIC_PICKAXES = ToolTags.FABRIC_PICKAXES; + /** + * @deprecated use {@link ToolTags#FABRIC_SHEARS} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey FABRIC_SHEARS = ToolTags.FABRIC_SHEARS; + /** + * @deprecated use {@link ToolTags#FABRIC_SHOVELS} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey FABRIC_SHOVELS = ToolTags.FABRIC_SHOVELS; + /** + * @deprecated use {@link ToolTags#FABRIC_SWORDS} instead + **/ + @Deprecated(forRemoval = true) + public static final TagKey FABRIC_SWORDS = ToolTags.FABRIC_SWORDS; + + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/tag/TagAPI.java b/src/main/java/org/betterx/bclib/api/v2/tag/TagAPI.java new file mode 100644 index 00000000..2c6475d7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/tag/TagAPI.java @@ -0,0 +1,281 @@ +package org.betterx.bclib.api.v2.tag; + +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; +import org.betterx.worlds.together.mixin.common.DiggerItemAccessor; +import org.betterx.worlds.together.tag.v3.TagManager; +import org.betterx.worlds.together.tag.v3.TagRegistry; + +import net.minecraft.core.DefaultedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.Tag; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.function.Function; + +/** + * @deprecated Replaced by {@link TagManager} + */ +@Deprecated(forRemoval = true) +public class TagAPI { + private static final Map> TYPES = Maps.newHashMap(); + + /** + * @deprecated Replaced by {@link TagManager#BLOCKS} + */ + @Deprecated(forRemoval = true) + public static TagType.RegistryBacked BLOCKS = registerType(Registry.BLOCK); + /** + * @deprecated Replaced by {@link TagManager#ITEMS} + */ + @Deprecated(forRemoval = true) + public static TagType.RegistryBacked ITEMS = registerType(Registry.ITEM); + + /** + * @deprecated Replaced by {@link TagManager#BIOMES} + */ + @Deprecated(forRemoval = true) + public static TagType.Simple BIOMES = registerType( + Registry.BIOME_REGISTRY, + "tags/worldgen/biome", + b -> BiomeAPI.getBiomeID(b) + ); + + /** + * @deprecated Replaced by {@link TagManager#registerType(DefaultedRegistry)} + */ + @Deprecated(forRemoval = true) + public static TagType.RegistryBacked registerType(DefaultedRegistry registry) { + TagType type = new TagType.RegistryBacked<>(registry); + return (TagType.RegistryBacked) TYPES.computeIfAbsent(type.directory, (dir) -> type); + } + + /** + * @deprecated Replaced by {@link TagManager#registerType(Registry, String)} + */ + @Deprecated(forRemoval = true) + public static TagType.Simple registerType(Registry registry, String directory) { + return registerType(registry.key(), directory, (o) -> registry.getKey(o)); + } + + /** + * @deprecated Replaced by {@link TagManager#registerType(ResourceKey, String, Function)} + */ + @Deprecated(forRemoval = true) + public static TagType.Simple registerType( + ResourceKey> registry, + String directory, + Function locationProvider + ) { + return (TagType.Simple) TYPES.computeIfAbsent( + directory, + (dir) -> new TagType.Simple<>( + registry, + dir, + locationProvider + ) + ); + } + + /** + * @deprecated Replaced by {@link TagManager#registerType(ResourceKey, String)} + */ + @Deprecated(forRemoval = true) + public static TagType.UnTyped registerType(ResourceKey> registry, String directory) { + return (TagType.UnTyped) TYPES.computeIfAbsent(directory, (dir) -> new TagType.UnTyped<>(registry, dir)); + } + + /** + * @deprecated Replaced by {@link TagRegistry#makeTag(String, String)} on {@link TagManager#BIOMES} + */ + @Deprecated(forRemoval = true) + public static TagKey makeBiomeTag(String modID, String name) { + return TagManager.BIOMES.makeTag(new ResourceLocation(modID, name)); + } + + + /** + * @deprecated Replaced by {@link TagRegistry.Biomes#makeStructureTag(String, String)} on {@link TagManager#BIOMES} + */ + @Deprecated(forRemoval = true) + public static TagKey makeStructureTag(String modID, String name) { + return TagManager.BIOMES.makeStructureTag(modID, name); + } + + + /** + * @deprecated Replaced by {@link TagRegistry#makeTag(String, String)} on {@link TagManager#BLOCKS} + */ + @Deprecated(forRemoval = true) + public static TagKey makeBlockTag(String modID, String name) { + return TagManager.BLOCKS.makeTag(new ResourceLocation(modID, name)); + } + + /** + * @deprecated Replaced by {@link TagRegistry#makeTag(ResourceLocation)} on {@link TagManager#BLOCKS} + */ + @Deprecated(forRemoval = true) + public static TagKey makeBlockTag(ResourceLocation id) { + return TagManager.BLOCKS.makeTag(id); + } + + /** + * @deprecated Replaced by {@link TagRegistry#makeTag(String, String)} on {@link TagManager#ITEMS} + */ + @Deprecated(forRemoval = true) + public static TagKey makeItemTag(String modID, String name) { + return TagManager.ITEMS.makeTag(new ResourceLocation(modID, name)); + } + + /** + * @deprecated Replaced by {@link TagRegistry#makeTag(ResourceLocation)} on {@link TagManager#ITEMS} + */ + @Deprecated(forRemoval = true) + public static TagKey makeItemTag(ResourceLocation id) { + return TagManager.ITEMS.makeTag(id); + } + + /** + * @deprecated Replaced by {@link TagRegistry#makeCommonTag(String)} on {@link TagManager#BLOCKS} + */ + @Deprecated(forRemoval = true) + public static TagKey makeCommonBlockTag(String name) { + return TagManager.BLOCKS.makeCommonTag(name); + } + + /** + * @deprecated Replaced by {@link TagRegistry#makeCommonTag(String)} on {@link TagManager#ITEMS} + */ + @Deprecated(forRemoval = true) + public static TagKey makeCommonItemTag(String name) { + return TagManager.ITEMS.makeCommonTag(name); + } + + /** + * @deprecated Replaced by {@link TagRegistry#makeCommonTag(String)} on {@link TagManager#BIOMES} + */ + @Deprecated(forRemoval = true) + public static TagKey makeCommonBiomeTag(String name) { + return TagManager.BIOMES.makeCommonTag(name); + } + + /** + * Initializes basic tags. Should be called only in BCLib main class. + */ + @Deprecated(forRemoval = true) + public static void init() { + } + + /** + * Please use {@link TagManager}.BIOMES.add(biome, tagIDs) instead + * + * @deprecated + */ + @Deprecated(forRemoval = true) + @SafeVarargs + public static void addBiomeTags(Biome biome, TagKey... tagIDs) { + TagManager.BIOMES.add(biome, tagIDs); + } + + + /** + * Please use {@link TagManager}.BIOMES.add(tagID, biomes) instead + * + * @deprecated + */ + @Deprecated(forRemoval = true) + public static void addBiomeTag(TagKey tagID, Biome... biomes) { + TagManager.BIOMES.add(tagID, biomes); + } + + + /** + * Please use {@link TagManager}.BLOCKS.add(block, tagIDs) instead + * + * @deprecated + */ + @Deprecated(forRemoval = true) + @SafeVarargs + public static void addBlockTags(Block block, TagKey... tagIDs) { + TagManager.BLOCKS.add(block, tagIDs); + } + + + /** + * Please use {@link TagManager}.BIOMES.add(tagID, blocks) instead + * + * @deprecated + */ + @Deprecated(forRemoval = true) + public static void addBlockTag(TagKey tagID, Block... blocks) { + TagManager.BLOCKS.add(tagID, blocks); + } + + /** + * Please use {@link TagManager}.ITEMS.add(item, tagIDs) instead + * + * @deprecated + */ + @Deprecated(forRemoval = true) + @SafeVarargs + public static void addItemTags(ItemLike item, TagKey... tagIDs) { + TagManager.ITEMS.add(item.asItem(), tagIDs); + } + + /** + * @deprecated + */ + @Deprecated(forRemoval = true) + public static void addItemTag(TagKey tagID, ItemLike... items) { + for (ItemLike i : items) { + TagManager.ITEMS.add(i.asItem(), tagID); + } + } + + /** + * Please use {@link TagManager}.ITEMS.add(tagID, items) instead + * + * @deprecated + */ + @Deprecated(forRemoval = true) + public static void addItemTag(TagKey tagID, Item... items) { + TagManager.ITEMS.add(tagID, items); + } + + + @Deprecated(forRemoval = true) + public static Map apply( + String directory, + Map tagsMap + ) { + TagType type = TYPES.get(directory); + if (type != null) { + type.apply(tagsMap); + } + return tagsMap; + } + + + /** + * @param stack + * @param tag + * @return + * @deprecated call {@link TagManager#isToolWithMineableTag(ItemStack, TagKey)} instead + */ + @Deprecated(forRemoval = true) + public static boolean isToolWithMineableTag(ItemStack stack, TagKey tag) { + if (stack.getItem() instanceof DiggerItemAccessor dig) { + return dig.bclib_getBlockTag().equals(tag); + } + return false; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/tag/TagType.java b/src/main/java/org/betterx/bclib/api/v2/tag/TagType.java new file mode 100644 index 00000000..2b5d401b --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/tag/TagType.java @@ -0,0 +1,257 @@ +package org.betterx.bclib.api.v2.tag; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.biomes.InternalBiomeAPI; +import org.betterx.worlds.together.tag.v3.TagRegistry; + +import net.minecraft.core.DefaultedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.Tag; +import net.minecraft.tags.TagKey; +import net.minecraft.tags.TagManager; +import net.minecraft.world.level.biome.Biome; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * @deprecated Replaced by {@link TagRegistry} + */ +@Deprecated(forRemoval = true) +public class TagType { + boolean isFrozen = false; + + /** + * @deprecated Replaced by {@link TagRegistry.RegistryBacked} + */ + @Deprecated(forRemoval = true) + 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) { + initializeTag(id); + return registry + .getTagNames() + .filter(tagKey -> tagKey.location().equals(id)) + .findAny() + .orElse(TagKey.create(registry.key(), id)); + } + } + + /** + * @deprecated Replaced by {@link TagRegistry.Simple} + */ + @Deprecated(forRemoval = true) + public static class Simple extends TagType { + Simple( + ResourceKey> registry, + String directory, + Function locationProvider + ) { + super(registry, directory, locationProvider); + } + + public void add(TagKey tagID, T... elements) { + super.add(tagID, elements); + } + + public void add(T element, TagKey... tagIDs) { + super.add(element, tagIDs); + } + + @Deprecated(forRemoval = true) + public void add(ResourceLocation tagID, T... elements) { + super.add(tagID, elements); + } + + @Deprecated(forRemoval = true) + public void add(T element, ResourceLocation... tagIDs) { + super.add(element, tagIDs); + } + } + + /** + * @deprecated Replaced by {@link TagRegistry.UnTyped} + */ + @Deprecated(forRemoval = true) + public static class UnTyped extends TagType { + 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> tags = Maps.newConcurrentMap(); + public final ResourceKey> registryKey; + private final Function locationProvider; + + private TagType( + ResourceKey> registry, + String directory, + Function locationProvider + ) { + this.registryKey = registry; + this.directory = directory; + this.locationProvider = locationProvider; + } + + protected void initializeTag(ResourceLocation tagID) { + getSetForTag(tagID); + } + + public Set getSetForTag(ResourceLocation tagID) { + return tags.computeIfAbsent(tagID, k -> Sets.newHashSet()); + } + + public Set getSetForTag(TagKey tag) { + if (tag == null) { + return new HashSet<>(); + } + return getSetForTag(tag.location()); + } + + + /** + * 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) { + initializeTag(id); + return TagKey.create(registryKey, id); + } + + /** + * 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 void addUntyped(TagKey tagID, ResourceLocation... elements) { + if (isFrozen) BCLib.LOGGER.warning("Adding Tag " + tagID + " after the API was frozen."); + Set set = getSetForTag(tagID); + for (ResourceLocation id : elements) { + if (id != null) { + set.add(new Tag.ElementEntry(id)); + } + } + } + + public void addUntyped(ResourceLocation element, TagKey... tagIDs) { + for (TagKey tagID : tagIDs) { + addUntyped(tagID, element); + } + } + + public void addOtherTags(TagKey tagID, TagKey... tags) { + if (isFrozen) BCLib.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(new Tag.TagEntry(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) { + if (isFrozen) BCLib.LOGGER.warning("Adding Tag " + tagID + " after the API was frozen."); + Set set = getSetForTag(tagID); + for (T element : elements) { + ResourceLocation id = locationProvider.apply(element); + if (id != null) { + set.add(new Tag.ElementEntry(id)); + } + } + } + + protected void add(T element, TagKey... tagIDs) { + for (TagKey tagID : tagIDs) { + add(tagID, element); + } + } + + @Deprecated(forRemoval = true) + protected void add(ResourceLocation tagID, T... elements) { + if (isFrozen) BCLib.LOGGER.warning("Adding Tag " + tagID + " after the API was frozen."); + Set set = getSetForTag(tagID); + for (T element : elements) { + ResourceLocation id = locationProvider.apply(element); + if (id != null) { + set.add(new Tag.ElementEntry(id)); + } + } + } + + @Deprecated(forRemoval = true) + protected void add(T element, ResourceLocation... tagIDs) { + for (ResourceLocation tagID : tagIDs) { + add(tagID, element); + } + } + + public void forEach(BiConsumer> consumer) { + tags.forEach(consumer); + } + + public void apply(Map tagsMap) { + if (Registry.BIOME_REGISTRY.equals(registryKey)) InternalBiomeAPI._runBiomeTagAdders(); + + //this.isFrozen = true; + this.forEach((id, ids) -> apply(id, tagsMap.computeIfAbsent(id, unused -> new Tag.Builder()), ids)); + } + + private static Tag.Builder apply( + ResourceLocation id, + Tag.Builder builder, + Set ids + ) { + ids.forEach(value -> builder.add(new Tag.BuilderEntry(value, BCLib.MOD_ID))); + return builder; + } +} 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..6b2fad46 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLConfigureFeature.java @@ -0,0 +1,107 @@ +package org.betterx.bclib.api.v3.levelgen.features; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +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; +import java.util.Random; + +public class BCLConfigureFeature, FC extends FeatureConfiguration> { + 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, Random random) { + return placeInWorld(getFeature(), getConfiguration(), level, pos, random); + } + + private static boolean placeUnboundInWorld( + Feature feature, + FeatureConfiguration config, + ServerLevel level, + BlockPos pos, + Random random + ) { + if (config instanceof RandomPatchConfiguration rnd) { + var configured = rnd.feature().value().feature().value(); + feature = configured.feature(); + config = configured.config(); + } + + if (feature instanceof UserGrowableFeature growable) { + return growable.grow(level, pos, random, config); + } + + FeaturePlaceContext context = new FeaturePlaceContext( + Optional.empty(), + level, + level.getChunkSource().getGenerator(), + random, + pos, + config + ); + return feature.place(context); + } + + public static boolean placeInWorld( + Feature feature, + ServerLevel level, + BlockPos pos, + Random random + ) { + return placeUnboundInWorld(feature, FeatureConfiguration.NONE, level, pos, random); + } + + public static boolean placeInWorld( + Feature feature, + FC config, + ServerLevel level, + BlockPos pos, + Random random + ) { + return placeUnboundInWorld(feature, config, level, pos, random); + } +} 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..fa2cf696 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLFeature.java @@ -0,0 +1,105 @@ +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.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 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 + @Deprecated(forRemoval = true) + public 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(Registry.FEATURE, location, feature); + } +} 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..7666938f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLFeatureBuilder.java @@ -0,0 +1,949 @@ +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.Triple; + +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.data.BuiltinRegistries; +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.function.BiFunction; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public abstract class BCLFeatureBuilder, FC extends FeatureConfiguration> { + private final ResourceLocation featureID; + private final F feature; + + private BCLFeatureBuilder(ResourceLocation featureID, F feature) { + this.featureID = featureID; + this.feature = feature; + } + + /** + * Starts a new {@link BCLFeature} builder. + * + * @param featureID {@link ResourceLocation} feature identifier. + * @param feature {@link Feature} to construct. + * @return {@link org.betterx.bclib.api.v2.levelgen.features.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 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 WithTemplates startWithTemplates( + ResourceLocation featureID + ) { + return new WithTemplates( + featureID, + (TemplateFeature) org.betterx.bclib.api.v3.levelgen.features.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 + ); + } + + /** + * Internally used by the builder. Normally you should not have to call this method directly as it is + * handled by {@link #buildAndRegister()} + * + * @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( + ResourceLocation id, + ConfiguredFeature cFeature + ) { + return (Holder>) (Object) BuiltinRegistries.register( + BuiltinRegistries.CONFIGURED_FEATURE, + id, + cFeature + ); + } + + public abstract FC createConfiguration(); + + protected BCLConfigureFeature buildAndRegister(BiFunction, Holder>> 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 new BCLConfigureFeature<>(featureID, holder, true); + } + + public BCLConfigureFeature buildAndRegister() { + return buildAndRegister(BCLFeatureBuilder::register); + } + + public BCLConfigureFeature build() { + return buildAndRegister((id, cFeature) -> Holder.direct(cFeature)); + } + + public BCLInlinePlacedBuilder inlinePlace() { + BCLConfigureFeature f = build(); + return BCLInlinePlacedBuilder.place(f); + } + + public Holder inlinePlace(BCLInlinePlacedBuilder placer) { + BCLConfigureFeature f = build(); + return placer.build(f); + } + + 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( + ResourceLocation featureID, + PillarFeature feature, + 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(ResourceLocation featureID, 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(ResourceLocation featureID, 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(ResourceLocation featureID, 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(ResourceLocation featureID, 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) return (FC) NoneFeatureConfiguration.NONE; + 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(ResourceLocation featureID, 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 WeightedBlock extends BCLFeatureBuilder { + SimpleWeightedRandomList.Builder stateBuilder = SimpleWeightedRandomList.builder(); + + private WeightedBlock( + @NotNull ResourceLocation featureID, + @NotNull SimpleBlockFeature feature + ) { + super(featureID, feature); + } + + public WeightedBlock add(Block block, int weight) { + return add(block.defaultBlockState(), weight); + } + + public WeightedBlock add(BlockState state, int weight) { + stateBuilder.add(state, weight); + return this; + } + + public WeightedBlock 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 WeightedBlock 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 SimpleBlockConfiguration createConfiguration() { + return new SimpleBlockConfiguration(new WeightedStateProvider(stateBuilder.build())); + } + } + + public static class AsRandomSelect extends BCLFeatureBuilder { + private final List features = new LinkedList<>(); + private Holder defaultFeature; + + private AsRandomSelect(ResourceLocation featureID, 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( + ResourceLocation featureID, + RandomSelectorFeature feature, + 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..ecb6a80f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLInlinePlacedBuilder.java @@ -0,0 +1,85 @@ +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.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 Holder build() { + return build(cFeature); + } + + /** + * Builds a new inline (not registered) {@link PlacedFeature}. + * + * @return created {@link PlacedFeature} instance. + */ + public Holder build(BCLConfigureFeature feature) { + return build(feature.configuredFeature); + } + + /** + * Builds a new inline (not registered) {@link PlacedFeature}. + * + * @return created {@link PlacedFeature} instance. + */ + public Holder build(Holder> feature) { + PlacementModifier[] modifiers = modifications.toArray(new PlacementModifier[modifications.size()]); + return PlacementUtils.inlinePlaced(feature, modifiers); + } + + /** + * 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..83658080 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLPlacedFeatureBuilder.java @@ -0,0 +1,88 @@ +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; + +public class BCLPlacedFeatureBuilder, FC extends FeatureConfiguration> extends CommonPlacedFeatureBuilder> { + private final ResourceLocation featureID; + private GenerationStep.Decoration decoration = GenerationStep.Decoration.VEGETAL_DECORATION; + private final BCLConfigureFeature cFeature; + + 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 Holder build() { + Holder p = PlacementUtils.register( + featureID.toString(), + cFeature.configuredFeature, + modifications + ); + return p; + } + + + /** + * Builds a new {@link BCLFeature} instance. + * Features will be registered during this process. + * + * @return created {@link BCLFeature} instance. + */ + public BCLFeature buildAndRegister() { + Holder p = build(); + return new BCLFeature(cFeature, p, decoration); + } +} 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..9740100d --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/CommonPlacedFeatureBuilder.java @@ -0,0 +1,423 @@ +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.Holder; +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.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 net.minecraft.world.level.material.Material; + +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()); + } + + + /** + * 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 (see {@link Material#isReplaceable()}) and will be accepted if it hits a block with the + * {@link CommonBlockTags#TERRAIN}-tag + * + * @param dir The direction the ray is cast + * @param distance The maximum search Distance + * @return The instance it was called on + * @see #findSolidSurface(Direction, int) for Details + */ + public 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 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.onEveryLayer(countPerLayer).onlyInBiome(); + } + + public T betterNetherGround(int countPerLayer) { + return (T) this.count(countPerLayer).squarePlacement().onEveryLayerMin4().onlyInBiome(); + } + + public T betterNetherCeiling(int countPerLayer) { + return (T) this.count(countPerLayer).squarePlacement().underEveryLayerMin4().onlyInBiome(); + } + + public T betterNetherOnWall(int countPerLayer) { + return (T) this.count(countPerLayer) + .squarePlacement() + .randomHeight4FromFloorCeil() + .onWalls(16, 0) + .onlyInBiome(); + } + + public T betterNetherInWall(int countPerLayer) { + return (T) this.count(countPerLayer) + .squarePlacement() + .randomHeight4FromFloorCeil() + .onWalls(16, 1) + .onlyInBiome(); + } + + /** + * Builds a new inline (not registered) {@link PlacedFeature}. + * + * @return created {@link PlacedFeature} instance. + */ + abstract Holder build(); + + public BCLFeatureBuilder.RandomPatch inRandomPatch(ResourceLocation id) { + return BCLFeatureBuilder.startRandomPatch(id, build()); + } +} 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..ee863b7d --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/UserGrowableFeature.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.api.v3.levelgen.features; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; + +import java.util.Random; + +public interface UserGrowableFeature { + boolean grow( + ServerLevelAccessor level, + BlockPos pos, + Random 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..28cd01fa --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/blockpredicates/BlockPredicates.java @@ -0,0 +1,48 @@ +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.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(Registry.BLOCK_PREDICATE_TYPES, 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..8dd40dfd --- /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.v2.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..4e0bd4a9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/PillarFeatureConfig.java @@ -0,0 +1,148 @@ +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.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; + +import java.util.Arrays; +import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; + +public class PillarFeatureConfig implements FeatureConfiguration { + @FunctionalInterface + public interface StateTransform { + BlockState apply(int height, int maxHeight, BlockState inputState, BlockPos pos, Random 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, + Random 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 Codec CODEC = StringRepresentable + .fromEnum(KnownTransformers::values, KnownTransformers::byName); + private static final Map BY_NAME = Arrays.stream(KnownTransformers.values()) + .collect(Collectors.toMap( + KnownTransformers::name, + a -> a + )); + + 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 KnownTransformers byName(String string) { + return BY_NAME.get(string); + } + } + + 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, Random 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..b88fae2c --- /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.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; +import java.util.Random; + +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(Random 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..91c8a7a0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/SequenceFeatureConfig.java @@ -0,0 +1,46 @@ +package org.betterx.bclib.api.v3.levelgen.features.config; + +import org.betterx.bclib.api.v2.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 create(List> features) { + return new SequenceFeatureConfig(features.stream().map(f -> f.getPlacedFeature()).toList()); + } + + 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..0d5c3715 --- /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.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.Random; +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 Random 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..618c189f --- /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.world.level.WorldGenLevel; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; + +import java.util.Random; + +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 Random rnd = featurePlaceContext.random(); + int maxHeight = config.maxHeight.sample(rnd); + int minHeight = config.minHeight.sample(rnd); + BlockPos.MutableBlockPos posnow = featurePlaceContext.origin().mutable(); + + for (height = 0; 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..7abc1903 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/TemplateFeature.java @@ -0,0 +1,46 @@ +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.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; + +import java.util.Random; + +public class TemplateFeature extends Feature { + public TemplateFeature(Codec codec) { + super(codec); + } + + protected StructureWorldNBT randomStructure(TemplateFeatureConfig cfg, Random 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..3eb423db --- /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.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.Random; +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, + Random Random, + 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..24aed896 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Debug.java @@ -0,0 +1,29 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import net.minecraft.core.BlockPos; +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.Random; +import java.util.stream.Stream; + +public class Debug extends PlacementModifier { + public static final Debug INSTANCE = new Debug(); + public static final Codec CODEC = Codec.unit(Debug::new); + + @Override + public Stream getPositions( + PlacementContext placementContext, + Random Random, + BlockPos 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..87c5e51b --- /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.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.Random; +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, + Random 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..9ef19640 --- /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.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.Random; +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(Random random) { + return direction.get(provider.sample(random)); + } + + @Override + public Stream getPositions( + PlacementContext placementContext, + Random Random, + BlockPos blockPos + ) { + var builder = Stream.builder(); + if (randomSelect) { + submitSingle(placementContext, blockPos, builder, randomDirection(Random)); + } 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..bc6dfab9 --- /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.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.Random; +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, + Random Random, + BlockPos blockPos + ) { + Stream.Builder stream = Stream.builder(); + for (PlacementModifier p : modifiers) { + p.getPositions(placementContext, Random, 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/Is.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Is.java new file mode 100644 index 00000000..4a9ed0a3 --- /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.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 java.util.Random; + +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, Random 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..2ac56ee2 --- /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.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 java.util.Random; + +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, Random 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..160a6090 --- /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.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 java.util.Random; +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, Random 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..e4f23094 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/NoiseFilter.java @@ -0,0 +1,61 @@ +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.Registry; +import net.minecraft.resources.ResourceKey; +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; + +import java.util.Random; + +public class NoiseFilter extends PlacementFilter { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + ResourceKey.codec(Registry.NOISE_REGISTRY).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, Random 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..8522ae8e --- /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.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.Random; +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, + Random Random, + 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..ed055fdf --- /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.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.Random; +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, + Random 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..62248abc --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/PlacementModifiers.java @@ -0,0 +1,90 @@ +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.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 + ); + + + 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(Registry.PLACEMENT_MODIFIERS, 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..5ffed950 --- /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.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.Random; +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, + Random Random, + 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..548dddd7 --- /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.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.Random; +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, + Random 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/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/BaseSignBlockEntity.java b/src/main/java/org/betterx/bclib/blockentities/BaseSignBlockEntity.java new file mode 100644 index 00000000..e91c936b --- /dev/null +++ b/src/main/java/org/betterx/bclib/blockentities/BaseSignBlockEntity.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.blockentities; + +import org.betterx.bclib.registry.BaseBlockEntities; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class BaseSignBlockEntity extends SignBlockEntity { + public BaseSignBlockEntity(BlockPos blockPos, BlockState blockState) { + super(blockPos, blockState); + } + + @Override + public BlockEntityType getType() { + return BaseBlockEntities.SIGN; + } +} \ No newline at end of file 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..3f44a90d --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseAnvilBlock.java @@ -0,0 +1,137 @@ +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.items.BaseAnvilItem; + +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.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.PickaxeItem; +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.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.MaterialColor; +import net.minecraft.world.level.storage.loot.LootContext; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.*; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseAnvilBlock extends AnvilBlock implements BlockModelProvider, CustomItemProvider { + public static final IntegerProperty DESTRUCTION = BlockProperties.DESTRUCTION; + public IntegerProperty durability; + + public BaseAnvilBlock(MaterialColor color) { + this(FabricBlockSettings.copyOf(Blocks.ANVIL).color(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, FabricItemSettings settings) { + return new BaseAnvilItem(this, settings); + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootContext.Builder builder) { + int destruction = state.getValue(DESTRUCTION); + int durability = state.getValue(getDurabilityProp()); + int value = destruction * getMaxDurability() + durability; + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && tool.getItem() instanceof PickaxeItem) { + 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 3; + } + + public BlockState damageAnvilUse(BlockState state, Random random) { + IntegerProperty durability = getDurabilityProp(); + int value = state.getValue(durability); + if (value < getMaxDurability() && random.nextInt(8) == 0) { + 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; + } +} 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..3ba71ece --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseAttachedBlock.java @@ -0,0 +1,83 @@ +package org.betterx.bclib.blocks; + +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; + + public 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); + } +} 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..b4875661 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBarkBlock.java @@ -0,0 +1,26 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.PatternsHelper; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; + +import java.util.Optional; + +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/org/betterx/bclib/blocks/BaseBarrelBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseBarrelBlock.java new file mode 100644 index 00000000..1cbdb4b7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBarrelBlock.java @@ -0,0 +1,163 @@ +package org.betterx.bclib.blocks; + +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.registry.BaseBlockEntities; + +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.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.level.storage.loot.LootContext; +import net.minecraft.world.phys.BlockHitResult; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import org.jetbrains.annotations.Nullable; + +public class BaseBarrelBlock extends BarrelBlock implements BlockModelProvider { + public BaseBarrelBlock(Block source) { + this(FabricBlockSettings.copyOf(source).noOcclusion()); + } + + public BaseBarrelBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return BaseBlockEntities.BARREL.create(blockPos, blockState); + } + + @Override + @SuppressWarnings("deprecation") + 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 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, Random 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); + } +} 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..ddea5ed0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBlock.java @@ -0,0 +1,78 @@ +package org.betterx.bclib.blocks; + +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.MaterialColor; +import net.minecraft.world.level.storage.loot.LootContext; + +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +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, LootContext.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, MaterialColor, 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 FabricBlockSettings acceptAndReturn( + Consumer customizeProperties, + FabricBlockSettings settings + ) { + customizeProperties.accept(settings); + return 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..22da4b48 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBlockNotFull.java @@ -0,0 +1,24 @@ +package org.betterx.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/org/betterx/bclib/blocks/BaseBlockWithEntity.java b/src/main/java/org/betterx/bclib/blocks/BaseBlockWithEntity.java new file mode 100644 index 00000000..5900b454 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBlockWithEntity.java @@ -0,0 +1,28 @@ +package org.betterx.bclib.blocks; + +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.LootContext; + +import java.util.Collections; +import java.util.List; + +public class BaseBlockWithEntity extends BaseEntityBlock { + public BaseBlockWithEntity(Properties settings) { + super(settings); + } + + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return null; + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootContext.Builder builder) { + return Collections.singletonList(new ItemStack(this)); + } +} 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..67b2546d --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBookshelfBlock.java @@ -0,0 +1,60 @@ +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 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.BlockBehaviour; +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.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseBookshelfBlock extends BaseBlock { + public BaseBookshelfBlock(Block source) { + this(FabricBlockSettings.copyOf(source)); + } + + public BaseBookshelfBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + public List getDrops(BlockState state, LootContext.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); + } + + private ResourceLocation replacePath(ResourceLocation blockId) { + String newPath = blockId.getPath().replace("_bookshelf", ""); + return new ResourceLocation(blockId.getNamespace(), newPath); + } +} 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..5c50bb4a --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseButtonBlock.java @@ -0,0 +1,110 @@ +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 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 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 BaseButtonBlock extends ButtonBlock implements BlockModelProvider { + private final Block parent; + + protected BaseButtonBlock(Block parent, Properties properties, boolean sensitive) { + super(sensitive, properties.noCollission()); + this.parent = parent; + } + + @Override + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseChainBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseChainBlock.java new file mode 100644 index 00000000..5f644679 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseChainBlock.java @@ -0,0 +1,77 @@ +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.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.item.ItemStack; +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.MaterialColor; +import net.minecraft.world.level.storage.loot.LootContext; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseChainBlock extends ChainBlock implements BlockModelProvider, RenderLayerProvider { + public BaseChainBlock(MaterialColor color) { + this(FabricBlockSettings.copyOf(Blocks.CHAIN).color(color)); + } + + public BaseChainBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseChestBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseChestBlock.java new file mode 100644 index 00000000..63d3901d --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseChestBlock.java @@ -0,0 +1,62 @@ +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.registry.BaseBlockEntities; + +import net.minecraft.client.renderer.block.model.BlockModel; +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.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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.List; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +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(BlockPos blockPos, BlockState blockState) { + return BaseBlockEntities.CHEST.create(blockPos, blockState); + } + + @Override + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseComposterBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseComposterBlock.java new file mode 100644 index 00000000..a019f4f5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseComposterBlock.java @@ -0,0 +1,77 @@ +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 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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseComposterBlock extends ComposterBlock implements BlockModelProvider { + public BaseComposterBlock(Block source) { + super(FabricBlockSettings.copyOf(source)); + } + + @Override + @SuppressWarnings("deprecation") + 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); + + 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(); + } +} 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..6335bd87 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseCraftingTableBlock.java @@ -0,0 +1,68 @@ +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 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.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootContext; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseCraftingTableBlock extends CraftingTableBlock implements BlockModelProvider { + public BaseCraftingTableBlock(Block source) { + this(FabricBlockSettings.copyOf(source)); + } + + public BaseCraftingTableBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseCropBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseCropBlock.java new file mode 100644 index 00000000..446eceab --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseCropBlock.java @@ -0,0 +1,114 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.util.BlocksHelper; +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.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.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.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 com.google.common.collect.Lists; + +import java.util.Collections; +import java.util.List; +import java.util.Random; + +public class BaseCropBlock extends BasePlantBlock { + public static final IntegerProperty AGE = IntegerProperty.create("age", 0, 3); + private static final VoxelShape SHAPE = box(2, 0, 2, 14, 14, 14); + + private final Block[] terrain; + private final Item drop; + + public BaseCropBlock(Item drop, Block... terrain) { + this(basePlantSettings().randomTicks(), drop, terrain); + } + + protected BaseCropBlock(BlockBehaviour.Properties properties, Item drop, Block... terrain) { + super(properties); + 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_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, Random 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(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { + return state.getValue(AGE) < 3; + } + + @Override + public boolean isBonemealSuccess(Level level, Random random, BlockPos pos, BlockState state) { + return state.getValue(AGE) < 3; + } + + @Override + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseDoorBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseDoorBlock.java new file mode 100644 index 00000000..c0b0b036 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseDoorBlock.java @@ -0,0 +1,183 @@ +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.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.DoorHingeSide; +import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; +import net.minecraft.world.level.storage.loot.LootContext; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseDoorBlock extends DoorBlock implements RenderLayerProvider, BlockModelProvider, TagProvider { + public BaseDoorBlock(Block source) { + this(FabricBlockSettings.copyOf(source).strength(3F, 3F).noOcclusion()); + } + + public BaseDoorBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + @SuppressWarnings("deprecation") + 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; + } + + @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; + } + } +} 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..a42ef0dd --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseDoublePlantBlock.java @@ -0,0 +1,168 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.bclib.util.BlocksHelper; + +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 net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Random; + +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( + FabricBlockSettings.of(Material.PLANT) + .sound(SoundType.GRASS) + .noCollission() + .offsetType(BlockBehaviour.OffsetType.NONE) + ); + } + + public BaseDoublePlantBlock(int light) { + this( + FabricBlockSettings.of(Material.PLANT) + .sound(SoundType.GRASS) + .lightLevel((state) -> state.getValue(TOP) ? light : 0) + .noCollission() + .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.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 + @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, LootContext.Builder builder) { + if (state.getValue(TOP)) { + return Lists.newArrayList(); + } + + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && BaseShearsItem.isShear(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(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { + return true; + } + + @Override + public boolean isBonemealSuccess(Level level, Random random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel level, Random 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..548a4ed5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseFenceBlock.java @@ -0,0 +1,99 @@ +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 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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseFenceBlock extends FenceBlock implements BlockModelProvider { + private final Block parent; + + public BaseFenceBlock(Block source) { + super(FabricBlockSettings.copyOf(source).noOcclusion()); + this.parent = source; + } + + @Override + @SuppressWarnings("deprecation") + 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); + + 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(); + } +} 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..9922bf67 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseFurnaceBlock.java @@ -0,0 +1,147 @@ +package org.betterx.bclib.blocks; + +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.LootContext; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +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 BaseFurnaceBlock extends FurnaceBlock implements BlockModelProvider, RenderLayerProvider { + public BaseFurnaceBlock(Block source) { + this(FabricBlockSettings.copyOf(source).luminance(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, 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; + } + + @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 + ); + } +} 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..fd6b62d2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseGateBlock.java @@ -0,0 +1,87 @@ +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 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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseGateBlock extends FenceGateBlock implements BlockModelProvider { + private final Block parent; + + public BaseGateBlock(Block source) { + super(FabricBlockSettings.copyOf(source).noOcclusion()); + this.parent = source; + } + + @Override + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseGlassBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseGlassBlock.java new file mode 100644 index 00000000..4a22d0f4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseGlassBlock.java @@ -0,0 +1,66 @@ +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.LootContext; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +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(FabricBlockSettings.copyOf(block) + .resistance(resistance) + .nonOpaque() + .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, 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.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..0aebadb7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseLadderBlock.java @@ -0,0 +1,74 @@ +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.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.item.ItemStack; +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.minecraft.world.level.storage.loot.LootContext; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseLadderBlock extends LadderBlock implements RenderLayerProvider, BlockModelProvider { + public BaseLadderBlock(Block block) { + this(FabricBlockSettings.copyOf(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); + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootContext.Builder builder) { + return Collections.singletonList(new ItemStack(this)); + } +} 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..2b774c35 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseLeavesBlock.java @@ -0,0 +1,123 @@ +package org.betterx.bclib.blocks; + +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 org.betterx.bclib.interfaces.tools.AddMineableHoe; +import org.betterx.bclib.interfaces.tools.AddMineableShears; +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.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +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.ItemLike; +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 net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +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, TagProvider, AddMineableShears, AddMineableHoe { + protected final Block sapling; + + private static FabricBlockSettings makeLeaves(MaterialColor color) { + return FabricBlockSettings + .copyOf(Blocks.OAK_LEAVES) + .mapColor(color) + //.requiresTool() + .allowsSpawning((state, world, pos, type) -> false) + .suffocates((state, world, pos) -> false) + .blockVision((state, world, pos) -> false); + } + + public BaseLeavesBlock(Block sapling, MaterialColor color, Consumer customizeProperties) { + super(BaseBlock.acceptAndReturn(customizeProperties, makeLeaves(color))); + this.sapling = sapling; + } + + public BaseLeavesBlock( + Block sapling, + MaterialColor color, + int light, + Consumer customizeProperties + ) { + super(BaseBlock.acceptAndReturn(customizeProperties, makeLeaves(color).luminance(light))); + this.sapling = sapling; + } + + public BaseLeavesBlock(Block sapling, MaterialColor color) { + super(makeLeaves(color)); + this.sapling = sapling; + } + + public BaseLeavesBlock(Block sapling, MaterialColor color, int light) { + super(makeLeaves(color).luminance(light)); + this.sapling = sapling; + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootContext.Builder builder) { + return BaseLeavesBlock.getLeaveDrops(this, this.sapling, builder, 16, 16); + } + + public static List getLeaveDrops( + ItemLike leaveBlock, + Block sapling, + LootContext.Builder builder, + int fortuneRate, + int dropRate + ) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null) { + if (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 void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.LEAVES); + itemTags.add(ItemTags.LEAVES); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseMetalBarsBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseMetalBarsBlock.java new file mode 100644 index 00000000..1405a7da --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseMetalBarsBlock.java @@ -0,0 +1,130 @@ +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.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.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.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootContext; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseMetalBarsBlock extends IronBarsBlock implements BlockModelProvider, RenderLayerProvider { + public BaseMetalBarsBlock(Block source) { + this(FabricBlockSettings.copyOf(source).strength(5.0F, 6.0F).noOcclusion()); + } + + public BaseMetalBarsBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + @SuppressWarnings("deprecation") + 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() == 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/org/betterx/bclib/blocks/BaseOreBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseOreBlock.java new file mode 100644 index 00000000..49855d37 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseOreBlock.java @@ -0,0 +1,131 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.util.LootUtil; +import org.betterx.bclib.util.MHelper; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +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.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.ItemLike; +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 net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +public class BaseOreBlock extends OreBlock implements BlockModelProvider { + 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( + FabricBlockSettings + .of(Material.STONE, MaterialColor.SAND) + .requiresTool() + .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, LootContext.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, + LootContext.Builder builder + ) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && tool.isCorrectToolForDrops(state)) { + 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()); + } +} 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..5bc1940b --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BasePathBlock.java @@ -0,0 +1,104 @@ +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 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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +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 class BasePathBlock extends BaseBlockNotFull { + private static final VoxelShape SHAPE = 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 + @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 = 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/org/betterx/bclib/blocks/BasePlantBlock.java b/src/main/java/org/betterx/bclib/blocks/BasePlantBlock.java new file mode 100644 index 00000000..a6c642d8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BasePlantBlock.java @@ -0,0 +1,207 @@ +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.interfaces.SettingsExtender; +import org.betterx.bclib.items.tool.BaseShearsItem; + +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.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.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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Optional; +import java.util.Random; +import org.jetbrains.annotations.Nullable; + +public abstract class BasePlantBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock { + public static Properties basePlantSettings() { + return basePlantSettings(false, 0); + } + + public static Properties basePlantSettings(int light) { + return basePlantSettings(false, light); + } + + public static Properties basePlantSettings(boolean replaceable) { + return basePlantSettings(replaceable, 0); + } + + public static Properties basePlantSettings(boolean replaceable, int light) { + return basePlantSettings(replaceable ? Material.REPLACEABLE_PLANT : Material.PLANT, light); + } + + public static Properties basePlantSettings(Material mat, int light) { + Properties props = FabricBlockSettings + .of(mat) + .sounds(SoundType.GRASS) + .noCollision() + .offsetType(BlockBehaviour.OffsetType.XZ); + if (light > 0) props.lightLevel(s -> light); + return props; + } + + private static final VoxelShape SHAPE = box(4, 0, 4, 12, 14, 12); + + public BasePlantBlock() { + this(basePlantSettings()); + } + + public BasePlantBlock(int light) { + this(basePlantSettings(light)); + } + + @Deprecated(forRemoval = true) + public BasePlantBlock(int light, SettingsExtender propMod) { + this(false, light, propMod); + } + + public BasePlantBlock(boolean replaceable) { + this(basePlantSettings(replaceable)); + } + + @Deprecated(forRemoval = true) + public BasePlantBlock(boolean replaceable, SettingsExtender propMod) { + this(replaceable, 0, propMod); + } + + + public BasePlantBlock(boolean replaceable, int light) { + this(basePlantSettings(replaceable, light)); + } + + @Deprecated(forRemoval = true) + public BasePlantBlock(boolean replaceable, int light, SettingsExtender propMod) { + this( + propMod.amend(FabricBlockSettings + .of(replaceable ? Material.REPLACEABLE_PLANT : Material.PLANT) + .luminance(light) + .sound(SoundType.GRASS) + .noCollission() + .offsetType(BlockBehaviour.OffsetType.XZ) + ) + ); + } + + 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, LootContext.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && BaseShearsItem.isShear(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(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { + return true; + } + + @Override + public boolean isBonemealSuccess(Level level, Random random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel level, Random 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..911af151 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BasePlantWithAgeBlock.java @@ -0,0 +1,61 @@ +package org.betterx.bclib.blocks; + +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.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.IntegerProperty; + +import java.util.Random; +import java.util.function.Function; + +public abstract class BasePlantWithAgeBlock extends BasePlantBlock { + public static final IntegerProperty AGE = BlockProperties.AGE; + + public BasePlantWithAgeBlock() { + this(p -> p); + } + + @Deprecated(forRemoval = true) + public BasePlantWithAgeBlock(Function propMod) { + this(propMod.apply(basePlantSettings().randomTicks())); + } + + protected 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 level, Random 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, Random random, BlockPos pos, BlockState state) { + return true; + } + + @Override + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BasePressurePlateBlock.java b/src/main/java/org/betterx/bclib/blocks/BasePressurePlateBlock.java new file mode 100644 index 00000000..c45f58e5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BasePressurePlateBlock.java @@ -0,0 +1,73 @@ +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 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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +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 + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseRotatedPillarBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseRotatedPillarBlock.java new file mode 100644 index 00000000..08e673d8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseRotatedPillarBlock.java @@ -0,0 +1,69 @@ +package org.betterx.bclib.blocks; + +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.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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseRotatedPillarBlock extends RotatedPillarBlock implements BlockModelProvider { + public BaseRotatedPillarBlock(Properties settings) { + super(settings); + } + + public BaseRotatedPillarBlock(Block block) { + this(FabricBlockSettings.copyOf(block)); + } + + @Override + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseSignBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseSignBlock.java new file mode 100644 index 00000000..086223ac --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseSignBlock.java @@ -0,0 +1,200 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.blockentities.BaseSignBlockEntity; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.CustomItemProvider; +import org.betterx.bclib.util.BlocksHelper; + +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.BlockItem; +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.*; +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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("deprecation") +public class BaseSignBlock extends SignBlock implements BlockModelProvider, CustomItemProvider { + 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(BlockPos blockPos, BlockState blockState) { + return new BaseSignBlockEntity(blockPos, blockState); + } + + @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(placer.getUUID()); + ((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.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(state.getValue(ROTATION), 16)); + } + + @Override + public BlockState mirror(BlockState state, Mirror mirror) { + return state.setValue(ROTATION, mirror.mirror(state.getValue(ROTATION), 16)); + } + + @Override + public List getDrops(BlockState state, LootContext.Builder builder) { + return Collections.singletonList(new ItemStack(this)); + } + + @Override + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return super.canPlaceLiquid(world, pos, state, fluid); + } + + @Override + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return super.placeLiquid(world, pos, state, fluidState); + } + + @Override + public BlockItem getCustomItem(ResourceLocation blockID, FabricItemSettings settings) { + return new BlockItem(this, settings.stacksTo(16)); + } +} \ No newline at end of file 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..21dae2a7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseSlabBlock.java @@ -0,0 +1,97 @@ +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 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.BlockItem; +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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseSlabBlock extends SlabBlock implements BlockModelProvider, CustomItemProvider { + private final Block parent; + public final boolean fireproof; + + public BaseSlabBlock(Block source) { + this(source, false); + } + + public BaseSlabBlock(Block source, boolean fireproof) { + super(FabricBlockSettings.copyOf(source)); + this.parent = source; + this.fireproof = fireproof; + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootContext.Builder builder) { + int count = state.getValue(TYPE) == SlabType.DOUBLE ? 2 : 1; + return Collections.singletonList(new ItemStack(this, count)); + } + + @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); + } + + @Override + public BlockItem getCustomItem(ResourceLocation blockID, FabricItemSettings settings) { + if (fireproof) settings = settings.fireproof(); + return new BlockItem(this, settings); + } +} 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..8fa521d3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseStairsBlock.java @@ -0,0 +1,121 @@ +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 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.BlockItem; +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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseStairsBlock extends StairBlock implements BlockModelProvider, CustomItemProvider { + private final Block parent; + public final boolean fireproof; + + public BaseStairsBlock(Block source) { + this(source, false); + } + + public BaseStairsBlock(Block source, boolean fireproof) { + super(source.defaultBlockState(), FabricBlockSettings.copyOf(source)); + this.parent = source; + this.fireproof = fireproof; + } + + @Override + @SuppressWarnings("deprecation") + 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 = 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, FabricItemSettings settings) { + if (fireproof) settings = settings.fireproof(); + return new BlockItem(this, settings); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseStoneButtonBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseStoneButtonBlock.java new file mode 100644 index 00000000..c1ffc89e --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseStoneButtonBlock.java @@ -0,0 +1,18 @@ +package org.betterx.bclib.blocks; + +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.level.block.Block; + +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +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/org/betterx/bclib/blocks/BaseStripableLogBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseStripableLogBlock.java new file mode 100644 index 00000000..4e5c2719 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseStripableLogBlock.java @@ -0,0 +1,57 @@ +package org.betterx.bclib.blocks; + +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.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; + +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +public class BaseStripableLogBlock extends BaseRotatedPillarBlock { + private final Block striped; + + public BaseStripableLogBlock(MaterialColor color, Block striped) { + super(FabricBlockSettings.copyOf(striped).color(color)); + 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; + } +} 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..0d3e52d9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseTerrainBlock.java @@ -0,0 +1,161 @@ +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.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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import com.google.common.collect.Maps; + +import java.util.*; +import org.jetbrains.annotations.Nullable; + +@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 && 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, 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(getBaseBlock())); + } + + @Override + public void randomTick(BlockState state, ServerLevel world, BlockPos pos, Random 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 = 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(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..b7b7c486 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseTrapdoorBlock.java @@ -0,0 +1,106 @@ +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.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.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.BlockBehaviour; +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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.*; +import org.jetbrains.annotations.Nullable; + +public class BaseTrapdoorBlock extends TrapDoorBlock implements RenderLayerProvider, BlockModelProvider { + public BaseTrapdoorBlock(Block source) { + this(FabricBlockSettings.copyOf(source).strength(3.0F, 3.0F).noOcclusion()); + } + + public BaseTrapdoorBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseUnderwaterWallPlantBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseUnderwaterWallPlantBlock.java new file mode 100644 index 00000000..389e9fdc --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseUnderwaterWallPlantBlock.java @@ -0,0 +1,49 @@ +package org.betterx.bclib.blocks; + +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 { + + public BaseUnderwaterWallPlantBlock() { + this(0); + } + + public BaseUnderwaterWallPlantBlock(int light) { + this( + UnderwaterPlantBlock.baseUnderwaterPlantSettings(light) + ); + } + + 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..a78f8aa8 --- /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.blocks.BlockProperties.TripleShape; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.bclib.util.BlocksHelper; + +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 net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Random; +import java.util.function.Function; + +@SuppressWarnings("deprecation") +public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock { + 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(FabricBlockSettings + .of(Material.PLANT) + .sound(SoundType.GRASS) + .lightLevel((state) -> bottomOnly ? state.getValue(SHAPE) == TripleShape.BOTTOM + ? light + : 0 : light) + .noCollission() + .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, LootContext.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && BaseShearsItem.isShear(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(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 level, Random random, BlockPos pos, BlockState state) { + while (level.getBlockState(pos).getBlock() == this) { + pos = pos.below(); + } + return level.isEmptyBlock(pos); + } + + @Override + public void performBonemeal(ServerLevel level, Random 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..675502c9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseWallBlock.java @@ -0,0 +1,127 @@ +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 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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseWallBlock extends WallBlock implements BlockModelProvider { + private final Block parent; + + public BaseWallBlock(Block source) { + super(FabricBlockSettings.copyOf(source).noOcclusion()); + this.parent = source; + } + + @Override + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseWallPlantBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseWallPlantBlock.java new file mode 100644 index 00000000..06bed55b --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseWallPlantBlock.java @@ -0,0 +1,112 @@ +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; + + public BaseWallPlantBlock() { + this(basePlantSettings()); + } + + public BaseWallPlantBlock(int light) { + this(basePlantSettings(light)); + } + + 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.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 + @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..367ef3e0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseWeightedPlateBlock.java @@ -0,0 +1,80 @@ +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 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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +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 + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/BaseWoodenButtonBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseWoodenButtonBlock.java new file mode 100644 index 00000000..96ef1246 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseWoodenButtonBlock.java @@ -0,0 +1,18 @@ +package org.betterx.bclib.blocks; + +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.level.block.Block; + +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +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/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..790886be --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/FeatureHangingSaplingBlock.java @@ -0,0 +1,70 @@ +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; + +import java.util.function.Function; + +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); + } + + @Deprecated(forRemoval = true) + public FeatureHangingSaplingBlock(Function featureSupplier) { + super(featureSupplier); + } + + @Deprecated(forRemoval = true) + public FeatureHangingSaplingBlock( + Function featureSupplier, + int light + ) { + super(light, featureSupplier); + } + + @Deprecated(forRemoval = true) + public FeatureHangingSaplingBlock( + BlockBehaviour.Properties properties, + Function 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..b221d3db --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/FeatureSaplingBlock.java @@ -0,0 +1,202 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.levelgen.features.BCLConfigureFeature; +import org.betterx.bclib.api.v3.levelgen.features.BCLFeature; +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.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.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +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.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 net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.function.Function; +import org.jetbrains.annotations.Nullable; + +public class FeatureSaplingBlock, FC extends FeatureConfiguration> extends SaplingBlock implements RenderLayerProvider, BlockModelProvider { + + @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( + FabricBlockSettings.of(Material.PLANT) + .collidable(false) + .instabreak() + .sound(SoundType.GRASS) + .randomTicks(), + featureSupplier + ); + } + + public FeatureSaplingBlock(int light, FeatureSupplier featureSupplier) { + this( + FabricBlockSettings.of(Material.PLANT) + .collidable(false) + .luminance(light) + .instabreak() + .sound(SoundType.GRASS) + .randomTicks(), + featureSupplier + ); + } + + public FeatureSaplingBlock( + BlockBehaviour.Properties properties, + FeatureSupplier featureSupplier + ) { + super(null, properties); + this.feature = featureSupplier; + } + + @Deprecated(forRemoval = true) + public FeatureSaplingBlock(Function featureSupplier) { + this( + FabricBlockSettings.of(Material.PLANT) + .collidable(false) + .instabreak() + .sound(SoundType.GRASS) + .randomTicks(), + featureSupplier + ); + } + + @Deprecated(forRemoval = true) + public FeatureSaplingBlock( + int light, + Function featureSupplier + ) { + this( + FabricBlockSettings.of(Material.PLANT) + .collidable(false) + .luminance(light) + .instabreak() + .sound(SoundType.GRASS) + .randomTicks(), + featureSupplier + ); + } + + @Deprecated(forRemoval = true) + public FeatureSaplingBlock( + BlockBehaviour.Properties properties, + Function featureSupplier + ) { + super(null, properties); + this.feature = (s) -> featureSupplier.apply(s).getConfFeature(); + } + + protected BCLConfigureFeature getConfiguredFeature(BlockState state) { + return feature != null ? feature.get(state) : null; + } + + @Deprecated(forRemoval = true) + protected org.betterx.bclib.api.v2.levelgen.features.BCLFeature getFeature(BlockState state) { + return new org.betterx.bclib.api.v2.levelgen.features.BCLFeature(new BCLFeature<>( + getConfiguredFeature(state), + null, + GenerationStep.Decoration.TOP_LAYER_MODIFICATION + )); + } + + @Override + public List getDrops(BlockState state, LootContext.Builder builder) { + return Collections.singletonList(new ItemStack(this)); + } + + @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, Random random, BlockPos pos, BlockState state) { + return random.nextInt(16) == 0; + } + + @Override + public void advanceTree(ServerLevel world, BlockPos pos, BlockState blockState, Random random) { + var conf = getConfiguredFeature(blockState); + if (conf == null) getFeature(blockState).place(world, pos, random); + else conf.placeInWorld(world, pos, random); + } + + @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); + } + + @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..9f900ed6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/LeveledAnvilBlock.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.blocks; + +import net.minecraft.world.level.material.MaterialColor; + +public class LeveledAnvilBlock extends BaseAnvilBlock { + protected final int level; + + public LeveledAnvilBlock(MaterialColor color, int level) { + super(color); + this.level = level; + } + + 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..4c5bf41a --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/SimpleLeavesBlock.java @@ -0,0 +1,67 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.bclib.interfaces.tools.AddMineableHoe; +import org.betterx.bclib.interfaces.tools.AddMineableShears; + +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.state.BlockBehaviour; +import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.material.MaterialColor; + +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.List; + +public class SimpleLeavesBlock extends BaseBlockNotFull implements RenderLayerProvider, TagProvider, AddMineableShears, AddMineableHoe { + public SimpleLeavesBlock(MaterialColor color) { + this( + FabricBlockSettings + .of(Material.LEAVES) + .strength(0.2F) + .color(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) { + this( + FabricBlockSettings + .of(Material.LEAVES) + .luminance(light) + .color(color) + .strength(0.2F) + .sound(SoundType.GRASS) + .noOcclusion() + .isValidSpawn((state, world, pos, type) -> false) + .isSuffocating((state, world, pos) -> false) + .isViewBlocking((state, world, pos) -> false) + ); + } + + public SimpleLeavesBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.LEAVES); + itemTags.add(ItemTags.LEAVES); + } +} \ 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..18d793e3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/StalactiteBlock.java @@ -0,0 +1,264 @@ +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 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 net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("deprecation") +public 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(FabricBlockSettings.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, 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] = 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/StonePressurePlateBlock.java b/src/main/java/org/betterx/bclib/blocks/StonePressurePlateBlock.java new file mode 100644 index 00000000..9cd3ba24 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/StonePressurePlateBlock.java @@ -0,0 +1,9 @@ +package org.betterx.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/org/betterx/bclib/blocks/StripableBarkBlock.java b/src/main/java/org/betterx/bclib/blocks/StripableBarkBlock.java new file mode 100644 index 00000000..52b37775 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/StripableBarkBlock.java @@ -0,0 +1,56 @@ +package org.betterx.bclib.blocks; + +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.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.state.BlockState; +import net.minecraft.world.level.material.MaterialColor; +import net.minecraft.world.phys.BlockHitResult; + +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +public class StripableBarkBlock extends BaseBarkBlock { + private final Block striped; + + public StripableBarkBlock(MaterialColor color, Block striped) { + super(FabricBlockSettings.copyOf(striped).color(color)); + 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(AXIS, state.getValue(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/org/betterx/bclib/blocks/TripleTerrainBlock.java b/src/main/java/org/betterx/bclib/blocks/TripleTerrainBlock.java new file mode 100644 index 00000000..825aeeac --- /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.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 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 java.util.Random; +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.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/org/betterx/bclib/blocks/UnderwaterPlantBlock.java b/src/main/java/org/betterx/bclib/blocks/UnderwaterPlantBlock.java new file mode 100644 index 00000000..691d222b --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/UnderwaterPlantBlock.java @@ -0,0 +1,191 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.items.tool.BaseShearsItem; + +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.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 net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Random; +import java.util.function.Function; + +public abstract class UnderwaterPlantBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock, LiquidBlockContainer { + public static Properties baseUnderwaterPlantSettings() { + return baseUnderwaterPlantSettings(false, 0); + } + + public static Properties baseUnderwaterPlantSettings(int light) { + return baseUnderwaterPlantSettings(false, light); + } + + public static Properties baseUnderwaterPlantSettings(boolean replaceable) { + return baseUnderwaterPlantSettings(replaceable, 0); + } + + public static Properties baseUnderwaterPlantSettings(boolean replaceable, int light) { + return baseUnderwaterPlantSettings( + replaceable ? Material.REPLACEABLE_WATER_PLANT : Material.WATER_PLANT, + light + ); + } + + public static Properties baseUnderwaterPlantSettings(Material mat, int light) { + Properties props = FabricBlockSettings + .of(mat) + .sound(SoundType.WET_GRASS) + .noCollission() + .offsetType(BlockBehaviour.OffsetType.XZ); + if (light > 0) props.lightLevel(s -> light); + return props; + } + + private static final VoxelShape SHAPE = box(4, 0, 4, 12, 14, 12); + + public UnderwaterPlantBlock() { + this(p -> p); + } + + @Deprecated(forRemoval = true) + public UnderwaterPlantBlock(Function propMod) { + this( + propMod.apply(baseUnderwaterPlantSettings()) + ); + } + + public UnderwaterPlantBlock(int light) { + this(light, p -> p); + } + + @Deprecated(forRemoval = true) + public UnderwaterPlantBlock(int light, Function propMod) { + this( + propMod.apply(baseUnderwaterPlantSettings(light)) + ); + } + + 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, LootContext.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && BaseShearsItem.isShear(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(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { + return true; + } + + @Override + public boolean isBonemealSuccess(Level level, Random random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel level, Random 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..324b7724 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/UnderwaterPlantWithAgeBlock.java @@ -0,0 +1,47 @@ +package org.betterx.bclib.blocks; + +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.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.IntegerProperty; + +import java.util.Random; + +public abstract class UnderwaterPlantWithAgeBlock extends UnderwaterPlantBlock { + public static final IntegerProperty AGE = BlockProperties.AGE; + + public UnderwaterPlantWithAgeBlock() { + super(baseUnderwaterPlantSettings().randomTicks()); + } + + @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 + @SuppressWarnings("deprecation") + 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/org/betterx/bclib/blocks/UpDownPlantBlock.java b/src/main/java/org/betterx/bclib/blocks/UpDownPlantBlock.java new file mode 100644 index 00000000..e7529873 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/UpDownPlantBlock.java @@ -0,0 +1,118 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.interfaces.tools.AddMineableHoe; +import org.betterx.bclib.interfaces.tools.AddMineableShears; +import org.betterx.bclib.items.tool.BaseShearsItem; + +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.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 net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import com.google.common.collect.Lists; + +import java.util.List; + +public abstract class UpDownPlantBlock extends BaseBlockNotFull implements RenderLayerProvider, AddMineableShears, AddMineableHoe { + private static final VoxelShape SHAPE = box(4, 0, 4, 12, 16, 12); + + public UpDownPlantBlock() { + this(FabricBlockSettings + .of(Material.PLANT) + .sound(SoundType.GRASS) + .noCollission() + ); + } + + 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, LootContext.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && BaseShearsItem.isShear(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..10e5867e --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/WallMushroomBlock.java @@ -0,0 +1,34 @@ +package org.betterx.bclib.blocks; + +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.LootContext; + +import com.google.common.collect.Lists; + +import java.util.List; + +public abstract class WallMushroomBlock extends BaseWallPlantBlock { + public WallMushroomBlock(int light) { + super(basePlantSettings(light).destroyTime(0.2F).sound(SoundType.WOOD)); + } + + protected WallMushroomBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @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/org/betterx/bclib/blocks/WoodenPressurePlateBlock.java b/src/main/java/org/betterx/bclib/blocks/WoodenPressurePlateBlock.java new file mode 100644 index 00000000..2a64768a --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/WoodenPressurePlateBlock.java @@ -0,0 +1,9 @@ +package org.betterx.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/org/betterx/bclib/client/BCLibClient.java b/src/main/java/org/betterx/bclib/client/BCLibClient.java new file mode 100644 index 00000000..6a6d632b --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/BCLibClient.java @@ -0,0 +1,58 @@ +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.config.Configs; +import org.betterx.bclib.registry.BaseBlockEntityRenders; +import org.betterx.bclib.registry.PresetsRegistryClient; +import org.betterx.worlds.together.WorldsTogether; + +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 { + public static CustomModelBakery modelBakery; + + @Override + public void onInitializeClient() { + ModIntegrationAPI.registerAll(); + BaseBlockEntityRenders.register(); + DataExchangeAPI.prepareClientside(); + PostInitAPI.postInit(true); + modelBakery = new CustomModelBakery(); + ModelLoadingRegistry.INSTANCE.registerResourceProvider(rm -> this); + ModelLoadingRegistry.INSTANCE.registerVariantProvider(rm -> this); + + PresetsRegistryClient.onLoad(); + WorldsTogether.SURPRESS_EXPERIMENTAL_DIALOG = Configs.CLIENT_CONFIG.suppressExperimentalDialog(); + //dumpDatapack(); + } + + @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/gridlayout/GridCell.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridCell.java new file mode 100644 index 00000000..13d5c6ba --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridCell.java @@ -0,0 +1,35 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import org.betterx.bclib.interfaces.TriConsumer; + +import com.mojang.blaze3d.vertex.PoseStack; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.function.Function; + +@Environment(EnvType.CLIENT) +class GridCell extends GridCellDefinition { + public final float height; + Function componentPlacer; + TriConsumer customRender; + + GridCell( + double width, + double height, + GridLayout.GridValueType widthType, + Function componentPlacer, + TriConsumer customRender + ) { + super(width, widthType); + this.height = (float) height; + this.componentPlacer = componentPlacer; + this.customRender = customRender; + } + + protected GridElement buildElementAt(int left, int top, int width, final List collector) { + return new GridElement(left, top, width, (int) this.height, componentPlacer, customRender); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridCheckboxCell.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridCheckboxCell.java new file mode 100644 index 00000000..60030f26 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridCheckboxCell.java @@ -0,0 +1,101 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import net.minecraft.client.gui.components.Checkbox; +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.function.Consumer; + +@Environment(EnvType.CLIENT) +class SignalingCheckBox extends Checkbox { + private final Consumer onChange; + + public SignalingCheckBox( + int left, + int top, + int width, + int height, + Component component, + boolean checked, + Consumer onChange + ) { + super(left, top, width, height, component, checked); + this.onChange = onChange; + if (onChange != null) + onChange.accept(checked); + } + + @Override + public void onPress() { + super.onPress(); + if (onChange != null) + onChange.accept(this.selected()); + } +} + +@Environment(EnvType.CLIENT) +public class GridCheckboxCell extends GridCell implements GridWidgetWithEnabledState { + private boolean checked; + private Checkbox lastCheckbox; + private boolean enabled; + private final float alpha; + + GridCheckboxCell( + Component text, + boolean checked, + float alpha, + double width, + GridLayout.GridValueType widthType, + double height + ) { + this(text, checked, alpha, width, widthType, height, null); + } + + GridCheckboxCell( + Component text, + boolean checked, + float alpha, + double width, + GridLayout.GridValueType widthType, + double height, + Consumer onChange + ) { + super(width, height, widthType, null, null); + lastCheckbox = null; + enabled = true; + this.alpha = alpha; + this.componentPlacer = (transform) -> { + Checkbox cb = new SignalingCheckBox(transform.left, transform.top, transform.width, transform.height, + text, + checked, + (state) -> { + this.checked = state; + if (onChange != null) onChange.accept(state); + } + ); + cb.setAlpha(alpha); + lastCheckbox = cb; + setEnabled(enabled); + return cb; + }; + + } + + public boolean isChecked() { + return checked; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + if (lastCheckbox != null) { + lastCheckbox.active = enabled; + lastCheckbox.setAlpha(enabled ? alpha : (alpha * 0.5f)); + } + this.enabled = enabled; + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridColumn.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridColumn.java new file mode 100644 index 00000000..54cda227 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridColumn.java @@ -0,0 +1,75 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; + +@Environment(EnvType.CLIENT) +public class GridColumn extends GridContainer { + GridColumn(double width) { + super(width); + } + + GridColumn(double width, GridLayout.GridValueType widthType) { + super(width, widthType); + } + + public GridRow addRow() { + return addRow(GridLayout.VerticalAlignment.TOP); + } + + public GridRow addRow(GridLayout.VerticalAlignment align) { + GridRow row = new GridRow( + 1.0, + widthType == GridLayout.GridValueType.INHERIT + ? GridLayout.GridValueType.INHERIT + : GridLayout.GridValueType.PERCENTAGE, + align + ); + this.cells.add(row); + return row; + } + + + public void addSpacerRow() { + this.addSpacerRow(4); + } + + public void addSpacerRow(int height) { + GridCell cell = new GridCell(1.0, height, GridLayout.GridValueType.PERCENTAGE, null, null); + this.cells.add(cell); + } + + @Override + public int calculateWidth(final int parentWidth) { + if (widthType == GridLayout.GridValueType.INHERIT) { + return cells.stream() + .filter(row -> row.widthType == GridLayout.GridValueType.INHERIT) + .map(row -> row.buildElement(0, 0, 1, 0, 0, null).width) + .reduce(0, (p, c) -> Math.max(p, c)); + + } else { + return super.calculateWidth(parentWidth); + } + } + + + @Override + protected GridElement buildElementAt(int left, int inTop, int width, final List collector) { + int height = 0; + int top = inTop; + + if (widthType == GridLayout.GridValueType.INHERIT) { + width = calculateWidth(width); + } + + for (GridCellDefinition row : cells) { + GridElement element = row.buildElement(width, 0, 1, left, top, collector); + top += element.height; + height += element.height; + } + + return new GridElement(left, inTop, width, height); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridCustomRenderCell.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridCustomRenderCell.java new file mode 100644 index 00000000..a3cb31b0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridCustomRenderCell.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import org.betterx.bclib.client.gui.gridlayout.GridLayout.GridValueType; + +import com.mojang.blaze3d.vertex.PoseStack; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + + +@Environment(EnvType.CLIENT) +public abstract class GridCustomRenderCell extends GridCell { + protected GridCustomRenderCell(double width, GridValueType widthType, double height) { + super(width, height, widthType, null, null); + this.customRender = this::onRender; + } + + public abstract void onRender(PoseStack poseStack, GridTransform transform, Object context); +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridImageCell.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridImageCell.java new file mode 100644 index 00000000..dbf64035 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridImageCell.java @@ -0,0 +1,51 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class GridImageCell extends GridCell { + GridImageCell( + ResourceLocation location, + double width, + GridLayout.GridValueType widthType, + double height, + float alpha, + int uvLeft, + int uvTop, + int uvWidth, + int uvHeight, + int resourceWidth, + int resourceHeight + ) { + super(width, height, widthType, null, (poseStack, transform, context) -> { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, location); + RenderSystem.enableBlend(); + RenderSystem.blendFunc( + GlStateManager.SourceFactor.SRC_ALPHA, + GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA + ); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); + GuiComponent.blit( + poseStack, + transform.left, + transform.top, + transform.width, + transform.height, + uvLeft, + uvTop, + uvWidth, + uvHeight, + resourceWidth, + resourceHeight + ); + }); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridLayout.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridLayout.java new file mode 100644 index 00000000..96c58e1d --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridLayout.java @@ -0,0 +1,226 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import org.betterx.bclib.client.gui.gridlayout.GridLayout.GridValueType; +import org.betterx.bclib.interfaces.TriConsumer; +import org.betterx.bclib.util.Pair; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.components.AbstractWidget; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + + +@Environment(EnvType.CLIENT) +abstract class GridCellDefinition { + public final float width; + public final GridLayout.GridValueType widthType; + + public GridCellDefinition(double width, GridLayout.GridValueType widthType) { + this.width = (float) width; + this.widthType = widthType; + } + + public int calculateWidth(final int parentWidth) { + if (widthType == GridLayout.GridValueType.CONSTANT) { + return (int) this.width; + } else if (widthType == GridValueType.PERCENTAGE) { + return (int) (this.width * parentWidth); + } else { + return 0; + } + } + + final GridElement buildElement( + final int parentWidth, + final int autoWidth, + final float autoWidthSum, + int left, + final int top, + final List collector + ) { + final int width = widthType == GridValueType.FILL + ? (int) ((this.width / autoWidthSum) * autoWidth) + : calculateWidth(parentWidth); + + final GridElement el = buildElementAt(left, top, width, collector); + if (collector != null) { + collector.add(el); + } + return el; + } + + abstract protected GridElement buildElementAt(int left, int top, int width, final List collector); +} + +@Environment(EnvType.CLIENT) +class GridElement extends GridTransform { + final Function componentPlacer; + final TriConsumer customRender; + Object renderContext; + + GridElement( + int left, + int top, + int width, + int height, + Function componentPlacer, + TriConsumer customRender + ) { + super(left, top, width, height); + this.componentPlacer = componentPlacer; + this.customRender = customRender; + } + + GridElement(int left, int top, int width, int height) { + this(left, top, width, height, null, null); + } + + GridTransform transformWithPadding(int leftPadding, int topPadding) { + return new GridTransform(left + leftPadding, top + topPadding, width, height); + } +} + +@Environment(EnvType.CLIENT) +abstract class GridContainer extends GridCellDefinition { + protected List cells; + + public GridContainer(double width) { + this(width, GridLayout.GridValueType.CONSTANT); + } + + GridContainer(double width, GridLayout.GridValueType widthType) { + super(width, widthType); + cells = new LinkedList<>(); + } +} + +@Environment(EnvType.CLIENT) +public class GridLayout extends GridColumn { + public static final int COLOR_WHITE = 0xFFFFFFFF; + public static final int COLOR_RED = 0xFFDB1F48; + public static final int COLOR_CYAN = 0xFF01949A; + public static final int COLOR_GREEN = 0xFF00FF00; + public static final int COLOR_DARK_GREEN = 0xFF007F00; + public static final int COLOR_YELLOW = 0xFFFAD02C; + public static final int COLOR_BLUE = 0xFF0000FF; + public static final int COLOR_GRAY = 0xFF7F7F7F; + + public final GridScreen screen; + public final int screenHeight; + public final int sidePadding; + public final int initialTopPadding; + public final boolean centerVertically; + private int height; + private int topPadding; + + private List elements; + + public GridLayout(GridScreen screen) { + this(screen, 0, true); + } + + public GridLayout(GridScreen screen, int topPadding, boolean centerVertically) { + this(screen, topPadding, 20, centerVertically); + } + + public GridLayout(GridScreen screen, int topPadding, int sidePadding, boolean centerVertically) { + super(screen.width - 2 * sidePadding, GridValueType.CONSTANT); + this.screen = screen; + this.screenHeight = screen.height; + height = 0; + this.topPadding = topPadding; + this.sidePadding = sidePadding; + this.initialTopPadding = topPadding; + this.centerVertically = centerVertically; + } + + public int getHeight() { + return height; + } + + public int getTopPadding() { + return topPadding; + } + + void buildLayout() { + elements = new LinkedList<>(); + GridElement el = this.buildElement((int) this.width, 0, 1, 0, 0, elements); + this.height = el.height; + if (centerVertically && el.height + initialTopPadding < screenHeight) { + topPadding = (screenHeight - el.height) >> 1; + } else { + topPadding = initialTopPadding; + } + + } + + public List> movableWidgets = new LinkedList<>(); + + public void finalizeLayout() { + buildLayout(); + + elements + .stream() + .filter(element -> element.componentPlacer != null) + .forEach(element -> { + final GridTransform transform = element.transformWithPadding(sidePadding, topPadding); + final Object context = element.componentPlacer.apply(transform); + if (element.customRender != null) { + element.renderContext = context; + } else if (context instanceof AbstractWidget) { + final AbstractWidget widget = (AbstractWidget) context; + movableWidgets.add(new Pair(widget, widget.y)); + screen.addRenderableWidget(widget); + } + }); + } + + public void render(PoseStack poseStack) { + if (elements == null) return; + elements + .stream() + .filter(element -> element.customRender != null) + .forEach(element -> element.customRender.accept( + poseStack, + element.transformWithPadding(sidePadding, topPadding), + element.renderContext + )); + } + + + public enum VerticalAlignment { + TOP, CENTER, BOTTOM + } + + public enum Alignment { + LEFT, CENTER, RIGHT + } + + /** + * Determines how a measurement value is interpreted + */ + public enum GridValueType { + /** + * The value is a constant pixel size + */ + CONSTANT, + /** + * The Value is relative to the parent size + */ + PERCENTAGE, + /** + * The value will be set to fill up the remaining space (i.e. when this is applied to a width of a row element, + * a {@link #FILL}-type may be used to right align (FILL - CONSTANT) or center (FILL - CONSTANT - FILL) elements. + */ + FILL, + /** + * Calculate size based on child-elements + */ + INHERIT + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridMessageCell.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridMessageCell.java new file mode 100644 index 00000000..892f7e6e --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridMessageCell.java @@ -0,0 +1,81 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.MultiLineLabel; +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; + +@Environment(EnvType.CLIENT) +public class GridMessageCell extends GridCell { + private final Font font; + private Component text; + private MultiLineLabel lastLabel; + private GridTransform lastTransform; + + GridMessageCell( + double width, + GridLayout.GridValueType widthType, + GridLayout.Alignment contentAlignment, + Font font, + Component text + ) { + this(width, widthType, contentAlignment, font, text, GridLayout.COLOR_WHITE); + } + + GridMessageCell( + double width, + GridLayout.GridValueType widthType, + GridLayout.Alignment contentAlignment, + Font font, + Component text, + int color + ) { + super(width, -1, widthType, null, null); + this.font = font; + this.text = text; + + customRender = (poseStack, transform, context) -> { + //MultiLineLabel label = (MultiLineLabel) context; + if (contentAlignment == GridLayout.Alignment.CENTER) { + lastLabel.renderCentered( + poseStack, + transform.width / 2 + transform.left, + transform.top, + font.lineHeight, + color + ); + } else if (contentAlignment == GridLayout.Alignment.LEFT) { + lastLabel.renderLeftAligned(poseStack, transform.left, transform.top, font.lineHeight, color); + } + }; + } + + public void setText(Component text) { + this.text = text; + if (lastTransform != null) { + create(lastTransform); + } + } + + private MultiLineLabel getLabel(GridTransform transform) { + return lastLabel; + } + + protected void create(GridTransform transform) { + this.lastTransform = transform; + this.lastLabel = MultiLineLabel.create(font, text, transform.width); + } + + @Override + protected GridElement buildElementAt(int left, int top, int width, List collector) { + create(new GridTransform(left, top, width, 0)); + int promptLines = this.lastLabel.getLineCount() + 1; + int height = promptLines * 9; + + return new GridElement(left, top, width, height, this::getLabel, customRender); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridRow.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridRow.java new file mode 100644 index 00000000..a8c692b0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridRow.java @@ -0,0 +1,451 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Button.OnPress; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +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.function.Consumer; +import java.util.function.Function; + +@Environment(EnvType.CLIENT) +public class GridRow extends GridContainer { + public final GridLayout.VerticalAlignment alignment; + + GridRow(double width) { + this(width, GridLayout.VerticalAlignment.TOP); + } + + GridRow(double width, GridLayout.GridValueType widthType) { + this(width, widthType, GridLayout.VerticalAlignment.CENTER); + } + + GridRow(double width, GridLayout.VerticalAlignment alignment) { + super(width); + this.alignment = alignment; + } + + GridRow(double width, GridLayout.GridValueType widthType, GridLayout.VerticalAlignment alignment) { + super(width, widthType); + this.alignment = alignment; + } + + public GridColumn addColumn(double width, GridLayout.GridValueType widthType) { + GridColumn cell = new GridColumn(width, widthType); + this.cells.add(cell); + return cell; + } + + public GridCell addComponent(double height, Function componentPlacer) { + return addComponent(1.0, GridLayout.GridValueType.PERCENTAGE, height, componentPlacer); + } + + public GridCell addComponent( + double width, + GridLayout.GridValueType widthType, + double height, + Function componentPlacer + ) { + GridCell cell = new GridCell(width, height, widthType, componentPlacer, null); + this.cells.add(cell); + return cell; + } + + + public GridCell addButton(Component text, double height, OnPress onPress) { + return addButton(text, 1.0, GridLayout.GridValueType.PERCENTAGE, height, onPress); + } + + public GridCell addButton(Component text, float alpha, double height, OnPress onPress) { + return addButton(text, alpha, 1.0, GridLayout.GridValueType.PERCENTAGE, height, onPress); + } + + public GridCell addButton(Component text, double height, Font font, OnPress onPress) { + return addButton(text, 1.0f, height, font, onPress); + } + + public GridCell addButton(Component text, float alpha, double height, Font font, OnPress onPress) { + final int width = font.width(text.getVisualOrderText()) + 24; + return addButton(text, alpha, width, GridLayout.GridValueType.CONSTANT, height, onPress); + } + + public GridCell addButton( + Component text, + double width, + GridLayout.GridValueType widthType, + double height, + OnPress onPress + ) { + return addButton(text, 1.0f, width, widthType, height, onPress); + } + + public GridCell addButton( + Component text, + float alpha, + double width, + GridLayout.GridValueType widthType, + double height, + OnPress onPress + ) { + GridCell cell = new GridCell(width, height, widthType, (transform) -> { + Button customButton = new Button( + transform.left, + transform.top, + transform.width, + transform.height, + text, + onPress + ); + customButton.setAlpha(alpha); + return customButton; + }, null); + this.cells.add(cell); + return cell; + } + + public GridCheckboxCell addCheckbox(Component text, boolean checked, Font font, Consumer onChange) { + final int width = font.width(text.getVisualOrderText()) + 24 + 2 * 12; + return addCheckbox(text, checked, width, widthType, onChange); + } + + public GridCheckboxCell addCheckbox( + Component text, boolean checked, double width, + GridLayout.GridValueType widthType, Consumer onChange + ) { + + GridCheckboxCell cell = new GridCheckboxCell(text, checked, 1.0f, width, widthType, 20, onChange); + this.cells.add(cell); + return cell; + } + + public GridCheckboxCell addCheckbox(Component text, boolean checked, int height) { + return addCheckbox(text, checked, 1.0f, height); + } + + public GridCheckboxCell addCheckbox(Component text, boolean checked, float alpha, int height) { + return addCheckbox(text, checked, alpha, 1.0, GridLayout.GridValueType.PERCENTAGE, height); + } + + public GridCheckboxCell addCheckbox(Component text, boolean checked, int height, Font font) { + return addCheckbox(text, checked, 1.0f, height, font); + } + + public GridCheckboxCell addCheckbox(Component text, boolean checked, float alpha, int height, Font font) { + final int width = font.width(text.getVisualOrderText()) + 24 + 2 * 12; + return addCheckbox(text, checked, alpha, width, GridLayout.GridValueType.CONSTANT, height); + } + + public GridCheckboxCell addCheckbox( + Component text, + boolean checked, + double width, + GridLayout.GridValueType widthType, + int height + ) { + return addCheckbox(text, checked, 1.0f, width, widthType, height); + } + + public GridCheckboxCell addCheckbox( + Component text, + boolean checked, + float alpha, + double width, + GridLayout.GridValueType widthType, + int height + ) { + GridCheckboxCell cell = new GridCheckboxCell(text, checked, alpha, width, widthType, height); + this.cells.add(cell); + return cell; + } + + public GridCustomRenderCell addCustomRender(GridCustomRenderCell cell) { + this.cells.add(cell); + return cell; + } + + public GridCell addImage(ResourceLocation location, int width, int height) { + return addImage(location, 1.0f, width, height); + } + + public GridCell addImage(ResourceLocation location, float alpha, int width, int height) { + return addImage( + location, + alpha, + width, + GridLayout.GridValueType.CONSTANT, + height, + 0, + 0, + width, + height, + width, + height + ); + } + + public GridCell addImage( + ResourceLocation location, + double width, + GridLayout.GridValueType widthType, + int height, + int resourceWidth, + int resourceHeight + ) { + return addImage(location, 1.0f, width, widthType, height, resourceWidth, resourceHeight); + } + + public GridCell addImage( + ResourceLocation location, + float alpha, + double width, + GridLayout.GridValueType widthType, + int height, + int resourceWidth, + int resourceHeight + ) { + return addImage( + location, + alpha, + width, + widthType, + height, + 0, + 0, + resourceWidth, + resourceWidth, + resourceWidth, + resourceHeight + ); + } + + public GridCell addImage( + ResourceLocation location, + double width, + GridLayout.GridValueType widthType, + int height, + int uvLeft, + int uvTop, + int uvWidth, + int uvHeight, + int resourceWidth, + int resourceHeight + ) { + return addImage( + location, + 1.0f, + width, + widthType, + height, + uvLeft, + uvTop, + uvWidth, + uvHeight, + resourceWidth, + resourceHeight + ); + } + + public GridCell addImage( + ResourceLocation location, + float alpha, + double width, + GridLayout.GridValueType widthType, + int height, + int uvLeft, + int uvTop, + int uvWidth, + int uvHeight, + int resourceWidth, + int resourceHeight + ) { + GridCell cell = new GridImageCell( + location, + width, + widthType, + height, + alpha, + uvLeft, + uvTop, + uvWidth, + uvHeight, + resourceWidth, + resourceHeight + ); + this.cells.add(cell); + return cell; + } + + + public GridColumn addFiller() { + return addFiller(1); + } + + public GridColumn addFiller(float portion) { + GridColumn cell = new GridColumn(portion, GridLayout.GridValueType.FILL); + this.cells.add(cell); + return cell; + } + + public void addSpacer() { + addSpacer(12); + } + + public void addSpacer(int width) { + GridCell cell = new GridCell(width, 0, GridLayout.GridValueType.CONSTANT, null, null); + this.cells.add(cell); + } + + + public GridMessageCell addMessage(Component text, Font font, GridLayout.Alignment contentAlignment) { + return addMessage(text, font, GridLayout.COLOR_WHITE, contentAlignment); + } + + public GridMessageCell addMessage(Component text, Font font, int color, GridLayout.Alignment contentAlignment) { + return addMessage(text, 1.0, GridLayout.GridValueType.PERCENTAGE, font, color, contentAlignment); + } + + public GridMessageCell addMessage( + Component text, + double width, + GridLayout.GridValueType widthType, + Font font, + GridLayout.Alignment contentAlignment + ) { + return addMessage(text, width, widthType, font, GridLayout.COLOR_WHITE, contentAlignment); + } + + public GridMessageCell addMessage( + Component text, + double width, + GridLayout.GridValueType widthType, + Font font, + int color, + GridLayout.Alignment contentAlignment + ) { + GridMessageCell cell = new GridMessageCell(width, widthType, contentAlignment, font, text, color); + this.cells.add(cell); + return cell; + } + + public GridStringCell addString(Component text, GridScreen parent) { + return this.addString(text, GridLayout.COLOR_WHITE, parent); + } + + + public GridStringCell addString(Component text, int color, GridScreen parent) { + final int width = parent.getWidth(text); + return this.addString( + text, + width, + GridLayout.GridValueType.CONSTANT, + color, + GridLayout.Alignment.CENTER, + parent + ); + } + + public GridStringCell addString(Component text, GridLayout.Alignment contentAlignment, GridScreen parent) { + return this.addString(text, GridLayout.COLOR_WHITE, contentAlignment, parent); + } + + public GridStringCell addString( + Component text, + int color, + GridLayout.Alignment contentAlignment, + GridScreen parent + ) { + return this.addString(text, 1.0, GridLayout.GridValueType.PERCENTAGE, color, contentAlignment, parent); + } + + public GridStringCell addString( + Component text, + double width, + GridLayout.GridValueType widthType, + GridLayout.Alignment contentAlignment, + GridScreen parent + ) { + return addString(text, width, widthType, GridLayout.COLOR_WHITE, contentAlignment, parent); + } + + public GridStringCell addString( + Component text, + double width, + GridLayout.GridValueType widthType, + int color, + GridLayout.Alignment contentAlignment, + GridScreen parent + ) { + GridStringCell cell = new GridStringCell( + width, + widthType, + parent.getFont().lineHeight, + contentAlignment, + parent, + text, + color + ); + this.cells.add(cell); + return cell; + } + + @Override + protected GridElement buildElementAt(int inLeft, int top, int width, final List collector) { + int height = 0; + int left = inLeft; + if (widthType == GridLayout.GridValueType.INHERIT) { + final int originalWidth = width; + width = cells.stream() + .filter(row -> row.widthType == GridLayout.GridValueType.CONSTANT || row.widthType == GridLayout.GridValueType.INHERIT) + .map(row -> row.buildElement(0, 0, 1, 0, 0, null).width) + .reduce(0, (p, c) -> p + c); + } + + final int inheritedWidth = width; + final int fixedWidth = cells.stream() + .filter(col -> col.widthType != GridLayout.GridValueType.FILL) + .map(col -> col.calculateWidth(inheritedWidth)) + .reduce(0, (p, c) -> p + c); + final float autoWidthSum = cells.stream() + .filter(col -> col.widthType == GridLayout.GridValueType.FILL) + .map(col -> col.width) + .reduce(0.0f, (p, c) -> p + c); + final int autoWidth = width - fixedWidth; + + if (alignment == GridLayout.VerticalAlignment.TOP) { + for (GridCellDefinition col : cells) { + GridElement element = col.buildElement(width, autoWidth, autoWidthSum, left, top, collector); + left += element.width; + height = Math.max(height, element.height); + } + } else { + //first iteration will collect heights, second one will transform top position for alignment + Map cache = new HashMap<>(); + for (GridCellDefinition col : cells) { + GridElement element = col.buildElement(width, autoWidth, autoWidthSum, left, top, null); + left += element.width; + height = Math.max(height, element.height); + cache.put(col, element); + } + + left = inLeft; + for (GridCellDefinition col : cells) { + GridElement element = cache.get(col); + final int topOffset = (alignment == GridLayout.VerticalAlignment.BOTTOM) + ? (height - element.height) + : (height - element.height) >> 1; + element = col.buildElement(width, autoWidth, autoWidthSum, left, top + topOffset, collector); + left += element.width; + } + } + + + return new GridElement(inLeft, top, width, height); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridScreen.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridScreen.java new file mode 100644 index 00000000..2a7413e8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridScreen.java @@ -0,0 +1,266 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.Widget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import org.jetbrains.annotations.Nullable; + + +@Environment(EnvType.CLIENT) +public abstract class GridScreen extends Screen { + protected GridLayout grid = null; + public final int topPadding; + public final int sidePadding; + public final boolean centerVertically; + @Nullable + public final Screen parent; + + protected int scrollPos = 0; + + public GridScreen(Component title) { + this(null, title); + } + + public GridScreen(@Nullable Screen parent, Component title) { + this(parent, title, 0, true); + } + + public GridScreen(Component title, int topPadding, boolean centerVertically) { + this(null, title, topPadding, 20, centerVertically); + } + + public GridScreen(@Nullable Screen parent, Component title, int topPadding, boolean centerVertically) { + this(parent, title, topPadding, 20, centerVertically); + } + + public GridScreen(Component title, int topPadding, int sidePadding, boolean centerVertically) { + this(null, title, topPadding, sidePadding, centerVertically); + } + + public GridScreen( + @Nullable Screen parent, + Component title, + int topPadding, + int sidePadding, + boolean centerVertically + ) { + super(title); + + this.parent = parent; + this.topPadding = topPadding; + this.sidePadding = sidePadding; + this.centerVertically = centerVertically; + } + + @Override + public void onClose() { + this.minecraft.setScreen(parent); + } + + public Font getFont() { + return this.font; + } + + @Override + public boolean isPauseScreen() { + return true; + } + + @Override + public T addRenderableWidget(T guiEventListener) { + return super.addRenderableWidget(guiEventListener); + } + + protected void addTitle() { + grid.addRow().addString(this.title, GridLayout.Alignment.CENTER, this); + grid.addSpacerRow(15); + } + + final protected void init() { + super.init(); + this.grid = new GridLayout(this, this.topPadding, this.sidePadding, this.centerVertically); + + addTitle(); + + initLayout(); + grid.finalizeLayout(); + } + + protected abstract void initLayout(); + + protected void renderScreen(PoseStack poseStack, int i, int j, float f) { + super.render(poseStack, i, j, f); + } + + public void render(PoseStack poseStack, int i, int j, float f) { + this.renderDirtBackground(i); + renderGrid(poseStack); + super.render(poseStack, i, j, f); + } + + protected void renderGrid(PoseStack poseStack) { + if (grid != null) { + if (isScrollable()) { + for (var item : grid.movableWidgets) { + item.first.y = item.second + scrollPos; + } + + renderScroll(poseStack); + + poseStack.pushPose(); + poseStack.translate(0, scrollPos, 0); + grid.render(poseStack); + poseStack.popPose(); + } else { + grid.render(poseStack); + } + } + } + + public static int getWidth(Component text, Font font) { + return font.width(text.getVisualOrderText()); + } + + public int getWidth(Component text) { + return getWidth(text, getFont()); + } + + public void setScrollPos(int sp) { + scrollPos = Math.max(getMaxScrollPos(), Math.min(0, sp)); + } + + public int getScrollPos() { + return scrollPos; + } + + public int getScrollHeight() { + if (grid != null) return grid.getHeight() + topPadding; + return height; + } + + public int getMaxScrollPos() { + return height - (getScrollHeight() + topPadding); + } + + public boolean isScrollable() { + return height < getScrollHeight(); + } + + public boolean isMouseOverScroller(double x, double y) { + return y >= 0 && y <= height && x >= width - SCROLLER_WIDTH && x <= width; + } + + private boolean scrolling = false; + + protected void updateScrollingState(double x, double y, int i) { + this.scrolling = i == 0 && x >= width - SCROLLER_WIDTH && x < width; + } + + private static final int SCROLLER_WIDTH = 6; + + private void renderScroll(PoseStack poseStack) { + final int y1 = height; + final int y0 = 0; + final int yd = y1 - y0; + final int maxPosition = getScrollHeight() + topPadding; + + final int x0 = width - SCROLLER_WIDTH; + final int x1 = width; + + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder bufferBuilder = tesselator.getBuilder(); + RenderSystem.disableTexture(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + int widgetHeight = (int) ((float) (yd * yd) / (float) maxPosition); + widgetHeight = Mth.clamp(widgetHeight, 32, yd - 8); + float relPos = (float) this.getScrollPos() / this.getMaxScrollPos(); + int top = (int) (relPos * (yd - widgetHeight)) + y0; + if (top < y0) { + top = y0; + } + + bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + //scroller background + bufferBuilder.vertex(x0, y1, 0.0D).color(0, 0, 0, 255).endVertex(); + bufferBuilder.vertex(x1, y1, 0.0D).color(0, 0, 0, 255).endVertex(); + bufferBuilder.vertex(x1, y0, 0.0D).color(0, 0, 0, 255).endVertex(); + bufferBuilder.vertex(x0, y0, 0.0D).color(0, 0, 0, 255).endVertex(); + + //scroll widget shadow + bufferBuilder.vertex(x0, top + widgetHeight, 0.0D).color(128, 128, 128, 255).endVertex(); + bufferBuilder.vertex(x1, top + widgetHeight, 0.0D).color(128, 128, 128, 255).endVertex(); + bufferBuilder.vertex(x1, top, 0.0D).color(128, 128, 128, 255).endVertex(); + bufferBuilder.vertex(x0, top, 0.0D).color(128, 128, 128, 255).endVertex(); + + //scroll widget + bufferBuilder.vertex(x0, top + widgetHeight - 1, 0.0D) + .color(192, 192, 192, 255) + .endVertex(); + bufferBuilder.vertex(x1 - 1, top + widgetHeight - 1, 0.0D) + .color(192, 192, 192, 255) + .endVertex(); + bufferBuilder.vertex(x1 - 1, top, 0.0D).color(192, 192, 192, 255).endVertex(); + bufferBuilder.vertex(x0, top, 0.0D).color(192, 192, 192, 255).endVertex(); + tesselator.end(); + } + + public boolean mouseClicked(double x, double y, int i) { + this.updateScrollingState(x, y, i); + if (this.scrolling) { + return true; + } else { + return super.mouseClicked(x, y, i); + } + } + + public boolean mouseDragged(double xAbs, double yAbs, int i, double dX, double dY) { + if (super.mouseDragged(xAbs, yAbs, i, dX, dY)) { + return true; + } else if (i == 0 && this.scrolling) { + if (yAbs < 0) { + this.setScrollPos(0); + } else if (yAbs > height) { + this.setScrollPos(this.getMaxScrollPos()); + } else { + this.setScrollPos((int) (this.getScrollPos() - dY * 2)); + } + + return true; + } else { + return false; + } + } + + public boolean mouseScrolled(double d, double e, double f) { + if (isScrollable()) { + setScrollPos((int) (scrollPos + f * 10)); + } + return true; + } + + public boolean keyPressed(int keyCode, int j, int k) { + if (super.keyPressed(keyCode, j, k)) { + return true; + } else if (keyCode == 264) { + this.mouseScrolled(0, -1.0f, 0); + return true; + } else if (keyCode == 265) { + this.mouseScrolled(0, 1.0, 0); + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridStringCell.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridStringCell.java new file mode 100644 index 00000000..538cdb0e --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridStringCell.java @@ -0,0 +1,55 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class GridStringCell extends GridCell { + private Component text; + + GridStringCell( + double width, + GridLayout.GridValueType widthType, + int height, + GridLayout.Alignment contentAlignment, + GridScreen parent, + Component text + ) { + this(width, widthType, height, contentAlignment, parent, text, GridLayout.COLOR_WHITE); + + } + + GridStringCell( + double width, + GridLayout.GridValueType widthType, + int height, + GridLayout.Alignment contentAlignment, + GridScreen parent, + Component text, + int color + ) { + super(width, height, widthType, null, null); + this.text = text; + this.customRender = (poseStack, transform, context) -> { + if (contentAlignment == GridLayout.Alignment.CENTER) { + GuiComponent.drawCenteredString( + poseStack, + parent.getFont(), + this.text, + transform.width / 2 + transform.left, + transform.top, + color + ); + } else if (contentAlignment == GridLayout.Alignment.LEFT) { + GuiComponent.drawString(poseStack, parent.getFont(), this.text, transform.left, transform.top, color); + } + }; + } + + public void setText(Component newText) { + this.text = newText; + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridTransform.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridTransform.java new file mode 100644 index 00000000..0d04206b --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridTransform.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.client.gui.gridlayout; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class GridTransform { + public final int left; + public final int top; + public final int width; + public final int height; + + GridTransform(int left, int top, int width, int height) { + this.left = left; + this.top = top; + this.width = width; + this.height = height; + } + + @Override + public String toString() { + return "{" + "left=" + left + ", top=" + top + ", width=" + width + ", height=" + height + '}'; + } + +} diff --git a/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridWidgetWithEnabledState.java b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridWidgetWithEnabledState.java new file mode 100644 index 00000000..25962995 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/gridlayout/GridWidgetWithEnabledState.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.client.gui.gridlayout; + +public interface GridWidgetWithEnabledState { + boolean isEnabled(); + void setEnabled(boolean enabled); +} 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..485ec9c5 --- /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..8cd1fb56 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/modmenu/MainScreen.java @@ -0,0 +1,104 @@ +package org.betterx.bclib.client.gui.modmenu; + +import org.betterx.bclib.client.gui.gridlayout.*; +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 java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import org.jetbrains.annotations.Nullable; + +public class MainScreen extends GridScreen { + + public MainScreen(@Nullable Screen parent) { + super(parent, Component.translatable("title.bclib.modmenu.main"), 10, false); + } + + 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(GridColumn grid, NamedPathConfig config, ConfigTokenDescription option) { + if (ConfigKeeper.BooleanEntry.class.isAssignableFrom(option.token.type)) { + addCheckbox(grid, config, (ConfigTokenDescription) option); + } + + grid.addSpacerRow(2); + } + + + protected void addCheckbox(GridColumn grid, NamedPathConfig config, ConfigTokenDescription option) { + if (option.topPadding > 0) { + grid.addSpacerRow(option.topPadding); + } + GridRow row = grid.addRow(); + if (option.leftPadding > 0) { + row.addSpacer(option.leftPadding); + } + GridCheckboxCell cb = row.addCheckbox( + getComponent(config, option, "title"), + config.getRaw(option.token), + font, + (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 void initLayout() { + final int BUTTON_HEIGHT = 20; + + Configs.GENERATOR_CONFIG.getAllOptions() + .stream() + .filter(o -> !o.hidden) + .forEach(o -> addRow(grid, Configs.GENERATOR_CONFIG, o)); + grid.addSpacerRow(12); + Configs.MAIN_CONFIG.getAllOptions() + .stream() + .filter(o -> !o.hidden) + .forEach(o -> addRow(grid, Configs.MAIN_CONFIG, o)); + grid.addSpacerRow(12); + Configs.CLIENT_CONFIG.getAllOptions() + .stream() + .filter(o -> !o.hidden) + .forEach(o -> addRow(grid, Configs.CLIENT_CONFIG, o)); + + grid.addSpacerRow(15); + GridRow row = grid.addRow(); + row.addFiller(); + row.addButton(CommonComponents.GUI_DONE, BUTTON_HEIGHT, font, (button) -> { + Configs.CLIENT_CONFIG.saveChanges(); + Configs.GENERATOR_CONFIG.saveChanges(); + Configs.MAIN_CONFIG.saveChanges(); + onClose(); + }); + grid.addSpacerRow(10); + } +} 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/BCLibScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/BCLibScreen.java new file mode 100644 index 00000000..377132f8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/BCLibScreen.java @@ -0,0 +1,61 @@ +package org.betterx.bclib.client.gui.screens; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.client.gui.gridlayout.GridLayout; +import org.betterx.bclib.client.gui.gridlayout.GridRow; +import org.betterx.bclib.client.gui.gridlayout.GridScreen; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import org.jetbrains.annotations.Nullable; + +@Environment(EnvType.CLIENT) +abstract class BCLibScreen extends GridScreen { + static final ResourceLocation BCLIB_LOGO_LOCATION = new ResourceLocation(BCLib.MOD_ID, "icon.png"); + + public BCLibScreen(Component title) { + super(title); + } + + public BCLibScreen(@Nullable Screen parent, Component title) { + super(parent, title); + } + + public BCLibScreen(Component title, int topPadding, boolean centerVertically) { + super(title, topPadding, 20, centerVertically); + } + + public BCLibScreen(@Nullable Screen parent, Component title, int topPadding, boolean centerVertically) { + super(parent, title, topPadding, centerVertically); + } + + public BCLibScreen(Component title, int topPadding, int sidePadding, boolean centerVertically) { + super(title, topPadding, sidePadding, centerVertically); + } + + public BCLibScreen( + @Nullable Screen parent, + Component title, + int topPadding, + int sidePadding, + boolean centerVertically + ) { + super(parent, title, topPadding, sidePadding, centerVertically); + } + + + protected void addTitle() { + GridRow row = grid.addRow(GridLayout.VerticalAlignment.CENTER); + row.addFiller(); + row.addImage(BCLIB_LOGO_LOCATION, 24, GridLayout.GridValueType.CONSTANT, 24, 512, 512); + row.addSpacer(4); + row.addString(this.title, this); + row.addFiller(); + grid.addSpacerRow(15); + } +} 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..157d5a16 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/ConfirmFixScreen.java @@ -0,0 +1,76 @@ +package org.betterx.bclib.client.gui.screens; + + +import org.betterx.bclib.client.gui.gridlayout.GridCheckboxCell; +import org.betterx.bclib.client.gui.gridlayout.GridLayout; +import org.betterx.bclib.client.gui.gridlayout.GridRow; + +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 BCLibScreen { + 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"); + } + + protected void initLayout() { + final int BUTTON_HEIGHT = 20; + + grid.addRow().addMessage(this.description, this.font, GridLayout.Alignment.CENTER); + grid.addSpacerRow(); + + GridRow row = grid.addRow(); + GridCheckboxCell backup = row.addCheckbox( + Component.translatable("bclib.datafixer.backupWarning.backup"), + true, + BUTTON_HEIGHT, + this.font + ); + + grid.addSpacerRow(10); + + row = grid.addRow(); + GridCheckboxCell fix = row.addCheckbox( + Component.translatable("bclib.datafixer.backupWarning.fix"), + true, + BUTTON_HEIGHT, + this.font + ); + + grid.addSpacerRow(20); + + row = grid.addRow(); + row.addFiller(); + row.addButton(CommonComponents.GUI_CANCEL, BUTTON_HEIGHT, this.font, (button) -> { + onClose(); + }); + row.addSpacer(); + row.addButton(CommonComponents.GUI_PROCEED, BUTTON_HEIGHT, this.font, (button) -> { + this.listener.proceed(backup.isChecked(), fix.isChecked()); + }); + row.addFiller(); + } + + public boolean shouldCloseOnEsc() { + return true; + } + + @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..191d96d5 --- /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 org.betterx.bclib.client.gui.gridlayout.GridLayout; +import org.betterx.bclib.client.gui.gridlayout.GridRow; + +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 BCLibScreen { + 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; + } + + protected void initLayout() { + final int BUTTON_HEIGHT = 20; + + grid.addRow().addMessage(this.description, this.font, GridLayout.Alignment.CENTER); + + grid.addSpacerRow(); + + GridRow row = grid.addRow(); + row.addFiller(); + row.addButton(CommonComponents.GUI_PROCEED, BUTTON_HEIGHT, font, (button) -> { + listener.proceed(); + }); + row.addFiller(); + } + + public boolean shouldCloseOnEsc() { + return false; + } + + @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..374edcd8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/LevelFixErrorScreen.java @@ -0,0 +1,62 @@ +package org.betterx.bclib.client.gui.screens; + +import org.betterx.bclib.client.gui.gridlayout.GridColumn; +import org.betterx.bclib.client.gui.gridlayout.GridLayout; +import org.betterx.bclib.client.gui.gridlayout.GridRow; + +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 BCLibScreen { + private final String[] errors; + final Listener onContinue; + + public LevelFixErrorScreen(Screen parent, String[] errors, Listener onContinue) { + super(parent, Component.translatable("title.bclib.datafixer.error"), 10, true); + this.errors = errors; + this.onContinue = onContinue; + } + + @Override + protected void initLayout() { + grid.addSpacerRow(); + grid.addRow() + .addMessage(Component.translatable("message.bclib.datafixer.error"), font, GridLayout.Alignment.CENTER); + grid.addSpacerRow(8); + + GridRow row = grid.addRow(); + row.addSpacer(10); + GridColumn col = row.addColumn(300, GridLayout.GridValueType.CONSTANT); + for (String error : errors) { + Component dash = Component.literal("-"); + row = col.addRow(); + row.addString(dash, this); + + row.addSpacer(4); + row.addString(Component.literal(error), this); + } + + grid.addSpacerRow(8); + row = grid.addRow(); + row.addFiller(); + row.addButton(Component.translatable("title.bclib.datafixer.error.continue"), 0.5f, 20, font, (n) -> { + onClose(); + onContinue.doContinue(true); + }); + row.addSpacer(); + row.addButton(CommonComponents.GUI_CANCEL, 20, font, (n) -> { + this.minecraft.setScreen(null); + }); + row.addFiller(); + } + + @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..e740d781 --- /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 org.betterx.bclib.api.v2.dataexchange.handler.autosync.HelloClient; +import org.betterx.bclib.client.gui.gridlayout.GridColumn; +import org.betterx.bclib.client.gui.gridlayout.GridLayout; +import org.betterx.bclib.client.gui.gridlayout.GridRow; +import org.betterx.bclib.client.gui.gridlayout.GridScreen; +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 BCLibScreen { + + 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, 10, true); + 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( + GridColumn grid, + java.util.List mods, + HelloClient.IServerModMap serverInfo, + GridScreen parent + ) { + 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() + .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 = GridLayout.COLOR_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 = GridLayout.COLOR_YELLOW; + } else if (state == STATE_VERSION_CLIENT_ONLY) { + color = GridLayout.COLOR_DARK_GREEN; + } + } else if (state == STATE_MISSING || state == STATE_MISSING_NOT_OFFERED) { + typeText = "[MISSING]"; + if (state == STATE_MISSING_NOT_OFFERED) { + color = GridLayout.COLOR_YELLOW; + } + } else if (state == STATE_SERVER_MISSING || state == STATE_SERVER_MISSING_CLIENT_MOD) { + if (state == STATE_SERVER_MISSING_CLIENT_MOD) { + color = GridLayout.COLOR_CYAN; + typeText = "[OK]"; + } else { + typeText = "[NOT ON SERVER]"; + } + } else { + color = GridLayout.COLOR_DARK_GREEN; + typeText = "[OK]"; + } + Component dash = Component.literal("-"); + Component typeTextComponent = Component.literal(typeText); + GridRow row = grid.addRow(); + + row.addString(dash, parent); + + row.addSpacer(4); + row.addString(Component.literal(name), parent); + + row.addSpacer(4); + row.addString(typeTextComponent, color, parent); + + if (!stateString.isEmpty()) { + row = grid.addRow(); + row.addSpacer(4 + parent.getWidth(dash)); + row.addString(Component.literal(stateString), GridLayout.COLOR_GRAY, parent); + } + + grid.addSpacerRow(); + }); + } + + @Override + protected void initLayout() { + if (description != null) { + grid.addSpacerRow(); + grid.addRow().addMessage(description, font, GridLayout.Alignment.CENTER); + grid.addSpacerRow(8); + } + + GridRow row = grid.addRow(); + row.addSpacer(10); + GridColumn col = row.addColumn(200, GridLayout.GridValueType.CONSTANT); + addModDesc(col, mods, serverInfo, this); + + grid.addSpacerRow(8); + row = grid.addRow(); + row.addFiller(); + row.addButton(buttonTitle, 20, font, (n) -> { + onClose(); + }); + row.addFiller(); + } + +} 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..1b9540cf --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/ProgressScreen.java @@ -0,0 +1,198 @@ +package org.betterx.bclib.client.gui.screens; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.client.gui.gridlayout.*; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.GuiComponent; +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 GridCustomRenderCell { + 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(SIZE, GridLayout.GridValueType.CONSTANT, SIZE); + } + + @Override + public void onRender(PoseStack poseStack, GridTransform transform, Object context) { + time += 0.03; + 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 = transform.top + 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); + RenderSystem.setShaderTexture(0, BCLibScreen.BCLIB_LOGO_LOCATION); + GuiComponent.blit(poseStack, + xOffset + transform.left, + yOffset + transform.top, + 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); + GuiComponent.blit(poseStack, + xOffset + transform.left, + yOffset + transform.top + yBarImage, + width, + height - yBarImage, + 0, uvTopPixelated, PIXELATED_SIZE, PIXELATED_SIZE - uvTopPixelated, + PIXELATED_SIZE, PIXELATED_SIZE + ); + } + + if (percentage > 0 && percentage < 1.0) { + GuiComponent.fill( + poseStack, + transform.left, + yBar, + transform.left + transform.width, + yBar + 1, + 0x3FFFFFFF + ); + } + } +} + +public class ProgressScreen extends GridScreen 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, 20, true); + this.description = description; + } + + + Component description; + private Component stageComponent; + private GridMessageCell stage; + private GridStringCell 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 + protected void initLayout() { + grid.addSpacerRow(); + + GridRow row = grid.addRow(GridLayout.VerticalAlignment.CENTER); + row.addFiller(); + progressImage = new ProgressLogoRender(); + progressImage.percentage = currentProgress / 100.0f; + row.addCustomRender(progressImage); + row.addSpacer(); + + int textWidth = Math.max(getWidth(description), getWidth(getProgressComponent(100))); + GridColumn textCol = row.addColumn(0, GridLayout.GridValueType.INHERIT); + textCol.addRow().addString(description, this); + textCol.addSpacerRow(); + progress = textCol.addRow() + .addString(getProgressComponent(), GridLayout.COLOR_GRAY, GridLayout.Alignment.LEFT, this); + + row.addFiller(); + + grid.addSpacerRow(20); + row = grid.addRow(); + stage = row.addMessage( + stageComponent != null ? stageComponent : Component.literal(""), + font, + GridLayout.Alignment.CENTER + ); + } + + @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); + } + + @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() { + + } +} 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..7d5d9f8f --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/SyncFilesScreen.java @@ -0,0 +1,143 @@ +package org.betterx.bclib.client.gui.screens; + +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.HelloClient; +import org.betterx.bclib.client.gui.gridlayout.GridCheckboxCell; +import org.betterx.bclib.client.gui.gridlayout.GridLayout; +import org.betterx.bclib.client.gui.gridlayout.GridRow; +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 BCLibScreen { + 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; + } + + protected void initLayout() { + final int BUTTON_HEIGHT = 20; + + grid.addRow() + .addMessage(this.description, this.font, GridLayout.Alignment.CENTER); + + grid.addSpacerRow(10); + + GridRow row; + + + final GridCheckboxCell mods; + row = grid.addRow(); + mods = row.addCheckbox( + Component.translatable("message.bclib.syncfiles.mods"), + hasMods, + BUTTON_HEIGHT, + this.font + ); + mods.setEnabled(hasMods); + + row.addSpacer(); + row.addButton(Component.translatable("title.bclib.syncfiles.modInfo"), 20, font, (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.addSpacerRow(); + + + final GridCheckboxCell configs; + row = grid.addRow(); + configs = row.addCheckbox( + Component.translatable("message.bclib.syncfiles.configs"), + hasConfigFiles, + BUTTON_HEIGHT, + this.font + ); + configs.setEnabled(hasConfigFiles); + + grid.addSpacerRow(); + + row = grid.addRow(); + + final GridCheckboxCell folder; + folder = row.addCheckbox( + Component.translatable("message.bclib.syncfiles.folders"), + hasFiles, + BUTTON_HEIGHT, + this.font + ); + folder.setEnabled(hasFiles); + row.addSpacer(); + + GridCheckboxCell delete; + delete = row.addCheckbox( + Component.translatable("message.bclib.syncfiles.delete"), + shouldDelete, + BUTTON_HEIGHT, + this.font + ); + delete.setEnabled(shouldDelete); + + + grid.addSpacerRow(30); + row = grid.addRow(); + row.addFiller(); + row.addButton(CommonComponents.GUI_NO, BUTTON_HEIGHT, this.font, (button) -> { + listener.proceed(false, false, false, false); + }); + row.addSpacer(); + row.addButton(CommonComponents.GUI_YES, BUTTON_HEIGHT, this.font, (button) -> { + listener.proceed( + mods.isChecked(), + configs.isChecked(), + folder.isChecked(), + delete.isChecked() + ); + }); + row.addFiller(); + } + + public boolean shouldCloseOnEsc() { + return false; + } + + @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/WarnBCLibVersionMismatch.java b/src/main/java/org/betterx/bclib/client/gui/screens/WarnBCLibVersionMismatch.java new file mode 100644 index 00000000..0fe4c33f --- /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 org.betterx.bclib.client.gui.gridlayout.GridLayout; +import org.betterx.bclib.client.gui.gridlayout.GridRow; + +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 BCLibScreen { + 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; + } + + protected void initLayout() { + final int BUTTON_HEIGHT = 20; + + grid.addRow().addMessage(this.description, this.font, GridLayout.Alignment.CENTER); + grid.addSpacerRow(20); + GridRow row = grid.addRow(); + row.addFiller(); + row.addButton(CommonComponents.GUI_NO, BUTTON_HEIGHT, this.font, (button) -> { + listener.proceed(false); + }); + row.addSpacer(); + row.addButton(CommonComponents.GUI_YES, BUTTON_HEIGHT, this.font, (button) -> { + listener.proceed(true); + }); + row.addFiller(); + } + + public boolean shouldCloseOnEsc() { + return false; + } + + @Environment(EnvType.CLIENT) + public interface Listener { + void proceed(boolean download); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/WorldSetupScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/WorldSetupScreen.java new file mode 100644 index 00000000..26b8aa89 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/WorldSetupScreen.java @@ -0,0 +1,258 @@ +package org.betterx.bclib.client.gui.screens; + +import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource; +import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource; +import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig; +import org.betterx.bclib.api.v2.generator.config.BCLNetherBiomeSourceConfig; +import org.betterx.bclib.api.v2.levelgen.LevelGenUtil; +import org.betterx.bclib.client.gui.gridlayout.GridCheckboxCell; +import org.betterx.bclib.client.gui.gridlayout.GridLayout; +import org.betterx.bclib.registry.PresetsRegistry; +import org.betterx.worlds.together.worldPreset.TogetherWorldPreset; +import org.betterx.worlds.together.worldPreset.WorldGenSettingsComponentAccessor; + +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; +import net.minecraft.client.gui.screens.worldselection.WorldCreationContext; +import net.minecraft.core.Holder; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +@Environment(EnvType.CLIENT) +public class WorldSetupScreen extends BCLibScreen { + private final WorldCreationContext context; + private final CreateWorldScreen createWorldScreen; + + public WorldSetupScreen(@Nullable CreateWorldScreen parent, WorldCreationContext context) { + super(parent, Component.translatable("title.screen.bclib.worldgen.main"), 10, true); + this.context = context; + this.createWorldScreen = parent; + } + + + private GridCheckboxCell bclibEnd; + private GridCheckboxCell bclibNether; + GridCheckboxCell endLegacy; + GridCheckboxCell endCustomTerrain; + GridCheckboxCell generateEndVoid; + GridCheckboxCell netherLegacy; + + @Override + protected void initLayout() { + BCLEndBiomeSourceConfig endConfig = BCLEndBiomeSourceConfig.VANILLA; + BCLNetherBiomeSourceConfig netherConfig = BCLNetherBiomeSourceConfig.VANILLA; + if (createWorldScreen.worldGenSettingsComponent instanceof WorldGenSettingsComponentAccessor acc + && acc.bcl_getPreset() + .isPresent() && acc.bcl_getPreset() + .get() + .value() instanceof TogetherWorldPreset wp) { + + LevelStem endStem = wp.getDimension(LevelStem.END); + if (endStem != null && endStem.generator().getBiomeSource() instanceof BCLibEndBiomeSource bs) { + endConfig = bs.getTogetherConfig(); + } + LevelStem netherStem = wp.getDimension(LevelStem.NETHER); + if (netherStem != null && netherStem.generator().getBiomeSource() instanceof BCLibNetherBiomeSource bs) { + netherConfig = bs.getTogetherConfig(); + } + } + + + final int BUTTON_HEIGHT = 20; + grid.addSpacerRow(20); + + var row = grid.addRow(); + var colNether = row.addColumn(0.5, GridLayout.GridValueType.PERCENTAGE); + var colEnd = row.addColumn(0.5, GridLayout.GridValueType.PERCENTAGE); + + row = colNether.addRow(); + row.addString(Component.translatable("title.bclib.the_nether"), GridLayout.Alignment.CENTER, this); + colNether.addSpacerRow(15); + + var mainSettingsRow = colNether.addRow(); + mainSettingsRow.addSpacer(16); + colNether.addSpacerRow(2); + row = colNether.addRow(); + row.addSpacer(20); + netherLegacy = row.addCheckbox( + Component.translatable("title.screen.bclib.worldgen.legacy_square"), + netherConfig.mapVersion == BCLNetherBiomeSourceConfig.NetherBiomeMapType.SQUARE, + 1.0, + GridLayout.GridValueType.PERCENTAGE, + (state) -> { + } + ); + bclibNether = mainSettingsRow.addCheckbox( + Component.translatable( + "title.screen.bclib.worldgen.custom_biome_source"), + netherConfig.mapVersion != BCLNetherBiomeSourceConfig.NetherBiomeMapType.VANILLA, + 1.0, + GridLayout.GridValueType.PERCENTAGE, + (state) -> { + netherLegacy.setEnabled(state); + } + ); + + + row = colEnd.addRow(GridLayout.VerticalAlignment.CENTER); + row.addString(Component.translatable("title.bclib.the_end"), GridLayout.Alignment.CENTER, this); + colEnd.addSpacerRow(15); + + mainSettingsRow = colEnd.addRow(); + mainSettingsRow.addSpacer(16); + colEnd.addSpacerRow(2); + row = colEnd.addRow(); + row.addSpacer(20); + endCustomTerrain = row.addCheckbox( + Component.translatable("title.screen.bclib.worldgen.custom_end_terrain"), + endConfig.generatorVersion != BCLEndBiomeSourceConfig.EndBiomeGeneratorType.VANILLA, + 1.0, + GridLayout.GridValueType.PERCENTAGE, + (state) -> { + } + ); + + row = colEnd.addRow(); + row.addSpacer(20); + generateEndVoid = row.addCheckbox( + Component.translatable("title.screen.bclib.worldgen.end_void"), + endConfig.withVoidBiomes, + 1.0, + GridLayout.GridValueType.PERCENTAGE, + (state) -> { + } + ); + + row = colEnd.addRow(); + row.addSpacer(20); + endLegacy = row.addCheckbox( + Component.translatable("title.screen.bclib.worldgen.legacy_square"), + endConfig.mapVersion == BCLEndBiomeSourceConfig.EndBiomeMapType.SQUARE, + 1.0, + GridLayout.GridValueType.PERCENTAGE, + (state) -> { + } + ); + + bclibEnd = mainSettingsRow.addCheckbox( + Component.translatable( + "title.screen.bclib.worldgen.custom_biome_source"), + endConfig.mapVersion != BCLEndBiomeSourceConfig.EndBiomeMapType.VANILLA, + 1.0, + GridLayout.GridValueType.PERCENTAGE, + (state) -> { + endLegacy.setEnabled(state); + endCustomTerrain.setEnabled(state); + generateEndVoid.setEnabled(state); + } + ); + + + grid.addSpacerRow(36); + row = grid.addRow(); + row.addFiller(); + row.addButton(CommonComponents.GUI_DONE, BUTTON_HEIGHT, font, (button) -> { + updateSettings(); + onClose(); + }); + grid.addSpacerRow(10); + } + + private void updateSettings() { + Map, ChunkGenerator> betterxDimensions = TogetherWorldPreset.getDimensionsMap( + PresetsRegistry.BCL_WORLD); + Map, ChunkGenerator> vanillaDimensions = TogetherWorldPreset.getDimensionsMap( + WorldPresets.NORMAL); + BCLEndBiomeSourceConfig.EndBiomeMapType endVersion = BCLEndBiomeSourceConfig.DEFAULT.mapVersion; + + + if (bclibEnd.isChecked()) { + BCLEndBiomeSourceConfig endConfig = new BCLEndBiomeSourceConfig( + endLegacy.isChecked() + ? BCLEndBiomeSourceConfig.EndBiomeMapType.SQUARE + : BCLEndBiomeSourceConfig.EndBiomeMapType.HEX, + endCustomTerrain.isChecked() + ? BCLEndBiomeSourceConfig.EndBiomeGeneratorType.PAULEVS + : BCLEndBiomeSourceConfig.EndBiomeGeneratorType.VANILLA, + generateEndVoid.isChecked(), + BCLEndBiomeSourceConfig.DEFAULT.innerVoidRadiusSquared, + BCLEndBiomeSourceConfig.DEFAULT.centerBiomesSize, + BCLEndBiomeSourceConfig.DEFAULT.voidBiomesSize, + BCLEndBiomeSourceConfig.DEFAULT.landBiomesSize, + BCLEndBiomeSourceConfig.DEFAULT.barrensBiomesSize + ); + + ChunkGenerator endGenerator = betterxDimensions.get(LevelStem.END); + ((BCLibEndBiomeSource) endGenerator.getBiomeSource()).setTogetherConfig(endConfig); + + updateConfiguration(LevelStem.END, BuiltinDimensionTypes.END, endGenerator); + } else { + ChunkGenerator endGenerator = vanillaDimensions.get(LevelStem.END); + updateConfiguration(LevelStem.END, BuiltinDimensionTypes.END, endGenerator); + } + + if (bclibNether.isChecked()) { + BCLNetherBiomeSourceConfig netherConfig = new BCLNetherBiomeSourceConfig( + netherLegacy.isChecked() + ? BCLNetherBiomeSourceConfig.NetherBiomeMapType.SQUARE + : BCLNetherBiomeSourceConfig.NetherBiomeMapType.HEX, + BCLNetherBiomeSourceConfig.DEFAULT.biomeSize, + BCLNetherBiomeSourceConfig.DEFAULT.biomeSizeVertical, + BCLNetherBiomeSourceConfig.DEFAULT.useVerticalBiomes + ); + + ChunkGenerator netherGenerator = betterxDimensions.get(LevelStem.NETHER); + ((BCLibNetherBiomeSource) netherGenerator.getBiomeSource()).setTogetherConfig(netherConfig); + + updateConfiguration(LevelStem.NETHER, BuiltinDimensionTypes.NETHER, netherGenerator); + } else { + ChunkGenerator endGenerator = vanillaDimensions.get(LevelStem.NETHER); + updateConfiguration(LevelStem.NETHER, BuiltinDimensionTypes.NETHER, endGenerator); + } + + if (createWorldScreen.worldGenSettingsComponent instanceof WorldGenSettingsComponentAccessor acc + && acc.bcl_getPreset() + .isPresent() && acc.bcl_getPreset() + .get() + .value() instanceof TogetherWorldPreset worldPreset) { + acc.bcl_setPreset(Optional.of(Holder.direct( + worldPreset.withDimensions( + createWorldScreen + .worldGenSettingsComponent + .settings() + .worldGenSettings() + .dimensions() + ) + ))); + } + } + + + private void updateConfiguration( + ResourceKey dimensionKey, + ResourceKey dimensionTypeKey, + ChunkGenerator chunkGenerator + ) { + createWorldScreen.worldGenSettingsComponent.updateSettings( + (registryAccess, worldGenSettings) -> LevelGenUtil.replaceGenerator( + dimensionKey, + dimensionTypeKey, + registryAccess, + worldGenSettings, + chunkGenerator + ) + ); + } + + +} 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..344ab807 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/BasePatterns.java @@ -0,0 +1,62 @@ +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"); + +} 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..58abd0c4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/CustomModelBakery.java @@ -0,0 +1,148 @@ +package org.betterx.bclib.client.models; + +import org.betterx.bclib.api.v2.ModIntegrationAPI; +import org.betterx.bclib.client.render.EmissiveTextureInfo; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.ItemModelProvider; + +import com.mojang.datafixers.util.Pair; +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.Material; +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.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class CustomModelBakery { + private final Map models = Maps.newConcurrentMap(); + + public UnbakedModel getBlockModel(ResourceLocation location) { + return models.get(location); + } + + public UnbakedModel getItemModel(ResourceLocation location) { + ResourceLocation storageID = new ResourceLocation( + location.getNamespace(), + "models/item/" + location.getPath() + ".json" + ); + return models.get(location); + } + + public void loadCustomModels(ResourceManager resourceManager) { + Registry.BLOCK.stream().parallel().filter(block -> block instanceof BlockModelProvider).forEach(block -> { + ResourceLocation blockID = Registry.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); + } + }); + + Registry.ITEM.stream().parallel().filter(item -> item instanceof ItemModelProvider).forEach(item -> { + ResourceLocation registryID = Registry.ITEM.getKey(item); + ResourceLocation storageID = new ResourceLocation( + registryID.getNamespace(), + "models/item/" + registryID.getPath() + ".json" + ); + if (resourceManager.getResource(storageID).isEmpty()) { + addItemModel(registryID, (ItemModelProvider) item); + } + }); + } + + 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); + } + + public static void loadEmissiveModels(Map unbakedCache) { + if (!ModIntegrationAPI.hasCanvas()) { + return; + } + + Map cacheCopy = new HashMap<>(unbakedCache); + Set> strings = Sets.newConcurrentHashSet(); + Registry.BLOCK.keySet().forEach(blockID -> { + Block block = Registry.BLOCK.get(blockID); + ImmutableList states = block.getStateDefinition().getPossibleStates(); + boolean addBlock = false; + + for (BlockState state : states) { + ResourceLocation stateID = BlockModelShaper.stateToModelLocation(blockID, state); + UnbakedModel model = cacheCopy.get(stateID); + if (model == null) { + continue; + } + Collection materials = model.getMaterials(cacheCopy::get, strings); + if (materials == null) { + continue; + } + for (Material material : materials) { + if (EmissiveTextureInfo.isEmissiveTexture(material.texture())) { + addBlock = true; + break; + } + } + if (addBlock) { + break; + } + } + + if (addBlock) { + EmissiveTextureInfo.addBlock(blockID); + } + }); + } +} 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..59e118a2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/OBJBlockModel.java @@ -0,0 +1,302 @@ +package org.betterx.bclib.client.models; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.MHelper; + +import com.mojang.datafixers.util.Pair; +import com.mojang.math.Vector3f; +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.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 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 Collection getMaterials( + Function function, + Set> set + ) { + return materials; + } + + @Nullable + @Override + public BakedModel bake( + ModelBakery 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, + Random 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..d08be7eb --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/OBJModelBuilder.java @@ -0,0 +1,112 @@ +package org.betterx.bclib.client.models; + +import com.mojang.math.Vector3f; +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Lists; + +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..9978d8bd --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/UnbakedQuad.java @@ -0,0 +1,69 @@ +package org.betterx.bclib.client.models; + +import com.mojang.math.Matrix4f; +import com.mojang.math.Vector3f; +import com.mojang.math.Vector4f; +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; + +@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.transform(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..ab1a3d59 --- /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.Vector3f; +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.Registry; +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(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 = 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 = 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 { + 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/BaseSignBlockEntityRenderer.java b/src/main/java/org/betterx/bclib/client/render/BaseSignBlockEntityRenderer.java new file mode 100644 index 00000000..e379d15e --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/render/BaseSignBlockEntityRenderer.java @@ -0,0 +1,195 @@ +package org.betterx.bclib.client.render; + +import org.betterx.bclib.blockentities.BaseSignBlockEntity; +import org.betterx.bclib.blocks.BaseSignBlock; + +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.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.Sheets; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; +import net.minecraft.client.renderer.blockentity.SignRenderer; +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.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.DyeColor; +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 net.minecraft.world.phys.Vec3; + +import com.google.common.collect.Maps; + +import java.util.HashMap; +import java.util.List; + +public class BaseSignBlockEntityRenderer implements BlockEntityRenderer { + private static final HashMap RENDER_TYPES = Maps.newHashMap(); + private static final int OUTLINE_RENDER_DISTANCE = Mth.square(16); + private static final RenderType RENDER_TYPE; + private final SignRenderer.SignModel model; + private final Font font; + + + public BaseSignBlockEntityRenderer(BlockEntityRendererProvider.Context ctx) { + super(); + this.font = ctx.getFont(); + model = new SignRenderer.SignModel(ctx.bakeLayer(ModelLayers.createSignModelName(WoodType.OAK))); + } + + 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)); + model.stick.visible = true; + } else { + matrixStack.mulPose(Vector3f.YP.rotationDegrees(angle + 180)); + matrixStack.translate(0.0D, -0.3125D, -0.4375D); + model.stick.visible = false; + } + + matrixStack.pushPose(); + matrixStack.scale(0.6666667F, -0.6666667F, -0.6666667F); + VertexConsumer vertexConsumer = getConsumer(provider, state.getBlock()); + + model.root.render(matrixStack, vertexConsumer, light, overlay); + matrixStack.popPose(); + 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); + + FormattedCharSequence[] formattedCharSequences = signBlockEntity.getRenderMessages( + Minecraft.getInstance() + .isTextFilteringEnabled(), + (component) -> { + List list = this.font.split(component, 90); + return list.isEmpty() ? FormattedCharSequence.EMPTY : list.get(0); + } + ); + int drawColor; + boolean drawOutlined; + int drawLight; + if (signBlockEntity.hasGlowingText()) { + drawColor = signBlockEntity.getColor().getTextColor(); + drawOutlined = isOutlineVisible(signBlockEntity, drawColor); + drawLight = 15728880; + } else { + drawColor = m; + drawOutlined = false; + drawLight = light; + } + + for (int s = 0; s < 4; ++s) { + FormattedCharSequence formattedCharSequence = formattedCharSequences[s]; + float t = (float) (-this.font.width(formattedCharSequence) / 2); + if (drawOutlined) { + this.font.drawInBatch8xOutline( + formattedCharSequence, + t, + (float) (s * 10 - 20), + drawColor, + m, + matrixStack.last().pose(), + provider, + drawLight + ); + } else { + this.font.drawInBatch( + formattedCharSequence, + t, + (float) (s * 10 - 20), + drawColor, + false, + matrixStack.last().pose(), + provider, + false, + 0, + drawLight + ); + } + } + + + matrixStack.popPose(); + } + + + private static boolean isOutlineVisible(BaseSignBlockEntity signBlockEntity, int i) { + if (i == DyeColor.BLACK.getTextColor()) { + return true; + } else { + Minecraft minecraft = Minecraft.getInstance(); + LocalPlayer localPlayer = minecraft.player; + if (localPlayer != null && minecraft.options.getCameraType().isFirstPerson() && localPlayer.isScoping()) { + return true; + } else { + Entity entity = minecraft.getCameraEntity(); + return entity != null && entity.distanceToSqr(Vec3.atCenterOf(signBlockEntity.getBlockPos())) < (double) OUTLINE_RENDER_DISTANCE; + } + } + } + + public static WoodType getSignType(Block block) { + WoodType signType2; + if (block instanceof SignBlock) { + signType2 = ((SignBlock) block).type(); + } else { + signType2 = WoodType.OAK; + } + + return signType2; + } + + public static Material getModelTexture(Block block) { + return Sheets.getSignMaterial(getSignType(block)); + } + + public static VertexConsumer getConsumer(MultiBufferSource provider, Block block) { + return provider.getBuffer(RENDER_TYPES.getOrDefault(block, RENDER_TYPE)); + } + + 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" + )); + RENDER_TYPES.put(block, layer); + } + + static { + RENDER_TYPE = RenderType.entitySolid(new ResourceLocation("textures/entity/signs/oak.png")); + } +} 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..b4f122fd --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/render/CustomFogRenderer.java @@ -0,0 +1,162 @@ +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 BiomeAPI.getRenderBiome(biome) == BCLBiomeRegistry.EMPTY_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); + return renderBiome.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/EmissiveTextureInfo.java b/src/main/java/org/betterx/bclib/client/render/EmissiveTextureInfo.java new file mode 100644 index 00000000..e2acddae --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/render/EmissiveTextureInfo.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.client.render; + +import net.minecraft.resources.ResourceLocation; + +import com.google.common.collect.Sets; + +import java.util.Set; + +public class EmissiveTextureInfo { + private static final Set EMISSIVE_TEXTURES = Sets.newHashSet(); + private static final Set EMISSIVE_BLOCKS = Sets.newHashSet(); + + public static void clear() { + EMISSIVE_TEXTURES.clear(); + EMISSIVE_BLOCKS.clear(); + } + + public static void addTexture(ResourceLocation texture) { + EMISSIVE_TEXTURES.add(texture); + } + + public static void addBlock(ResourceLocation blockID) { + EMISSIVE_BLOCKS.add(blockID); + } + + public static boolean isEmissiveTexture(ResourceLocation texture) { + return EMISSIVE_TEXTURES.contains(texture); + } + + public static boolean isEmissiveBlock(ResourceLocation blockID) { + return EMISSIVE_BLOCKS.contains(blockID); + } +} 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/commands/CommandRegistry.java b/src/main/java/org/betterx/bclib/commands/CommandRegistry.java new file mode 100644 index 00000000..ca79745e --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/CommandRegistry.java @@ -0,0 +1,197 @@ +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.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +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((a, b, c) -> register(a, c)); + } + + + private static void register( + CommandDispatcher dispatcher, + Commands.CommandSelection commandSelection + ) { + dispatcher.register( + Commands.literal("bclib") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .then(Commands.literal("request_garbage_collection") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .executes(ctx -> requestGC(ctx)) + ) + .then(Commands.literal("debug_ore") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .executes(ctx -> revealOre(ctx)) + ) + .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(pos).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(pos), 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/complexmaterials/ComplexMaterial.java b/src/main/java/org/betterx/bclib/complexmaterials/ComplexMaterial.java new file mode 100644 index 00000000..88b05575 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/ComplexMaterial.java @@ -0,0 +1,371 @@ +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.RecipeEntry; +import org.betterx.bclib.config.PathConfig; +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.Item; +import net.minecraft.world.level.block.Block; + +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; +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 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; + * @param recipeConfig {@link PathConfig} for recipes check. + * @return {@link ComplexMaterial}. + */ + public ComplexMaterial init(BlockRegistry blocksRegistry, ItemRegistry itemsRegistry, PathConfig recipeConfig) { + initTags(); + + final FabricBlockSettings blockSettings = getBlockSettings(); + final FabricItemSettings 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, recipeConfig); + }); + + initFlammable(FlammableBlockRegistry.getDefaultInstance()); + return this; + } + + /** + * Init default content for {@link ComplexMaterial} - blocks and items. + * + * @param blockSettings {@link FabricBlockSettings} default block settings for this material; + * @param itemSettings {@link FabricItemSettings} default item settings for this material. + */ + protected abstract void initDefault(FabricBlockSettings blockSettings, FabricItemSettings 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 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 default block settings for this material. + * + * @return {@link FabricBlockSettings} + */ + protected abstract FabricBlockSettings getBlockSettings(); + + /** + * Get default item settings for this material. + * + * @return {@link FabricItemSettings} + */ + protected FabricItemSettings 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/WoodenComplexMaterial.java b/src/main/java/org/betterx/bclib/complexmaterials/WoodenComplexMaterial.java new file mode 100644 index 00000000..5a2a873c --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/WoodenComplexMaterial.java @@ -0,0 +1,430 @@ +package org.betterx.bclib.complexmaterials; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.blocks.*; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.RecipeEntry; +import org.betterx.bclib.recipes.GridRecipe; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; +import org.betterx.worlds.together.tag.v3.CommonItemTags; +import org.betterx.worlds.together.tag.v3.TagManager; + +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.Blocks; +import net.minecraft.world.level.material.MaterialColor; + +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; +import net.fabricmc.fabric.api.registry.FlammableBlockRegistry; + +public class WoodenComplexMaterial extends ComplexMaterial { + public static final ResourceLocation MATERIAL_ID = BCLib.makeID("wooden_material"); + + public static final String BLOCK_CRAFTING_TABLE = "crafting_table"; + public static final String BLOCK_STRIPPED_BARK = "stripped_bark"; + public static final String BLOCK_STRIPPED_LOG = "stripped_log"; + public static final String BLOCK_PRESSURE_PLATE = "plate"; + public static final String BLOCK_BOOKSHELF = "bookshelf"; + public static final String BLOCK_COMPOSTER = "composter"; + public static final String BLOCK_TRAPDOOR = "trapdoor"; + public static final String BLOCK_BARREL = "barrel"; + public static final String BLOCK_BUTTON = "button"; + public static final String BLOCK_LADDER = "ladder"; + public static final String BLOCK_PLANKS = "planks"; + public static final String BLOCK_STAIRS = "stairs"; + public static final String BLOCK_CHEST = "chest"; + public static final String BLOCK_FENCE = "fence"; + public static final String BLOCK_BARK = "bark"; + public static final String BLOCK_DOOR = "door"; + public static final String BLOCK_GATE = "gate"; + public static final String BLOCK_SIGN = "sign"; + public static final String BLOCK_SLAB = "slab"; + public static final String BLOCK_LOG = "log"; + + public static final String TAG_LOGS = "logs"; + + public final MaterialColor planksColor; + public final MaterialColor woodColor; + + public WoodenComplexMaterial( + String modID, + String baseName, + String receipGroupPrefix, + MaterialColor woodColor, + MaterialColor planksColor + ) { + super(modID, baseName, receipGroupPrefix); + this.planksColor = planksColor; + this.woodColor = woodColor; + } + + @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 void initDefault(FabricBlockSettings blockSettings, FabricItemSettings itemSettings) { + initBase(blockSettings, itemSettings); + initStorage(blockSettings, itemSettings); + initDecorations(blockSettings, itemSettings); + } + + final protected void initBase(FabricBlockSettings blockSettings, FabricItemSettings itemSettings) { + TagKey tagBlockLog = getBlockTag(TAG_LOGS); + TagKey tagItemLog = getItemTag(TAG_LOGS); + + addBlockEntry( + new BlockEntry(BLOCK_STRIPPED_LOG, (complexMaterial, settings) -> new BaseRotatedPillarBlock(settings)) + .setBlockTags(BlockTags.LOGS, BlockTags.LOGS_THAT_BURN, tagBlockLog) + .setItemTags(ItemTags.LOGS, ItemTags.LOGS_THAT_BURN, tagItemLog) + ); + addBlockEntry( + new BlockEntry(BLOCK_STRIPPED_BARK, (complexMaterial, settings) -> new BaseBarkBlock(settings)) + .setBlockTags(BlockTags.LOGS, BlockTags.LOGS_THAT_BURN, tagBlockLog) + .setItemTags(ItemTags.LOGS, ItemTags.LOGS_THAT_BURN, tagItemLog) + ); + + addBlockEntry( + new BlockEntry( + BLOCK_LOG, + (complexMaterial, settings) -> new BaseStripableLogBlock( + woodColor, + getBlock(BLOCK_STRIPPED_LOG) + ) + ) + .setBlockTags(BlockTags.LOGS, BlockTags.LOGS_THAT_BURN, tagBlockLog) + .setItemTags(ItemTags.LOGS, ItemTags.LOGS_THAT_BURN, tagItemLog) + ); + addBlockEntry( + new BlockEntry( + BLOCK_BARK, + (complexMaterial, settings) -> new StripableBarkBlock( + woodColor, + getBlock(BLOCK_STRIPPED_BARK) + ) + ) + .setBlockTags(BlockTags.LOGS, BlockTags.LOGS_THAT_BURN, tagBlockLog) + .setItemTags(ItemTags.LOGS, ItemTags.LOGS_THAT_BURN, tagItemLog) + ); + addBlockEntry(new BlockEntry(BLOCK_PLANKS, (complexMaterial, settings) -> new BaseBlock(settings)) + .setBlockTags(BlockTags.PLANKS) + .setItemTags(ItemTags.PLANKS)); + + addBlockEntry(new BlockEntry( + BLOCK_STAIRS, + (complexMaterial, settings) -> new BaseStairsBlock(getBlock(BLOCK_PLANKS), false) + ) + .setBlockTags(BlockTags.WOODEN_STAIRS, BlockTags.STAIRS) + .setItemTags(ItemTags.WOODEN_STAIRS, ItemTags.STAIRS)); + + addBlockEntry(new BlockEntry( + BLOCK_SLAB, + (complexMaterial, settings) -> new BaseSlabBlock(getBlock(BLOCK_PLANKS), false) + ) + .setBlockTags(BlockTags.WOODEN_SLABS, BlockTags.SLABS) + .setItemTags(ItemTags.WOODEN_SLABS, ItemTags.SLABS)); + + addBlockEntry(new BlockEntry( + BLOCK_FENCE, + (complexMaterial, settings) -> new BaseFenceBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(BlockTags.FENCES, BlockTags.WOODEN_FENCES) + .setItemTags(ItemTags.FENCES, ItemTags.WOODEN_FENCES)); + + addBlockEntry(new BlockEntry( + BLOCK_GATE, + (complexMaterial, settings) -> new BaseGateBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(BlockTags.FENCE_GATES)); + + addBlockEntry(new BlockEntry( + BLOCK_BUTTON, + (complexMaterial, settings) -> new BaseWoodenButtonBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(BlockTags.BUTTONS, BlockTags.WOODEN_BUTTONS) + .setItemTags(ItemTags.BUTTONS, ItemTags.WOODEN_BUTTONS)); + + addBlockEntry(new BlockEntry( + BLOCK_PRESSURE_PLATE, + (complexMaterial, settings) -> new WoodenPressurePlateBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(BlockTags.PRESSURE_PLATES, BlockTags.WOODEN_PRESSURE_PLATES) + .setItemTags(ItemTags.WOODEN_PRESSURE_PLATES)); + + addBlockEntry(new BlockEntry( + BLOCK_TRAPDOOR, + (complexMaterial, settings) -> new BaseTrapdoorBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(BlockTags.TRAPDOORS, BlockTags.WOODEN_TRAPDOORS) + .setItemTags(ItemTags.TRAPDOORS, ItemTags.WOODEN_TRAPDOORS)); + + addBlockEntry(new BlockEntry( + BLOCK_DOOR, + (complexMaterial, settings) -> new BaseDoorBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(BlockTags.DOORS, BlockTags.WOODEN_DOORS) + .setItemTags(ItemTags.DOORS, ItemTags.WOODEN_DOORS)); + + addBlockEntry(new BlockEntry( + BLOCK_LADDER, + (complexMaterial, settings) -> new BaseLadderBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(BlockTags.CLIMBABLE)); + + addBlockEntry(new BlockEntry( + BLOCK_SIGN, + (complexMaterial, settings) -> new BaseSignBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(BlockTags.SIGNS) + .setItemTags(ItemTags.SIGNS)); + } + + final protected void initStorage(FabricBlockSettings blockSettings, FabricItemSettings itemSettings) { + addBlockEntry(new BlockEntry( + BLOCK_CHEST, + (complexMaterial, settings) -> new BaseChestBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(CommonBlockTags.CHEST, CommonBlockTags.WOODEN_CHEST) + .setItemTags(CommonItemTags.CHEST, CommonItemTags.WOODEN_CHEST)); + + addBlockEntry(new BlockEntry( + BLOCK_BARREL, + (complexMaterial, settings) -> new BaseBarrelBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(CommonBlockTags.BARREL, CommonBlockTags.WOODEN_BARREL) + .setItemTags(CommonItemTags.BARREL, CommonItemTags.WOODEN_BARREL)); + } + + final protected void initDecorations(FabricBlockSettings blockSettings, FabricItemSettings itemSettings) { + addBlockEntry(new BlockEntry( + BLOCK_CRAFTING_TABLE, + (complexMaterial, settings) -> new BaseCraftingTableBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(CommonBlockTags.WORKBENCHES) + .setItemTags(CommonItemTags.WORKBENCHES)); + + addBlockEntry(new BlockEntry( + BLOCK_BOOKSHELF, + (complexMaterial, settings) -> new BaseBookshelfBlock(getBlock(BLOCK_PLANKS)) + ) + .setBlockTags(CommonBlockTags.BOOKSHELVES)); + + addBlockEntry(new BlockEntry( + BLOCK_COMPOSTER, + (complexMaterial, settings) -> new BaseComposterBlock(getBlock(BLOCK_PLANKS)) + )); + } + + @Override + protected void initFlammable(FlammableBlockRegistry registry) { + getBlocks().forEach(block -> { + registry.add(block, 5, 20); + }); + + registry.add(getBlock(BLOCK_LOG), 5, 5); + registry.add(getBlock(BLOCK_BARK), 5, 5); + registry.add(getBlock(BLOCK_STRIPPED_LOG), 5, 5); + registry.add(getBlock(BLOCK_STRIPPED_BARK), 5, 5); + } + + @Override + public void initDefaultRecipes() { + Block planks = getBlock(BLOCK_PLANKS); + addRecipeEntry(new RecipeEntry("planks", (material, config, id) -> { + Block log_stripped = getBlock(BLOCK_STRIPPED_LOG); + Block bark_stripped = getBlock(BLOCK_STRIPPED_BARK); + Block log = getBlock(BLOCK_LOG); + Block bark = getBlock(BLOCK_BARK); + GridRecipe.make(id, planks) + .checkConfig(config) + .setOutputCount(4) + .setList("#") + .addMaterial('#', log, bark, log_stripped, bark_stripped) + .setGroup(receipGroupPrefix + "_planks") + .build(); + })); + addRecipeEntry(new RecipeEntry("stairs", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_STAIRS)) + .checkConfig(config) + .setOutputCount(4) + .setShape("# ", "## ", "###") + .addMaterial('#', planks) + .setGroup(receipGroupPrefix + "_planks_stairs") + .build(); + })); + addRecipeEntry(new RecipeEntry("slab", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_SLAB)) + .checkConfig(config) + .setOutputCount(6) + .setShape("###") + .addMaterial('#', planks) + .setGroup(receipGroupPrefix + "_planks_slabs") + .build(); + })); + addRecipeEntry(new RecipeEntry("fence", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_FENCE)) + .checkConfig(config) + .setOutputCount(3) + .setShape("#I#", "#I#") + .addMaterial('#', planks) + .addMaterial('I', Items.STICK) + .setGroup(receipGroupPrefix + "_planks_fences") + .build(); + })); + addRecipeEntry(new RecipeEntry("gate", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_GATE)) + .checkConfig(config) + .setShape("I#I", "I#I") + .addMaterial('#', planks) + .addMaterial('I', Items.STICK) + .setGroup(receipGroupPrefix + "_planks_gates") + .build(); + })); + addRecipeEntry(new RecipeEntry("button", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_BUTTON)) + .checkConfig(config) + .setList("#") + .addMaterial('#', planks) + .setGroup(receipGroupPrefix + "_planks_buttons") + .build(); + })); + addRecipeEntry(new RecipeEntry("pressure_plate", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_PRESSURE_PLATE)) + .checkConfig(config) + .setShape("##") + .addMaterial('#', planks) + .setGroup(receipGroupPrefix + "_planks_plates") + .build(); + })); + addRecipeEntry(new RecipeEntry("trapdoor", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_TRAPDOOR)) + .checkConfig(config) + .setOutputCount(2) + .setShape("###", "###") + .addMaterial('#', planks) + .setGroup(receipGroupPrefix + "_trapdoors") + .build(); + })); + addRecipeEntry(new RecipeEntry("door", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_DOOR)) + .checkConfig(config) + .setOutputCount(3) + .setShape("##", "##", "##") + .addMaterial('#', planks) + .setGroup(receipGroupPrefix + "_doors") + .build(); + })); + addRecipeEntry(new RecipeEntry("crafting_table", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_CRAFTING_TABLE)) + .checkConfig(config) + .setShape("##", "##") + .addMaterial('#', planks) + .setGroup(receipGroupPrefix + "_tables") + .build(); + })); + addRecipeEntry(new RecipeEntry("ladder", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_LADDER)) + .checkConfig(config) + .setOutputCount(3) + .setShape("I I", "I#I", "I I") + .addMaterial('#', planks) + .addMaterial('I', Items.STICK) + .setGroup(receipGroupPrefix + "_ladders") + .build(); + })); + addRecipeEntry(new RecipeEntry("sign", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_SIGN)) + .checkConfig(config) + .setOutputCount(3) + .setShape("###", "###", " I ") + .addMaterial('#', planks) + .addMaterial('I', Items.STICK) + .setGroup(receipGroupPrefix + "_signs") + .build(); + })); + addRecipeEntry(new RecipeEntry("chest", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_CHEST)) + .checkConfig(config) + .setShape("###", "# #", "###") + .addMaterial('#', planks) + .setGroup(receipGroupPrefix + "_chests") + .build(); + })); + addRecipeEntry(new RecipeEntry("barrel", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_BARREL)) + .checkConfig(config) + .setShape("#S#", "# #", "#S#") + .addMaterial('#', planks) + .addMaterial('S', getBlock(BLOCK_SLAB)) + .setGroup(receipGroupPrefix + "_barrels") + .build(); + })); + addRecipeEntry(new RecipeEntry("bookshelf", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_BOOKSHELF)) + .checkConfig(config) + .setShape("###", "PPP", "###") + .addMaterial('#', planks) + .addMaterial('P', Items.BOOK) + .setGroup(receipGroupPrefix + "_bookshelves") + .build(); + })); + addRecipeEntry(new RecipeEntry("bark", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_BARK)) + .checkConfig(config) + .setShape("##", "##") + .addMaterial('#', getBlock(BLOCK_LOG)) + .setOutputCount(3) + .build(); + })); + addRecipeEntry(new RecipeEntry("log", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_LOG)) + .checkConfig(config) + .setShape("##", "##") + .addMaterial('#', getBlock(BLOCK_BARK)) + .setOutputCount(3) + .build(); + })); + addRecipeEntry(new RecipeEntry("stripped_bark", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_STRIPPED_BARK)) + .checkConfig(config) + .setShape("##", "##") + .addMaterial('#', getBlock(BLOCK_STRIPPED_LOG)) + .setOutputCount(3) + .build(); + })); + addRecipeEntry(new RecipeEntry("stripped_log", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_STRIPPED_LOG)) + .checkConfig(config) + .setShape("##", "##") + .addMaterial('#', getBlock(BLOCK_STRIPPED_BARK)) + .setOutputCount(3) + .build(); + })); + addRecipeEntry(new RecipeEntry("composter", (material, config, id) -> { + GridRecipe.make(id, getBlock(BLOCK_COMPOSTER)) + .checkConfig(config) + .setShape("# #", "# #", "###") + .addMaterial('#', getBlock(BLOCK_SLAB)) + .build(); + })); + } +} \ 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..6738e4ca --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/entry/BlockEntry.java @@ -0,0 +1,63 @@ +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.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.function.BiFunction; + +public class BlockEntry extends ComplexMaterialEntry { + final BiFunction initFunction; + final boolean hasItem; + + TagKey[] blockTags; + TagKey[] itemTags; + + public BlockEntry(String suffix, BiFunction initFunction) { + this(suffix, true, initFunction); + } + + public BlockEntry( + String suffix, + boolean hasItem, + BiFunction initFunction + ) { + super(suffix); + this.initFunction = initFunction; + this.hasItem = hasItem; + } + + public BlockEntry setBlockTags(TagKey... blockTags) { + this.blockTags = blockTags; + return this; + } + + public BlockEntry setItemTags(TagKey... itemTags) { + this.itemTags = itemTags; + return this; + } + + public Block init(ComplexMaterial material, FabricBlockSettings blockSettings, BlockRegistry registry) { + ResourceLocation location = getLocation(material.getModID(), material.getBaseName()); + Block block = initFunction.apply(material, blockSettings); + if (hasItem) { + registry.register(location, block); + if (itemTags != null) { + TagManager.ITEMS.add(block.asItem(), itemTags); + } + } else { + registry.registerBlockOnly(location, block); + } + 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..433e56bf --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/entry/ItemEntry.java @@ -0,0 +1,39 @@ +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 net.fabricmc.fabric.api.item.v1.FabricItemSettings; + +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, FabricItemSettings 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/RecipeEntry.java b/src/main/java/org/betterx/bclib/complexmaterials/entry/RecipeEntry.java new file mode 100644 index 00000000..8828f6af --- /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 org.betterx.bclib.config.PathConfig; +import org.betterx.bclib.interfaces.TriConsumer; + +import net.minecraft.resources.ResourceLocation; + +public class RecipeEntry extends ComplexMaterialEntry { + final TriConsumer initFunction; + + public RecipeEntry(String suffix, TriConsumer initFunction) { + super(suffix); + this.initFunction = initFunction; + } + + public void init(ComplexMaterial material, PathConfig recipeConfig) { + initFunction.accept(material, recipeConfig, getLocation(material.getModID(), material.getBaseName())); + } +} 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..30c185da --- /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", false); + 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/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..c93dcf73 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/ClientConfig.java @@ -0,0 +1,118 @@ +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 { + public static final ConfigToken SUPPRESS_EXPERIMENTAL_DIALOG = ConfigToken.Boolean( + false, + "suppressExperimentalDialogOnLoad", + "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( + false, + "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(topPadding = 12) + public static final ConfigToken DEBUG_HASHES = ConfigToken.Boolean( + false, + "debugHashes", + AutoSync.SYNC_CATEGORY + ); + + @ConfigUI(leftPadding = 8) + public static final ConfigToken CUSTOM_FOG_RENDERING = ConfigToken.Boolean( + true, + "customFogRendering", + "rendering" + ); + @ConfigUI(leftPadding = 8) + public static final ConfigToken NETHER_THICK_FOG = ConfigToken.Boolean( + true, + "netherThickFog", + "rendering" + ); + + public static final ConfigToken FOG_DENSITY = ConfigToken.Float( + 1.0f, + "FogDensity", + "rendering" + ); + + 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 boolean netherThickFog() { + return get(NETHER_THICK_FOG); + } + + public boolean renderCustomFog() { + return get(CUSTOM_FOG_RENDERING); + } + + public float fogDensity() { + return get(FOG_DENSITY); + } +} 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..56a9a465 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/Config.java @@ -0,0 +1,238 @@ +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..c4fc171e --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/ConfigKeeper.java @@ -0,0 +1,458 @@ +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) { + 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..2e7942c7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/ConfigUI.java @@ -0,0 +1,25 @@ +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; +} 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..5670759c --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/Configs.java @@ -0,0 +1,29 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.BCLib; + +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 PathConfig RECIPE_CONFIG = new PathConfig(BCLib.MOD_ID, "recipes"); + public static final BiomesConfig BIOMES_CONFIG = new BiomesConfig(); + + public static final String MAIN_PATCH_CATEGORY = "patches"; + + public static void save() { + MAIN_CONFIG.saveChanges(); + RECIPE_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..48091087 --- /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", false); + } + +} 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..685b2994 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/MainConfig.java @@ -0,0 +1,32 @@ +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) + ); + + public MainConfig() { + super(BCLib.MOD_ID, "main", true, true); + } + + public boolean applyPatches() { + return get(APPLY_PATCHES); + } + + public boolean repairBiomes() { + return get(REPAIR_BIOMES); + } +} 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..f8e35b0d --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/NamedPathConfig.java @@ -0,0 +1,280 @@ +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; + + @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(); + } else { + this.hidden = false; + this.leftPadding = 0; + topPadding = 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 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) { + //TODO: Check if we can make config fully Generic to avoid runtime type checks... + 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..92ae389d --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/ServerConfig.java @@ -0,0 +1,97 @@ +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/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/integration/ModIntegration.java b/src/main/java/org/betterx/bclib/integration/ModIntegration.java new file mode 100644 index 00000000..f5a42211 --- /dev/null +++ b/src/main/java/org/betterx/bclib/integration/ModIntegration.java @@ -0,0 +1,223 @@ +package org.betterx.bclib.integration; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.features.BCLFeature; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.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 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(Registry.PLACED_FEATURE_REGISTRY, getID(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); + } + + @Deprecated(forRemoval = true) + public BCLFeature getFeature(String featureID, String placedFeatureID, GenerationStep.Decoration featureStep) { + ResourceLocation id = getID(featureID); + Feature feature = Registry.FEATURE.get(id); + Holder featurePlaced = BuiltinRegistries.PLACED_FEATURE.getHolder(getFeatureKey(placedFeatureID)) + .orElse(null); + return new BCLFeature( + id, + feature, + featureStep, + featurePlaced.value().getFeatures().map(f -> f.config()).findFirst().orElse(null), + featurePlaced + ); + } + + @Deprecated(forRemoval = true) + 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 Holder getBiome(String name) { + return 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/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/integration/modmenu/ModMenuIntegration.java b/src/main/java/org/betterx/bclib/integration/modmenu/ModMenuIntegration.java new file mode 100644 index 00000000..4687d418 --- /dev/null +++ b/src/main/java/org/betterx/bclib/integration/modmenu/ModMenuIntegration.java @@ -0,0 +1,183 @@ +package org.betterx.bclib.integration.modmenu; + +import org.betterx.bclib.integration.modmenu.ModMenuIntegration.ModMenuScreenFactory; + +import net.minecraft.client.gui.screens.Screen; + +import com.google.common.collect.ImmutableMap; +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; + +@Deprecated +class ModMenuScreenFactoryImpl { + record ScreenFactoryInvocationHandler(ModMenuScreenFactory act) implements InvocationHandler { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return switch (method.getName()) { + case "toString" -> ""; + case "equals" -> false; + case "hashCode" -> 0; + default -> act.create((Screen) args[0]); + }; + } + } + + public static ModMenuScreenFactory create(ModMenuScreenFactory act) { + Class iConfigScreenFactory = null; + try { + iConfigScreenFactory = Class.forName("com.terraformersmc.modmenu.api.ConfigScreenFactory"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + return null; + } + + Object o = Proxy.newProxyInstance( + ModMenuIntegration.class.getClassLoader(), + new Class[]{iConfigScreenFactory, ModMenuScreenFactory.class}, + new ScreenFactoryInvocationHandler(act) + ); + + return (ModMenuScreenFactory) o; + } +} + +@Deprecated +/** + * 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, and the correct ModMenu-EntryPoint is registered in your fabric.mod.json + * the screen will show up. + *

+ * You only need to subclass this class, and initialize a static Field of Type {@link ModMenuApi} using + * the {@link #createEntrypoint(ModMenuIntegration)}-Method. + *

+ * Example: + *

{@code public class ModMenu extends ModMenuIntegration {
+ *	 public static final ModMenuApiMarker entrypointObject = createEntrypoint(new EntryPoint());
+ *
+ * 		public EntryPoint() {
+ * 			super(GridScreen::new);
+ *      }
+ * }}
+ * You'd also need to add the ModMenu-Entrypoint to your fabric.mod.json: + *
"entrypoints": {
+ * 		...
+ *	 "modmenu": [ "your.mod.ModMenu::entrypointObject" ]
+ * }
+ */ +public abstract class ModMenuIntegration { + /** + * Creates a new EntryPoint Object that is accepted by ModMenu + * + * @param target The delegate Object that will receive calls from ModMenu + * @return A Proxy that conforms to the ModMenu spec + */ + public static ModMenuApi createEntrypoint(ModMenuIntegration target) { + Class iModMenuAPI; + //Class iModMenuAPIMarker = null; + try { + iModMenuAPI = Class.forName("com.terraformersmc.modmenu.api.ModMenuApi"); + //iModMenuAPIMarker = Class.forName("com.terraformersmc.modmenu.util.ModMenuApiMarker"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + return null; + } + + Object o = Proxy.newProxyInstance( + ModMenuIntegration.class.getClassLoader(), + new Class[]{iModMenuAPI}, + new BCLibModMenuInvocationHandler(target) + ); + + return (ModMenuApi) o; + } + + /** + * The factory passed to {@link #ModMenuIntegration(ModMenuScreenFactory)} + */ + protected final ModMenuScreenFactory screenFactory; + + /** + * Create a new ModMenu delegate + * + * @param screenFactory A Factory. The Factory receives the currently visible {@code parent}-Screen + * and must return a new Screen Object. + */ + public ModMenuIntegration(ModMenuScreenFactory screenFactory) { + this.screenFactory = screenFactory; + } + + /** + * A Helper class to make a BCLib-Factory conform to the ModMenu-Factory Interface. + * + * @param factory A BCLib-Type Factory, ie. {@code GridScreen::new } + * @return A ModMenu Factory for a Screen + */ + final protected ModMenuScreenFactory createFactory(ModMenuScreenFactory factory) { + return ModMenuScreenFactoryImpl.create(factory); + } + + /** + * Used to construct a new config screen instance when your mod's + * configuration button is selected on the mod menu screen. The + * screen instance parameter is the active mod menu screen. + * (Text copied from ModMenu) + * + * @return A factory for constructing config screen instances. + */ + public ModMenuScreenFactory getModConfigScreenFactory() { + return createFactory(screenFactory); + } + + /** + * Used to provide config screen factories for other mods. This takes second + * priority to a mod's own config screen factory provider. For example, if + * mod `xyz` supplies a config screen factory, mod `abc` providing a config + * screen to `xyz` will be pointless, as the one provided by `xyz` will be + * used. + *

+ * This method is NOT meant to be used to add a config screen factory to + * your own mod. + * (Text copied from ModMenu) + * + * @return a map of mod ids to screen factories. + */ + public Map getProvidedConfigScreenFactories() { + return ImmutableMap.of(); + } + + @Override + public String toString() { + return super.toString(); + } + + /** + * A Factory Interface for ModMenu-Screens + *

+ * The Interface matches {@code com.terraformersmc.modmenu.api.ConfigScreenFactory} + */ + @FunctionalInterface + public interface ModMenuScreenFactory extends ConfigScreenFactory { + } + + record BCLibModMenuInvocationHandler(ModMenuIntegration target) implements InvocationHandler { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return switch (method.getName()) { + case "getModConfigScreenFactory" -> target.getModConfigScreenFactory(); + case "getProvidedConfigScreenFactories" -> target.getProvidedConfigScreenFactories(); + case "toString" -> target.toString(); + case "equals" -> target.equals(args[0]); + case "hashCode" -> target.hashCode(); + default -> null; + }; + } + } + +} 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..b17116f2 --- /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 be_updateCurrentRecipe(AnvilRecipe recipe); + + AnvilRecipe be_getCurrentRecipe(); + + List be_getRecipes(); + + default void be_nextRecipe() { + List recipes = be_getRecipes(); + if (recipes.size() < 2) return; + AnvilRecipe current = be_getCurrentRecipe(); + int i = recipes.indexOf(current) + 1; + if (i >= recipes.size()) { + i = 0; + } + be_updateCurrentRecipe(recipes.get(i)); + } + + default void be_previousRecipe() { + List recipes = be_getRecipes(); + if (recipes.size() < 2) return; + AnvilRecipe current = be_getCurrentRecipe(); + int i = recipes.indexOf(current) - 1; + if (i <= 0) { + i = recipes.size() - 1; + } + be_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/BiomeChunk.java b/src/main/java/org/betterx/bclib/interfaces/BiomeChunk.java new file mode 100644 index 00000000..66752937 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/BiomeChunk.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.api.v2.generator.BiomePicker; + +public interface BiomeChunk { + void setBiome(int x, int z, BiomePicker.ActualBiome biome); + BiomePicker.ActualBiome getBiome(int x, int z); + int getSide(); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/BiomeMap.java b/src/main/java/org/betterx/bclib/interfaces/BiomeMap.java new file mode 100644 index 00000000..2dd82747 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/BiomeMap.java @@ -0,0 +1,10 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.api.v2.generator.BiomePicker; + +public interface BiomeMap { + void setChunkProcessor(TriConsumer processor); + BiomeChunk getChunk(int cx, int cz, boolean update); + BiomePicker.ActualBiome getBiome(double x, double y, double z); + void clearCache(); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/BiomeSetter.java b/src/main/java/org/betterx/bclib/interfaces/BiomeSetter.java new file mode 100644 index 00000000..eee82afa --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/BiomeSetter.java @@ -0,0 +1,8 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.biome.Biome; + +public interface BiomeSetter { + void bclib_setBiome(Biome biome, BlockPos pos); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/BiomeSourceAccessor.java b/src/main/java/org/betterx/bclib/interfaces/BiomeSourceAccessor.java new file mode 100644 index 00000000..837af156 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/BiomeSourceAccessor.java @@ -0,0 +1,5 @@ +package org.betterx.bclib.interfaces; + +public interface BiomeSourceAccessor { + void bclRebuildFeatures(); +} 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/ChunkGeneratorAccessor.java b/src/main/java/org/betterx/bclib/interfaces/ChunkGeneratorAccessor.java new file mode 100644 index 00000000..07aea210 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/ChunkGeneratorAccessor.java @@ -0,0 +1,8 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.core.Registry; +import net.minecraft.world.level.levelgen.structure.StructureSet; + +public interface ChunkGeneratorAccessor { + Registry bclib_getStructureSetsRegistry(); +} 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..ccd59262 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/CustomItemProvider.java @@ -0,0 +1,15 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.BlockItem; + +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; + +public interface CustomItemProvider { + /** + * Used to replace default Block Item when block is registered. + * + * @return {@link BlockItem} + */ + BlockItem getCustomItem(ResourceLocation blockID, FabricItemSettings settings); +} 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/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/NoiseGeneratorSettingsProvider.java b/src/main/java/org/betterx/bclib/interfaces/NoiseGeneratorSettingsProvider.java new file mode 100644 index 00000000..24dbb70f --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/NoiseGeneratorSettingsProvider.java @@ -0,0 +1,13 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.synth.NormalNoise; + +public interface NoiseGeneratorSettingsProvider { + NoiseGeneratorSettings bclib_getNoiseGeneratorSettings(); + Holder bclib_getNoiseGeneratorSettingHolders(); + + Registry bclib_getNoises(); +} 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..fb141b51 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/NumericProvider.java @@ -0,0 +1,27 @@ +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(), + null + ); + 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/SettingsExtender.java b/src/main/java/org/betterx/bclib/interfaces/SettingsExtender.java new file mode 100644 index 00000000..357d106a --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SettingsExtender.java @@ -0,0 +1,8 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.world.level.block.state.BlockBehaviour; + +@FunctionalInterface +public interface SettingsExtender { + BlockBehaviour.Properties amend(BlockBehaviour.Properties props); +} 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..b49a7a3a --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SpawnRule.java @@ -0,0 +1,20 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.core.BlockPos; +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 java.util.Random; + +@FunctionalInterface +public interface SpawnRule { + boolean canSpawn( + EntityType type, + LevelAccessor world, + MobSpawnType spawnReason, + BlockPos pos, + Random 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..c2ce3ce8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SurfaceMaterialProvider.java @@ -0,0 +1,14 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.api.v2.levelgen.surface.SurfaceRuleBuilder; + +import net.minecraft.world.level.block.state.BlockState; + +public interface SurfaceMaterialProvider { + BlockState getTopMaterial(); + BlockState getUnderMaterial(); + BlockState getAltTopMaterial(); + + boolean generateFloorRule(); + SurfaceRuleBuilder surface(); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/SurfaceProvider.java b/src/main/java/org/betterx/bclib/interfaces/SurfaceProvider.java new file mode 100644 index 00000000..60283118 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SurfaceProvider.java @@ -0,0 +1,11 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.state.BlockState; + +public interface SurfaceProvider { + BlockState bclib_getSurface(BlockPos pos, Holder biome, ServerLevel level); +} 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..04ec5562 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SurvivesOnSpecialGround.java @@ -0,0 +1,66 @@ +package org.betterx.bclib.interfaces; + +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 { + String getSurvivableBlocksString(); + + @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(List list, String description) { + final int MAX_LINES = 7; + List lines = splitLines(description); + if (lines.size() == 1) { + list.add(Component.translatable("tooltip.bclib.place_on", lines.get(0)).withStyle(ChatFormatting.GREEN)); + } else if (lines.size() > 1) { + list.add(Component.translatable("tooltip.bclib.place_on", "").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)); + } + } + } + + boolean isSurvivable(BlockState state); + + 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..11820474 --- /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.Registry; +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 -> Registry.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/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/TheEndBiomesAccessor.java b/src/main/java/org/betterx/bclib/interfaces/TheEndBiomesAccessor.java new file mode 100644 index 00000000..aa39ebb6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/TheEndBiomesAccessor.java @@ -0,0 +1,23 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; + +public interface TheEndBiomesAccessor { + boolean bcl_canGenerateAsEndBiome(ResourceKey key); + + boolean bcl_canGenerateAsEndMidlandBiome(ResourceKey key); + + boolean bcl_canGenerateAsEndBarrensBiome(ResourceKey key); + + default boolean bcl_isNonVanillaAndCanGenerateInEnd(ResourceKey key) { + return !"minecraft".equals(key.location().getNamespace()) && + bcl_canGenerateInEnd(key); + } + default boolean bcl_canGenerateInEnd(ResourceKey key) { + return bcl_canGenerateAsEndBarrensBiome(key) || + bcl_canGenerateAsEndMidlandBiome(key) || + bcl_canGenerateAsEndBiome(key) + ; + } +} diff --git a/src/main/java/org/betterx/bclib/interfaces/TileEntityRenderProvider.java b/src/main/java/org/betterx/bclib/interfaces/TileEntityRenderProvider.java new file mode 100644 index 00000000..732d8f4b --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/TileEntityRenderProvider.java @@ -0,0 +1,5 @@ +package org.betterx.bclib.interfaces; + +public interface TileEntityRenderProvider { + +} 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..7a110d3d --- /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 { +} 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..6826e308 --- /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 { +} 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..6db28739 --- /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 { +} 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..9a9017a9 --- /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 { +} 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..d26fba7b --- /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 { +} 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..d9ae344d --- /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 { +} 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..c38f3767 --- /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 { +} 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..03b7401f --- /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.Registry; +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 = Registry.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..625904c7 --- /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, 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/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/ru/bclib/items/BaseBucketItem.java b/src/main/java/org/betterx/bclib/items/BaseBucketItem.java similarity index 55% rename from src/main/java/ru/bclib/items/BaseBucketItem.java rename to src/main/java/org/betterx/bclib/items/BaseBucketItem.java index fae47053..0b2378b1 100644 --- a/src/main/java/ru/bclib/items/BaseBucketItem.java +++ b/src/main/java/org/betterx/bclib/items/BaseBucketItem.java @@ -1,14 +1,16 @@ -package ru.bclib.items; +package org.betterx.bclib.items; + +import org.betterx.bclib.interfaces.ItemModelProvider; -import net.fabricmc.fabric.api.item.v1.FabricItemSettings; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.MobBucketItem; import net.minecraft.world.level.material.Fluids; -import ru.bclib.client.models.ItemModelProvider; + +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; public class BaseBucketItem extends MobBucketItem implements ItemModelProvider { - public BaseBucketItem(EntityType type, FabricItemSettings settings) { - super(type, Fluids.WATER, SoundEvents.BUCKET_EMPTY_FISH, settings.stacksTo(1)); - } + public BaseBucketItem(EntityType type, FabricItemSettings 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..fecf586c --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/BaseDiscItem.java @@ -0,0 +1,17 @@ +package org.betterx.bclib.items; + +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.item.RecordItem; + +public class BaseDiscItem extends RecordItem implements ItemModelProvider { + @Deprecated(forRemoval = true) + public BaseDiscItem(int comparatorOutput, SoundEvent sound, Properties settings) { + this(comparatorOutput, sound, settings, 30); + } + + public BaseDiscItem(int comparatorOutput, SoundEvent sound, Properties settings, int lengthInSeconds) { + super(comparatorOutput, sound, settings); + } +} 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/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/elytra/BCLElytraItem.java b/src/main/java/org/betterx/bclib/items/elytra/BCLElytraItem.java new file mode 100644 index 00000000..b60a171a --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/elytra/BCLElytraItem.java @@ -0,0 +1,26 @@ +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) { + 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..d8b86a17 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/elytra/BCLElytraUtils.java @@ -0,0 +1,18 @@ +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 = null; +} 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..ee31ae79 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/AnvilScreenMixin.java @@ -0,0 +1,94 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.interfaces.AnvilScreenHandlerExtended; + +import com.mojang.blaze3d.vertex.PoseStack; +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.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.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 java.util.List; + +@Mixin(AnvilScreen.class) +public class AnvilScreenMixin extends ItemCombinerScreen { + + @Shadow + private EditBox name; + + private final List be_buttons = Lists.newArrayList(); + + public AnvilScreenMixin(AnvilMenu handler, Inventory playerInventory, Component title, ResourceLocation texture) { + super(handler, playerInventory, title, texture); + } + + @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(new Button(x + 8, y + 45, 15, 20, Component.literal("<"), b -> be_previousRecipe())); + be_buttons.add(new Button(x + 154, y + 45, 15, 20, Component.literal(">"), b -> be_nextRecipe())); + } + + @Inject(method = "renderFg", at = @At("TAIL")) + protected void be_renderForeground(PoseStack matrices, int mouseX, int mouseY, float delta, CallbackInfo info) { + be_buttons.forEach(button -> { + button.render(matrices, 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.be_getCurrentRecipe() != null) { + if (anvilHandler.be_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(); + } + + @Override + public boolean 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/BlockMixin.java b/src/main/java/org/betterx/bclib/mixin/client/BlockMixin.java new file mode 100644 index 00000000..5d85f93b --- /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(list, surv.getSurvivableBlocksString()); + } + } +} 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/GameMixin.java b/src/main/java/org/betterx/bclib/mixin/client/GameMixin.java new file mode 100644 index 00000000..bf4ba667 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/GameMixin.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI; + +import net.minecraft.client.Game; + +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(Game.class) +public class GameMixin { + + @Inject(method = "onStartGameSession", at = @At("TAIL")) + public void bclib_onStart(CallbackInfo ci) { + DataExchangeAPI.sendOnEnter(); + } +} 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..90679b0e --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/MinecraftMixin.java @@ -0,0 +1,37 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.interfaces.CustomColorProvider; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.color.block.BlockColors; +import net.minecraft.client.color.item.ItemColors; +import net.minecraft.client.main.GameConfig; +import net.minecraft.core.Registry; + +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(Minecraft.class) +public abstract class MinecraftMixin { + @Final + @Shadow + private BlockColors blockColors; + + @Final + @Shadow + private ItemColors itemColors; + + @Inject(method = "*", at = @At("TAIL")) + private void bclib_onMCInit(GameConfig args, CallbackInfo info) { + Registry.BLOCK.forEach(block -> { + if (block instanceof CustomColorProvider provider) { + blockColors.register(provider.getProvider(), block); + itemColors.register(provider.getItemProvider(), block.asItem()); + } + }); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/ModelBakeryMixin.java b/src/main/java/org/betterx/bclib/mixin/client/ModelBakeryMixin.java new file mode 100644 index 00000000..4d0ae211 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/ModelBakeryMixin.java @@ -0,0 +1,41 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.api.v2.ModIntegrationAPI; +import org.betterx.bclib.client.models.CustomModelBakery; + +import net.minecraft.client.color.block.BlockColors; +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.profiling.ProfilerFiller; + +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 java.util.Map; + +@Mixin(ModelBakery.class) +public abstract class ModelBakeryMixin { + @Final + @Shadow + private Map unbakedCache; + + @Inject(method = "*", at = @At("TAIL")) + private void bclib_findEmissiveModels( + ResourceManager resourceManager, + BlockColors blockColors, + ProfilerFiller profiler, + int mipmap, + CallbackInfo info + ) { + //CustomModelBakery.setModelsLoaded(false); + if (ModIntegrationAPI.hasCanvas()) { + CustomModelBakery.loadEmissiveModels(unbakedCache); + } + } +} 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..89b0feb7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/ModelManagerMixin.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.client.BCLibClient; + +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.ModelManager; +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; + +@Mixin(ModelManager.class) +public class ModelManagerMixin { + @Inject(method = "prepare", at = @At("HEAD")) + private void bclib_loadCustomModels( + ResourceManager resourceManager, + ProfilerFiller profilerFiller, + CallbackInfoReturnable info + ) { + BCLibClient.modelBakery.loadCustomModels(resourceManager); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/MultiPackResourceManagerMixin.java b/src/main/java/org/betterx/bclib/mixin/client/MultiPackResourceManagerMixin.java new file mode 100644 index 00000000..728a02e7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/MultiPackResourceManagerMixin.java @@ -0,0 +1,58 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.ModIntegrationAPI; +import org.betterx.bclib.client.render.EmissiveTextureInfo; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.FallbackResourceManager; +import net.minecraft.server.packs.resources.MultiPackResourceManager; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; + +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.io.IOException; +import java.util.Map; + +@Mixin(MultiPackResourceManager.class) +public class MultiPackResourceManagerMixin { + @Final + @Shadow + private Map namespacedManagers; + + private final ResourceLocation bclib_alphaEmissionMaterial = BCLib.makeID("materialmaps/block/alpha_emission.json"); + + @Inject(method = "getResource", at = @At("HEAD"), cancellable = true) + private void bclib_getResource( + ResourceLocation resourceLocation, + CallbackInfoReturnable info + ) throws IOException { + if (!ModIntegrationAPI.hasCanvas()) { + return; + } + if (!resourceLocation.getPath().startsWith("materialmaps")) { + return; + } + if (!resourceLocation.getPath().contains("/block/")) { + return; + } + + String name = resourceLocation.getPath().replace("materialmaps/block/", "").replace(".json", ""); + ResourceLocation blockID = new ResourceLocation(resourceLocation.getNamespace(), name); + + if (!EmissiveTextureInfo.isEmissiveBlock(blockID)) { + return; + } + + ResourceManager resourceManager = this.namespacedManagers.get(resourceLocation.getNamespace()); + if (resourceManager != null && resourceManager.getResource(resourceLocation).isEmpty()) { + info.setReturnValue(resourceManager.getResource(bclib_alphaEmissionMaterial).get()); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/PresetEditorMixin.java b/src/main/java/org/betterx/bclib/mixin/client/PresetEditorMixin.java new file mode 100644 index 00000000..694741e8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/PresetEditorMixin.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.mixin.client; + +import net.minecraft.client.gui.screens.worldselection.PresetEditor; + +import com.google.common.collect.Maps; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.Map; + + +@Mixin(PresetEditor.class) +interface PresetEditorMixin { + //Make Sure the PresetEditor.EDITORS Field is a mutable List. Allows us to add new Custom WorldPreset UIs in + //WorldPresetsUI + @Redirect(method = "", at = @At(value = "INVOKE", target = "Ljava/util/Map;of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;")) + private static Map bcl_foo(K k1, V v1, K k2, V v2) { + Map a = Maps.newHashMap(); + a.put(k1, v1); + a.put(k2, v2); + return a; + } + +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/mixin/client/SignEditScreenMixin.java b/src/main/java/org/betterx/bclib/mixin/client/SignEditScreenMixin.java new file mode 100644 index 00000000..2a8e10a1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/SignEditScreenMixin.java @@ -0,0 +1,80 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.blocks.BaseSignBlock; +import org.betterx.bclib.client.render.BaseSignBlockEntityRenderer; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.SignEditScreen; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.blockentity.SignRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.SignBlockEntity; +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.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(SignEditScreen.class) +public abstract class SignEditScreenMixin extends Screen { + @Shadow + @Final + private SignBlockEntity sign; + @Shadow + private SignRenderer.SignModel signModel; + @Unique + private boolean bclib_renderStick; + @Unique + private boolean bclib_isSign; + + protected SignEditScreenMixin(Component component) { + super(component); + } + + @Inject(method = "render(Lcom/mojang/blaze3d/vertex/PoseStack;IIF)V", locals = LocalCapture.CAPTURE_FAILSOFT, at = @At(ordinal = 1, + value = "INVOKE", + target = "Lcom/mojang/blaze3d/vertex/PoseStack;pushPose()V", + shift = Shift.BEFORE + )) + private void bclib_checkOffset( + PoseStack poseStack, + int i, + int j, + float f, + CallbackInfo ci, + float g, + BlockState blockState, + boolean bl, + boolean bl2, + float h + ) { + bclib_isSign = blockState.getBlock() instanceof BaseSignBlock; + if (bclib_isSign) { + bclib_renderStick = blockState.getValue(BaseSignBlock.FLOOR); + if (bclib_renderStick) { + poseStack.translate(0.0, 0.3125, 0.0); + } + } + } + + @ModifyArg(method = "render(Lcom/mojang/blaze3d/vertex/PoseStack;IIF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/geom/ModelPart;render(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;II)V"), index = 1) + private VertexConsumer bclib_signRender(VertexConsumer consumer) { + if (bclib_isSign) { + signModel.stick.visible = bclib_renderStick; + Block block = sign.getBlockState().getBlock(); + MultiBufferSource.BufferSource bufferSource = this.minecraft.renderBuffers().bufferSource(); + return BaseSignBlockEntityRenderer.getConsumer(bufferSource, block); + } + return consumer; + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/TextureAtlasMixin.java b/src/main/java/org/betterx/bclib/mixin/client/TextureAtlasMixin.java new file mode 100644 index 00000000..3ef912b8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/TextureAtlasMixin.java @@ -0,0 +1,114 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.client.render.EmissiveTextureInfo; + +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; + +import net.fabricmc.fabric.impl.client.texture.FabricSprite; +import net.fabricmc.loader.api.FabricLoader; + +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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.io.IOException; +import java.util.Optional; + +@Mixin(TextureAtlas.class) +public class TextureAtlasMixin { + private static final int EMISSIVE_ALPHA = 254 << 24; + private boolean bclib_modifyAtlas; + + @Inject(method = "*", at = @At("TAIL")) + private void bclib_onAtlasInit(ResourceLocation resourceLocation, CallbackInfo info) { + boolean hasOptifine = FabricLoader.getInstance().isModLoaded("optifabric"); + bclib_modifyAtlas = !hasOptifine && resourceLocation.toString().equals("minecraft:textures/atlas/blocks.png"); + if (bclib_modifyAtlas) { + EmissiveTextureInfo.clear(); + } + } + + @Inject(method = "load(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite$Info;IIIII)Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;", at = @At("HEAD"), cancellable = true) + private void bclib_loadSprite( + ResourceManager resourceManager, + TextureAtlasSprite.Info spriteInfo, + int atlasWidth, + int atlasHeight, + int maxLevel, + int posX, + int posY, + CallbackInfoReturnable info + ) { + if (!bclib_modifyAtlas) { + return; + } + + ResourceLocation location = spriteInfo.name(); + if (!location.getPath().startsWith("block")) { + return; + } + + ResourceLocation emissiveLocation = new ResourceLocation( + location.getNamespace(), + "textures/" + location.getPath() + "_e.png" + ); + Optional emissiveRes = resourceManager.getResource(emissiveLocation); + if (emissiveRes.isPresent()) { + NativeImage sprite = null; + NativeImage emission = null; + try { + ResourceLocation spriteLocation = new ResourceLocation( + location.getNamespace(), + "textures/" + location.getPath() + ".png" + ); + Resource resource = resourceManager.getResource(spriteLocation).orElse(null); + sprite = NativeImage.read(resource.open()); + + resource = emissiveRes.get(); + emission = NativeImage.read(resource.open()); + } catch (IOException e) { + BCLib.LOGGER.warning(e.getMessage()); + } + if (sprite != null && emission != null) { + int width = Math.min(sprite.getWidth(), emission.getWidth()); + int height = Math.min(sprite.getHeight(), emission.getHeight()); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int argb = emission.getPixelRGBA(x, y); + int alpha = (argb >> 24) & 255; + if (alpha > 127) { + int r = (argb >> 16) & 255; + int g = (argb >> 8) & 255; + int b = argb & 255; + if (r > 0 || g > 0 || b > 0) { + argb = (argb & 0x00FFFFFF) | EMISSIVE_ALPHA; + sprite.setPixelRGBA(x, y, argb); + } + } + } + } + TextureAtlas self = (TextureAtlas) (Object) this; + FabricSprite result = new FabricSprite( + self, + spriteInfo, + maxLevel, + atlasWidth, + atlasHeight, + posX, + posY, + sprite + ); + EmissiveTextureInfo.addTexture(location); + info.setReturnValue(result); + } + } + } +} 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..5976432d --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/AnvilMenuMixin.java @@ -0,0 +1,189 @@ +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 be_recipes = Collections.emptyList(); + private AnvilRecipe be_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(); + if (anvilBlock instanceof LeveledAnvilBlock) { + return ((LeveledAnvilBlock) anvilBlock).getCraftingLevel(); + } + return 1; + }, 1); + anvilLevel.set(level); + } else { + anvilLevel.set(1); + } + } + + @Shadow + public abstract void createResult(); + + @Inject(method = "mayPickup", at = @At("HEAD"), cancellable = true) + protected void be_canTakeOutput(Player player, boolean present, CallbackInfoReturnable info) { + if (be_currentRecipe != null) { + info.setReturnValue(be_currentRecipe.checkHammerDurability(inputSlots, player)); + } + } + + @Inject(method = "method_24922", at = @At(value = "HEAD"), cancellable = true) + private static void bclib_onDamageAnvil(Player player, Level level, BlockPos blockPos, CallbackInfo ci) { + BlockState blockState = level.getBlockState(blockPos); + if (blockState.getBlock() instanceof BaseAnvilBlock anvil) { + BlockState damaged = anvil.damageAnvilUse(blockState, player.getRandom()); + bcl_destroyWhenNull(level, blockPos, damaged); + ci.cancel(); + } + } + + private static void bcl_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, 2); + level.levelEvent(LevelEvent.SOUND_ANVIL_USED, blockPos, 0); + } + } + + @Inject(method = "onTake", at = @At("HEAD"), cancellable = true) + protected void bclib_onTakeAnvilOutput(Player player, ItemStack stack, CallbackInfo info) { + if (be_currentRecipe != null) { + final int ingredientSlot = AnvilRecipe.getIngredientSlot(inputSlots); + + inputSlots.getItem(ingredientSlot).shrink(be_currentRecipe.getInputCount()); + stack = be_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) { + final BaseAnvilBlock anvil = (BaseAnvilBlock) anvilBlock; + if (!player.getAbilities().instabuild && anvilState.is(BlockTags.ANVIL) && player.getRandom() + .nextDouble() < 0.1) { + BlockState damagedState = anvil.damageAnvilUse(anvilState, player.getRandom()); + bcl_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 be_updateOutput(CallbackInfo info) { + RecipeManager recipeManager = this.player.level.getRecipeManager(); + be_recipes = recipeManager.getRecipesFor(AnvilRecipe.TYPE, inputSlots, player.level); + if (be_recipes.size() > 0) { + int anvilLevel = this.anvilLevel.get(); + be_recipes = be_recipes.stream() + .filter(recipe -> anvilLevel >= recipe.getAnvilLevel()) + .collect(Collectors.toList()); + if (be_recipes.size() > 0) { + if (be_currentRecipe == null || !be_recipes.contains(be_currentRecipe)) { + be_currentRecipe = be_recipes.get(0); + } + be_updateResult(); + info.cancel(); + } else { + be_currentRecipe = null; + } + } + } + + @Inject(method = "setItemName", at = @At("HEAD"), cancellable = true) + public void be_setNewItemName(String string, CallbackInfo info) { + if (be_currentRecipe != null) { + info.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 be_updateResult() { + if (be_currentRecipe == null) return; + resultSlots.setItem(0, be_currentRecipe.assemble(inputSlots)); + broadcastChanges(); + } + + @Override + public void be_updateCurrentRecipe(AnvilRecipe recipe) { + this.be_currentRecipe = recipe; + be_updateResult(); + } + + @Override + public AnvilRecipe be_getCurrentRecipe() { + return be_currentRecipe; + } + + @Override + public List be_getRecipes() { + return be_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/BiomeSourceMixin.java b/src/main/java/org/betterx/bclib/mixin/common/BiomeSourceMixin.java new file mode 100644 index 00000000..f1580e32 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/BiomeSourceMixin.java @@ -0,0 +1,39 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.interfaces.BiomeSourceAccessor; + +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; + +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 java.util.List; +import java.util.Set; + +@Mixin(BiomeSource.class) +public abstract class BiomeSourceMixin implements BiomeSourceAccessor { + + @Shadow + public abstract Set possibleBiomes(); + + public void bclRebuildFeatures() { + //Feature sorting is now a task in ChunkGenerator + BCLib.LOGGER.info("Rebuilding features in BiomeSource " + this); + //featuresPerStep = Suppliers.memoize(() -> FeatureSorter.buildFeaturesPerStep(this.possibleBiomes().stream().toList(), true)); + } + + @Inject(method = "(Ljava/util/List;)V", at = @At("TAIL")) + public void bcl_init(List list, CallbackInfo ci) { +// System.out.println("new BiomeSource (" + Integer.toHexString(hashCode()) + ", biomes=" + possibleBiomes().size() + ")"); +// if (possibleBiomes().size() == 27) { +// System.out.println("Nether????"); +// } else if (possibleBiomes().size() == 2) { +// System.out.println("Datapack Nether???"); +// } + } +} 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..9ed3f337 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/BoneMealItemMixin.java @@ -0,0 +1,169 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.api.v2.BonemealAPI; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.MHelper; + +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.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +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.BiConsumer; + +@Mixin(BoneMealItem.class) +public class BoneMealItemMixin { + @Unique + 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()) { + if (BonemealAPI.isTerrain(world.getBlockState(blockPos).getBlock())) { + boolean consume = false; + if (BonemealAPI.isSpreadableTerrain(world.getBlockState(blockPos).getBlock())) { + BlockState terrain = bclib_getSpreadable(world, blockPos); + if (terrain != null) { + BlocksHelper.setWithoutUpdate(world, blockPos, terrain); + consume = true; + } + } else { + BlockState stateAbove = world.getBlockState(blockPos.above()); + if (!stateAbove.getFluidState().isEmpty()) { + if (stateAbove.is(Blocks.WATER)) { + consume = bclib_growWaterGrass(world, blockPos); + } + } else if (stateAbove.isAir()) { + 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(); + } + } + } + } + + @Unique + private boolean bclib_growLandGrass(Level level, BlockPos pos) { + 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); + 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 (level.isEmptyBlock(BCLIB_BLOCK_POS) && !level.isEmptyBlock(down)) { + BiConsumer grass = bclib_getLandGrassState(level, down); + if (grass != null) { + grass.accept(level, BCLIB_BLOCK_POS); + result = true; + } + break; + } + } + } + return result; + } + + @Unique + private boolean bclib_growWaterGrass(Level level, BlockPos pos) { + 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); + 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(level.getBlockState(BCLIB_BLOCK_POS)) && !BlocksHelper.isFluid(level.getBlockState( + down))) { + BiConsumer grass = bclib_getWaterGrassState(level, down); + if (grass != null) { + grass.accept(level, BCLIB_BLOCK_POS); + result = true; + } + break; + } + } + } + return result; + } + + @Unique + private BiConsumer bclib_getLandGrassState(Level level, BlockPos pos) { + BlockState state = level.getBlockState(pos); + return BonemealAPI.getLandGrass(BiomeAPI.getBiomeID(level.getBiome(pos)), state.getBlock(), level.getRandom()); + } + + @Unique + private BiConsumer bclib_getWaterGrassState(Level level, BlockPos pos) { + BlockState state = level.getBlockState(pos); + return BonemealAPI.getWaterGrass(BiomeAPI.getBiomeID(level.getBiome(pos)), state.getBlock(), level.getRandom()); + } + + @Unique + private BlockState bclib_getSpreadable(Level level, BlockPos pos) { + Vec3i[] offsets = MHelper.getOffsets(level.getRandom()); + BlockState center = level.getBlockState(pos); + for (Vec3i dir : offsets) { + BlockPos p = pos.offset(dir); + BlockState state = level.getBlockState(p); + Block terrain = BonemealAPI.getSpreadable(state.getBlock()); + if (center.is(terrain)) { + if (bclib_haveSameProperties(state, center)) { + for (Property property : center.getProperties()) { + state = state.setValue(property, center.getValue(property)); + } + } + return state; + } + } + return null; + } + + @Unique + private boolean bclib_haveSameProperties(BlockState state1, BlockState state2) { + Property[] properties1 = state1.getProperties().toArray(new Property[0]); + Property[] properties2 = state2.getProperties().toArray(new Property[0]); + if (properties1.length != properties2.length) { + return false; + } + for (int i = 0; i < properties1.length; i++) { + String name1 = properties1[i].getName(); + String name2 = properties2[i].getName(); + if (!name1.equals(name2)) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/mixin/common/BuiltinRegistriesMixin.java b/src/main/java/org/betterx/bclib/mixin/common/BuiltinRegistriesMixin.java new file mode 100644 index 00000000..b5733252 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/BuiltinRegistriesMixin.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; + +import com.mojang.serialization.Lifecycle; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.core.WritableRegistry; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.resources.ResourceKey; + +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(BuiltinRegistries.class) +public abstract class BuiltinRegistriesMixin { + @Shadow + protected static > R internalRegister( + ResourceKey> resourceKey, + R writableRegistry, + BuiltinRegistries.RegistryBootstrap registryBootstrap, + Lifecycle lifecycle + ) { + throw new RuntimeException("Shadowed Call"); + } + + //this needs to be added BEFORE the WORLD_PRESET-Registry. Otherwise decoding will fail! + @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/data/BuiltinRegistries;registerSimple(Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/data/BuiltinRegistries$RegistryBootstrap;)Lnet/minecraft/core/Registry;", ordinal = 0)) + private static void bcl_registerBuiltin(CallbackInfo ci) { + BCLBiomeRegistry.BUILTIN_BCL_BIOMES = internalRegister( + BCLBiomeRegistry.BCL_BIOMES_REGISTRY, + (MappedRegistry) BCLBiomeRegistry.BUILTIN_BCL_BIOMES, + BCLBiomeRegistry::bootstrap, + Lifecycle.stable() + ); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/ChunkGeneratorAccessor.java b/src/main/java/org/betterx/bclib/mixin/common/ChunkGeneratorAccessor.java new file mode 100644 index 00000000..39a8e25e --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/ChunkGeneratorAccessor.java @@ -0,0 +1,23 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.FeatureSorter; +import net.minecraft.world.level.chunk.ChunkGenerator; + +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.function.Supplier; + +@Mixin(ChunkGenerator.class) +public interface ChunkGeneratorAccessor { + @Accessor("biomeSource") + @Mutable + void bcl_setBiomeSource(BiomeSource biomeSource); + + @Accessor("featuresPerStep") + @Mutable + void bcl_setFeaturesPerStep(Supplier> supplier); +} 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..cd726c4b --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/ChunkGeneratorMixin.java @@ -0,0 +1,46 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.interfaces.ChunkGeneratorAccessor; + +import net.minecraft.core.Registry; +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 net.minecraft.world.level.levelgen.structure.StructureSet; + +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.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ChunkGenerator.class) +public class ChunkGeneratorMixin implements ChunkGeneratorAccessor { + @Shadow + @Final + protected Registry structureSets; + 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; + } + + public Registry bclib_getStructureSetsRegistry() { + return structureSets; + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/ChunkGeneratorsMixin.java b/src/main/java/org/betterx/bclib/mixin/common/ChunkGeneratorsMixin.java new file mode 100644 index 00000000..f7602586 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/ChunkGeneratorsMixin.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.generator.BCLChunkGenerator; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Registry; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.ChunkGenerators; + +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(ChunkGenerators.class) +public class ChunkGeneratorsMixin { + @Inject(method = "bootstrap", at = @At(value = "HEAD")) + private static void bcl_bootstrap( + Registry> registry, + CallbackInfoReturnable> cir + ) { + Registry.register(registry, BCLib.makeID("betterx"), BCLChunkGenerator.CODEC); + } +} 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/DimensionTypeMixin.java b/src/main/java/org/betterx/bclib/mixin/common/DimensionTypeMixin.java new file mode 100644 index 00000000..34ce1afb --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/DimensionTypeMixin.java @@ -0,0 +1,64 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.WritableRegistry; +import net.minecraft.world.level.dimension.DimensionType; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(DimensionType.class) +public class DimensionTypeMixin { + // @Inject( +// method = "defaultDimensions(Lnet/minecraft/core/RegistryAccess;JZ)Lnet/minecraft/core/Registry;", +// locals = LocalCapture.CAPTURE_FAILHARD, +// at = @At("TAIL") +// ) + private static void bclib_updateDimensions( + RegistryAccess registryAccess, + long seed, + boolean bl, + CallbackInfoReturnable info, + WritableRegistry writableRegistry, + Registry registry, + Registry biomeRegistry, + Registry structureRegistry, + Registry noiseSettingsRegistry, + Registry noiseParamRegistry + ) { + //This probably moved to WorldPresets.bootstrap(); +// int id = writableRegistry.getId(writableRegistry.get(LevelStem.NETHER)); +// writableRegistry.registerOrOverride( +// OptionalInt.of(id), +// LevelStem.NETHER, +// new LevelStem( +// registry.getOrCreateHolder(BuiltinDimensionTypes.NETHER), +// new NoiseBasedChunkGenerator( +// structureRegistry, +// noiseParamRegistry, +// new BCLibNetherBiomeSource(biomeRegistry, seed), +// seed, +// noiseSettingsRegistry.getOrCreateHolder(NoiseGeneratorSettings.NETHER)) +// ), +// Lifecycle.stable() +// ); +// +// +// id = writableRegistry.getId(writableRegistry.get(LevelStem.END)); +// writableRegistry.registerOrOverride( +// OptionalInt.of(id), +// LevelStem.END, +// new LevelStem( +// registry.getOrCreateHolder(BuiltinDimensionTypes.END), +// new NoiseBasedChunkGenerator( +// structureRegistry, +// noiseParamRegistry, +// new BCLibEndBiomeSource(biomeRegistry, seed), +// seed, +// noiseSettingsRegistry.getOrCreateHolder(NoiseGeneratorSettings.END)) +// ), +// Lifecycle.stable() +// ); + } +} 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..a0cbd667 --- /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.LootContext; +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/MinecraftServerMixin.java b/src/main/java/org/betterx/bclib/mixin/common/MinecraftServerMixin.java new file mode 100644 index 00000000..429e315c --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/MinecraftServerMixin.java @@ -0,0 +1,41 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.recipes.BCLRecipeManager; + +import net.minecraft.server.MinecraftServer; + +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.Collection; +import java.util.concurrent.CompletableFuture; + +@Mixin(value = MinecraftServer.class) +public class MinecraftServerMixin { + @Shadow + private MinecraftServer.ReloadableResources resources; + + + @Inject(method = "reloadResources", at = @At(value = "RETURN"), cancellable = true) + private void bclib_reloadResources( + Collection collection, + CallbackInfoReturnable> info + ) { + bclib_injectRecipes(); + } + + @Inject(method = "loadLevel", at = @At(value = "RETURN"), cancellable = true) + private void bclib_loadLevel(CallbackInfo info) { + bclib_injectRecipes(); + } + + private void bclib_injectRecipes() { + RecipeManagerAccessor accessor = (RecipeManagerAccessor) resources.managers().getRecipeManager(); + accessor.bclib_setRecipesByName(BCLRecipeManager.getMapByName(accessor.bclib_getRecipesByName())); + accessor.bclib_setRecipes(BCLRecipeManager.getMap(accessor.bclib_getRecipes())); + } +} 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/NoiseBasedChunkGeneratorMixin.java b/src/main/java/org/betterx/bclib/mixin/common/NoiseBasedChunkGeneratorMixin.java new file mode 100644 index 00000000..eeb56c85 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/NoiseBasedChunkGeneratorMixin.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.interfaces.NoiseGeneratorSettingsProvider; +import org.betterx.bclib.interfaces.SurfaceProvider; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.synth.NormalNoise; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(NoiseBasedChunkGenerator.class) +public abstract class NoiseBasedChunkGeneratorMixin implements SurfaceProvider, NoiseGeneratorSettingsProvider { + @Final + @Shadow + protected Holder settings; + + @Shadow + @Final + private Registry noises; + + @Override + public NoiseGeneratorSettings bclib_getNoiseGeneratorSettings() { + return settings.value(); + } + + @Override + public Holder bclib_getNoiseGeneratorSettingHolders() { + return settings; + } + + @Override + public Registry bclib_getNoises() { + return noises; + } +} 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/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/RecipeManagerAccessor.java b/src/main/java/org/betterx/bclib/mixin/common/RecipeManagerAccessor.java new file mode 100644 index 00000000..d30a860a --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/RecipeManagerAccessor.java @@ -0,0 +1,26 @@ +package org.betterx.bclib.mixin.common; + +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; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(RecipeManager.class) +public interface RecipeManagerAccessor { + @Accessor("recipes") + Map, Map>> bclib_getRecipes(); + + @Accessor("recipes") + void bclib_setRecipes(Map, Map>> recipes); + + @Accessor("byName") + Map> bclib_getRecipesByName(); + + @Accessor("byName") + void bclib_setRecipesByName(Map> recipes); +} \ No newline at end of file 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..066618ab --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/RecipeManagerMixin.java @@ -0,0 +1,37 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.recipes.BCLRecipeManager; + +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.Level; + +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.Map; +import java.util.Optional; + +@Mixin(RecipeManager.class) +public abstract class RecipeManagerMixin { + @Shadow + private > Map> byType(RecipeType type) { + return null; + } + + @Inject(method = "getRecipeFor", at = @At(value = "HEAD"), cancellable = true) + private > void bclib_getRecipeFor( + RecipeType type, + C inventory, + Level level, + CallbackInfoReturnable> info + ) { + info.setReturnValue(BCLRecipeManager.getSortedRecipe(type, inventory, level, this::byType)); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/mixin/common/RegistryAccessMixin.java b/src/main/java/org/betterx/bclib/mixin/common/RegistryAccessMixin.java new file mode 100644 index 00000000..79b6478e --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/RegistryAccessMixin.java @@ -0,0 +1,48 @@ +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 com.mojang.serialization.Codec; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; + +import com.google.common.collect.ImmutableMap; +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.Map; +import java.util.function.Supplier; + +@Mixin(RegistryAccess.class) +public interface RegistryAccessMixin { + + @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;make(Ljava/util/function/Supplier;)Ljava/lang/Object;")) + private static Supplier>, RegistryAccess.RegistryData>> together_addRegistry( + Supplier>, RegistryAccess.RegistryData>> supplier + ) { + return () -> { + ImmutableMap.Builder>, RegistryAccess.RegistryData> builder = ImmutableMap.builder(); + //Make sure this gets added before WORLD_PRESETS + put(builder, BCLBiomeRegistry.BCL_BIOMES_REGISTRY, BiomeData.CODEC); + + Map>, RegistryAccess.RegistryData> res = supplier.get(); + builder.putAll(res); + + return builder.build(); + }; + } + + @Shadow + static void put( + ImmutableMap.Builder>, RegistryAccess.RegistryData> builder, + ResourceKey> resourceKey, + Codec codec + ) { + throw new RuntimeException("Shadowed Call"); + } + +} 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..b85859e9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/ServerLevelMixin.java @@ -0,0 +1,93 @@ +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.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.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, + Holder holder, + Supplier supplier, + boolean bl, + boolean bl2, + long l, + int i + ) { + super(writableLevelData, resourceKey, holder, supplier, bl, bl2, l, i); + } + + + @Inject(method = "*", at = @At("TAIL")) + private void bclib_onServerWorldInit( + MinecraftServer server, + Executor executor, + LevelStorageAccess levelStorageAccess, + ServerLevelData serverLevelData, + ResourceKey resourceKey, + LevelStem levelStem, + ChunkProgressListener chunkProgressListener, + boolean bl, + long l, + List list, + boolean bl2, + CallbackInfo ci + ) { + ServerLevel level = ServerLevel.class.cast(this); + LifeCycleAPI._runLevelLoad( + level, + server, + 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/StructuresAccessor.java b/src/main/java/org/betterx/bclib/mixin/common/StructuresAccessor.java new file mode 100644 index 00000000..463ebc03 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/StructuresAccessor.java @@ -0,0 +1,17 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.core.Holder; +import net.minecraft.data.worldgen.Structures; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.levelgen.structure.Structure; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(Structures.class) +public interface StructuresAccessor { + @Invoker + static Holder callRegister(ResourceKey resourceKey, Structure structure) { + throw new RuntimeException("Unexpected call"); + } +} 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/TheEndBiomesMixin.java b/src/main/java/org/betterx/bclib/mixin/common/TheEndBiomesMixin.java new file mode 100644 index 00000000..7a3aa38b --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/TheEndBiomesMixin.java @@ -0,0 +1,64 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.api.v2.generator.TheEndBiomesHelper; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome; +import org.betterx.bclib.api.v2.levelgen.biomes.InternalBiomeAPI; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; + +import net.fabricmc.fabric.api.biome.v1.TheEndBiomes; + +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(value = TheEndBiomes.class, remap = false) +public class TheEndBiomesMixin { + @Inject(method = "addBarrensBiome", at = @At("HEAD")) + private static void bcl_registerBarrens( + ResourceKey highlands, + ResourceKey barrens, + double weight, + CallbackInfo ci + ) { + TheEndBiomesHelper.add(InternalBiomeAPI.OTHER_END_BARRENS, barrens); + } + + @Inject(method = "addMidlandsBiome", at = @At("HEAD")) + private static void bcl_registerMidlands( + ResourceKey highlands, + ResourceKey midlands, + double weight, + CallbackInfo ci + ) { + BCLBiome highland = InternalBiomeAPI.wrapNativeBiome(highlands, InternalBiomeAPI.OTHER_END_LAND); + BCLBiome midland = InternalBiomeAPI.wrapNativeBiome(midlands, InternalBiomeAPI.OTHER_END_LAND); + if (highland != null) { + highland.addEdge(midland); + } + TheEndBiomesHelper.add(InternalBiomeAPI.OTHER_END_LAND, midlands); + } + + @Inject(method = "addSmallIslandsBiome", at = @At("HEAD")) + private static void bcl_registerSmallIslands( + ResourceKey biome, double weight, CallbackInfo ci + ) { + TheEndBiomesHelper.add(InternalBiomeAPI.OTHER_END_VOID, biome); + } + + @Inject(method = "addHighlandsBiome", at = @At("HEAD")) + private static void bcl_registerHighlands( + ResourceKey biome, double weight, CallbackInfo ci + ) { + TheEndBiomesHelper.add(InternalBiomeAPI.OTHER_END_LAND, biome); + } + + @Inject(method = "addMainIslandBiome", at = @At("HEAD")) + private static void bcl_registerMainIsnalnd( + ResourceKey biome, double weight, CallbackInfo ci + ) { + TheEndBiomesHelper.add(InternalBiomeAPI.OTHER_END_CENTER, biome); + } +} 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..3715b3fb --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/WorldGenRegionMixin.java @@ -0,0 +1,28 @@ +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; + + //TODO: 1.19 Is it ok to remove this? + @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/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/noise/Noises.java b/src/main/java/org/betterx/bclib/noise/Noises.java new file mode 100644 index 00000000..5c85ad1d --- /dev/null +++ b/src/main/java/org/betterx/bclib/noise/Noises.java @@ -0,0 +1,42 @@ +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.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.synth.NormalNoise; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +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(Registry.NOISE_REGISTRY, loc); + } + + public static NormalNoise createNoise( + Registry registry, + Random Random, + ResourceKey resourceKey + ) { + Holder holder = registry.getHolderOrThrow(resourceKey); + return NormalNoise.create(Random, holder.value()); + } + + public static NormalNoise getOrCreateNoise( + RegistryAccess registryAccess, + Random Random, + ResourceKey noise + ) { + final Registry registry = registryAccess.registryOrThrow(Registry.NOISE_REGISTRY); + return noiseIntances.computeIfAbsent(noise, (key) -> createNoise(registry, Random, 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..49bf43c1 --- /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( + (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/org/betterx/bclib/particles/BCLParticleType.java b/src/main/java/org/betterx/bclib/particles/BCLParticleType.java new file mode 100644 index 00000000..4c0c8eff --- /dev/null +++ b/src/main/java/org/betterx/bclib/particles/BCLParticleType.java @@ -0,0 +1,84 @@ +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.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(Registry.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(Registry.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(Registry.PARTICLE_TYPE, location, simple(overrideLimiter)); + ParticleFactoryRegistry.getInstance().register(type, provider); + return type; + } +} diff --git a/src/main/java/org/betterx/bclib/presets/FlatLevelPresets.java b/src/main/java/org/betterx/bclib/presets/FlatLevelPresets.java new file mode 100644 index 00000000..c5c1e9b2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/presets/FlatLevelPresets.java @@ -0,0 +1,28 @@ +package org.betterx.bclib.presets; + +import org.betterx.worlds.together.flatLevel.FlatLevelGeneratorPreset; +import org.betterx.worlds.together.tag.v3.TagRegistry; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; + +/** + * @deprecated Use {@link org.betterx.worlds.together.flatLevel.FlatLevelPresets} instead + */ +@Deprecated(forRemoval = true) +public class FlatLevelPresets { + /** + * @deprecated Use {@link org.betterx.worlds.together.flatLevel.FlatLevelPresets#FLAT_LEVEL_PRESETS} instead + */ + @Deprecated(forRemoval = true) + public static TagRegistry.Simple FLAT_LEVEL_PRESETS = org.betterx.worlds.together.flatLevel.FlatLevelPresets.FLAT_LEVEL_PRESETS; + + + /** + * @deprecated Use {@link org.betterx.worlds.together.flatLevel.FlatLevelPresets#register(ResourceLocation)} instead + */ + @Deprecated(forRemoval = true) + public static ResourceKey register(ResourceLocation loc) { + return org.betterx.worlds.together.flatLevel.FlatLevelPresets.register(loc); + } +} 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..7ba043a5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java @@ -0,0 +1,386 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.config.PathConfig; +import org.betterx.bclib.interfaces.UnknownReceipBookCategory; +import org.betterx.bclib.util.ItemUtil; +import org.betterx.bclib.util.RecipeHelper; +import org.betterx.worlds.together.tag.v3.CommonItemTags; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.core.NonNullList; +import net.minecraft.core.Registry; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.TagParser; +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() { + + } + + 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; + } + + public static Builder create(String id) { + return create(BCLib.makeID(id)); + } + + public static Builder create(ResourceLocation id) { + Builder.INSTANCE.id = id; + Builder.INSTANCE.input = null; + Builder.INSTANCE.output = null; + Builder.INSTANCE.inputCount = 1; + Builder.INSTANCE.toolLevel = 1; + Builder.INSTANCE.anvilLevel = 1; + Builder.INSTANCE.damage = 1; + Builder.INSTANCE.alright = true; + Builder.INSTANCE.exist = true; + + return Builder.INSTANCE; + } + + @Override + public RecipeSerializer getSerializer() { + return SERIALIZER; + } + + @Override + public ItemStack getResultItem() { + return this.output; + } + + @Override + public boolean matches(@NotNull Container craftingInventory, @NotNull Level world) { + return this.matches(craftingInventory); + } + + @Override + public ItemStack assemble(@NotNull Container craftingInventory) { + return this.output.copy(); + } + + 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); + } + + 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 int getAnvilLevel() { + return this.anvilLevel; + } + + @Override + public NonNullList getIngredients() { + NonNullList defaultedList = NonNullList.create(); + defaultedList.add(Ingredient.of(Registry.ITEM.stream() + .filter(item -> item.builtInRegistryHolder() + .is(CommonItemTags.HAMMERS)) + .filter(hammer -> ((TieredItem) hammer).getTier() + .getLevel() >= toolLevel) + .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 { + private final static Builder INSTANCE = new Builder(); + + private ResourceLocation id; + private Ingredient input; + private ItemStack output; + private int inputCount = 1; + private int toolLevel = 1; + private int anvilLevel = 1; + private int damage = 1; + private boolean alright; + private boolean exist; + + private Builder() { + } + + public Builder setInput(ItemLike... inputItems) { + this.alright &= RecipeHelper.exists(inputItems); + this.setInput(Ingredient.of(inputItems)); + return this; + } + + public Builder setInput(TagKey inputTag) { + this.setInput(Ingredient.of(inputTag)); + return this; + } + + public Builder setInput(Ingredient ingredient) { + this.input = ingredient; + return this; + } + + public Builder setInputCount(int count) { + this.inputCount = count; + return this; + } + + public Builder setOutput(ItemLike output) { + return this.setOutput(output, 1); + } + + public Builder setOutput(ItemLike output, int amount) { + this.alright &= RecipeHelper.exists(output); + this.output = new ItemStack(output, amount); + 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; + } + + public Builder checkConfig(PathConfig config) { + exist &= config.getBoolean("anvil", id.getPath(), true); + return this; + } + + public void build() { + if (!exist) { + return; + } + + if (input == null) { + BCLib.LOGGER.warning("Input for Anvil recipe can't be 'null', recipe {} will be ignored!", id); + return; + } + if (output == null) { + BCLib.LOGGER.warning("Output for Anvil recipe can't be 'null', recipe {} will be ignored!", id); + return; + } + if (BCLRecipeManager.getRecipe(TYPE, id) != null) { + BCLib.LOGGER.warning("Can't add Anvil recipe! Id {} already exists!", id); + return; + } + if (!alright) { + BCLib.LOGGER.debug("Can't add Anvil recipe {}! Ingeredient or output not exists.", id); + return; + } + BCLRecipeManager.addRecipe( + TYPE, + new AnvilRecipe(id, input, output, inputCount, toolLevel, anvilLevel, 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.fromJsonRecipe(result); + if (output == null) { + throw new IllegalStateException("Output item does not exists!"); + } + if (result.has("nbt")) { + try { + String nbtData = GsonHelper.getAsString(result, "nbt"); + CompoundTag nbt = TagParser.parseTag(nbtData); + output.setTag(nbt); + } catch (CommandSyntaxException ex) { + BCLib.LOGGER.warning("Error parse nbt data for output.", ex); + } + } + 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/BCLRecipeManager.java b/src/main/java/org/betterx/bclib/recipes/BCLRecipeManager.java new file mode 100644 index 00000000..97dba05d --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/BCLRecipeManager.java @@ -0,0 +1,119 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.util.CollectionsUtil; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Container; +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.minecraft.world.level.block.Block; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; + +import java.util.*; +import java.util.Map.Entry; +import java.util.function.Function; + +public class BCLRecipeManager { + private static final Map, Map>> RECIPES = Maps.newHashMap(); + private static final Map, Object> SORTED = Maps.newHashMap(); + private static final String MINECRAFT = "minecraft"; + + public static > Optional getSortedRecipe( + RecipeType type, + C inventory, + Level level, + Function, Map>> getter + ) { + List> recipes = (List>) SORTED.computeIfAbsent(type, t -> { + Collection> values = getter.apply(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) : v1.getId().compareTo(v2.getId()); + }); + return ImmutableList.copyOf(list); + }); + return (Optional) recipes.stream().filter(recipe -> recipe.matches(inventory, level)).findFirst(); + } + + public static void addRecipe(RecipeType type, Recipe recipe) { + Map> list = RECIPES.computeIfAbsent(type, i -> Maps.newHashMap()); + list.put(recipe.getId(), recipe); + } + + public static > T getRecipe(RecipeType type, ResourceLocation id) { + Map> map = RECIPES.get(type); + return map != null ? (T) map.get(id) : 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); + } + + SORTED.clear(); + RECIPES.forEach((type, list) -> { + if (list != null) { + Map> typeList = result.computeIfAbsent(type, i -> Maps.newHashMap()); + for (Entry> entry : list.entrySet()) { + ResourceLocation id = entry.getKey(); + typeList.computeIfAbsent(id, i -> entry.getValue()); + } + } + }); + + return result; + } + + public static Map> getMapByName(Map> recipes) { + Map> result = CollectionsUtil.getMutable(recipes); + RECIPES.values() + .forEach(map -> map.forEach((location, recipe) -> result.computeIfAbsent(location, i -> recipe))); + 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/org/betterx/bclib/recipes/CraftingRecipes.java b/src/main/java/org/betterx/bclib/recipes/CraftingRecipes.java new file mode 100644 index 00000000..fca78723 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/CraftingRecipes.java @@ -0,0 +1,88 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.config.Configs; +import org.betterx.worlds.together.tag.v3.CommonItemTags; + +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() { + GridRecipe.make(BCLib.MOD_ID, "tag_smith_table", Blocks.SMITHING_TABLE) + .setShape("II", "##", "##") + .addMaterial('#', ItemTags.PLANKS) + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .checkConfig(Configs.RECIPE_CONFIG) + .build(); + GridRecipe.make(BCLib.MOD_ID, "tag_cauldron", Blocks.CAULDRON) + .setShape("I I", "I I", "III") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .checkConfig(Configs.RECIPE_CONFIG) + .build(); + GridRecipe.make(BCLib.MOD_ID, "tag_hopper", Blocks.HOPPER) + .setShape("I I", "ICI", " I ") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .addMaterial('C', CommonItemTags.CHEST) + .checkConfig(Configs.RECIPE_CONFIG) + .build(); + GridRecipe.make(BCLib.MOD_ID, "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) + .checkConfig(Configs.RECIPE_CONFIG) + .build(); + GridRecipe.make(BCLib.MOD_ID, "tag_rail", Blocks.RAIL) + .setOutputCount(16) + .setShape("I I", "ISI", "I I") + .addMaterial('I', CommonItemTags.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', CommonItemTags.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', CommonItemTags.IRON_INGOTS) + .checkConfig(Configs.RECIPE_CONFIG) + .build(); + GridRecipe.make(BCLib.MOD_ID, "tag_compass", Items.COMPASS) + .setShape(" I ", "IDI", " I ") + .addMaterial('I', CommonItemTags.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', CommonItemTags.IRON_INGOTS) + .checkConfig(Configs.RECIPE_CONFIG) + .build(); + GridRecipe.make(BCLib.MOD_ID, "tag_shield", Items.SHIELD) + .setShape("WIW", "WWW", " W ") + .addMaterial('I', CommonItemTags.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', CommonItemTags.IRON_INGOTS) + .addMaterial('C', CommonItemTags.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', CommonItemTags.CHEST) + .checkConfig(Configs.RECIPE_CONFIG) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/FurnaceRecipe.java b/src/main/java/org/betterx/bclib/recipes/FurnaceRecipe.java new file mode 100644 index 00000000..a51d874b --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/FurnaceRecipe.java @@ -0,0 +1,125 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.config.PathConfig; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.level.ItemLike; + +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) { + return; + } + + SmeltingRecipe recipe = new SmeltingRecipe( + new ResourceLocation(id + "_smelting"), + group, + Ingredient.of(input), + new ItemStack(output, count), + xp, + time + ); + BCLRecipeManager.addRecipe(RecipeType.SMELTING, recipe); + + if (blasting) { + BlastingRecipe recipe2 = new BlastingRecipe( + new ResourceLocation(id + "_blasting"), + group, + Ingredient.of(input), + new ItemStack(output, count), + xp, + time / 2 + ); + BCLRecipeManager.addRecipe(RecipeType.BLASTING, recipe2); + } + + if (campfire) { + CampfireCookingRecipe recipe2 = new CampfireCookingRecipe( + new ResourceLocation(id + "_campfire"), + group, + Ingredient.of(input), + new ItemStack(output, count), + xp, + time * 3 + ); + BCLRecipeManager.addRecipe(RecipeType.CAMPFIRE_COOKING, recipe2); + } + + if (smoker) { + SmokingRecipe recipe2 = new SmokingRecipe( + new ResourceLocation(id + "_smoker"), + group, + Ingredient.of(input), + new ItemStack(output, count), + xp, + time / 2 + ); + BCLRecipeManager.addRecipe(RecipeType.SMOKING, recipe2); + } + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/GridRecipe.java b/src/main/java/org/betterx/bclib/recipes/GridRecipe.java new file mode 100644 index 00000000..aa1fe9ab --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/GridRecipe.java @@ -0,0 +1,134 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.config.PathConfig; + +import net.minecraft.core.NonNullList; +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.*; +import net.minecraft.world.level.ItemLike; + +import com.google.common.collect.Maps; + +import java.util.Arrays; +import java.util.Map; + +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 final Map materialKeys = Maps.newHashMap(); + private int count; + private boolean exist; + + private GridRecipe() { + } + + public static GridRecipe make(String modID, String name, ItemLike output) { + return make(new ResourceLocation(modID, name), output); + } + + public static GridRecipe make(ResourceLocation id, ItemLike output) { + INSTANCE.id = id; + INSTANCE.output = output; + + INSTANCE.group = ""; + INSTANCE.type = RecipeType.CRAFTING; + INSTANCE.shaped = true; + INSTANCE.shape = new String[]{"#"}; + INSTANCE.materialKeys.clear(); + INSTANCE.count = 1; + + INSTANCE.exist = output != null && 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, TagKey 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) { + return; + } + + 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); + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/SmithingTableRecipe.java b/src/main/java/org/betterx/bclib/recipes/SmithingTableRecipe.java new file mode 100644 index 00000000..96d07e78 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/SmithingTableRecipe.java @@ -0,0 +1,104 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.config.PathConfig; + +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.item.crafting.RecipeType; +import net.minecraft.world.item.crafting.UpgradeRecipe; +import net.minecraft.world.level.ItemLike; + +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.exist = true; + + return BUILDER; + } + + private ResourceLocation id; + private Ingredient base; + private Ingredient addition; + private ItemStack result; + 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.exist &= BCLRecipeManager.exists(item); + this.result = new ItemStack(item, count); + return this; + } + + public SmithingTableRecipe setBase(ItemLike... items) { + this.exist &= BCLRecipeManager.exists(items); + this.base = Ingredient.of(items); + return this; + } + + public SmithingTableRecipe setBase(TagKey tag) { + this.base = (Ingredient.of(tag)); + return this; + } + + public SmithingTableRecipe setAddition(ItemLike... items) { + this.exist &= BCLRecipeManager.exists(items); + this.addition = Ingredient.of(items); + return this; + } + + public SmithingTableRecipe setAddition(TagKey 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; + } + + BCLRecipeManager.addRecipe(TYPE, new UpgradeRecipe(id, base, addition, result)); + } +} 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..8988b4d6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/BaseBlockEntities.java @@ -0,0 +1,65 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.blockentities.*; +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.BaseSignBlock; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; + +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, + BlockEntitySupplier supplier + ) { + return Registry.register(Registry.BLOCK_ENTITY_TYPE, typeId, new DynamicBlockEntityType<>(supplier)); + } + + public static void register() { + } + + public static Block[] getChests() { + return Registry.BLOCK + .stream() + .filter(block -> block instanceof BaseChestBlock) + .toArray(Block[]::new); + } + + public static Block[] getBarrels() { + return Registry.BLOCK + .stream() + .filter(block -> block instanceof BaseBarrelBlock) + .toArray(Block[]::new); + } + + public static Block[] getSigns() { + return Registry.BLOCK + .stream() + .filter(block -> block instanceof BaseSignBlock) + .toArray(Block[]::new); + } + + public static Block[] getFurnaces() { + return Registry.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..89faed81 --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/BaseBlockEntityRenders.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.client.render.BaseChestBlockEntityRenderer; +import org.betterx.bclib.client.render.BaseSignBlockEntityRenderer; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.rendering.v1.BlockEntityRendererRegistry; + +@Environment(EnvType.CLIENT) +public class BaseBlockEntityRenders { + public static void register() { + BlockEntityRendererRegistry.register(BaseBlockEntities.CHEST, BaseChestBlockEntityRenderer::new); + BlockEntityRendererRegistry.register(BaseBlockEntities.SIGN, BaseSignBlockEntityRenderer::new); + } +} 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..361de24f --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/BaseRegistry.java @@ -0,0 +1,83 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.config.PathConfig; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.List; +import java.util.Map; + +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 CreativeModeTab creativeTab; + protected final PathConfig config; + + protected BaseRegistry(CreativeModeTab creativeTab, PathConfig config) { + this.creativeTab = creativeTab; + this.config = config; + REGISTRIES.add(this); + } + + public abstract T register(ResourceLocation objId, T obj); + + public abstract void registerItem(ResourceLocation id, Item item); + + public FabricItemSettings makeItemSettings() { + FabricItemSettings properties = new FabricItemSettings(); + return (FabricItemSettings) properties.tab(creativeTab); + } + + 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; + } + + 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..b334b2db --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/BlockRegistry.java @@ -0,0 +1,95 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.blocks.BaseLeavesBlock; +import org.betterx.bclib.blocks.BaseOreBlock; +import org.betterx.bclib.blocks.FeatureSaplingBlock; +import org.betterx.bclib.config.PathConfig; +import org.betterx.bclib.interfaces.CustomItemProvider; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; +import org.betterx.worlds.together.tag.v3.CommonItemTags; +import org.betterx.worlds.together.tag.v3.MineableTags; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTab; +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(CreativeModeTab creativeTab, PathConfig config) { + super(creativeTab, config); + } + + @Override + public Block register(ResourceLocation id, Block block) { + if (!config.getBooleanRoot(id.getNamespace(), true)) { + return 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().getMaterial().isFlammable() && FlammableBlockRegistry.getDefaultInstance() + .get(block) + .getBurnChance() == 0) { + FlammableBlockRegistry.getDefaultInstance().add(block, 5, 5); + } + + block = Registry.register(Registry.BLOCK, id, block); + getModBlocks(id.getNamespace()).add(block); + + if (block instanceof BaseLeavesBlock) { + TagManager.BLOCKS.add( + block, + BlockTags.LEAVES, + CommonBlockTags.LEAVES, + MineableTags.HOE, + MineableTags.SHEARS + ); + if (item != null) { + TagManager.ITEMS.add(item, CommonItemTags.LEAVES, ItemTags.LEAVES); + } + } else if (block instanceof BaseOreBlock) { + TagManager.BLOCKS.add(block, MineableTags.PICKAXE); + } else if (block instanceof FeatureSaplingBlock) { + TagManager.BLOCKS.add(block, CommonBlockTags.SAPLINGS, BlockTags.SAPLINGS); + if (item != null) { + TagManager.ITEMS.add(item, CommonItemTags.SAPLINGS, ItemTags.SAPLINGS); + } + } + + 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(Registry.BLOCK, id, block); + } + + private Item registerBlockItem(ResourceLocation id, Item item) { + registerItem(id, item); + return item; + } + + @Override + public void registerItem(ResourceLocation id, Item item) { + if (item != null && item != Items.AIR) { + Registry.register(Registry.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..704ef50b --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/ItemRegistry.java @@ -0,0 +1,149 @@ +package org.betterx.bclib.registry; + +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.items.tool.BaseAxeItem; +import org.betterx.bclib.items.tool.BaseHoeItem; +import org.betterx.bclib.items.tool.BasePickaxeItem; +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.worlds.together.tag.v3.CommonItemTags; +import org.betterx.worlds.together.tag.v3.TagManager; +import org.betterx.worlds.together.tag.v3.ToolTags; + +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.dispenser.ShearsDispenseItemBehavior; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvent; +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; + +public class ItemRegistry extends BaseRegistry { + public ItemRegistry(CreativeModeTab creativeTab, PathConfig config) { + super(creativeTab, config); + } + + public Item registerDisc(ResourceLocation itemId, int power, SoundEvent sound, int lengthInSeconds) { + BaseDiscItem item = new BaseDiscItem(power, sound, makeItemSettings().stacksTo(1), lengthInSeconds); + + if (!config.getBoolean("musicDiscs", itemId.getPath(), true)) { + return item; + } + register(itemId, 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); + + if (item instanceof ShovelItem) { + TagManager.ITEMS.add(ToolTags.FABRIC_SHOVELS, item); + } else if (item instanceof SwordItem) { + TagManager.ITEMS.add(ToolTags.FABRIC_SWORDS, item); + } else if (item instanceof BasePickaxeItem) { + TagManager.ITEMS.add(ToolTags.FABRIC_PICKAXES, item); + } else if (item instanceof BaseAxeItem) { + TagManager.ITEMS.add(ToolTags.FABRIC_AXES, item); + } else if (item instanceof BaseHoeItem) { + TagManager.ITEMS.add(ToolTags.FABRIC_HOES, item); + } else if (item instanceof BaseShearsItem) { + TagManager.ITEMS.add(item, ToolTags.FABRIC_SHEARS, CommonItemTags.SHEARS); + DispenserBlock.registerBehavior(item.asItem(), new ShearsDispenseItemBehavior()); + } + + 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(Registry.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/registry/PresetsRegistry.java b/src/main/java/org/betterx/bclib/registry/PresetsRegistry.java new file mode 100644 index 00000000..21919891 --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/PresetsRegistry.java @@ -0,0 +1,82 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig; +import org.betterx.bclib.api.v2.generator.config.BCLNetherBiomeSourceConfig; +import org.betterx.bclib.api.v2.levelgen.LevelGenUtil; +import org.betterx.worlds.together.entrypoints.WorldPresetBootstrap; +import org.betterx.worlds.together.levelgen.WorldGenUtil; +import org.betterx.worlds.together.worldPreset.TogetherWorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPresets; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.dimension.LevelStem; + +import java.util.Map; + +public class PresetsRegistry implements WorldPresetBootstrap { + public static ResourceKey BCL_WORLD; + public static ResourceKey BCL_WORLD_17; + + public void bootstrapWorldPresets() { + BCL_WORLD = + WorldPresets.register( + BCLib.makeID("normal"), + (overworldStem, netherContext, endContext) -> + buildPreset( + overworldStem, + netherContext, + BCLNetherBiomeSourceConfig.DEFAULT, endContext, + BCLEndBiomeSourceConfig.DEFAULT + ), + true + ); + + BCL_WORLD_17 = WorldPresets.register( + BCLib.makeID("legacy_17"), + (overworldStem, netherContext, endContext) -> + buildPreset( + overworldStem, + netherContext, + BCLNetherBiomeSourceConfig.MINECRAFT_17, endContext, + BCLEndBiomeSourceConfig.MINECRAFT_17 + ), + false + ); + + WorldPresets.setDEFAULT(BCL_WORLD); + } + + public static TogetherWorldPreset buildPreset( + LevelStem overworldStem, + WorldGenUtil.Context netherContext, + BCLNetherBiomeSourceConfig netherConfig, + WorldGenUtil.Context endContext, + BCLEndBiomeSourceConfig endConfig + ) { + return new TogetherWorldPreset(buildDimensionMap( + overworldStem, + netherContext, + netherConfig, endContext, + endConfig + ), 1000); + } + + public static Map, LevelStem> buildDimensionMap( + LevelStem overworldStem, + WorldGenUtil.Context netherContext, + BCLNetherBiomeSourceConfig netherConfig, + WorldGenUtil.Context endContext, + BCLEndBiomeSourceConfig endConfig + ) { + return Map.of( + LevelStem.OVERWORLD, + overworldStem, + LevelStem.NETHER, + LevelGenUtil.getBCLNetherLevelStem(netherContext, netherConfig), + LevelStem.END, + LevelGenUtil.getBCLEndLevelStem(endContext, endConfig) + ); + } +} diff --git a/src/main/java/org/betterx/bclib/registry/PresetsRegistryClient.java b/src/main/java/org/betterx/bclib/registry/PresetsRegistryClient.java new file mode 100644 index 00000000..be28feb3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/PresetsRegistryClient.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.client.gui.screens.WorldSetupScreen; +import org.betterx.worlds.together.worldPreset.client.WorldPresetsClient; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class PresetsRegistryClient { + public static void onLoad() { + WorldPresetsClient.registerCustomizeUI(PresetsRegistry.BCL_WORLD, (createWorldScreen, worldCreationContext) -> { + return new WorldSetupScreen(createWorldScreen, worldCreationContext); + }); + } +} 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..31adeabe --- /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.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/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..b2b4a638 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFCoordModify.java @@ -0,0 +1,22 @@ +package org.betterx.bclib.sdf.operator; + +import com.mojang.math.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..a5c2477d --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFDisplacement.java @@ -0,0 +1,21 @@ +package org.betterx.bclib.sdf.operator; + +import com.mojang.math.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..fb782d4c --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFRotation.java @@ -0,0 +1,21 @@ +package org.betterx.bclib.sdf.operator; + +import com.mojang.math.Quaternion; +import com.mojang.math.Vector3f; + +public class SDFRotation extends SDFUnary { + private 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/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..65330a89 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/BlocksHelper.java @@ -0,0 +1,362 @@ +package org.betterx.bclib.util; + +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.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; +import net.minecraft.world.level.material.LavaFluid; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Optional; +import java.util.Random; +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)).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) { + final MutableBlockPos POS = TL_POS.get(); + 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 "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; + } + + /** + * 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. + */ + @Deprecated(forRemoval = true) + public static boolean isFluidOld(BlockState state) { + return !state.getFluidState().isEmpty(); + } + + public static boolean isFluid(BlockState state) { + return state.getMaterial().isLiquid(); + } + + public static boolean isFree(BlockState state) { + return state.isAir(); + } + + public static boolean isFreeOrReplaceable(BlockState state) { + return state.isAir() || state.getMaterial().isReplaceable(); + } + + 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); + } +} 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..aa73d7bb --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/ColorExtractor.java @@ -0,0 +1,148 @@ +package org.betterx.bclib.util; + +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/ColorUtil.java b/src/main/java/org/betterx/bclib/util/ColorUtil.java new file mode 100644 index 00000000..b567d5cd --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/ColorUtil.java @@ -0,0 +1,245 @@ +package org.betterx.bclib.util; + +import org.betterx.bclib.BCLib; + +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.client.Minecraft; +import net.minecraft.core.Registry; +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 net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper; + +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 { + 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 final 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(); + 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/bclib/util/ItemUtil.java b/src/main/java/org/betterx/bclib/util/ItemUtil.java new file mode 100644 index 00000000..0d964bb6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/ItemUtil.java @@ -0,0 +1,74 @@ +package org.betterx.bclib.util; + +import org.betterx.bclib.BCLib; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; + +import com.google.gson.JsonObject; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ItemUtil { + + public static String toStackString(@NotNull ItemStack stack) { + try { + if (stack == null) { + throw new IllegalStateException("Stack can't be null!"); + } + Item item = stack.getItem(); + return Registry.ITEM.getKey(item) + ":" + stack.getCount(); + } catch (Exception ex) { + BCLib.LOGGER.error("ItemStack serialization error!", ex); + } + return ""; + } + + @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 = Registry.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 = Registry.ITEM.getOptional(itemId).orElseThrow(() -> { + return 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; + } + + @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 = Registry.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; + } +} 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..5c7304da --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/JsonFactory.java @@ -0,0 +1,132 @@ +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/Logger.java b/src/main/java/org/betterx/bclib/util/Logger.java new file mode 100644 index 00000000..d7115089 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/Logger.java @@ -0,0 +1,72 @@ +package org.betterx.bclib.util; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; + + +/** + * @deprecated Please use {@link org.betterx.worlds.together.util.Logger} instead + */ +@Deprecated(forRemoval = true) +public final class Logger { + private static final org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger(); + private final String modPref; + + + /** + * @deprecated Please use {@link org.betterx.worlds.together.util.Logger#Logger(String)} instead + */ + @Deprecated(forRemoval = true) + 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/org/betterx/bclib/util/LootUtil.java b/src/main/java/org/betterx/bclib/util/LootUtil.java new file mode 100644 index 00000000..7d1b16a2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/LootUtil.java @@ -0,0 +1,75 @@ +package org.betterx.bclib.util; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.interfaces.LootPoolAccessor; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.ItemStack; +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.LootContext; +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, + LootContext.Builder builder + ) { + ResourceLocation tableID = block.getLootTable(); + if (tableID == BuiltInLootTables.EMPTY) { + return Optional.empty(); + } + + final LootContext ctx = builder.withParameter(LootContextParams.BLOCK_STATE, state) + .create(LootContextParamSets.BLOCK); + final ServerLevel level = ctx.getLevel(); + final LootTable table = level.getServer().getLootTables().get(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; + } +} 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..1c45836a --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/MHelper.java @@ -0,0 +1,284 @@ +package org.betterx.bclib.util; + +import com.mojang.math.Vector3f; +import net.minecraft.core.Vec3i; +import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.levelgen.RandomSource; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.PositionalRandomFactory; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class MHelper { + 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, 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/org/betterx/bclib/util/MethodReplace.java b/src/main/java/org/betterx/bclib/util/MethodReplace.java new file mode 100644 index 00000000..dbe1a7f5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/MethodReplace.java @@ -0,0 +1,48 @@ +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; + } + + @Deprecated(forRemoval = true) + public static void addBlockReplace(Block block, Function blockReplace) { + MethodReplace.blockReplace = blockReplace; + MethodReplace.block = block; + } + + @Nullable + public static Function getItemReplace(Item item) { + if (MethodReplace.item != item) { + return null; + } + Function replace = itemReplace; + itemReplace = null; + return replace; + } + + @Nullable + @Deprecated(forRemoval = true) + public static Function getBlockReplace(Block block) { + if (MethodReplace.block != block) { + return null; + } + Function replace = blockReplace; + blockReplace = null; + return replace; + } +} diff --git a/src/main/java/org/betterx/bclib/util/ModUtil.java b/src/main/java/org/betterx/bclib/util/ModUtil.java new file mode 100644 index 00000000..e90d988a --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/ModUtil.java @@ -0,0 +1,439 @@ +package org.betterx.bclib.util; + +import org.betterx.bclib.BCLib; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.util.PathUtil; + +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; + +/** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil} + */ +@Deprecated(forRemoval = true) +public class ModUtil { + private static Map mods; + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil#invalidateCachedMods()} + */ + @Deprecated(forRemoval = true) + public static void invalidateCachedMods() { + mods = null; + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil#getMods()} ()} + */ + @Deprecated(forRemoval = true) + 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 (com.google.gson.stream.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) { + BCLib.LOGGER.error("Unable to parse Version in " + sourceFile); + return null; + } + + if (data.get("id") == null) { + BCLib.LOGGER.error("Unable to read ID in " + sourceFile); + return null; + } + + if (data.get("name") == null) { + BCLib.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) { + BCLib.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 { + BCLib.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; + } + }; + } + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil#getModInfo(String)} + */ + @Deprecated(forRemoval = true) + public static ModInfo getModInfo(String modID) { + return getModInfo(modID, true); + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil#getModInfo(String, boolean)} + */ + @Deprecated(forRemoval = true) + public static ModInfo getModInfo(String modID, boolean matchVersion) { + getMods(); + final ModInfo mi = mods.get(modID); + if (mi == null || (matchVersion && !org.betterx.worlds.together.util.ModUtil.getModVersion(modID) + .equals(mi.getVersion()))) + return null; + return mi; + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil#getModVersion(String)} + */ + @Deprecated(forRemoval = true) + public static String getModVersion(String modID) { + if (modID == WorldsTogether.MOD_ID) modID = BCLib.MOD_ID; + + Optional optional = FabricLoader.getInstance() + .getModContainer(modID); + if (optional.isPresent()) { + ModContainer modContainer = optional.get(); + return org.betterx.worlds.together.util.ModUtil.ModInfo.versionToString(modContainer.getMetadata() + .getVersion()); + + } + + return org.betterx.worlds.together.util.ModUtil.getModVersionFromJar(modID); + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil#getModVersionFromJar(String)} + */ + @Deprecated(forRemoval = true) + public static String getModVersionFromJar(String modID) { + final ModInfo mi = getModInfo(modID, false); + if (mi != null) return mi.getVersion(); + + return "0.0.0"; + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil#convertModVersion(String)} + */ + @Deprecated(forRemoval = true) + 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; + } + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil#convertModVersion(int)} + */ + @Deprecated(forRemoval = true) + 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); + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil#isLargerVersion(String, String)} + */ + @Deprecated(forRemoval = true) + public static boolean isLargerVersion(String v1, String v2) { + return org.betterx.worlds.together.util.ModUtil.convertModVersion(v1) > org.betterx.worlds.together.util.ModUtil.convertModVersion( + v2); + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil#isLargerOrEqualVersion(String, String)} + */ + @Deprecated(forRemoval = true) + public static boolean isLargerOrEqualVersion(String v1, String v2) { + return org.betterx.worlds.together.util.ModUtil.convertModVersion(v1) >= org.betterx.worlds.together.util.ModUtil.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) { + BCLib.LOGGER.error("Error for " + uri + ": " + e); + } + //if (doClose) fs.close(); + } + } catch (Exception e) { + BCLib.LOGGER.error("Error for " + file.toUri() + ": " + e); + e.printStackTrace(); + } + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil.ModInfo} + */ + @Deprecated(forRemoval = true) + public static class ModInfo { + public final ModMetadata metadata; + public final Path jarPath; + + ModInfo(ModMetadata metadata, Path jarPath) { + this.metadata = metadata; + this.jarPath = jarPath; + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil.ModInfo#versionToString(Version)} + */ + @Deprecated(forRemoval = true) + public static String versionToString(Version v) { + if (v instanceof SemanticVersion) { + return org.betterx.worlds.together.util.ModUtil.ModInfo.versionToString((SemanticVersion) v); + } + return org.betterx.worlds.together.util.ModUtil.convertModVersion( + org.betterx.worlds.together.util.ModUtil.convertModVersion(v.toString()) + ); + } + + /** + * @deprecated Replaced by {@link org.betterx.worlds.together.util.ModUtil.ModInfo#versionToString(SemanticVersion)} + */ + @Deprecated(forRemoval = true) + 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 org.betterx.worlds.together.util.ModUtil.ModInfo.versionToString(metadata.getVersion()); + } + } +} 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/PathUtil.java b/src/main/java/org/betterx/bclib/util/PathUtil.java new file mode 100644 index 00000000..3abf09ab --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/PathUtil.java @@ -0,0 +1,62 @@ +package org.betterx.bclib.util; + +import java.io.File; +import java.nio.file.Path; +import java.util.function.Consumer; + + +/** + * @deprecated replaced by {@link org.betterx.worlds.together.util.PathUtil} + */ +@Deprecated(forRemoval = true) +public class PathUtil { + /** + * @deprecated replaced by {@link org.betterx.worlds.together.util.PathUtil#GAME_FOLDER} + */ + @Deprecated(forRemoval = true) + public final static Path GAME_FOLDER = org.betterx.worlds.together.util.PathUtil.GAME_FOLDER; + + /** + * @deprecated replaced by {@link org.betterx.worlds.together.util.PathUtil#MOD_FOLDER} + */ + @Deprecated(forRemoval = true) + public final static Path MOD_FOLDER = org.betterx.worlds.together.util.PathUtil.MOD_FOLDER; + + /** + * @deprecated replaced by {@link org.betterx.worlds.together.util.PathUtil#MOD_BAK_FOLDER} + */ + @Deprecated(forRemoval = true) + public final static Path MOD_BAK_FOLDER = org.betterx.worlds.together.util.PathUtil.MOD_BAK_FOLDER; + + /** + * @deprecated replaced by {@link org.betterx.worlds.together.util.PathUtil#isChildOf(Path, Path)} + */ + @Deprecated(forRemoval = true) + public static boolean isChildOf(Path parent, Path child) { + return org.betterx.worlds.together.util.PathUtil.isChildOf(parent, child); + } + + /** + * @deprecated replaced by {@link org.betterx.worlds.together.util.PathUtil#fileWalker(File, Consumer)} + */ + @Deprecated(forRemoval = true) + public static void fileWalker(File path, Consumer pathConsumer) { + org.betterx.worlds.together.util.PathUtil.fileWalker(path, pathConsumer); + } + + /** + * @deprecated replaced by {@link org.betterx.worlds.together.util.PathUtil#fileWalker(File, boolean, Consumer)} + */ + @Deprecated(forRemoval = true) + public static void fileWalker(File path, boolean recursive, Consumer pathConsumer) { + org.betterx.worlds.together.util.PathUtil.fileWalker(path, recursive, pathConsumer); + } + + /** + * @deprecated replaced by {@link org.betterx.worlds.together.util.PathUtil#humanReadableFileSize(long)} + */ + @Deprecated(forRemoval = true) + public static String humanReadableFileSize(long size) { + return org.betterx.worlds.together.util.PathUtil.humanReadableFileSize(size); + } +} 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..340e090b --- /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.Registry; +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 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; + } +} 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..de091e59 --- /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 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 com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +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, 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/org/betterx/bclib/util/StructureErode.java b/src/main/java/org/betterx/bclib/util/StructureErode.java new file mode 100644 index 00000000..c15fb988 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/StructureErode.java @@ -0,0 +1,277 @@ +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.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.RandomSource; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.material.Material; + +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).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 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).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) { + if (state.is(CommonBlockTags.GEN_END_STONES) || state.is(BlockTags.NYLIUM)) { + return true; + } + return !state.getMaterial().equals(Material.STONE) || 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()) + .getMaterial() + .isSolidBlocking()) { + 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..313af8d1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/StructureHelper.java @@ -0,0 +1,143 @@ +package org.betterx.bclib.util; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.resources.ResourceLocation; +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.Random; +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(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(-offset.x * 0.5, 0, -offset.z * 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.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(-min.x, -min.y, -min.z); + return BoundingBox.fromCorners(pos.offset(min.x, min.y, 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/TranslationHelper.java b/src/main/java/org/betterx/bclib/util/TranslationHelper.java new file mode 100644 index 00000000..28339d19 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/TranslationHelper.java @@ -0,0 +1,124 @@ +package org.betterx.bclib.util; + +import net.minecraft.core.Registry; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.resources.ResourceLocation; + +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Set; + +public class TranslationHelper { + /** + * Print English translation file lines. Translation is "auto-beautified" text (example "strange_thing" -> "Strange Thing"). + * + * @param modID {@link String} mod ID string. + */ + public static void printMissingEnNames(String modID) { + printMissingNames(modID, "en_us"); + } + + /** + * Prints translation file lines for specified language. + * + * @param modID {@link String} mod ID string; + * @param languageCode {@link String} language code (example "en_us", "ru_ru"). + */ + public static void printMissingNames(String modID, String languageCode) { + Set missingNames = Sets.newHashSet(); + + Gson gson = new Gson(); + InputStream inputStream = TranslationHelper.class.getResourceAsStream("/assets/" + modID + "/lang/" + languageCode + ".json"); + JsonObject translation = inputStream == null + ? new JsonObject() + : gson.fromJson(new InputStreamReader(inputStream), JsonObject.class); + + Registry.BLOCK.forEach(block -> { + if (Registry.BLOCK.getKey(block).getNamespace().equals(modID)) { + String name = block.getName().getString(); + if (!translation.has(name)) { + missingNames.add(name); + } + } + }); + + Registry.ITEM.forEach(item -> { + if (Registry.ITEM.getKey(item).getNamespace().equals(modID)) { + String name = item.getDescription().getString(); + if (!translation.has(name)) { + missingNames.add(name); + } + } + }); + + BuiltinRegistries.BIOME.forEach(biome -> { + ResourceLocation id = BuiltinRegistries.BIOME.getKey(biome); + if (id.getNamespace().equals(modID)) { + String name = "biome." + modID + "." + id.getPath(); + if (!translation.has(name)) { + missingNames.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 (!translation.has(name)) { + missingNames.add(name); + } + } + }); + + if (!missingNames.isEmpty()) { + + System.out.println("========================================"); + System.out.println(" MISSING NAMES LIST"); + + if (!missingNames.isEmpty()) { + if (languageCode.equals("en_us")) { + System.out.println("========================================"); + System.out.println(" AUTO ENGLISH BEAUTIFICATION"); + System.out.println("========================================"); + missingNames.stream().sorted().forEach(name -> { + System.out.println(" \"" + name + "\": \"" + fastTranslateEn(name) + "\","); + }); + } else { + System.out.println("========================================"); + System.out.println(" TEMPLATE: [" + languageCode + "]"); + System.out.println("========================================"); + missingNames.stream().sorted().forEach(name -> { + System.out.println(" \"" + name + "\": \"\","); + }); + } + } + + System.out.println("========================================"); + } + } + + /** + * Simple fast text beautification (example "strange_thing" -> "Strange Thing"). + * + * @param text {@link String} to process; + * @return {@link String} result. + */ + 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/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..cadcdbdb --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/WeightedList.java @@ -0,0 +1,181 @@ +package org.betterx.bclib.util; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +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(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); + } + 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/bclib/world/structures/BCLStructure.java b/src/main/java/org/betterx/bclib/world/structures/BCLStructure.java new file mode 100644 index 00000000..29fd1565 --- /dev/null +++ b/src/main/java/org/betterx/bclib/world/structures/BCLStructure.java @@ -0,0 +1,108 @@ +package org.betterx.bclib.world.structures; + +import org.betterx.bclib.api.v2.levelgen.structures.BCLStructureBuilder; +import org.betterx.worlds.together.tag.v3.TagManager; + +import com.mojang.serialization.Codec; +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.TerrainAdjustment; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType; + +import java.util.function.Function; + +@Deprecated(forRemoval = true) +/** + * Please use the {@link org.betterx.bclib.api.v2.levelgen.structures.BCLStructure} and + * {@link BCLStructureBuilder} instead. + * @deprecated Use {@link org.betterx.bclib.api.v2.levelgen.structures.BCLStructure} instead + */ +public class BCLStructure extends org.betterx.bclib.api.v2.levelgen.structures.BCLStructure { + + @Deprecated(forRemoval = true) + /** + * Please use the {@link BCLStructureBuilder} instead: + * + * BCLStructureBuilder + * .start(id, structureBuilder) + * .step(step) + * .randomPlacement(spacing, separation) + * .build(); + */ + public BCLStructure( + ResourceLocation id, + Function structureBuilder, + GenerationStep.Decoration step, + int spacing, + int separation + ) { + this(id, structureBuilder, step, spacing, separation, false); + } + + @Deprecated(forRemoval = true) + /** + * Please use the {@link BCLStructureBuilder} instead: + * + * BCLStructureBuilder + * .start(id, structureBuilder) + * .step(step) + * .randomPlacement(spacing, separation) + * .build(); + */ + public BCLStructure( + ResourceLocation id, + Function structureBuilder, + GenerationStep.Decoration step, + int spacing, + int separation, + boolean adaptNoise + ) { + this( + id, + structureBuilder, + step, + spacing, + separation, + adaptNoise, + Structure.simpleCodec(structureBuilder), + TagManager.BIOMES.makeStructureTag(id.getNamespace(), id.getPath()) + ); + } + + @Deprecated(forRemoval = true) + /** + * + * Please use the {@link BCLStructureBuilder} instead: + * + * BCLStructureBuilder + * .start(id, structureBuilder) + * .step(step) + * .randomPlacement(spacing, separation) + * .codec(codec) + * .biomeTag(biomeTag) + * .build(); + * + */ + public BCLStructure( + ResourceLocation id, + Function structureBuilder, + GenerationStep.Decoration step, + int spacing, + int separation, + boolean adaptNoise, + Codec codec, + TagKey biomeTag + ) { + super(id, structureBuilder, step, new RandomSpreadStructurePlacement( + spacing, + separation, + RandomSpreadType.LINEAR, + id.toString().hashCode() + ), codec, biomeTag, TerrainAdjustment.NONE); + } + +} 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..24ae4bef --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/WorldsTogether.java @@ -0,0 +1,40 @@ +package org.betterx.worlds.together; + +import org.betterx.worlds.together.surfaceRules.SurfaceRuleRegistry; +import org.betterx.worlds.together.tag.v3.TagManager; +import org.betterx.worlds.together.util.Logger; +import org.betterx.worlds.together.world.WorldConfig; +import org.betterx.worlds.together.worldPreset.WorldPresets; + +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; + +public class WorldsTogether implements ModInitializer { + 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 void onInitialize() { + TagManager.ensureStaticallyLoaded(); + SurfaceRuleRegistry.ensureStaticallyLoaded(); + + + WorldConfig.registerModCache(WorldsTogether.MOD_ID); + WorldPresets.ensureStaticallyLoaded(); + + } + + public static ResourceLocation makeID(String s) { + return new ResourceLocation(MOD_ID, s); + } +} diff --git a/src/main/java/org/betterx/worlds/together/biomesource/BiomeSourceFromRegistry.java b/src/main/java/org/betterx/worlds/together/biomesource/BiomeSourceFromRegistry.java new file mode 100644 index 00000000..dd908026 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/biomesource/BiomeSourceFromRegistry.java @@ -0,0 +1,26 @@ +package org.betterx.worlds.together.biomesource; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.world.level.biome.Biome; + +import java.util.Set; + +public interface BiomeSourceFromRegistry { + Registry getBiomeRegistry(); + Set> possibleBiomes(); + + default boolean sameRegistryButDifferentBiomes(BiomeSourceFromRegistry other) { + if (other.getBiomeRegistry() == getBiomeRegistry()) { + Set> mySet = this.possibleBiomes(); + Set> otherSet = other.possibleBiomes(); + if (otherSet.size() != mySet.size()) return true; + for (Holder b : mySet) { + if (!otherSet.contains(b)) + return true; + } + } + + return false; + } +} 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..93cb1e66 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/biomesource/MergeableBiomeSource.java @@ -0,0 +1,17 @@ +package org.betterx.worlds.together.biomesource; + +import net.minecraft.world.level.biome.BiomeSource; + +public interface MergeableBiomeSource { + + /** + * 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/MultiNoiseBiomeSourceAccessor.java b/src/main/java/org/betterx/worlds/together/biomesource/MultiNoiseBiomeSourceAccessor.java new file mode 100644 index 00000000..bc6fe466 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/biomesource/MultiNoiseBiomeSourceAccessor.java @@ -0,0 +1,7 @@ +package org.betterx.worlds.together.biomesource; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public interface MultiNoiseBiomeSourceAccessor { +} 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/chunkgenerator/ChunkGeneratorUtils.java b/src/main/java/org/betterx/worlds/together/chunkgenerator/ChunkGeneratorUtils.java new file mode 100644 index 00000000..b85f3127 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/chunkgenerator/ChunkGeneratorUtils.java @@ -0,0 +1,20 @@ +package org.betterx.worlds.together.chunkgenerator; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldGenSettings; + +public class ChunkGeneratorUtils { + public static void restoreOriginalBiomeSourceInAllDimension(WorldGenSettings settings) { + for (var entry : settings.dimensions().entrySet()) { + ResourceKey key = entry.getKey(); + LevelStem stem = entry.getValue(); + + if (stem.generator() instanceof RestorableBiomeSource generator) { + generator.restoreInitialBiomeSource(key); + } + } + } + + +} diff --git a/src/main/java/org/betterx/worlds/together/chunkgenerator/EnforceableChunkGenerator.java b/src/main/java/org/betterx/worlds/together/chunkgenerator/EnforceableChunkGenerator.java new file mode 100644 index 00000000..505b3bbb --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/chunkgenerator/EnforceableChunkGenerator.java @@ -0,0 +1,42 @@ +package org.betterx.worlds.together.chunkgenerator; + +import org.betterx.worlds.together.biomesource.BiomeSourceFromRegistry; +import org.betterx.worlds.together.biomesource.BiomeSourceWithConfig; + +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldGenSettings; + +public interface EnforceableChunkGenerator { + WorldGenSettings enforceGeneratorInWorldGenSettings( + RegistryAccess access, + ResourceKey dimensionKey, + ResourceKey dimensionTypeKey, + ChunkGenerator loadedChunkGenerator, + WorldGenSettings settings + ); + + default boolean needsChunkGeneratorRepair(ChunkGenerator chunkGenerator) { + ChunkGenerator self = (ChunkGenerator) this; + if (this == chunkGenerator || chunkGenerator == null) return false; + + BiomeSource one = self.getBiomeSource(); + BiomeSource two = chunkGenerator.getBiomeSource(); + if (one == two) return false; + + if (one instanceof BiomeSourceWithConfig ba && two instanceof BiomeSourceWithConfig bb) { + if (!ba.getTogetherConfig().couldSetWithoutRepair(bb.getTogetherConfig())) + return true; + } + if (one instanceof BiomeSourceFromRegistry ba && two instanceof BiomeSourceFromRegistry bb) { + if (ba.sameRegistryButDifferentBiomes(bb)) + return true; + } + + return !one.getClass().isAssignableFrom(two.getClass()) && !two.getClass().isAssignableFrom(one.getClass()); + } +} diff --git a/src/main/java/org/betterx/worlds/together/chunkgenerator/InjectableSurfaceRules.java b/src/main/java/org/betterx/worlds/together/chunkgenerator/InjectableSurfaceRules.java new file mode 100644 index 00000000..9fc53dd7 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/chunkgenerator/InjectableSurfaceRules.java @@ -0,0 +1,21 @@ +package org.betterx.worlds.together.chunkgenerator; + +import org.betterx.worlds.together.surfaceRules.SurfaceRuleUtil; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; + +public interface InjectableSurfaceRules { + /** + * Called when the Surface Rules for this BiomeSource need to be + * + * @param dimensionKey The Dimension for which this injection is performed + */ + default void injectSurfaceRules(ResourceKey dimensionKey) { + if (this instanceof NoiseBasedChunkGenerator nbc) { + SurfaceRuleUtil.injectSurfaceRules(nbc.generatorSettings().value(), nbc.getBiomeSource()); + } + } +} diff --git a/src/main/java/org/betterx/worlds/together/chunkgenerator/RestorableBiomeSource.java b/src/main/java/org/betterx/worlds/together/chunkgenerator/RestorableBiomeSource.java new file mode 100644 index 00000000..11ca787f --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/chunkgenerator/RestorableBiomeSource.java @@ -0,0 +1,9 @@ +package org.betterx.worlds.together.chunkgenerator; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.LevelStem; + +public interface RestorableBiomeSource { + void restoreInitialBiomeSource(ResourceKey dimensionKey); +} 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..2a646f80 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/client/WorldsTogetherClient.java @@ -0,0 +1,11 @@ +package org.betterx.worlds.together.client; + +import org.betterx.worlds.together.worldPreset.client.WorldPresetsClient; + +import net.fabricmc.api.ClientModInitializer; + +public class WorldsTogetherClient implements ClientModInitializer { + public void onInitializeClient() { + WorldPresetsClient.setupClientside(); + } +} 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..0476a601 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/entrypoints/EntrypointUtil.java @@ -0,0 +1,29 @@ +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(); + } + + public static List getCommon(Class select) { + return getEntryPoints(false, select); + } + + public static List getClient(Class select) { + return getEntryPoints(true, select); + } +} diff --git a/src/main/java/org/betterx/worlds/together/entrypoints/WorldPresetBootstrap.java b/src/main/java/org/betterx/worlds/together/entrypoints/WorldPresetBootstrap.java new file mode 100644 index 00000000..238fec8e --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/entrypoints/WorldPresetBootstrap.java @@ -0,0 +1,5 @@ +package org.betterx.worlds.together.entrypoints; + +public interface WorldPresetBootstrap extends WorldsTogetherEntrypoint { + void bootstrapWorldPresets(); +} 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/FlatLevelGeneratorPreset.java b/src/main/java/org/betterx/worlds/together/flatLevel/FlatLevelGeneratorPreset.java new file mode 100644 index 00000000..054aacbd --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/flatLevel/FlatLevelGeneratorPreset.java @@ -0,0 +1,23 @@ +package org.betterx.worlds.together.flatLevel; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.resources.RegistryFileCodec; +import net.minecraft.resources.RegistryFixedCodec; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; + +public record FlatLevelGeneratorPreset(Holder displayItem, FlatLevelGeneratorSettings settings) { + public static final Codec DIRECT_CODEC = RecordCodecBuilder.create(instance -> instance + .group( + RegistryFixedCodec.create(Registry.ITEM_REGISTRY).fieldOf("display").forGetter(o -> o.displayItem), + FlatLevelGeneratorSettings.CODEC.fieldOf("settings").forGetter(o -> o.settings) + ).apply(instance, FlatLevelGeneratorPreset::new) + ); + public static final Codec> CODEC = RegistryFileCodec.create( + FlatLevelPresets.FLAT_LEVEL_GENERATOR_PRESET_REGISTRY, + DIRECT_CODEC + ); +} \ No newline at end of file diff --git a/src/main/java/org/betterx/worlds/together/flatLevel/FlatLevelGeneratorPresetTags.java b/src/main/java/org/betterx/worlds/together/flatLevel/FlatLevelGeneratorPresetTags.java new file mode 100644 index 00000000..97d542ee --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/flatLevel/FlatLevelGeneratorPresetTags.java @@ -0,0 +1,11 @@ +package org.betterx.worlds.together.flatLevel; + +import org.betterx.worlds.together.WorldsTogether; + +import net.minecraft.tags.TagKey; + +public class FlatLevelGeneratorPresetTags { + public static final TagKey VISIBLE = FlatLevelPresets.FLAT_LEVEL_PRESETS.makeTag( + WorldsTogether.makeID("visible") + ); +} 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..36b9e399 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/flatLevel/FlatLevelPresets.java @@ -0,0 +1,34 @@ +package org.betterx.worlds.together.flatLevel; + +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.tag.v3.TagManager; +import org.betterx.worlds.together.tag.v3.TagRegistry; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; + +public class FlatLevelPresets { + static final ResourceKey> FLAT_LEVEL_GENERATOR_PRESET_REGISTRY + = ResourceKey.createRegistryKey(WorldsTogether.makeID("worldgen/flat_level_generator_preset")); + + public static final Registry FLAT_LEVEL_GENERATOR_PRESET + = Registry.registerSimple(FLAT_LEVEL_GENERATOR_PRESET_REGISTRY, (registry) -> null); + + public static TagRegistry.Simple FLAT_LEVEL_PRESETS = + TagManager.registerType( + FLAT_LEVEL_GENERATOR_PRESET_REGISTRY, + "tags/worldgen/flat_level_generator_preset", + (b) -> null + ); + + + public static ResourceKey register(ResourceLocation loc) { + ResourceKey key = ResourceKey.create( + FLAT_LEVEL_GENERATOR_PRESET_REGISTRY, + 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..7c5f734b --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/levelgen/WorldGenUtil.java @@ -0,0 +1,192 @@ +package org.betterx.worlds.together.levelgen; + +import org.betterx.bclib.util.MHelper; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.biomesource.BiomeSourceWithConfig; +import org.betterx.worlds.together.biomesource.ReloadableBiomeSource; +import org.betterx.worlds.together.chunkgenerator.EnforceableChunkGenerator; +import org.betterx.worlds.together.world.BiomeSourceWithNoiseRelatedSettings; +import org.betterx.worlds.together.world.BiomeSourceWithSeed; +import org.betterx.worlds.together.world.WorldConfig; +import org.betterx.worlds.together.world.event.WorldBootstrap; +import org.betterx.worlds.together.worldPreset.TogetherWorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPresets; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.synth.NormalNoise; + +public class WorldGenUtil { + public static final String TAG_PRESET = "preset"; + public static final String TAG_GENERATOR = "generator"; + + public static WorldGenSettings createWorldFromPreset( + ResourceKey preset, + RegistryAccess registryAccess, + long seed, + boolean generateStructures, + boolean generateBonusChest + ) { + WorldGenSettings settings = registryAccess + .registryOrThrow(WorldPresets.WORLD_PRESET_REGISTRY) + .getHolderOrThrow(preset) + .value() + .createWorldGenSettings(seed, generateStructures, generateBonusChest); + + for (LevelStem stem : settings.dimensions()) { + if (stem.generator().getBiomeSource() instanceof BiomeSourceWithSeed bcl) { + bcl.setSeed(seed); + } + + if (stem.generator().getBiomeSource() instanceof BiomeSourceWithNoiseRelatedSettings bcl + && stem.generator() instanceof NoiseBasedChunkGenerator noiseGenerator) { + bcl.onLoadGeneratorSettings(noiseGenerator.generatorSettings().value()); + } + } + + return settings; + } + + public static WorldGenSettings createDefaultWorldFromPreset( + RegistryAccess registryAccess, + long seed, + boolean generateStructures, + boolean generateBonusChest + ) { + return createWorldFromPreset( + WorldPresets.getDEFAULT(), + registryAccess, + seed, + generateStructures, + generateBonusChest + ); + } + + public static Pair defaultWorldDataSupplier(RegistryAccess.Frozen frozen) { + WorldGenSettings worldGenSettings = createDefaultWorldFromPreset(frozen); + return Pair.of(worldGenSettings, frozen); + } + + public static WorldGenSettings createDefaultWorldFromPreset(RegistryAccess registryAccess, long seed) { + return createDefaultWorldFromPreset(registryAccess, seed, true, false); + } + + public static WorldGenSettings createDefaultWorldFromPreset(RegistryAccess registryAccess) { + return createDefaultWorldFromPreset(registryAccess, MHelper.RANDOM.nextLong()); + } + + public static CompoundTag getPresetsNbt() { + return WorldConfig.getCompoundTag(WorldsTogether.MOD_ID, TAG_PRESET); + } + + public static CompoundTag getGeneratorNbt() { + CompoundTag root = WorldConfig.getRootTag(WorldsTogether.MOD_ID); + if (root.contains(TAG_GENERATOR)) + return WorldConfig.getCompoundTag(WorldsTogether.MOD_ID, TAG_GENERATOR); + return null; + } + + public static class Context extends StemContext { + public final Registry biomes; + + public Context( + Registry biomes, + Holder dimension, + Registry structureSets, + Registry noiseParameters, + Holder generatorSettings + ) { + super(dimension, structureSets, noiseParameters, generatorSettings); + this.biomes = biomes; + } + } + + public static class StemContext { + public final Holder dimension; + public final Registry structureSets; + public final Registry noiseParameters; + public final Holder generatorSettings; + + public StemContext( + Holder dimension, + Registry structureSets, + Registry noiseParameters, + Holder generatorSettings + ) { + this.dimension = dimension; + this.structureSets = structureSets; + this.noiseParameters = noiseParameters; + this.generatorSettings = generatorSettings; + } + } + + + @SuppressWarnings("unchecked") + public static WorldGenSettings repairBiomeSourceInAllDimensions( + RegistryAccess registryAccess, + WorldGenSettings settings + ) { + var dimensions = TogetherWorldPreset.loadWorldDimensions(); + for (var entry : settings.dimensions().entrySet()) { + boolean didRepair = false; + ResourceKey key = entry.getKey(); + LevelStem loadedStem = entry.getValue(); + + ChunkGenerator referenceGenerator = dimensions.get(key); + if (referenceGenerator instanceof EnforceableChunkGenerator enforcer) { + final ChunkGenerator loadedChunkGenerator = loadedStem.generator(); + + if (enforcer.needsChunkGeneratorRepair(loadedChunkGenerator)) { + settings = enforcer.enforceGeneratorInWorldGenSettings( + registryAccess, + key, + loadedStem.typeHolder().unwrapKey().orElseThrow(), + loadedChunkGenerator, + settings + ); + didRepair = true; + } else if (loadedChunkGenerator.getBiomeSource() instanceof BiomeSourceWithConfig bs) { + if (referenceGenerator.getBiomeSource() instanceof BiomeSourceWithConfig refbs) { + if (!refbs.getTogetherConfig().sameConfig(bs.getTogetherConfig())) { + bs.setTogetherConfig(refbs.getTogetherConfig()); + } + } + } + } + + if (!didRepair) { + if (loadedStem.generator().getBiomeSource() instanceof ReloadableBiomeSource reload) { + reload.reloadBiomes(); + } + } + } + return settings; + } + + public static ResourceLocation getBiomeID(Biome biome) { + ResourceLocation id = null; + RegistryAccess access = WorldBootstrap.getLastRegistryAccessOrElseBuiltin(); + + id = access.registryOrThrow(Registry.BIOME_REGISTRY).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/CreateWorldScreenMixin.java b/src/main/java/org/betterx/worlds/together/mixin/client/CreateWorldScreenMixin.java new file mode 100644 index 00000000..255f0784 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/client/CreateWorldScreenMixin.java @@ -0,0 +1,67 @@ +package org.betterx.worlds.together.mixin.client; + +import org.betterx.worlds.together.levelgen.WorldGenUtil; +import org.betterx.worlds.together.world.event.WorldBootstrap; +import org.betterx.worlds.together.worldPreset.WorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPresets; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; +import net.minecraft.client.gui.screens.worldselection.WorldGenSettingsComponent; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.WorldLoader; +import net.minecraft.world.level.DataPackConfig; +import net.minecraft.world.level.levelgen.WorldGenSettings; +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.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Optional; + +@Mixin(CreateWorldScreen.class) +public class CreateWorldScreenMixin { + @Shadow + @Final + public WorldGenSettingsComponent worldGenSettingsComponent; + + @Inject(method = "", at = @At("TAIL")) + private void wt_init( + Screen screen, + DataPackConfig dataPackConfig, + WorldGenSettingsComponent worldGenSettingsComponent, + CallbackInfo ci + ) { + WorldBootstrap.InGUI.registryReadyOnNewWorld(worldGenSettingsComponent); + } + + //Change the WorldPreset that is selected by default on the Create World Screen + @ModifyArg(method = "openFresh", index = 1, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/worldselection/WorldGenSettingsComponent;(Lnet/minecraft/client/gui/screens/worldselection/WorldCreationContext;Ljava/util/Optional;Ljava/util/OptionalLong;)V")) + private static Optional> wt_NewDefault(Optional> preset) { + return Optional.of(WorldPresets.getDEFAULT()); + } + + //Make sure the WorldGenSettings used to populate the create screen match the default WorldPreset + @ModifyArg(method = "openFresh", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/WorldLoader;load(Lnet/minecraft/server/WorldLoader$InitConfig;Lnet/minecraft/server/WorldLoader$WorldDataSupplier;Lnet/minecraft/server/WorldLoader$ResultFactory;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;")) + private static WorldLoader.WorldDataSupplier wt_NewDefaultSettings(WorldLoader.WorldDataSupplier worldDataSupplier) { + return (resourceManager, dataPackConfig) -> { + Pair res = worldDataSupplier.get(resourceManager, dataPackConfig); + return WorldGenUtil.defaultWorldDataSupplier(res.getSecond()); + }; + } + + //this is called when a new world is first created + @Inject(method = "createNewWorldDirectory", at = @At("RETURN")) + void wt_createNewWorld(CallbackInfoReturnable> cir) { + WorldBootstrap.InGUI.registryReadyOnNewWorld(this.worldGenSettingsComponent); + WorldBootstrap.InGUI.setupNewWorld(cir.getReturnValue(), this.worldGenSettingsComponent); + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/client/MinecraftMixin.java b/src/main/java/org/betterx/worlds/together/mixin/client/MinecraftMixin.java new file mode 100644 index 00000000..5320256e --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/client/MinecraftMixin.java @@ -0,0 +1,80 @@ +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.Minecraft; +import net.minecraft.core.RegistryAccess; +import net.minecraft.server.WorldStem; +import net.minecraft.world.level.LevelSettings; +import net.minecraft.world.level.levelgen.WorldGenSettings; +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; + +import java.util.function.Function; + +@Mixin(Minecraft.class) +public abstract class MinecraftMixin { + @Shadow + protected abstract void doLoadLevel( + String string, + Function function, + Function function2, + boolean bl, + Minecraft.ExperimentalDialogType experimentalDialogType + ); + + @Shadow + @Final + private LevelStorageSource levelSource; + + @Inject(method = "loadLevel", cancellable = true, at = @At("HEAD")) + private void wt_callFixerOnLoad(String levelID, CallbackInfo ci) { + WorldBootstrap.InGUI.setupLoadedWorld(levelID, this.levelSource); + + //if (DataFixerAPI.fixData(this.levelSource, levelID, true, (appliedFixes) -> { + if (WorldBootstrap.InGUI.applyWorldPatches(levelSource, levelID, (appliedFixes) -> { + WorldBootstrap.InGUI.finishedWorldLoad(levelID, this.levelSource); + this.doLoadLevel( + levelID, + WorldStem.DataPackConfigSupplier::loadFromWorld, + WorldStem.WorldDataSupplier::loadFromWorld, + false, + Minecraft.ExperimentalDialogType.NONE + ); + })) { + //cancel call when fix-screen is presented + ci.cancel(); + } else { + WorldBootstrap.InGUI.finishedWorldLoad(levelID, this.levelSource); + if (WorldsTogether.SURPRESS_EXPERIMENTAL_DIALOG) { + this.doLoadLevel( + levelID, + WorldStem.DataPackConfigSupplier::loadFromWorld, + WorldStem.WorldDataSupplier::loadFromWorld, + false, + Minecraft.ExperimentalDialogType.NONE + ); + //cancel call as we manually start the level load here + ci.cancel(); + } + } + } + + @Inject(method = "createLevel", at = @At("HEAD")) + private void wt_callOnCreate( + String levelID, + LevelSettings levelSettings, + RegistryAccess registryAccess, + WorldGenSettings worldGenSettings, + CallbackInfo ci + ) { + + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/client/WorldGenSettingsComponentMixin.java b/src/main/java/org/betterx/worlds/together/mixin/client/WorldGenSettingsComponentMixin.java new file mode 100644 index 00000000..5ad139db --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/client/WorldGenSettingsComponentMixin.java @@ -0,0 +1,45 @@ +package org.betterx.worlds.together.mixin.client; + +import org.betterx.worlds.together.worldPreset.WorldGenSettingsComponentAccessor; +import org.betterx.worlds.together.worldPreset.WorldPreset; + +import net.minecraft.client.gui.screens.worldselection.WorldGenSettingsComponent; +import net.minecraft.core.Holder; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@Mixin(WorldGenSettingsComponent.class) +public abstract class WorldGenSettingsComponentMixin implements WorldGenSettingsComponentAccessor { + @Override + @Accessor("preset") + public abstract Optional> bcl_getPreset(); + + @Override + @Accessor("preset") + public abstract void bcl_setPreset(Optional> preset); + + @ModifyArg(method = "init", index = 0, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/components/CycleButton$Builder;withValues(Ljava/util/List;Ljava/util/List;)Lnet/minecraft/client/gui/components/CycleButton$Builder;")) + public List> bcl_SortLists(List> list) { + final Predicate> vanilla = (p -> p.unwrapKey() + .orElseThrow() + .location() + .getNamespace() + .equals("minecraft")); + List> custom = list + .stream() + .filter(p -> !vanilla.test(p)) + .collect(Collectors.toCollection(LinkedList::new)); + custom.addAll(list.stream().filter(vanilla).toList()); + + return custom; + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/BuiltinRegistriesMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/BuiltinRegistriesMixin.java new file mode 100644 index 00000000..508e1d2b --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/BuiltinRegistriesMixin.java @@ -0,0 +1,37 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.surfaceRules.SurfaceRuleRegistry; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.resources.ResourceKey; + +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 java.util.function.Supplier; + +@Mixin(BuiltinRegistries.class) +public abstract class BuiltinRegistriesMixin { + + @Shadow + static protected Registry registerSimple( + ResourceKey> resourceKey, + Supplier> supplier + ) { + throw new RuntimeException("Shadowed Call"); + } + + @Inject(method = "", at = @At(value = "INVOKE", target = "Ljava/util/Map;forEach(Ljava/util/function/BiConsumer;)V")) + private static void together_registerSurface(CallbackInfo ci) { + SurfaceRuleRegistry.BUILTIN_SURFACE_RULES = registerSimple( + SurfaceRuleRegistry.SURFACE_RULES_REGISTRY, + SurfaceRuleRegistry::bootstrap + ); + } + +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/DedicatedServerPropertiesMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/DedicatedServerPropertiesMixin.java new file mode 100644 index 00000000..a26d5be1 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/DedicatedServerPropertiesMixin.java @@ -0,0 +1,35 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.worldPreset.WorldPresets; + +import net.minecraft.server.dedicated.DedicatedServerProperties; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.util.Properties; + +@Mixin(DedicatedServerProperties.class) +public class DedicatedServerPropertiesMixin { + //Make sure the default server properties use our Default World Preset by default (read from "level-type") + @ModifyArg(method = "", index = 3, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/dedicated/DedicatedServerProperties$WorldGenProperties;(Ljava/lang/String;Lcom/google/gson/JsonObject;ZLjava/lang/String;)V")) + protected String wt_defaultPreset(String string) { + if (WorldsTogether.FORCE_SERVER_TO_BETTERX_PRESET) { + return WorldPresets.getDEFAULT().location().toString(); + } + + return string; + } + + @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/dedicated/Settings;(Ljava/util/Properties;)V")) + private static Properties wt_defaultPreset(Properties property) { + //init default value level preset in server.properties + property.setProperty( + "level-type", + property.getProperty("level-type", WorldPresets.getDEFAULT().location().toString()) + ); + return property; + } +} 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..1e9087f5 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/MainMixin.java @@ -0,0 +1,39 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import com.mojang.serialization.DynamicOps; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.server.Main; +import net.minecraft.world.level.levelgen.WorldGenSettings; +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 { + @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) { + WorldBootstrap.DedicatedServer.setupWorld(levelStorageAccess); + return levelStorageAccess; + } + + + @ModifyArg(method = "method_43613", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;getDataTag(Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/world/level/DataPackConfig;Lcom/mojang/serialization/Lifecycle;)Lnet/minecraft/world/level/storage/WorldData;")) + private static DynamicOps bcl_onCreate(DynamicOps dynamicOps) { + if (dynamicOps instanceof RegistryOps regOps) { + WorldBootstrap.DedicatedServer.registryReady(regOps); + } + return dynamicOps; + } + + @ModifyArg(method = "method_43613", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/PrimaryLevelData;(Lnet/minecraft/world/level/LevelSettings;Lnet/minecraft/world/level/levelgen/WorldGenSettings;Lcom/mojang/serialization/Lifecycle;)V")) + private static WorldGenSettings bcl_onCreateLevelData(WorldGenSettings worldGenSettings) { + WorldBootstrap.DedicatedServer.applyDatapackChangesOnNewWorld(worldGenSettings); + return worldGenSettings; + } +} 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..37b48469 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/MinecraftServerMixin.java @@ -0,0 +1,35 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.core.RegistryAccess; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.level.storage.WorldData; + +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.MixinMinecraftServer} + */ +@Mixin(value = MinecraftServer.class, priority = 2000) +public class MinecraftServerMixin { + @Shadow + @Final + private RegistryAccess.Frozen registryHolder; + @Shadow + @Final + protected WorldData worldData; + + @Inject(method = "createLevels", at = @At(value = "HEAD")) + private void together_addSurfaceRules(ChunkProgressListener worldGenerationProgressListener, CallbackInfo ci) { + WorldBootstrap.finalizeWorldGenSettings(this.worldData.worldGenSettings()); + + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/MinecraftServerMixinLate.java b/src/main/java/org/betterx/worlds/together/mixin/common/MinecraftServerMixinLate.java new file mode 100644 index 00000000..fe6e4bc8 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/MinecraftServerMixinLate.java @@ -0,0 +1,40 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.chunkgenerator.ChunkGeneratorUtils; + +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.datafixers.DataFixer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldStem; +import net.minecraft.server.level.progress.ChunkProgressListenerFactory; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.server.players.GameProfileCache; +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.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.net.Proxy; + +@Mixin(value = MinecraftServer.class, priority = 2000) +public class MinecraftServerMixinLate { + @Inject(at = @At("RETURN"), method = "") + private void bcl_restoreBiomeSource( + Thread thread, + LevelStorageSource.LevelStorageAccess levelStorageAccess, + PackRepository packRepository, + WorldStem worldStem, + Proxy proxy, + DataFixer dataFixer, + MinecraftSessionService minecraftSessionService, + GameProfileRepository gameProfileRepository, + GameProfileCache gameProfileCache, + ChunkProgressListenerFactory chunkProgressListenerFactory, + CallbackInfo ci + ) { + ChunkGeneratorUtils.restoreOriginalBiomeSourceInAllDimension(worldStem.worldData().worldGenSettings()); + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/NoiseBasedChunkGeneratorMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/NoiseBasedChunkGeneratorMixin.java new file mode 100644 index 00000000..9ecdf11e --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/NoiseBasedChunkGeneratorMixin.java @@ -0,0 +1,11 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.chunkgenerator.InjectableSurfaceRules; + +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; + +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(NoiseBasedChunkGenerator.class) +public class NoiseBasedChunkGeneratorMixin implements InjectableSurfaceRules { +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/NoiseGeneratorSettingsMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/NoiseGeneratorSettingsMixin.java new file mode 100644 index 00000000..3ddaec14 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/NoiseGeneratorSettingsMixin.java @@ -0,0 +1,32 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.surfaceRules.SurfaceRuleProvider; + +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.SurfaceRules; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(NoiseGeneratorSettings.class) +public class NoiseGeneratorSettingsMixin implements SurfaceRuleProvider { + @Mutable + @Final + @Shadow + private SurfaceRules.RuleSource surfaceRule; + + public void bclib_overwrite(SurfaceRules.RuleSource surfaceRule) { + if (surfaceRule == this.surfaceRule) return; + if (this.bcl_containsOverride) { + WorldsTogether.LOGGER.warning("Overwriting an overwritten set of Surface Rules."); + } + this.bcl_containsOverride = true; + this.surfaceRule = surfaceRule; + } + + private boolean bcl_containsOverride = false; + +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/PrimaryLevelDataMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/PrimaryLevelDataMixin.java new file mode 100644 index 00000000..00b58840 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/PrimaryLevelDataMixin.java @@ -0,0 +1,69 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import com.mojang.datafixers.DataFixer; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.Lifecycle; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.world.level.LevelSettings; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.storage.LevelVersion; +import net.minecraft.world.level.storage.PrimaryLevelData; + +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.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +@Mixin(PrimaryLevelData.class) +public class PrimaryLevelDataMixin { + @Shadow + @Final + private WorldGenSettings worldGenSettings; + private static final ThreadLocal>> bcl_lastRegistryAccess = ThreadLocal.withInitial( + () -> Optional.empty()); + + //This is the way a created (new) world is initializing the PrimaryLevelData + @ModifyArg(method = "(Lnet/minecraft/world/level/LevelSettings;Lnet/minecraft/world/level/levelgen/WorldGenSettings;Lcom/mojang/serialization/Lifecycle;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/PrimaryLevelData;(Lcom/mojang/datafixers/DataFixer;ILnet/minecraft/nbt/CompoundTag;ZIIIFJJIIIZIZZZLnet/minecraft/world/level/border/WorldBorder$Settings;IILjava/util/UUID;Ljava/util/Set;Lnet/minecraft/world/level/timers/TimerQueue;Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/level/LevelSettings;Lnet/minecraft/world/level/levelgen/WorldGenSettings;Lcom/mojang/serialization/Lifecycle;)V")) + private static WorldGenSettings bcl_fixOtherSettings(WorldGenSettings worldGenSettings) { + return WorldBootstrap.enforceInNewWorld(worldGenSettings); + } + + @Inject(method = "parse", at = @At("HEAD")) + private static void bcl_parse( + Dynamic dynamic, + DataFixer dataFixer, + int i, + @Nullable CompoundTag compoundTag, + LevelSettings levelSettings, + LevelVersion levelVersion, + WorldGenSettings worldGenSettings, + Lifecycle lifecycle, + CallbackInfoReturnable cir + ) { + if (dynamic.getOps() instanceof RegistryOps regOps) { + bcl_lastRegistryAccess.set(Optional.of(regOps)); + } + } + + + //This is the way a loaded (existing) world is initializing the PrimaryLevelData + @ModifyArg(method = "parse", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/PrimaryLevelData;(Lcom/mojang/datafixers/DataFixer;ILnet/minecraft/nbt/CompoundTag;ZIIIFJJIIIZIZZZLnet/minecraft/world/level/border/WorldBorder$Settings;IILjava/util/UUID;Ljava/util/Set;Lnet/minecraft/world/level/timers/TimerQueue;Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/level/LevelSettings;Lnet/minecraft/world/level/levelgen/WorldGenSettings;Lcom/mojang/serialization/Lifecycle;)V")) + private static WorldGenSettings bcl_fixSettings(WorldGenSettings settings) { + Optional> registryOps = bcl_lastRegistryAccess.get(); + WorldBootstrap.InGUI.registryReadyOnLoadedWorld(registryOps); + settings = WorldBootstrap.enforceInLoadedWorld(registryOps, settings); + bcl_lastRegistryAccess.set(Optional.empty()); + return settings; + } + +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/RegistryAccessMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/RegistryAccessMixin.java new file mode 100644 index 00000000..bd8a2649 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/RegistryAccessMixin.java @@ -0,0 +1,46 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.surfaceRules.AssignedSurfaceRule; +import org.betterx.worlds.together.surfaceRules.SurfaceRuleRegistry; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; + +import com.google.common.collect.ImmutableMap; +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.Map; +import java.util.function.Supplier; + +@Mixin(RegistryAccess.class) +public interface RegistryAccessMixin { + + @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;make(Ljava/util/function/Supplier;)Ljava/lang/Object;")) + private static Supplier>, RegistryAccess.RegistryData>> together_addRegistry( + Supplier>, RegistryAccess.RegistryData>> supplier + ) { + return () -> { + Map>, RegistryAccess.RegistryData> res = supplier.get(); + ImmutableMap.Builder>, RegistryAccess.RegistryData> builder = ImmutableMap.builder(); + + builder.putAll(res); + put(builder, SurfaceRuleRegistry.SURFACE_RULES_REGISTRY, AssignedSurfaceRule.CODEC); + return builder.build(); + }; + } + + @Shadow + static void put( + ImmutableMap.Builder>, RegistryAccess.RegistryData> builder, + ResourceKey> resourceKey, + Codec codec + ) { + throw new RuntimeException("Shadowed Call"); + } + +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/RegistryOpsAccessor.java b/src/main/java/org/betterx/worlds/together/mixin/common/RegistryOpsAccessor.java new file mode 100644 index 00000000..70a1da14 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/RegistryOpsAccessor.java @@ -0,0 +1,13 @@ +package org.betterx.worlds.together.mixin.common; + +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.RegistryOps; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(RegistryOps.class) +public interface RegistryOpsAccessor { + @Accessor("registryAccess") + RegistryAccess bcl_getRegistryAccess(); +} 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..0b7ab80a --- /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.Tag; +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.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/WorldGenPropertiesMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/WorldGenPropertiesMixin.java new file mode 100644 index 00000000..aee5be6d --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/WorldGenPropertiesMixin.java @@ -0,0 +1,28 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.worldPreset.WorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPresets; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.dedicated.DedicatedServerProperties; + +import com.google.gson.JsonObject; +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(DedicatedServerProperties.WorldGenProperties.class) +public class WorldGenPropertiesMixin { +// @ModifyArg(method = "create", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/presets/WorldPreset;createWorldGenSettings(JZZ)Lnet/minecraft/world/level/levelgen/WorldGenSettings;")) +// public long bcl_create(long seed) { +// return seed; +// } + + //Make sure Servers use our Default World Preset + @ModifyArg(method = "create", at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/core/Registry;getHolder(Lnet/minecraft/resources/ResourceKey;)Ljava/util/Optional;")) + private ResourceKey wt_returnDefault(ResourceKey resourceKey) { + return WorldPresets.getDEFAULT(); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/WorldPresetAccessor.java b/src/main/java/org/betterx/worlds/together/mixin/common/WorldPresetAccessor.java new file mode 100644 index 00000000..a2812163 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/WorldPresetAccessor.java @@ -0,0 +1,17 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.worldPreset.WorldPreset; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.dimension.LevelStem; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(WorldPreset.class) +public interface WorldPresetAccessor { + @Accessor("dimensions") + Map, LevelStem> bcl_getDimensions(); +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/WorldPresetMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/WorldPresetMixin.java new file mode 100644 index 00000000..ab260995 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/WorldPresetMixin.java @@ -0,0 +1,53 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.worldPreset.TogetherWorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPreset; + +import com.mojang.datafixers.kinds.App; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.dimension.LevelStem; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +@Mixin(WorldPreset.class) +public class WorldPresetMixin { + + + @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lcom/mojang/serialization/codecs/RecordCodecBuilder;create(Ljava/util/function/Function;)Lcom/mojang/serialization/Codec;")) + private static Function, ? extends App, WorldPreset>> foo( + Function, ? extends App, WorldPreset>> builder + ) { + final Function, App, WorldPreset>> CODEC_FUNCTION = builderInstance -> { + RecordCodecBuilder, LevelStem>> dimensionsBuilder = Codec + .unboundedMap( + ResourceKey.codec(Registry.LEVEL_STEM_REGISTRY), + LevelStem.CODEC + ) + .fieldOf("dimensions") + .forGetter((wp) -> (wp instanceof WorldPresetAccessor) + ? ((WorldPresetAccessor) wp).bcl_getDimensions() + : null); + + RecordCodecBuilder> sortBuilder = Codec.INT + .optionalFieldOf("sort_order") + .forGetter(wp -> (wp instanceof TogetherWorldPreset) + ? Optional.of(((TogetherWorldPreset) wp).sortOrder) + : Optional.empty()); + + return builderInstance + .group(dimensionsBuilder, sortBuilder) + .apply(builderInstance, TogetherWorldPreset::new); + }; + + return CODEC_FUNCTION; + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/WorldPresetsBootstrapMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/WorldPresetsBootstrapMixin.java new file mode 100644 index 00000000..c75cb9ad --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/WorldPresetsBootstrapMixin.java @@ -0,0 +1,66 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.levelgen.WorldGenUtil; +import org.betterx.worlds.together.worldPreset.WorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPresets; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.synth.NormalNoise; + +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(WorldPreset.class) +public abstract class WorldPresetsBootstrapMixin { + + //see WorldPresets.register + + @Inject(method = "", at = @At(value = "TAIL")) + private static void bcl_getOverworldStem(CallbackInfo ci) { + Registry presets = WorldPresets.WORLD_PRESET; + Registry dimensionTypes = BuiltinRegistries.ACCESS.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY); + Registry biomes = BuiltinRegistries.BIOME; + Registry structureSets = BuiltinRegistries.STRUCTURE_SETS; + Registry noiseSettings = BuiltinRegistries.NOISE_GENERATOR_SETTINGS; + Registry noises = BuiltinRegistries.NOISE; + Holder overworldDimensionType = dimensionTypes.getOrCreateHolder(DimensionType.OVERWORLD_LOCATION); + Holder netherDimensionType = dimensionTypes.getOrCreateHolder(DimensionType.NETHER_LOCATION); + Holder netherNoiseSettings = noiseSettings.getOrCreateHolder(NoiseGeneratorSettings.NETHER); + Holder endDimensionType = dimensionTypes.getOrCreateHolder(DimensionType.END_LOCATION); + Holder endNoiseSettings = noiseSettings.getOrCreateHolder(NoiseGeneratorSettings.END); + + + LevelStem overworldStem = new LevelStem( + overworldDimensionType, + WorldGenSettings.makeDefaultOverworld(BuiltinRegistries.ACCESS, 0) + ); + + WorldGenUtil.Context netherContext = new WorldGenUtil.Context( + biomes, + netherDimensionType, + structureSets, + noises, + netherNoiseSettings + ); + WorldGenUtil.Context endContext = new WorldGenUtil.Context( + biomes, + endDimensionType, + structureSets, + noises, + endNoiseSettings + ); + + WorldPresets.bootstrapPresets(presets, overworldStem, netherContext, endContext); + } + +} diff --git a/src/main/java/org/betterx/worlds/together/surfaceRules/AssignedSurfaceRule.java b/src/main/java/org/betterx/worlds/together/surfaceRules/AssignedSurfaceRule.java new file mode 100644 index 00000000..58d09ddd --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/surfaceRules/AssignedSurfaceRule.java @@ -0,0 +1,26 @@ +package org.betterx.worlds.together.surfaceRules; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.SurfaceRules; + +public class AssignedSurfaceRule { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + SurfaceRules.RuleSource.CODEC.fieldOf("ruleSource").forGetter(o -> o.ruleSource), + ResourceLocation.CODEC.fieldOf("biome").forGetter(o -> o.biomeID) + ) + .apply(instance, AssignedSurfaceRule::new) + ); + + public final SurfaceRules.RuleSource ruleSource; + public final ResourceLocation biomeID; + + AssignedSurfaceRule(SurfaceRules.RuleSource ruleSource, ResourceLocation biomeID) { + this.ruleSource = ruleSource; + this.biomeID = biomeID; + } + + +} diff --git a/src/main/java/org/betterx/worlds/together/surfaceRules/SurfaceRuleProvider.java b/src/main/java/org/betterx/worlds/together/surfaceRules/SurfaceRuleProvider.java new file mode 100644 index 00000000..0f7d51af --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/surfaceRules/SurfaceRuleProvider.java @@ -0,0 +1,7 @@ +package org.betterx.worlds.together.surfaceRules; + +import net.minecraft.world.level.levelgen.SurfaceRules; + +public interface SurfaceRuleProvider { + void bclib_overwrite(SurfaceRules.RuleSource surfaceRule); +} diff --git a/src/main/java/org/betterx/worlds/together/surfaceRules/SurfaceRuleRegistry.java b/src/main/java/org/betterx/worlds/together/surfaceRules/SurfaceRuleRegistry.java new file mode 100644 index 00000000..3c3b9ba8 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/surfaceRules/SurfaceRuleRegistry.java @@ -0,0 +1,64 @@ +package org.betterx.worlds.together.surfaceRules; + +import org.betterx.worlds.together.WorldsTogether; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.SurfaceRules; + +import java.util.function.Predicate; +import org.jetbrains.annotations.ApiStatus; + +public class SurfaceRuleRegistry { + public static final ResourceKey> SURFACE_RULES_REGISTRY = + createRegistryKey(WorldsTogether.makeID("worldgen/betterx/surface_rules")); + public static final Predicate> NON_MANAGED_DIMENSIONS = dim -> dim != LevelStem.NETHER && dim != LevelStem.END; + public static final Predicate> ALL_DIMENSIONS = dim -> true; + + public static Registry BUILTIN_SURFACE_RULES; + + private static ResourceKey> createRegistryKey(ResourceLocation location) { + return ResourceKey.createRegistryKey(location); + } + + @ApiStatus.Internal + public static Holder bootstrap() { + return BuiltinRegistries.register( + BUILTIN_SURFACE_RULES, + WorldsTogether.makeID("dummy"), + new AssignedSurfaceRule( + SurfaceRules.state(Blocks.YELLOW_CONCRETE.defaultBlockState()), + WorldsTogether.makeID("none") + ) + ); + } + + public static ResourceKey registerRule( + ResourceLocation ruleID, + SurfaceRules.RuleSource rules, + ResourceLocation biomeID + ) { + ResourceKey key = ResourceKey.create( + SurfaceRuleRegistry.SURFACE_RULES_REGISTRY, + ruleID + ); + Registry.register(SurfaceRuleRegistry.BUILTIN_SURFACE_RULES, key, new AssignedSurfaceRule( + SurfaceRules.ifTrue( + SurfaceRules.isBiome(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID)), + rules + ), biomeID + ) + ); + return key; + } + + public static void ensureStaticallyLoaded() { + + } + +} diff --git a/src/main/java/org/betterx/worlds/together/surfaceRules/SurfaceRuleUtil.java b/src/main/java/org/betterx/worlds/together/surfaceRules/SurfaceRuleUtil.java new file mode 100644 index 00000000..2002bd12 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/surfaceRules/SurfaceRuleUtil.java @@ -0,0 +1,96 @@ +package org.betterx.worlds.together.surfaceRules; + +import org.betterx.worlds.together.chunkgenerator.InjectableSurfaceRules; +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +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.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.SurfaceRules; +import net.minecraft.world.level.levelgen.WorldGenSettings; + +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +public class SurfaceRuleUtil { + private static List getRulesForBiome(ResourceLocation biomeID) { + Registry registry = SurfaceRuleRegistry.BUILTIN_SURFACE_RULES; + if (WorldBootstrap.getLastRegistryAccess() != null) + registry = WorldBootstrap.getLastRegistryAccess() + .registryOrThrow(SurfaceRuleRegistry.SURFACE_RULES_REGISTRY); + return registry.stream() + .filter(a -> a != null && a.biomeID != null && a.biomeID.equals( + biomeID)) + .map(a -> a.ruleSource) + .toList(); + + } + + private static List getRulesForBiomes(List biomes) { + Registry biomeRegistry = WorldBootstrap.getLastRegistryAccess().registryOrThrow(Registry.BIOME_REGISTRY); + List biomeIDs = biomes.stream() + .map(b -> biomeRegistry.getKey(b)) + .filter(id -> id != null) + .toList(); + + return biomeIDs.stream() + .map(biomeID -> getRulesForBiome(biomeID)) + .flatMap(List::stream) + .collect(Collectors.toCollection(LinkedList::new)); + } + + private static SurfaceRules.RuleSource mergeSurfaceRulesFromBiomes( + SurfaceRules.RuleSource org, + BiomeSource source + ) { + return mergeSurfaceRules( + org, + getRulesForBiomes(source.possibleBiomes().stream().map(h -> h.value()).toList()) + ); + } + + private static SurfaceRules.RuleSource mergeSurfaceRules( + SurfaceRules.RuleSource org, + List additionalRules + ) { + if (additionalRules == null || additionalRules.isEmpty()) return org; + + if (org instanceof SurfaceRules.SequenceRuleSource sequenceRule) { + List existingSequence = sequenceRule.sequence(); + additionalRules = additionalRules + .stream() + .filter(r -> existingSequence.indexOf(r) < 0) + .collect(Collectors.toList()); + if (additionalRules.size() == 0) return org; + additionalRules.addAll(existingSequence); + } else { + if (!additionalRules.contains(org)) + additionalRules.add(org); + } + + return new SurfaceRules.SequenceRuleSource(additionalRules); + } + + public static void injectSurfaceRules(NoiseGeneratorSettings noiseSettings, BiomeSource loadedBiomeSource) { + if (((Object) noiseSettings) instanceof SurfaceRuleProvider srp) { + SurfaceRules.RuleSource originalRules = noiseSettings.surfaceRule(); + srp.bclib_overwrite(mergeSurfaceRulesFromBiomes(originalRules, loadedBiomeSource)); + } + } + + public static void injectSurfaceRulesToAllDimensions(WorldGenSettings settings) { + for (var entry : settings.dimensions().entrySet()) { + ResourceKey key = entry.getKey(); + LevelStem stem = entry.getValue(); + + if (stem.generator() instanceof InjectableSurfaceRules generator) { + generator.injectSurfaceRules(key); + } + } + } +} 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..8976ddfe --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/CommonBiomeTags.java @@ -0,0 +1,12 @@ +package org.betterx.worlds.together.tag.v3; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.biome.Biome; + +public class CommonBiomeTags { + public static final TagKey IN_NETHER = TagManager.BIOMES.makeCommonTag("in_nether"); + public static final TagKey IN_END = TagManager.BIOMES.makeCommonTag("in_end"); + + static void prepareTags() { + } +} 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..9a6064aa --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/CommonBlockTags.java @@ -0,0 +1,117 @@ +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 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 END_ORES = TagManager.BLOCKS.makeCommonTag("end_ores"); + public static final TagKey SAPLINGS = TagManager.BLOCKS.makeCommonTag("saplings"); + 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 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 NETHER_TERRAIN = TagManager.BLOCKS.makeCommonTag("nether_terrain"); + + public static final TagKey NETHER_CARVER_REPLACEABLES = TagManager.BLOCKS.makeCommonTag( + "nether_carver_replaceables"); + + static void prepareTags() { + 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.add( + TERRAIN, + Blocks.MAGMA_BLOCK, + Blocks.GRAVEL, + Blocks.SAND, + Blocks.RED_SAND, + Blocks.GLOWSTONE, + Blocks.BONE_BLOCK + ); + 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( + NETHER_CARVER_REPLACEABLES, + Blocks.BASALT, + Blocks.RED_SAND, + Blocks.MAGMA_BLOCK + ); + TagManager.BLOCKS.addOtherTags( + NETHER_CARVER_REPLACEABLES, + CommonBlockTags.NETHER_STONES, + CommonBlockTags.NETHERRACK + ); + } +} 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..22445500 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/CommonItemTags.java @@ -0,0 +1,29 @@ +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 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"); + + 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()); + } +} 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..38f878ea --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/MineableTags.java @@ -0,0 +1,21 @@ +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"); + + 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..c79680ae --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/TagManager.java @@ -0,0 +1,124 @@ +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.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.tags.Tag; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; + +import com.google.common.collect.Maps; + +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(Registry.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)}. + *

+ * 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..9edc6f17 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/WorldConfig.java @@ -0,0 +1,165 @@ +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; + +/** + * 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; + + public static void load(File dataDir) { + WorldConfig.dataDir = 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.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/AdaptWorldPresetSettingEvent.java b/src/main/java/org/betterx/worlds/together/world/event/AdaptWorldPresetSettingEvent.java new file mode 100644 index 00000000..4c46b680 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/AdaptWorldPresetSettingEvent.java @@ -0,0 +1,17 @@ +package org.betterx.worlds.together.world.event; + +import org.betterx.worlds.together.worldPreset.WorldPreset; + +import net.minecraft.core.Holder; +import net.minecraft.world.level.levelgen.WorldGenSettings; + +import java.util.Optional; + +public class AdaptWorldPresetSettingEvent extends EventImpl { + public Optional> emit(Optional> start, WorldGenSettings settings) { + for (OnAdaptWorldPresetSettings a : handlers) { + start = a.adapt(start, settings); + } + return start; + } +} 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..3f0734e1 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/BeforeAddingTags.java @@ -0,0 +1,13 @@ +package org.betterx.worlds.together.world.event; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.Tag; + +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..2f3a3929 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/BeforeWorldLoad.java @@ -0,0 +1,16 @@ +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; + +public interface BeforeWorldLoad { + void prepareWorld( + LevelStorageSource.LevelStorageAccess storageAccess, + Map, ChunkGenerator> settings, + boolean isNewWorld + ); +} 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/OnAdaptWorldPresetSettings.java b/src/main/java/org/betterx/worlds/together/world/event/OnAdaptWorldPresetSettings.java new file mode 100644 index 00000000..e2bff07d --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/OnAdaptWorldPresetSettings.java @@ -0,0 +1,16 @@ +package org.betterx.worlds.together.world.event; + +import org.betterx.worlds.together.worldPreset.WorldPreset; + +import net.minecraft.core.Holder; +import net.minecraft.world.level.levelgen.WorldGenSettings; + +import java.util.Optional; + +@FunctionalInterface +public interface OnAdaptWorldPresetSettings { + Optional> adapt( + Optional> currentPreset, + WorldGenSettings worldGenSettings + ); +} 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..95d4e7c7 --- /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.resources.ResourceKey; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldGenSettings; + +@FunctionalInterface +public interface OnFinalizeLevelStem { + void now(WorldGenSettings worldGenSettings, ResourceKey dimensionKey, LevelStem stem); +} 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..8635459c --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/WorldBootstrap.java @@ -0,0 +1,321 @@ +package org.betterx.worlds.together.world.event; + +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.levelgen.WorldGenUtil; +import org.betterx.worlds.together.mixin.common.RegistryOpsAccessor; +import org.betterx.worlds.together.mixin.common.WorldPresetAccessor; +import org.betterx.worlds.together.surfaceRules.SurfaceRuleUtil; +import org.betterx.worlds.together.world.WorldConfig; +import org.betterx.worlds.together.worldPreset.TogetherWorldPreset; +import org.betterx.worlds.together.worldPreset.WorldGenSettingsComponentAccessor; +import org.betterx.worlds.together.worldPreset.WorldPreset; +import org.betterx.worlds.together.worldPreset.WorldPresets; + +import net.minecraft.client.gui.screens.worldselection.WorldGenSettingsComponent; +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.storage.LevelResource; +import net.minecraft.world.level.storage.LevelStorageSource; + +import java.io.File; +import java.util.Map; +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; + } + + public static RegistryAccess getLastRegistryAccessOrElseBuiltin() { + if (LAST_REGISTRY_ACCESS == null) return BuiltinRegistries.ACCESS; + 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")); + } + + private 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 defaultServerPreset() { + return WorldPresets.get( + LAST_REGISTRY_ACCESS, + WorldPresets.getDEFAULT() + ); + } + + private static Map, ChunkGenerator> defaultServerDimensions() { + final Holder defaultPreset = defaultServerPreset(); + return defaultServerDimensions(defaultPreset); + } + + private static Map, ChunkGenerator> defaultServerDimensions(Holder defaultPreset) { + final Map, ChunkGenerator> dimensions; + if (defaultPreset.value() instanceof TogetherWorldPreset t) { + dimensions = t.getDimensionsMap(); + } else { + dimensions = TogetherWorldPreset.getDimensionsMap(net.minecraft.world.level.levelgen.presets.WorldPresets.NORMAL); + } + return dimensions; + } + + private static Optional> presetFromDatapack(Optional> currentPreset) { + if (currentPreset.isPresent() && LAST_REGISTRY_ACCESS != null) { + var presetKey = currentPreset.get().unwrapKey(); + if (presetKey.isPresent()) { + Optional> newPreset = LAST_REGISTRY_ACCESS + .registryOrThrow(WorldPresets.WORLD_PRESET_REGISTRY) + .getHolder(presetKey.get()); + if (newPreset.isPresent()) currentPreset = newPreset; + } + } + return currentPreset; + } + } + + public static class DedicatedServer { + public static void registryReady(RegistryOps regOps) { + if (regOps instanceof RegistryOpsAccessor acc) { + Helpers.onRegistryReady(acc.bcl_getRegistryAccess()); + } + } + + 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"); + final Map, ChunkGenerator> settings = Helpers.defaultServerDimensions(); + + Helpers.initializeWorldConfig(levelStorageAccess, true); + WorldEventsImpl.BEFORE_SERVER_WORLD_LOAD.emit(e -> e.prepareWorld( + levelStorageAccess, settings, true + )); + } else { + Helpers.initializeWorldConfig(levelStorageAccess, false); + WorldEventsImpl.BEFORE_SERVER_WORLD_LOAD.emit(e -> e.prepareWorld( + levelStorageAccess, + TogetherWorldPreset.loadWorldDimensions(), + false + )); + WorldEventsImpl.ON_WORLD_LOAD.emit(OnWorldLoad::onLoad); + } + } + + //Needs to get called after setupWorld + public static void applyDatapackChangesOnNewWorld(WorldGenSettings worldGenSettings) { + Optional> currentPreset = Optional.of(Helpers.defaultServerPreset()); + currentPreset = WorldEventsImpl.ADAPT_WORLD_PRESET.emit(currentPreset, worldGenSettings); + + if (currentPreset.map(Holder::value).orElse(null) instanceof WorldPresetAccessor acc) { + TogetherWorldPreset.writeWorldPresetSettings(acc.bcl_getDimensions()); + } else { + WorldsTogether.LOGGER.error("Failed writing together File"); + TogetherWorldPreset.writeWorldPresetSettings(worldGenSettings.dimensions()); + } + WorldEventsImpl.ON_WORLD_LOAD.emit(OnWorldLoad::onLoad); + } + } + + public static class InGUI { + public static void registryReadyOnNewWorld(WorldGenSettingsComponent worldGenSettingsComponent) { + Helpers.onRegistryReady(worldGenSettingsComponent.registryHolder()); + } + + public static void registryReadyOnLoadedWorld(Optional> registryOps) { + if (registryOps.orElse(null) instanceof RegistryOpsAccessor acc) { + Helpers.onRegistryReady(acc.bcl_getRegistryAccess()); + } + } + + public static void registryReady(RegistryAccess access) { + Helpers.onRegistryReady(access); + } + + public static void setupNewWorld( + Optional levelStorageAccess, + WorldGenSettingsComponent worldGenSettingsComponent + ) { + if (levelStorageAccess.isPresent()) { + if (worldGenSettingsComponent instanceof WorldGenSettingsComponentAccessor acc) { + Optional> currentPreset = acc.bcl_getPreset(); + currentPreset = Helpers.presetFromDatapack(currentPreset); + Optional> newPreset = setupNewWorldCommon( + levelStorageAccess.get(), + currentPreset, + worldGenSettingsComponent.settings().worldGenSettings() + ); + if (newPreset != currentPreset) { + acc.bcl_setPreset(newPreset); + } + } else { + WorldsTogether.LOGGER.error("Unable to access WorldGenSettingsComponent."); + } + } else { + WorldsTogether.LOGGER.error("Unable to access Level Folder."); + } + + } + + static Optional> setupNewWorldCommon( + LevelStorageSource.LevelStorageAccess levelStorageAccess, + Optional> currentPreset, + WorldGenSettings worldGenSettings + ) { + Helpers.initializeWorldConfig(levelStorageAccess, true); + + + final Map, ChunkGenerator> dimensions; + if (currentPreset.map(Holder::value).orElse(null) instanceof TogetherWorldPreset t) { + dimensions = t.getDimensionsMap(); + } else { + dimensions = TogetherWorldPreset.getDimensionsMap(net.minecraft.world.level.levelgen.presets.WorldPresets.NORMAL); + } + + // Helpers.setupWorld(); + // DataFixerAPI.initializePatchData(); + WorldEventsImpl.BEFORE_WORLD_LOAD.emit(e -> e.prepareWorld( + levelStorageAccess, + dimensions, + true + )); + + currentPreset = WorldEventsImpl.ADAPT_WORLD_PRESET.emit(currentPreset, worldGenSettings); + + if (currentPreset.map(Holder::value).orElse(null) instanceof WorldPresetAccessor acc) { + TogetherWorldPreset.writeWorldPresetSettings(acc.bcl_getDimensions()); + } else { + WorldsTogether.LOGGER.error("Failed writing together File"); + TogetherWorldPreset.writeWorldPresetSettings(worldGenSettings.dimensions()); + } + + //LifeCycleAPI._runBeforeLevelLoad(); + WorldEventsImpl.ON_WORLD_LOAD.emit(OnWorldLoad::onLoad); + + return currentPreset; + } + + /** + * Does not call {@link WorldEventsImpl#ON_WORLD_LOAD} + */ + public static void setupLoadedWorld( + String levelID, + LevelStorageSource levelSource + ) { + try { + var levelStorageAccess = levelSource.createAccess(levelID); + try { + Helpers.initializeWorldConfig(levelStorageAccess, false); + + //Helpers.setupWorld(); + WorldEventsImpl.BEFORE_WORLD_LOAD.emit(e -> e.prepareWorld( + levelStorageAccess, + TogetherWorldPreset.loadWorldDimensions(), + false + )); + } catch (Exception e) { + WorldsTogether.LOGGER.error("Failed to initialize data in world", e); + } + 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; + } + + public static void finishedWorldLoad( + String levelID, + LevelStorageSource levelSource + ) { + //LifeCycleAPI._runBeforeLevelLoad(); + WorldEventsImpl.ON_WORLD_LOAD.emit(OnWorldLoad::onLoad); + } + } + + public static class InFreshLevel { + public static void setupNewWorld( + String levelID, + WorldGenSettings worldGenSettings, + LevelStorageSource levelSource, + Optional> worldPreset + ) { + try { + var levelStorageAccess = levelSource.createAccess(levelID); + InGUI.setupNewWorldCommon(levelStorageAccess, worldPreset, worldGenSettings); + levelStorageAccess.close(); + } catch (Exception e) { + WorldsTogether.LOGGER.error("Failed to initialize data in world", e); + } + } + } + + public static void finalizeWorldGenSettings(WorldGenSettings worldGenSettings) { + for (var entry : worldGenSettings.dimensions().entrySet()) { + WorldEventsImpl.ON_FINALIZE_LEVEL_STEM.emit(e -> e.now( + worldGenSettings, + entry.getKey(), + entry.getValue() + )); + } + SurfaceRuleUtil.injectSurfaceRulesToAllDimensions(worldGenSettings); + } + + public static WorldGenSettings enforceInNewWorld(WorldGenSettings worldGenSettings) { + return WorldGenUtil.repairBiomeSourceInAllDimensions(LAST_REGISTRY_ACCESS, worldGenSettings); + } + + public static WorldGenSettings enforceInLoadedWorld( + Optional> registryOps, + WorldGenSettings worldGenSettings + ) { + if (registryOps.orElse(null) instanceof RegistryOpsAccessor acc) { + return WorldGenUtil.repairBiomeSourceInAllDimensions(acc.bcl_getRegistryAccess(), worldGenSettings); + //.repairSettingsOnLoad(LAST_REGISTRY_ACCESS, worldGenSettings); + } else { + WorldsTogether.LOGGER.error("Unable to obtain registryAccess when enforcing generators."); + } + return worldGenSettings; + } + +} 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..fa0ff83a --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/WorldEvents.java @@ -0,0 +1,13 @@ +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 BEFORE_SERVER_WORLD_LOAD = WorldEventsImpl.BEFORE_SERVER_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 PATCH_WORLD = WorldEventsImpl.PATCH_WORLD; + public static final Event ADAPT_WORLD_PRESET = WorldEventsImpl.ADAPT_WORLD_PRESET; + + 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..cc1e93ab --- /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 BEFORE_SERVER_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 PatchWorldEvent PATCH_WORLD = new PatchWorldEvent(); + public static final AdaptWorldPresetSettingEvent ADAPT_WORLD_PRESET = new AdaptWorldPresetSettingEvent(); + + public static final EventImpl BEFORE_ADDING_TAGS = new EventImpl<>(); +} diff --git a/src/main/java/org/betterx/worlds/together/worldPreset/TogetherWorldPreset.java b/src/main/java/org/betterx/worlds/together/worldPreset/TogetherWorldPreset.java new file mode 100644 index 00000000..2d5dc3fa --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/worldPreset/TogetherWorldPreset.java @@ -0,0 +1,190 @@ +package org.betterx.worlds.together.worldPreset; + +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.levelgen.WorldGenUtil; +import org.betterx.worlds.together.mixin.common.WorldPresetAccessor; +import org.betterx.worlds.together.world.WorldConfig; +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.LevelStem; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +public class TogetherWorldPreset extends org.betterx.worlds.together.worldPreset.WorldPreset { + public final int sortOrder; + + private static int NEXT_IN_SORT_ORDER = 1000; + + public TogetherWorldPreset( + Map, LevelStem> map, + Optional sortOrder + ) { + this(map, sortOrder.orElse(NEXT_IN_SORT_ORDER++)); + } + + public TogetherWorldPreset( + Map, LevelStem> map, + int sortOrder + ) { + super(map); + this.sortOrder = sortOrder; + } + + public TogetherWorldPreset withDimensions(Registry dimensions) { + Map, LevelStem> map = new HashMap<>(); + for (var entry : dimensions.entrySet()) { + ResourceKey key = entry.getKey(); + LevelStem stem = entry.getValue(); + map.put(key, stem); + } + return new TogetherWorldPreset(map, sortOrder); + } + + private Map, LevelStem> getDimensions() { + return ((WorldPresetAccessor) this).bcl_getDimensions(); + } + + public Map, ChunkGenerator> getDimensionsMap() { + return DimensionsWrapper.build(getDimensions()); + } + + public LevelStem getDimension(ResourceKey key) { + return getDimensions().get(key); + } + + public static void writeWorldPresetSettings(Registry dimensions) { + DimensionsWrapper wrapper = new DimensionsWrapper(dimensions); + writeWorldPresetSettings(wrapper); + } + + public static void writeWorldPresetSettings(Map, LevelStem> settings) { + DimensionsWrapper wrapper = new DimensionsWrapper(DimensionsWrapper.build(settings)); + writeWorldPresetSettings(wrapper); + } + + public static void writeWorldPresetSettingsDirect(Map, ChunkGenerator> settings) { + DimensionsWrapper wrapper = new DimensionsWrapper(settings); + writeWorldPresetSettings(wrapper); + } + + private static void writeWorldPresetSettings(DimensionsWrapper wrapper) { + final RegistryOps registryOps = RegistryOps.create( + NbtOps.INSTANCE, + WorldBootstrap.getLastRegistryAccessOrElseBuiltin() + ); + final var encodeResult = DimensionsWrapper.CODEC.encodeStart(registryOps, wrapper); + + if (encodeResult.result().isPresent()) { + final CompoundTag settingsNbt = WorldConfig.getRootTag(WorldsTogether.MOD_ID); + settingsNbt.put(WorldGenUtil.TAG_PRESET, encodeResult.result().get()); + } else { + WorldsTogether.LOGGER.error("Unable to encode world generator settings for level.dat."); + } + + WorldConfig.saveFile(WorldsTogether.MOD_ID); + } + + private static DimensionsWrapper DEFAULT_DIMENSIONS_WRAPPER = null; + + public static @NotNull Map, ChunkGenerator> loadWorldDimensions() { + final RegistryAccess registryAccess = WorldBootstrap.getLastRegistryAccessOrElseBuiltin(); + final RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, registryAccess); + if (DEFAULT_DIMENSIONS_WRAPPER == null) { + DEFAULT_DIMENSIONS_WRAPPER = new DimensionsWrapper(TogetherWorldPreset.getDimensionsMap(WorldPresets.getDEFAULT())); + } + + CompoundTag presetNBT = WorldGenUtil.getPresetsNbt(); + if (!presetNBT.contains("dimensions")) { + return DEFAULT_DIMENSIONS_WRAPPER.dimensions; + } + + Optional oLevelStem = DimensionsWrapper.CODEC + .parse(new Dynamic<>(registryOps, presetNBT)) + .resultOrPartial(WorldsTogether.LOGGER::error); + + + return oLevelStem.orElse(DEFAULT_DIMENSIONS_WRAPPER).dimensions; + } + + public static Registry getDimensions(ResourceKey key) { + RegistryAccess access = WorldBootstrap.getLastRegistryAccessOrElseBuiltin(); + var preset = access.registryOrThrow(WorldPresets.WORLD_PRESET_REGISTRY).getHolder(key); + if (preset.isEmpty()) return null; + return preset + .get() + .value() + .createWorldGenSettings( + 0, + true, + true + ) + .dimensions(); + } + + public static @NotNull Map, ChunkGenerator> getDimensionsMap(ResourceKey key) { + Registry reg = getDimensions(key); + if (reg == null) return new HashMap<>(); + return DimensionsWrapper.build(reg); + } + + @Override + public ChunkGenerator generator(RegistryAccess registryAccess, long l) { + return null; + } + + private static class DimensionsWrapper { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group(Codec.unboundedMap( + ResourceKey.codec(Registry.LEVEL_STEM_REGISTRY), + ChunkGenerator.CODEC + ) + .fieldOf("dimensions") + .orElse(new HashMap<>()) + .forGetter(o -> o.dimensions)) + .apply(instance, DimensionsWrapper::new)); + final Map, ChunkGenerator> dimensions; + + static Map, ChunkGenerator> build(Registry dimensions) { + Map, ChunkGenerator> map = new HashMap<>(); + for (var entry : dimensions.entrySet()) { + ResourceKey key = entry.getKey(); + LevelStem stem = entry.getValue(); + map.put(key, stem.generator()); + } + return map; + } + + static Map, ChunkGenerator> build(Map, LevelStem> input) { + Map, ChunkGenerator> map = new HashMap<>(); + for (var entry : input.entrySet()) { + ResourceKey key = entry.getKey(); + LevelStem stem = entry.getValue(); + map.put(key, stem.generator()); + } + return map; + } + + + DimensionsWrapper(Registry dimensions) { + this(build(dimensions)); + } + + private DimensionsWrapper(Map, ChunkGenerator> dimensions) { + this.dimensions = dimensions; + } + } +} diff --git a/src/main/java/org/betterx/worlds/together/worldPreset/WorldGenSettingsComponentAccessor.java b/src/main/java/org/betterx/worlds/together/worldPreset/WorldGenSettingsComponentAccessor.java new file mode 100644 index 00000000..7265ab98 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/worldPreset/WorldGenSettingsComponentAccessor.java @@ -0,0 +1,10 @@ +package org.betterx.worlds.together.worldPreset; + +import net.minecraft.core.Holder; + +import java.util.Optional; + +public interface WorldGenSettingsComponentAccessor { + Optional> bcl_getPreset(); + void bcl_setPreset(Optional> preset); +} diff --git a/src/main/java/org/betterx/worlds/together/worldPreset/WorldPreset.java b/src/main/java/org/betterx/worlds/together/worldPreset/WorldPreset.java new file mode 100644 index 00000000..087350d5 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/worldPreset/WorldPreset.java @@ -0,0 +1,16 @@ +package org.betterx.worlds.together.worldPreset; + +import net.minecraft.core.RegistryAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; + +public class WorldPreset { + private final net.minecraft.client.gui.screens.worldselection.WorldPreset parent; + + public WorldPreset(net.minecraft.client.gui.screens.worldselection.WorldPreset parent) { + this.parent = parent; + } + + protected ChunkGenerator generator(RegistryAccess registryAccess, long l) { + return null; + } +} diff --git a/src/main/java/org/betterx/worlds/together/worldPreset/WorldPresetTags.java b/src/main/java/org/betterx/worlds/together/worldPreset/WorldPresetTags.java new file mode 100644 index 00000000..c0b22d1e --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/worldPreset/WorldPresetTags.java @@ -0,0 +1,9 @@ +package org.betterx.worlds.together.worldPreset; + +import org.betterx.worlds.together.WorldsTogether; + +import net.minecraft.tags.TagKey; + +public class WorldPresetTags { + public static final TagKey NORMAL = WorldPresets.WORLD_PRESETS.makeTag(WorldsTogether.makeID("normal")); +} diff --git a/src/main/java/org/betterx/worlds/together/worldPreset/WorldPresets.java b/src/main/java/org/betterx/worlds/together/worldPreset/WorldPresets.java new file mode 100644 index 00000000..6abbf07f --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/worldPreset/WorldPresets.java @@ -0,0 +1,179 @@ +package org.betterx.worlds.together.worldPreset; + +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.entrypoints.EntrypointUtil; +import org.betterx.worlds.together.entrypoints.WorldPresetBootstrap; +import org.betterx.worlds.together.levelgen.WorldGenUtil; +import org.betterx.worlds.together.tag.v3.TagManager; +import org.betterx.worlds.together.tag.v3.TagRegistry; +import org.betterx.worlds.together.worldPreset.client.WorldPresetsClient; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.data.BuiltinRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.dimension.LevelStem; + +import com.google.common.collect.Maps; + +import java.util.Map; +import org.jetbrains.annotations.ApiStatus; + +public class WorldPresets { + public static final ResourceKey> WORLD_PRESET_REGISTRY + = ResourceKey.createRegistryKey(WorldsTogether.makeID("worldgen/world_preset")); + + public static final Registry WORLD_PRESET + = Registry.registerSimple(WORLD_PRESET_REGISTRY, (registry) -> null); + + public static final TagRegistry.Simple WORLD_PRESETS = + TagManager.registerType(WORLD_PRESET, "tags/worldgen/world_preset"); + + + private static Map, PresetBuilder> BUILDERS = Maps.newHashMap(); + + private static ResourceKey NORMAL = ResourceKey.create( + WORLD_PRESET_REGISTRY, + WorldsTogether.makeID("vanilla_normal") + ); + private static ResourceKey LARGE_BIOMES = ResourceKey.create( + WORLD_PRESET_REGISTRY, + WorldsTogether.makeID("vanilla_large_biomes") + ); + private static ResourceKey AMPLIFIED = ResourceKey.create( + WORLD_PRESET_REGISTRY, + WorldsTogether.makeID("vanilla_amplified") + ); + private static ResourceKey FLAT = ResourceKey.create( + WORLD_PRESET_REGISTRY, + WorldsTogether.makeID("vanilla_flat") + ); + private static ResourceKey SINGLE_BIOME_SURFACE = ResourceKey.create( + WORLD_PRESET_REGISTRY, + WorldsTogether.makeID("vanilla_single_biome_surface") + ); + private static ResourceKey DEFAULT = NORMAL; + + public static Holder get(RegistryAccess access, ResourceKey key) { + return ((access != null) ? access : BuiltinRegistries.ACCESS) + .registryOrThrow(WORLD_PRESET_REGISTRY) + .getHolderOrThrow(key); + } + + /** + * Registers a custom WorldPreset (with custom rules and behaviour) + *

+ * See also {@link WorldPresetsClient} if you need to add a Customize Button/Screen + * for your preset + * + * @param loc The ID of your Preset + * @param visibleInUI if true, the preset will show up in the UI on world creataion + * @return The key you may use to reference your new Preset + */ + private static ResourceKey register(ResourceLocation loc, boolean visibleInUI) { + ResourceKey key = ResourceKey.create(WORLD_PRESET_REGISTRY, loc); + if (visibleInUI) { + if (!didExplicitlySetDefault && DEFAULT == NORMAL) { + DEFAULT = key; + } + WORLD_PRESETS.addUntyped(WorldPresetTags.NORMAL, key.location()); + } + + return key; + } + + public static void ensureStaticallyLoaded() { + + } + + public static ResourceKey register( + ResourceLocation loc, + PresetBuilder builder, + boolean visibleInUI + ) { + ResourceKey key = register(loc, visibleInUI); + + if (BUILDERS == null) { + WorldsTogether.LOGGER.error("Unable to register WorldPreset '" + loc + "'."); + + } else { + BUILDERS.put(key, builder); + } + return key; + } + + public static void bootstrapPresets( + Registry presets, + LevelStem overworldStem, + WorldGenUtil.Context netherContext, + WorldGenUtil.Context endContext + ) { + EntrypointUtil.getCommon(WorldPresetBootstrap.class) + .forEach(e -> e.bootstrapWorldPresets()); + + for (Map.Entry, PresetBuilder> e : BUILDERS.entrySet()) { + TogetherWorldPreset preset = e.getValue().create(overworldStem, netherContext, endContext); + BuiltinRegistries.register(presets, e.getKey(), preset); + } + BUILDERS = null; + } + + public static ResourceKey getDEFAULT() { + return DEFAULT; + } + + private static boolean didExplicitlySetDefault = false; + + @ApiStatus.Internal + public static void setDEFAULT(ResourceKey DEFAULT) { + didExplicitlySetDefault = true; + WorldPresets.DEFAULT = DEFAULT; + } + + + @FunctionalInterface + public interface PresetBuilder { + TogetherWorldPreset create( + LevelStem overworldStem, + WorldGenUtil.Context netherContext, + WorldGenUtil.Context endContext + ); + } + + static { + Registry.register( + WORLD_PRESET, + NORMAL, + new WorldPreset(net.minecraft.client.gui.screens.worldselection.WorldPreset.NORMAL) + ); + + Registry.register( + WORLD_PRESET, + LARGE_BIOMES, + new WorldPreset(net.minecraft.client.gui.screens.worldselection.WorldPreset.LARGE_BIOMES) + ); + + + Registry.register( + WORLD_PRESET, + AMPLIFIED, + new WorldPreset(net.minecraft.client.gui.screens.worldselection.WorldPreset.AMPLIFIED) + ); + + + Registry.register( + WORLD_PRESET, + FLAT, + new WorldPreset(net.minecraft.client.gui.screens.worldselection.WorldPreset.FLAT) + ); + + + Registry.register( + WORLD_PRESET, + SINGLE_BIOME_SURFACE, + new WorldPreset(net.minecraft.client.gui.screens.worldselection.WorldPreset.SINGLE_BIOME_SURFACE) + ); + } +} diff --git a/src/main/java/org/betterx/worlds/together/worldPreset/client/WorldPresetsClient.java b/src/main/java/org/betterx/worlds/together/worldPreset/client/WorldPresetsClient.java new file mode 100644 index 00000000..224ccd08 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/worldPreset/client/WorldPresetsClient.java @@ -0,0 +1,22 @@ +package org.betterx.worlds.together.worldPreset.client; + + +import org.betterx.worlds.together.worldPreset.WorldPreset; +import net.minecraft.resources.ResourceKey; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Optional; + +@Environment(EnvType.CLIENT) +public class WorldPresetsClient { + public static void registerCustomizeUI(ResourceKey key, WorldPreset.PresetEditor setupScreen) { + if (setupScreen != null) { + PresetEditor.EDITORS.put(Optional.of(key), setupScreen); + } + } + + public static void setupClientside() { + } +} 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 c181a7a7..00000000 --- a/src/main/java/ru/bclib/blockentities/BaseBarrelBlockEntity.java +++ /dev/null @@ -1,140 +0,0 @@ -package ru.bclib.blockentities; - -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.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, 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); - } - - public CompoundTag save(CompoundTag tag) { - super.save(tag); - if (!this.trySaveLootTable(tag)) { - ContainerHelper.saveAllItems(tag, this.inventory); - } - - return tag; - } - - 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); - } - } - - 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) { - 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); - 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 97581739..00000000 --- a/src/main/java/ru/bclib/blockentities/BaseChestBlockEntity.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.bclib.blockentities; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.entity.ChestBlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.registry.BaseBlockEntities; - -public class BaseChestBlockEntity extends ChestBlockEntity { - public BaseChestBlockEntity(BlockPos blockPos, BlockState blockState) { - super(BaseBlockEntities.CHEST, blockPos, blockState); - } -} 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 fa681007..00000000 --- a/src/main/java/ru/bclib/blockentities/BaseFurnaceBlockEntity.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.bclib.blockentities; - -import net.minecraft.core.BlockPos; -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 net.minecraft.world.level.block.state.BlockState; -import ru.bclib.registry.BaseBlockEntities; - -public class BaseFurnaceBlockEntity extends AbstractFurnaceBlockEntity { - public BaseFurnaceBlockEntity(BlockPos blockPos, BlockState blockState) { - super(BaseBlockEntities.FURNACE, blockPos, blockState, 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 64a9b75f..00000000 --- a/src/main/java/ru/bclib/blockentities/BaseSignBlockEntity.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.bclib.blockentities; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.entity.SignBlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.registry.BaseBlockEntities; - -public class BaseSignBlockEntity extends SignBlockEntity { - public BaseSignBlockEntity(BlockPos blockPos, BlockState blockState) { - super(blockPos, blockState); - } - - @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 2f992c86..00000000 --- a/src/main/java/ru/bclib/blockentities/DynamicBlockEntityType.java +++ /dev/null @@ -1,45 +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.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 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/ru/bclib/blocks/BaseAnvilBlock.java b/src/main/java/ru/bclib/blocks/BaseAnvilBlock.java deleted file mode 100644 index 5dafc0d5..00000000 --- a/src/main/java/ru/bclib/blocks/BaseAnvilBlock.java +++ /dev/null @@ -1,96 +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).mapColor(color)); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(DESTRUCTION); - } - - @Override - @SuppressWarnings("deprecation") - 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 ecf12d48..00000000 --- a/src/main/java/ru/bclib/blocks/BaseAttachedBlock.java +++ /dev/null @@ -1,76 +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; - -@SuppressWarnings("deprecation") -public abstract class BaseAttachedBlock extends BaseBlockNotFull { - public static final DirectionProperty FACING = BlockStateProperties.FACING; - - public 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); - } -} 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 b36f8843..00000000 --- a/src/main/java/ru/bclib/blocks/BaseBarrelBlock.java +++ /dev/null @@ -1,139 +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(BlockPos blockPos, BlockState blockState) { - return BaseBlockEntities.BARREL.create(blockPos, blockState); - } - - @Override - @SuppressWarnings("deprecation") - 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 97878a79..00000000 --- a/src/main/java/ru/bclib/blocks/BaseBlock.java +++ /dev/null @@ -1,29 +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 - @SuppressWarnings("deprecation") - 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 718a58f7..00000000 --- a/src/main/java/ru/bclib/blocks/BaseBlockWithEntity.java +++ /dev/null @@ -1,29 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; - -import net.minecraft.core.BlockPos; -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(BlockPos blockPos, BlockState blockState) { - return null; - } - - @Override - @SuppressWarnings("deprecation") - 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 5f8642ce..00000000 --- a/src/main/java/ru/bclib/blocks/BaseButtonBlock.java +++ /dev/null @@ -1,86 +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 - @SuppressWarnings("deprecation") - 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 c2a14fe3..00000000 --- a/src/main/java/ru/bclib/blocks/BaseChainBlock.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.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).mapColor(color)); - } - - @Override - @SuppressWarnings("deprecation") - 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 2b89b5d2..00000000 --- a/src/main/java/ru/bclib/blocks/BaseChestBlock.java +++ /dev/null @@ -1,63 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; -import java.util.Optional; - -import net.minecraft.core.BlockPos; -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(BlockPos blockPos, BlockState blockState) { - return BaseBlockEntities.CHEST.create(blockPos, blockState); - } - - @Override - @SuppressWarnings("deprecation") - 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 faecc6e5..00000000 --- a/src/main/java/ru/bclib/blocks/BaseComposterBlock.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.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 - @SuppressWarnings("deprecation") - 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 570758fe..00000000 --- a/src/main/java/ru/bclib/blocks/BaseCraftingTableBlock.java +++ /dev/null @@ -1,61 +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 - @SuppressWarnings("deprecation") - 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 e395d338..00000000 --- a/src/main/java/ru/bclib/blocks/BaseCropBlock.java +++ /dev/null @@ -1,123 +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 - @SuppressWarnings("deprecation") - 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 1c2c6245..00000000 --- a/src/main/java/ru/bclib/blocks/BaseDoorBlock.java +++ /dev/null @@ -1,164 +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 - @SuppressWarnings("deprecation") - 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 73f0ed6c..00000000 --- a/src/main/java/ru/bclib/blocks/BaseDoublePlantBlock.java +++ /dev/null @@ -1,150 +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; - -@SuppressWarnings("deprecation") -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 && FabricToolTags.SHEARS.contains(tool.getItem()) || 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 85adff6e..00000000 --- a/src/main/java/ru/bclib/blocks/BaseFenceBlock.java +++ /dev/null @@ -1,88 +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 - @SuppressWarnings("deprecation") - 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 925a3e45..00000000 --- a/src/main/java/ru/bclib/blocks/BaseFurnaceBlock.java +++ /dev/null @@ -1,124 +0,0 @@ -package ru.bclib.blocks; - -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.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.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import org.jetbrains.annotations.Nullable; -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; -import ru.bclib.registry.BaseBlockEntities; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -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(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, 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; - } - - @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); - } -} 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 d765ca2d..00000000 --- a/src/main/java/ru/bclib/blocks/BaseGateBlock.java +++ /dev/null @@ -1,75 +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 - @SuppressWarnings("deprecation") - 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 93939d8f..00000000 --- a/src/main/java/ru/bclib/blocks/BaseLadderBlock.java +++ /dev/null @@ -1,164 +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; - -@SuppressWarnings("deprecation") -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); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - return switch (state.getValue(FACING)) { - case SOUTH -> SOUTH_SHAPE; - case WEST -> WEST_SHAPE; - case EAST -> EAST_SHAPE; - default -> 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 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 4e644f8e..00000000 --- a/src/main/java/ru/bclib/blocks/BaseLeavesBlock.java +++ /dev/null @@ -1,80 +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) - .mapColor(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) - .mapColor(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 - @SuppressWarnings("deprecation") - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null) { - if (FabricToolTags.SHEARS.contains(tool.getItem()) || 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 d4ecb9bf..00000000 --- a/src/main/java/ru/bclib/blocks/BaseMetalBarsBlock.java +++ /dev/null @@ -1,113 +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 - @SuppressWarnings("deprecation") - 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() == 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 9f0bfaf2..00000000 --- a/src/main/java/ru/bclib/blocks/BaseOreBlock.java +++ /dev/null @@ -1,71 +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.util.valueproviders.UniformInt; -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; - - 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), UniformInt.of(1, experience)); - this.dropItem = drop; - this.minCount = minCount; - this.maxCount = maxCount; - } - - @Override - @SuppressWarnings("deprecation") - 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 b3181e9d..00000000 --- a/src/main/java/ru/bclib/blocks/BasePlantBlock.java +++ /dev/null @@ -1,129 +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; - -@SuppressWarnings("deprecation") -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 && FabricToolTags.SHEARS.contains(tool.getItem()) || 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 e78a0189..00000000 --- a/src/main/java/ru/bclib/blocks/BasePlantWithAgeBlock.java +++ /dev/null @@ -1,65 +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 - @SuppressWarnings("deprecation") - 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 9cd483f4..00000000 --- a/src/main/java/ru/bclib/blocks/BasePressurePlateBlock.java +++ /dev/null @@ -1,69 +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 - @SuppressWarnings("deprecation") - 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 e5d3af50..00000000 --- a/src/main/java/ru/bclib/blocks/BaseRotatedPillarBlock.java +++ /dev/null @@ -1,64 +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 - @SuppressWarnings("deprecation") - 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 5eeb16a0..00000000 --- a/src/main/java/ru/bclib/blocks/BaseSignBlock.java +++ /dev/null @@ -1,194 +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; - -@SuppressWarnings("deprecation") -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(BlockPos blockPos, BlockState blockState) { - return new BaseSignBlockEntity(blockPos, blockState); - } - - @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(placer.getUUID()); - ((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 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 73c2e109..00000000 --- a/src/main/java/ru/bclib/blocks/BaseSlabBlock.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.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 - @SuppressWarnings("deprecation") - 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 2f057e78..00000000 --- a/src/main/java/ru/bclib/blocks/BaseStairsBlock.java +++ /dev/null @@ -1,118 +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 - @SuppressWarnings("deprecation") - 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 a552e9fc..00000000 --- a/src/main/java/ru/bclib/blocks/BaseStripableLogBlock.java +++ /dev/null @@ -1,42 +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).mapColor(color)); - this.striped = striped; - } - - @Override - @SuppressWarnings("deprecation") - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - if (FabricToolTags.AXES.contains(player.getMainHandItem().getItem())) { - 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 fd0537c1..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 && FabricToolTags.SHOVELS.contains(player.getMainHandItem().getItem())) { - 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 b0167f0e..00000000 --- a/src/main/java/ru/bclib/blocks/BaseTrapdoorBlock.java +++ /dev/null @@ -1,95 +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 - @SuppressWarnings("deprecation") - 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 2254146b..00000000 --- a/src/main/java/ru/bclib/blocks/BaseUnderwaterWallPlantBlock.java +++ /dev/null @@ -1,60 +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 - @SuppressWarnings("deprecation") - 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 108dc183..00000000 --- a/src/main/java/ru/bclib/blocks/BaseVineBlock.java +++ /dev/null @@ -1,147 +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; - -@SuppressWarnings("deprecation") -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 && FabricToolTags.SHEARS.contains(tool.getItem()) || 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 031f4d31..00000000 --- a/src/main/java/ru/bclib/blocks/BaseWallBlock.java +++ /dev/null @@ -1,102 +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 - @SuppressWarnings("deprecation") - 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 9adb0c91..00000000 --- a/src/main/java/ru/bclib/blocks/BaseWallPlantBlock.java +++ /dev/null @@ -1,126 +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 - @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/ru/bclib/blocks/BaseWeightedPlateBlock.java b/src/main/java/ru/bclib/blocks/BaseWeightedPlateBlock.java deleted file mode 100644 index 28bbe68e..00000000 --- a/src/main/java/ru/bclib/blocks/BaseWeightedPlateBlock.java +++ /dev/null @@ -1,69 +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 - @SuppressWarnings("deprecation") - 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 38548c2f..00000000 --- a/src/main/java/ru/bclib/blocks/FeatureSaplingBlock.java +++ /dev/null @@ -1,124 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Random; - -import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; -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; - -@SuppressWarnings("deprecation") -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) { - FeaturePlaceContext context = new FeaturePlaceContext(world, world.getChunkSource().getGenerator(), random, pos, null); - getFeature().place(context); - } - - @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 ac46338c..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) - .mapColor(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) - .mapColor(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 50401565..00000000 --- a/src/main/java/ru/bclib/blocks/StalactiteBlock.java +++ /dev/null @@ -1,251 +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; - -@SuppressWarnings("deprecation") -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 d0195177..00000000 --- a/src/main/java/ru/bclib/blocks/StripableBarkBlock.java +++ /dev/null @@ -1,42 +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).mapColor(color)); - this.striped = striped; - } - - @Override - @SuppressWarnings("deprecation") - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - if (FabricToolTags.AXES.contains(player.getMainHandItem().getItem())) { - 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 4d168107..00000000 --- a/src/main/java/ru/bclib/blocks/UnderwaterPlantBlock.java +++ /dev/null @@ -1,142 +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; - -@SuppressWarnings("deprecation") -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 && FabricToolTags.SHEARS.contains(tool.getItem()) || 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 8b6499a2..00000000 --- a/src/main/java/ru/bclib/blocks/UnderwaterPlantWithAgeBlock.java +++ /dev/null @@ -1,57 +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 - @SuppressWarnings("deprecation") - 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 0773a45b..00000000 --- a/src/main/java/ru/bclib/blocks/UpDownPlantBlock.java +++ /dev/null @@ -1,93 +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; - -@SuppressWarnings("deprecation") -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 && FabricToolTags.SHEARS.contains(tool.getItem()) || 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 89954126..00000000 --- a/src/main/java/ru/bclib/client/gui/BlockSignEditScreen.java +++ /dev/null @@ -1,233 +0,0 @@ -package ru.bclib.client.gui; - -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.blaze3d.vertex.VertexFormat; -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.model.geom.ModelLayers; -import net.minecraft.client.multiplayer.ClientPacketListener; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.blockentity.SignRenderer; -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 net.minecraft.world.level.block.state.properties.WoodType; -import ru.bclib.blockentities.BaseSignBlockEntity; -import ru.bclib.blocks.BaseSignBlock; -import ru.bclib.client.render.BaseSignBlockEntityRenderer; - -import java.util.Arrays; - -@Environment(EnvType.CLIENT) -public class BlockSignEditScreen extends Screen { - 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, ""); - }); - private SignRenderer.SignModel model; - - public BlockSignEditScreen(BaseSignBlockEntity sign) { - super(new TranslatableComponent("sign.edit")); - this.sign = sign; - } - - protected void init() { - //set up a default model - model = new SignRenderer.SignModel(this.minecraft.getEntityModels().bakeLayer(ModelLayers.createSignModelName(WoodType.OAK))); - - minecraft.keyboardHandler.setSendRepeatsToGui(true); - this.addRenderableWidget(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())) { - 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.root.getChild("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(VertexFormat.Mode.QUADS, 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/BaseChestBlockModel.java b/src/main/java/ru/bclib/client/models/BaseChestBlockModel.java deleted file mode 100644 index ec5bf62d..00000000 --- a/src/main/java/ru/bclib/client/models/BaseChestBlockModel.java +++ /dev/null @@ -1,91 +0,0 @@ -package ru.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/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 96d59f04..00000000 --- a/src/main/java/ru/bclib/client/render/BaseChestBlockEntityRenderer.java +++ /dev/null @@ -1,130 +0,0 @@ -package ru.bclib.client.render; - -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.BlockEntityRenderer; -import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; -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.*; -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 ru.bclib.blockentities.BaseChestBlockEntity; -import ru.bclib.client.models.BaseChestBlockModel; - -import java.util.HashMap; - -@Environment(EnvType.CLIENT) -public class BaseChestBlockEntityRenderer implements 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 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 = ((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(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, 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 6d26f1d6..00000000 --- a/src/main/java/ru/bclib/client/render/BaseSignBlockEntityRenderer.java +++ /dev/null @@ -1,171 +0,0 @@ -package ru.bclib.client.render; - -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.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.model.geom.ModelLayers; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; -import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; -import net.minecraft.client.renderer.blockentity.SignRenderer; -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.util.Mth; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.item.DyeColor; -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 net.minecraft.world.phys.Vec3; -import ru.bclib.blockentities.BaseSignBlockEntity; -import ru.bclib.blocks.BaseSignBlock; - -import java.util.HashMap; -import java.util.List; - -public class BaseSignBlockEntityRenderer implements BlockEntityRenderer { - private static final HashMap LAYERS = Maps.newHashMap(); - private static final RenderType defaultLayer; - private final Font font; - private final SignRenderer.SignModel model; - - - private static final int OUTLINE_RENDER_DISTANCE = Mth.square(16); - - public BaseSignBlockEntityRenderer(BlockEntityRendererProvider.Context ctx) { - super(); - this.font = ctx.getFont(); - - //set up a default model - model = new SignRenderer.SignModel(ctx.bakeLayer(ModelLayers.createSignModelName(WoodType.OAK))); - } - - 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)); - model.stick.visible = true; - } else { - matrixStack.mulPose(Vector3f.YP.rotationDegrees(angle + 180)); - matrixStack.translate(0.0D, -0.3125D, -0.4375D); - model.stick.visible = false; - } - - matrixStack.pushPose(); - matrixStack.scale(0.6666667F, -0.6666667F, -0.6666667F); - VertexConsumer vertexConsumer = getConsumer(provider, state.getBlock()); - - model.root.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); - - FormattedCharSequence[] formattedCharSequences = signBlockEntity - .getRenderMessages(Minecraft.getInstance().isTextFilteringEnabled(), (component) -> { - List list = this.font.split(component, 90); - return list.isEmpty() ? FormattedCharSequence.EMPTY : (FormattedCharSequence) list.get(0); - }); - int drawColor; - boolean drawOutlined; - int drawLight; - if (signBlockEntity.hasGlowingText()) { - drawColor = signBlockEntity.getColor().getTextColor(); - drawOutlined = isOutlineVisible(signBlockEntity, drawColor); - drawLight = 15728880; - } else { - drawColor = m; - drawOutlined = false; - drawLight = light; - } - - for (int s = 0; s < 4; ++s) { - FormattedCharSequence formattedCharSequence = formattedCharSequences[s]; - float t = (float) (-this.font.width(formattedCharSequence) / 2); - if (drawOutlined) { - this.font.drawInBatch8xOutline(formattedCharSequence, t, (float) (s * 10 - 20), drawColor, m, - matrixStack.last().pose(), provider, drawLight); - } else { - this.font.drawInBatch((FormattedCharSequence) formattedCharSequence, t, (float) (s * 10 - 20), drawColor, false, - matrixStack.last().pose(), provider, false, 0, drawLight); - } - } - - - matrixStack.popPose(); - } - - - - private static boolean isOutlineVisible(BaseSignBlockEntity signBlockEntity, int i) { - if (i == DyeColor.BLACK.getTextColor()) { - return true; - } else { - Minecraft minecraft = Minecraft.getInstance(); - LocalPlayer localPlayer = minecraft.player; - if (localPlayer != null && minecraft.options.getCameraType().isFirstPerson() && localPlayer.isScoping()) { - return true; - } else { - Entity entity = minecraft.getCameraEntity(); - return entity != null && entity.distanceToSqr( - Vec3.atCenterOf(signBlockEntity.getBlockPos())) < (double) OUTLINE_RENDER_DISTANCE; - } - } - } - - public static WoodType getSignType(Block block) { - WoodType signType2; - if (block instanceof SignBlock) { - signType2 = ((SignBlock) block).type(); - } else { - signType2 = WoodType.OAK; - } - - return signType2; - } - - public static Material getModelTexture(Block block) { - return Sheets.getSignMaterial(getSignType(block)); - } - - 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 c1b36914..00000000 --- a/src/main/java/ru/bclib/items/BaseAnvilItem.java +++ /dev/null @@ -1,60 +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); - if (blockState != null) { - 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/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 bb2216f2..00000000 --- a/src/main/java/ru/bclib/items/BaseDrinkItem.java +++ /dev/null @@ -1,59 +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.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/ru/bclib/items/BaseSpawnEggItem.java b/src/main/java/ru/bclib/items/BaseSpawnEggItem.java deleted file mode 100644 index 7badc6f9..00000000 --- a/src/main/java/ru/bclib/items/BaseSpawnEggItem.java +++ /dev/null @@ -1,28 +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.entity.Mob; -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 10495c5a..00000000 --- a/src/main/java/ru/bclib/mixin/client/BackgroundRendererMixin.java +++ /dev/null @@ -1,142 +0,0 @@ -package ru.bclib.mixin.client; - -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; - -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) { - 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 bcl_fogDensity(Camera camera, FogRenderer.FogMode fogMode, float viewDistance, boolean thickFog, CallbackInfo info) { - Entity entity = camera.getEntity(); - FogType fogType = camera.getFluidInCamera(); - if (fogType != FogType.WATER) { - 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.setShaderFogStart(start); - RenderSystem.setShaderFogEnd(end); - 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/FeatureDecoratorsAccessor.java b/src/main/java/ru/bclib/mixin/common/FeatureDecoratorsAccessor.java deleted file mode 100644 index d54b39cc..00000000 --- a/src/main/java/ru/bclib/mixin/common/FeatureDecoratorsAccessor.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.bclib.mixin.common; - -import net.minecraft.data.worldgen.Features; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.crafting.Recipe; -import net.minecraft.world.item.crafting.RecipeType; -import net.minecraft.world.level.block.ComposterBlock; -import net.minecraft.world.level.levelgen.placement.ConfiguredDecorator; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -import java.util.Map; - -@Mixin(targets = "net.minecraft.data.worldgen.Features$Decorators") -public interface FeatureDecoratorsAccessor { - @Accessor("HEIGHTMAP_SQUARE") - ConfiguredDecorator bcl_getHeightmapSquare(); -} 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 e01df428..00000000 --- a/src/main/java/ru/bclib/mixin/common/TagLoaderMixin.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.bclib.mixin.common; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.Tag; -import net.minecraft.tags.TagLoader; -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 ru.bclib.util.TagHelper; - -import java.util.Map; - -@Mixin(TagLoader.class) -public class TagLoaderMixin { - @Shadow - private String directory; - - @ModifyArg(method = "loadAndBuild", at = @At(value = "INVOKE", target = "Lnet/minecraft/tags/TagLoader;build(Ljava/util/Map;)Lnet/minecraft/tags/TagCollection;")) - public Map be_modifyTags(Map tagsMap) { - return TagHelper.apply(directory, tagsMap); - } -} 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 7da1352b..00000000 --- a/src/main/java/ru/bclib/registry/BaseBlockEntities.java +++ /dev/null @@ -1,57 +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.blockentities.DynamicBlockEntityType.BlockEntitySupplier; -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, BlockEntitySupplier 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 7dcc9744..00000000 --- a/src/main/java/ru/bclib/registry/ItemsRegistry.java +++ /dev/null @@ -1,110 +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.Mob; -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 9d724ac8..00000000 --- a/src/main/java/ru/bclib/util/StructureHelper.java +++ /dev/null @@ -1,375 +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.core.Vec3i; -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 net.minecraft.world.phys.Vec3; -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) { - Vec3 offset = StructureTemplate.transform(Vec3.atCenterOf(structure.getSize()), mirror, rotation, BlockPos.ZERO); - return pos.offset(-offset.x * 0.5, 0, -offset.z * 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.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(-min.x, -min.y, -min.z); - return BoundingBox.fromCorners(pos.offset(min.x, min.y, 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)); - } - - 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.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, Random 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).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.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).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.minX(); x <= bounds.maxX(); x++) { - mut.setX(x); - for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) { - mut.setZ(z); - BlockState top = world.getBiome(mut).getGenerationSettings().getSurfaceBuilderConfig().getTopMaterial(); - for (int y = bounds.maxY(); y >= bounds.minY(); 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 dae193d0..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 directory, Map tagsMap) { - Map> endTags = null; - if ("tags/blocks".equals(directory)) { - endTags = TAGS_BLOCK; - } else if ("tags/items".equals(directory)) { - 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 1d77c26d..00000000 --- a/src/main/java/ru/bclib/world/biomes/BCLBiomeDef.java +++ /dev/null @@ -1,389 +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.CarverConfiguration; -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/BCLDecorators.java b/src/main/java/ru/bclib/world/features/BCLDecorators.java deleted file mode 100644 index d037c5f6..00000000 --- a/src/main/java/ru/bclib/world/features/BCLDecorators.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.bclib.world.features; - -import net.minecraft.data.worldgen.Features; -import net.minecraft.world.level.levelgen.placement.ConfiguredDecorator; -import ru.bclib.BCLib; - -import java.lang.reflect.Field; - -public class BCLDecorators { - public static final ConfiguredDecorator HEIGHTMAP_SQUARE; - - private static final ConfiguredDecorator getDecorator(Field[] fields, int index) { - try { - return (ConfiguredDecorator) fields[index].get(null); - } - catch (IllegalAccessException e) { - BCLib.LOGGER.error(e.getLocalizedMessage()); - return null; - } - } - - static { - Class[] classes = Features.class.getDeclaredClasses(); - Field[] fields = classes[1].getDeclaredFields(); // Decorators class - HEIGHTMAP_SQUARE = getDecorator(fields, 17); - } -} 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 2227cddd..00000000 --- a/src/main/java/ru/bclib/world/features/BCLFeature.java +++ /dev/null @@ -1,100 +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.tags.BlockTags; -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.VerticalAnchor; -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; -import net.minecraft.world.level.levelgen.structure.templatesystem.RuleTest; -import net.minecraft.world.level.levelgen.structure.templatesystem.TagMatchTest; -import ru.bclib.api.TagAPI; - -public class BCLFeature { - private static final RuleTest ANY_TERRAIN = new TagMatchTest(TagAPI.GEN_TERRAIN); - private ConfiguredFeature featureConfigured; - private GenerationStep.Decoration featureStep; - private Feature feature; - - 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(BCLDecorators.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.LAVA_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); - OreConfiguration config = new OreConfiguration(ANY_TERRAIN, blockOre.defaultBlockState(), 33); - ConfiguredFeature oreFeature = Feature.ORE.configured(featureConfig) - .rangeUniform(VerticalAnchor.absolute(minY), VerticalAnchor.absolute(maxY)) - .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 e48bfb5a..00000000 --- a/src/main/java/ru/bclib/world/features/NBTStructureFeature.java +++ /dev/null @@ -1,220 +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.core.Vec3i; -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.FeaturePlaceContext; -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 net.minecraft.world.phys.Vec3; -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(FeaturePlaceContext context) { - WorldGenLevel world = context.level(); - Random random = context.random(); - BlockPos center = context.origin(); - - 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(new BlockPos(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.placeInWorld(world, center, center, placementData, random, 4); - - 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.fromCorners(new Vec3i(sx, 0, sz), new Vec3i(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 8f3c80d5..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.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((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 14e6bba4..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; - } - - 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)); - } - - @Override - public void apply(Random random, ChunkAccess chunkAccess, Biome biome, int x, int z, int height, double noise, BlockState defaultBlock, BlockState defaultFluid, int l, int m, long seed, SurfaceBuilderBaseConfiguration surfaceBuilderConfiguration) { - noise = NOISE.eval(x * 0.1, z * 0.1) + MHelper.randRange(-0.4, 0.4, random); - SurfaceBuilder.DEFAULT.apply(random, chunkAccess, biome, x, z, height, noise, defaultBlock, defaultFluid, l, m, seed, noise > 0 ? config1 : config2); - } -} \ No newline at end of file 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/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..78048dbb --- /dev/null +++ b/src/main/resources/assets/bclib/lang/de_de.json @@ -0,0 +1,61 @@ +{ + "message.bclib.anvil_damage": "§cSchaden", + "bclib.datafixer.backupWarning.title": "Der Wächter hat eine inkompatible Welt entdeckt", + "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.backup": "Backup erstellen bevor die Reperaturen angewendet werden", + "bclib.datafixer.backupWarning.nofixes": "Weiter ohne Reperaturen", + "bclib.datafixer.backupWarning.fix": "Reperaturen Anwenden", + "title.bclib.bclibmissmatch": "Versionsunterschied", + "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.", + "title.bclib.syncfiles": "Inkonsistente Daten", + "message.bclib.syncfiles": "Einige Daten (Konfigurationen, Mods, ...) sind unterschiedlich.\nSollen die unten ausgewählten Inhalte vom Server auf diese Maschine kopieren?", + "message.bclib.syncfiles.mods": "Mods synchronisieren", + "message.bclib.syncfiles.configs": "Einstellungen synchronisieren", + "message.bclib.syncfiles.folders": "Dateien und Ordner synchronisieren", + "message.bclib.syncfiles.delete": "Unnötige löschen", + "title.bclib.confirmrestart": "Neustart erforderlich", + "message.bclib.confirmrestart": "Die angeforderten Inhalte wurden erfolgreich übertragen. Minecraft muss nun neu gestartet werden.", + "title.link.bclib.discord": "Discord", + "title.bclib.modmenu.main": "BCLib Einstellungen", + "title.bclib.progress": "Fortschritt", + "title.bclib.filesync.progress": "Datenübertragung", + "message.bclib.filesync.progress": "Snychronisiere Dateien und Verzeichnise vom Server", + "message.bclib.filesync.progress.stage.empty": "", + "title.config.bclib.client.auto_sync.enabled": "Auto-Sync Aktivieren", + "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.displayModInfo": "Warnung anzeigen, wenn Mods auf Server/Client unterschiedlich", + "title.config.bclib.client.auto_sync.debugHashes": "Erweiterete Logausgabe für Auto-Sync", + "title.config.bclib.generator.options.useOldBiomeGenerator": "Alten 1.17 Biome Generator verwenden", + "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.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Experimenteller Warnbildschirm beim Laden deaktivieren", + "title.bclib.syncfiles.modInfo": "Mod Info", + "title.bclib.syncfiles.modlist": "Mod Information", + "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.", + "title.bclib.modmissmatch": "Mod-Konflikt", + "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.datafixer.progress.waitbackup": "Ich warte auf das Ende der Sicherung. Dies kann eine Weile dauern!", + "message.bclib.datafixer.progress.reading": "Lese Daten", + "message.bclib.datafixer.progress.players": "Repariere Spieler", + "message.bclib.datafixer.progress.level": "Patches auf level.dat anwenden", + "message.bclib.datafixer.progress.worlddata": "Benutzerdefinierte Weltdaten patchen", + "message.bclib.datafixer.progress.regions": "Alle Regionen reparieren", + "message.bclib.datafixer.progress.saving": "Patch-Status speichern", + "title.bclib.datafixer.progress": "Welt in Ordnung bringen", + "message.bclib.datafixer.progress": "Anwenden aller Änderungen", + "title.bclib.datafixer.error": "Fehler beim Reparieren der Welt", + "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.", + "title.bclib.datafixer.error.continue": "Continue and Mark as Fixed", + "title.config.bclib.main.ui.suppressExperimentalDialogOnLoad": "Disable Experimental Warning Screen on Load", + "tooltip.bclib.place_on": "Lebt auf: %s", + "generator.bclib.normal": "BetterX", + "title.screen.bclib.worldgen.main": "Welt-Generator Eigenschaften", + "title.bclib.the_nether": "Nether", + "title.bclib.the_end": "Das Ende", + "title.screen.bclib.worldgen.custom_biome_source": "Benutzerdefinierte Biomquelle verwenden", + "title.screen.bclib.worldgen.legacy_square": "Legacy-Verteilung (1.17)", + "title.screen.bclib.worldgen.custom_end_terrain": "Angepasster End-Terrain-Generator", + "title.screen.bclib.worldgen.end_void": "Kleine End-Inseln erzeugen" +} \ 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..78c8618b --- /dev/null +++ b/src/main/resources/assets/bclib/lang/en_us.json @@ -0,0 +1,62 @@ +{ + "message.bclib.anvil_damage": "§cDamage", + "bclib.datafixer.backupWarning.title": "Guardian detected an incompatible World", + "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": "Create Backup before applying Fixes", + "bclib.datafixer.backupWarning.nofixes": "Continue Without Fixes", + "bclib.datafixer.backupWarning.fix": "Apply Fixes", + "title.bclib.bclibmissmatch": "Version Mismatch", + "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.", + "title.bclib.syncfiles": "Mismatching Data", + "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.mods": "Synchronize Mods", + "message.bclib.syncfiles.configs": "Synchronize Configs", + "message.bclib.syncfiles.folders": "Synchronize Folders and Files", + "message.bclib.syncfiles.delete": "Delete unneeded", + "title.bclib.confirmrestart": "Restart Required", + "message.bclib.confirmrestart": "The requested content was synchronized. You need to restart Minecraft now.", + "title.link.bclib.discord": "Discord", + "title.bclib.modmenu.main": "BCLib Settings", + "title.bclib.progress": "Progress", + "title.bclib.filesync.progress": "File Transfer", + "message.bclib.filesync.progress": "Syncing File-Content with Server", + "message.bclib.filesync.progress.stage.empty": "", + "title.config.bclib.client.auto_sync.enabled": "Enable Auto-Sync", + "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.displayModInfo": "Display warning when Serverside Mods differ from Client", + "title.config.bclib.client.auto_sync.debugHashes": "Print Auto-Sync Debug-Hashes to Log", + "title.config.bclib.generator.options.useOldBiomeGenerator": "Use legacy 1.17 Biome Generator", + "title.config.bclib.main.patches.applyPatches": "Automatically apply patches when loading level", + "title.config.bclib.main.patches.repairBiomesOnLoad": "Fix Biomesource on level load", + "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Disable Experimental Warning Screen on Load", + "title.bclib.syncfiles.modInfo": "Mod Info", + "title.bclib.syncfiles.modlist": "Mod Information", + "message.bclib.syncfiles.modlist": "The following shows the state of your installed installed Mods.\n\nAll Mods that do not exist locally, or have a different version on the Server will be synchronized.", + "title.bclib.modmissmatch": "Mod Version Conflict", + "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.datafixer.progress.waitbackup": "Waiting for Backup to finish. This may take a while!", + "message.bclib.datafixer.progress.reading": "Reading Data", + "message.bclib.datafixer.progress.players": "Fixing Players", + "message.bclib.datafixer.progress.level": "Applying Patches to level.dat", + "message.bclib.datafixer.progress.worlddata": "Patching Custom World-Data", + "message.bclib.datafixer.progress.regions": "Repairing all Regions", + "message.bclib.datafixer.progress.saving": "Saving Patch State", + "title.bclib.datafixer.progress": "Fixing World", + "message.bclib.datafixer.progress": "Applying all Patches to your World.", + "title.bclib.datafixer.error": "Errors while fixing World", + "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.", + "title.bclib.datafixer.error.continue": "Proceed and mark as fixed", + "title.config.bclib.client.rendering.customFogRendering": "Custom Fog Rendering", + "title.config.bclib.client.rendering.netherThickFog": "Nether Thick Fog", + "tooltip.bclib.place_on": "Survives on: %s", + "generator.bclib.normal": "BetterX", + "title.screen.bclib.worldgen.main": "World Generator Settings", + "title.bclib.the_nether": "The Nether", + "title.bclib.the_end": "The End", + "title.screen.bclib.worldgen.custom_biome_source": "Use Custom Biome Source", + "title.screen.bclib.worldgen.legacy_square": "Use Legacy Map (1.17)", + "title.screen.bclib.worldgen.custom_end_terrain": "Custom End Terrain Generator", + "title.screen.bclib.worldgen.end_void": "Generate small Islands" +} \ 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/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/uk_ua.json b/src/main/resources/assets/bclib/lang/uk_ua.json new file mode 100644 index 00000000..4378b5d1 --- /dev/null +++ b/src/main/resources/assets/bclib/lang/uk_ua.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 автоматично завантажувалася із сервера? \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": "Продовжити і позначити як виправлене" +} 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..8069ef0c --- /dev/null +++ b/src/main/resources/assets/bclib/lang/zh_cn.json @@ -0,0 +1,51 @@ +{ + "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": "继续并标记为已修复" +} 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/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/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/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/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/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/shaders/material/alpha_emission.frag b/src/main/resources/assets/bclib/shaders/material/alpha_emission.frag new file mode 100644 index 00000000..23835661 --- /dev/null +++ b/src/main/resources/assets/bclib/shaders/material/alpha_emission.frag @@ -0,0 +1,14 @@ +#include frex:shaders/api/fragment.glsl +#include frex:shaders/lib/math.glsl + +// Value near 254 +bool isEmissive(float alpha) { + return 0.9960 < alpha && alpha < 0.9962; +} + +void frx_startFragment(inout frx_FragmentData fragData) { + if (isEmissive(fragData.spriteColor.a)) { + fragData.emissivity = 1.0; + fragData.spriteColor.a = 1.0; + } +} diff --git a/src/main/resources/assets/minecraft/shaders/core/rendertype_cutout.fsh b/src/main/resources/assets/minecraft/shaders/core/rendertype_cutout.fsh new file mode 100644 index 00000000..b8e57609 --- /dev/null +++ b/src/main/resources/assets/minecraft/shaders/core/rendertype_cutout.fsh @@ -0,0 +1,53 @@ +#version 150 +#moj_import + +uniform sampler2D Sampler0; + +uniform vec4 ColorModulator; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; + +in float vertexDistance; +in vec4 vertexColor; +in vec2 texCoord0; +in vec4 normal; + +out vec4 fragColor; + +vec3 rgbToHSV(vec3 color) { + vec4 k = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(color.bg, k.wz), vec4(color.gb, k.xy), step(color.b, color.g)); + vec4 q = mix(vec4(p.xyw, color.r), vec4(color.r, p.yzx), step(p.x, color.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsvToRGB(vec3 color) { + vec4 k = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(color.xxx + k.xyz) * 6.0 - k.www); + return color.z * mix(k.xxx, clamp(p - k.xxx, 0.0, 1.0), color.y); +} + +// Value near 254 +bool isEmissive(float alpha) { + return 0.9960 < alpha && alpha < 0.9962; +} + +void main() { + vec4 tex = texture(Sampler0, texCoord0); + if (tex.a < 0.1) { + discard; + } + vec4 color = tex * ColorModulator; + vec4 vertex = vertexColor; + if (isEmissive(tex.a)) { + vec3 hsv = rgbToHSV(vertex.rgb); + hsv.z = 1.0; + vertex.rgb = hsvToRGB(hsv); + color.a = 1.0; + } + color = linear_fog(color * vertex, vertexDistance, FogStart, FogEnd, FogColor); + fragColor = color; +} diff --git a/src/main/resources/assets/minecraft/shaders/core/rendertype_entity_cutout.fsh b/src/main/resources/assets/minecraft/shaders/core/rendertype_entity_cutout.fsh new file mode 100644 index 00000000..253b1244 --- /dev/null +++ b/src/main/resources/assets/minecraft/shaders/core/rendertype_entity_cutout.fsh @@ -0,0 +1,56 @@ +#version 150 +#moj_import + +uniform sampler2D Sampler0; + +uniform vec4 ColorModulator; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; + +in float vertexDistance; +in vec4 vertexColor; +in vec4 lightMapColor; +in vec4 overlayColor; +in vec2 texCoord0; +in vec4 normal; + +out vec4 fragColor; + +vec3 rgbToHSV(vec3 color) { + vec4 k = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(color.bg, k.wz), vec4(color.gb, k.xy), step(color.b, color.g)); + vec4 q = mix(vec4(p.xyw, color.r), vec4(color.r, p.yzx), step(p.x, color.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsvToRGB(vec3 color) { + vec4 k = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(color.xxx + k.xyz) * 6.0 - k.www); + return color.z * mix(k.xxx, clamp(p - k.xxx, 0.0, 1.0), color.y); +} + +// Value near 254 +bool isEmissive(float alpha) { + return 0.9960 < alpha && alpha < 0.9962; +} + +void main() { + vec4 tex = texture(Sampler0, texCoord0); + if (tex.a < 0.1) { + discard; + } + vec4 color = tex * ColorModulator; + color.rgb = mix(overlayColor.rgb, color.rgb, overlayColor.a); + vec4 vertex = vertexColor * lightMapColor; + if (isEmissive(tex.a)) { + vec3 hsv = rgbToHSV(vertex.rgb); + hsv.z = 1.0; + vertex.rgb = hsvToRGB(hsv); + color.a = 1.0; + } + color = linear_fog(color * vertex, vertexDistance, FogStart, FogEnd, FogColor); + fragColor = color; +} \ No newline at end of file diff --git a/src/main/resources/assets/minecraft/shaders/core/rendertype_item_entity_translucent_cull.fsh b/src/main/resources/assets/minecraft/shaders/core/rendertype_item_entity_translucent_cull.fsh new file mode 100644 index 00000000..c2cefa02 --- /dev/null +++ b/src/main/resources/assets/minecraft/shaders/core/rendertype_item_entity_translucent_cull.fsh @@ -0,0 +1,53 @@ +#version 150 +#moj_import + +uniform sampler2D Sampler0; + +uniform vec4 ColorModulator; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; + +in float vertexDistance; +in vec4 vertexColor; +in vec2 texCoord0; +in vec2 texCoord1; +in vec4 normal; + +out vec4 fragColor; + +vec3 rgbToHSV(vec3 color) { + vec4 k = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(color.bg, k.wz), vec4(color.gb, k.xy), step(color.b, color.g)); + vec4 q = mix(vec4(p.xyw, color.r), vec4(color.r, p.yzx), step(p.x, color.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsvToRGB(vec3 color) { + vec4 k = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(color.xxx + k.xyz) * 6.0 - k.www); + return color.z * mix(k.xxx, clamp(p - k.xxx, 0.0, 1.0), color.y); +} + +// Value near 254 +bool isEmissive(float alpha) { + return 0.9960 < alpha && alpha < 0.9962; +} + +void main() { + vec4 tex = texture(Sampler0, texCoord0); + if (tex.a < 0.1) { + discard; + } + vec4 color = tex * ColorModulator; + vec4 vertex = vertexColor; + if (isEmissive(tex.a)) { + vec3 hsv = rgbToHSV(vertex.rgb); + hsv.z = 1.0; + vertex.rgb = hsvToRGB(hsv); + } + color = linear_fog(color * vertex, vertexDistance, FogStart, FogEnd, FogColor); + fragColor = color; +} \ No newline at end of file diff --git a/src/main/resources/assets/minecraft/shaders/core/rendertype_solid.fsh b/src/main/resources/assets/minecraft/shaders/core/rendertype_solid.fsh new file mode 100644 index 00000000..d75ee23d --- /dev/null +++ b/src/main/resources/assets/minecraft/shaders/core/rendertype_solid.fsh @@ -0,0 +1,50 @@ +#version 150 +#moj_import + +uniform sampler2D Sampler0; + +uniform vec4 ColorModulator; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; + +in float vertexDistance; +in vec4 vertexColor; +in vec2 texCoord0; +in vec4 normal; + +out vec4 fragColor; + +vec3 rgbToHSV(vec3 color) { + vec4 k = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(color.bg, k.wz), vec4(color.gb, k.xy), step(color.b, color.g)); + vec4 q = mix(vec4(p.xyw, color.r), vec4(color.r, p.yzx), step(p.x, color.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsvToRGB(vec3 color) { + vec4 k = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(color.xxx + k.xyz) * 6.0 - k.www); + return color.z * mix(k.xxx, clamp(p - k.xxx, 0.0, 1.0), color.y); +} + +// Value near 254 +bool isEmissive(float alpha) { + return 0.9960 < alpha && alpha < 0.9962; +} + +void main() { + vec4 tex = texture(Sampler0, texCoord0); + vec4 color = tex * ColorModulator; + vec4 vertex = vertexColor; + if (isEmissive(tex.a)) { + vec3 hsv = rgbToHSV(vertex.rgb); + hsv.z = 1.0; + vertex.rgb = hsvToRGB(hsv); + color.a = 1.0; + } + color = linear_fog(color * vertex, vertexDistance, FogStart, FogEnd, FogColor); + fragColor = color; +} diff --git a/src/main/resources/bclib.accesswidener b/src/main/resources/bclib.accesswidener new file mode 100644 index 00000000..e3b74aab --- /dev/null +++ b/src/main/resources/bclib.accesswidener @@ -0,0 +1,33 @@ +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 +extendable class net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator +accessible class net/minecraft/core/Registry$RegistryBootstrap +accessible class net/minecraft/world/level/levelgen/SurfaceRules$SequenceRuleSource +accessible class net/minecraft/core/Registry$RegistryBootstrap +accessible class net/minecraft/tags/Tag$ElementEntry +accessible class net/minecraft/tags/Tag$TagEntry +accessible class net/minecraft/client/Minecraft$ExperimentalDialogType + +#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/level/levelgen/SurfaceRules$SequenceRuleSource (Ljava/util/List;)V +accessible method net/minecraft/core/Registry registerSimple (Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/core/Registry$RegistryBootstrap;)Lnet/minecraft/core/Registry; + +#Fields +accessible field net/minecraft/client/gui/screens/worldselection/WorldPreset FLAT Lnet/minecraft/client/gui/screens/worldselection/WorldPreset; +accessible field net/minecraft/client/gui/screens/worldselection/WorldPreset LARGE_BIOMES Lnet/minecraft/client/gui/screens/worldselection/WorldPreset; +accessible field net/minecraft/client/gui/screens/worldselection/WorldPreset AMPLIFIED Lnet/minecraft/client/gui/screens/worldselection/WorldPreset; +accessible field net/minecraft/client/gui/screens/worldselection/WorldPreset SINGLE_BIOME_SURFACE Lnet/minecraft/client/gui/screens/worldselection/WorldPreset; +accessible field net/minecraft/client/gui/screens/worldselection/WorldPreset PRESETS Ljava/util/List; +accessible field net/minecraft/client/gui/screens/worldselection/WorldPreset EDITORS Ljava/util/Map; + + +accessible method net/minecraft/core/Registry registerSimple (Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/core/Registry$RegistryBootstrap;)Lnet/minecraft/core/Registry; \ 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..0639eab1 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", + "BlockMixin", + "ClientRecipeBookMixin", + "FogRendererMixin", + "GameMixin", + "MinecraftMixin", + "ModelBakeryMixin", + "ModelManagerMixin", + "PresetEditorMixin", + "SignEditScreenMixin", + "TextureAtlasMixin" + ], + "injectors": { + "defaultRequire": 1 + } } diff --git a/src/main/resources/bclib.mixins.common.json b/src/main/resources/bclib.mixins.common.json index 20e03d8d..538b02ed 100644 --- a/src/main/resources/bclib.mixins.common.json +++ b/src/main/resources/bclib.mixins.common.json @@ -1,20 +1,52 @@ { - "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", + "BiomeSourceMixin", + "BoneMealItemMixin", + "BuiltinRegistriesMixin", + "ChunkGeneratorAccessor", + "ChunkGeneratorMixin", + "ChunkGeneratorsMixin", + "ComposterBlockAccessor", + "CraftingMenuMixin", + "DiggerItemMixin", + "EnchantingTableBlockMixin", + "ItemStackMixin", + "LayerLightSectionStorageMixin", + "LootPoolMixin", + "MinecraftServerMixin", + "MobSpawnSettingsAccessor", + "NoiseBasedChunkGeneratorMixin", + "PistonBaseBlockMixin", + "PortalShapeMixin", + "PotionBrewingAccessor", + "RecipeManagerAccessor", + "RecipeManagerMixin", + "RegistryAccessMixin", + "ServerLevelMixin", + "ShovelItemAccessor", + "StructuresAccessor", + "SurfaceRulesContextAccessor", + "TheEndBiomesMixin", + "WorldGenRegionMixin", + "elytra.LivingEntityMixin", + "shears.BeehiveBlockMixin", + "shears.DiggingEnchantmentMixin", + "shears.ItemPredicateBuilderMixin", + "shears.MushroomCowMixin", + "shears.PumpkinBlockMixin", + "shears.SheepMixin", + "shears.SnowGolemMixin", + "shears.TripWireBlockMixin" + ], + "injectors": { + "defaultRequire": 1 + } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index d4c9f8f1..ef506323 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,43 +1,56 @@ { - "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": "2.0.11", + "name": "BCLib", + "description": "A library for BetterX team mods", + "authors": [ + "Quiqueck", + "paulevs", + "Bulldog83" + ], + "contact": { + "homepage": "https://www.curseforge.com/minecraft/mc-mods/bclib", + "issues": "https://github.com/quiqueck/bclib/issues", + "sources": "https://github.com/quiqueck/bclib" + }, + "license": "MIT", + "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" + ], + "worlds_together": [ + "org.betterx.bclib.registry.PresetsRegistry" + ] + }, + "accessWidener": "bclib.accesswidener", + "mixins": [ + "together.mixins.common.json", + "together.mixins.client.json", + "bclib.mixins.common.json", + "bclib.mixins.client.json" + ], + "depends": { + "fabricloader": ">=0.14.6", + "fabric": ">=0.56.0", + "minecraft": "1.19" + }, + "custom": { + "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..5bffc371 --- /dev/null +++ b/src/main/resources/together.mixins.client.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.betterx.worlds.together.mixin.client", + "compatibilityLevel": "JAVA_17", + "client": [ + "CreateWorldScreenMixin", + "MinecraftMixin", + "WorldGenSettingsComponentMixin" + ], + "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..75e009db --- /dev/null +++ b/src/main/resources/together.mixins.common.json @@ -0,0 +1,27 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.betterx.worlds.together.mixin.common", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "BuiltinRegistriesMixin", + "DedicatedServerPropertiesMixin", + "DiggerItemAccessor", + "MainMixin", + "MinecraftServerMixin", + "MinecraftServerMixinLate", + "NoiseBasedChunkGeneratorMixin", + "NoiseGeneratorSettingsMixin", + "PrimaryLevelDataMixin", + "RegistryAccessMixin", + "RegistryOpsAccessor", + "TagLoaderMixin", + "WorldGenPropertiesMixin", + "WorldPresetAccessor", + "WorldPresetMixin", + "WorldPresetsBootstrapMixin" + ], + "injectors": { + "defaultRequire": 1 + } +}

+ * 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().equals(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..6e940de9 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/TagRegistry.java @@ -0,0 +1,277 @@ +package org.betterx.worlds.together.tag.v3; + +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome; +import org.betterx.worlds.together.WorldsTogether; + +import net.minecraft.core.DefaultedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.Tag; +import net.minecraft.tags.TagKey; +import net.minecraft.tags.TagManager; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.biome.Biome; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +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) { + initializeTag(id); + return registry + .getTagNames() + .filter(tagKey -> tagKey.location().equals(id)) + .findAny() + .orElse(TagKey.create(registry.key(), id)); + } + } + + 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 add(T element, TagKey... tagIDs) { + super.add(element, tagIDs); + } + } + + public static class Biomes extends Simple { + Biomes(String directory, Function locationProvider) { + super(Registry.BIOME_REGISTRY, directory, locationProvider); + } + + public TagKey makeStructureTag(String modID, String name) { + return makeTag(modID, "has_structure/" + name); + } + + public void apply(Map tagsMap) { + InternalBiomeAPI._runBiomeTagAdders(); + super.apply(tagsMap); + } + } + + public static class Items extends RegistryBacked { + + Items() { + super(Registry.ITEM); + } + + @SafeVarargs + public final void add(TagKey tagID, ItemLike... elements) { + for (ItemLike element : elements) { + add(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> 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(ResourceLocation tagID) { + getSetForTag(tagID); + } + + public Set getSetForTag(ResourceLocation tagID) { + return tags.computeIfAbsent(tagID, k -> Sets.newHashSet()); + } + + public Set getSetForTag(TagKey tag) { + if (tag == null) { + return new HashSet<>(); + } + return getSetForTag(tag.location()); + } + + /** + * 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) { + initializeTag(id); + return TagKey.create(registryKey, id); + } + + /** + * 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 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(new Tag.ElementEntry(id)); + } + } + } + + public void addUntyped(ResourceLocation element, TagKey... tagIDs) { + for (TagKey tagID : tagIDs) { + addUntyped(tagID, element); + } + } + + public void addOtherTags(TagKey tagID, 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(new Tag.TagEntry(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) { + 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); + if (id != null) { + set.add(new Tag.ElementEntry(id)); + } + } + } + + protected void add(T element, TagKey... tagIDs) { + for (TagKey tagID : tagIDs) { + add(tagID, element); + } + } + + @Deprecated(forRemoval = true) + protected void add(ResourceLocation tagID, 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); + if (id != null) { + set.add(new Tag.ElementEntry(id)); + } + } + } + + @Deprecated(forRemoval = true) + protected void add(T element, ResourceLocation... tagIDs) { + for (ResourceLocation tagID : tagIDs) { + add(tagID, element); + } + } + + public void forEach(BiConsumer> consumer) { + tags.forEach(consumer); + } + + public void apply(Map tagsMap) { + + //this.isFrozen = true; + this.forEach((id, ids) -> apply(id, tagsMap.computeIfAbsent(id, key -> new Tag.Builder()), ids)); + } + + private static Tag.Builder apply( + ResourceLocation id, + Tag.Builder builder, + Set ids + ) { + ids.forEach(value -> builder.add(new Tag.BuilderEntry(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/Logger.java b/src/main/java/org/betterx/worlds/together/util/Logger.java new file mode 100644 index 00000000..8600e677 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/util/Logger.java @@ -0,0 +1,62 @@ +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, 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. + *