Compare commits

..

20 commits
1.0.0 ... 1.17

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

1
.gitignore vendored
View file

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

105
README.md
View file

@ -1,98 +1,33 @@
[![](https://jitpack.io/v/paulevsGitch/BCLib.svg)](https://jitpack.io/#paulevsGitch/BCLib) [![](https://jitpack.io/v/paulevsGitch/BCLib.svg)](https://jitpack.io/#paulevsGitch/BCLib)
# BCLib # BCLib
BCLib is a library mod for BetterX team mods, developed for Fabric, MC 1.18 BCLib is a library mod for BetterX team mods, developed for Fabric, MC 1.16.5
## Features: ## 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: ### API:
* Simple Mod Integration API: * Simple Mod Integration API;
* Get mod inner methods, classes and objects on runtime. * Structure Features API;
* Structure Features API: * World Data API;
* Sructure Features with automatical registration, Helpers and math stuff. * Bonemeal API;
* World Data API: * Features API;
* World fixers for comfortable migration between mod versions when content was removed; * Biome API;
* Support for Block name changes and Tile Entities (WIP). * Tag API.
* 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: ### Libs:
* Spline library (simple): * Spline library (simple);
* Helper to create simple splines as set of points; * Recipe manager;
* Some basic operation with splines; * Noise library;
* Converting splines to SDF. * Math library;
* Recipe manager: * SDF library.
* 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: ### Helpers And Utils:
* Custom surface builders. * Custom surface builders;
* Translation helper: * Translation helper;
* Generates translation template. * Weighted list;
* Weighted list: * Block helper.
* 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 ### Rendering:
* Utility classes used for mass content generation (wooden blocks, stone blocks, etc.); * Procedural block models (from paterns or from code);
* Contains a set of defined blocks, items, recipes and tags; * Block render layer interface.
* Can be modified before mods startup (will add new block type for all instances in all mods);
* All inner blocks and items are Patterned (will have auto-generated models with ability to override them with resource packs or mod resources).
### Pre-Defined Blocks and Items:
* Most basic blocks from MC;
* Automatic item & block model generation;
### Configs:
* Custom config system based on Json;
* Hierarchical configs;
* Different entry types;
* Only-changes saves.
### Interfaces:
* BlockModelProvider:
* Allows block to return custom model and blockstate.
* ItemModelProvider:
* Allows block to return custom item model.
* CustomColorProvider:
* Make available to add block and item color provider.
* RenderLayerProvider:
* Determine block render layer (Transparent and Translucent).
* PostInitable:
* Allows block to init something after all mods are loaded.
* CustomItemProvider:
* Allows block to change its registered item (example - signs, water lilies).
## Importing: ## Importing:
* Clone repo * Clone repo

View file

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

View file

@ -1,145 +0,0 @@
buildscript {
dependencies {
classpath 'org.kohsuke:github-api:1.114'
}
repositories {
gradlePluginPortal()
}
}
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
archivesBaseName = project.archives_base_name
version = project.mod_version
group = project.maven_group
repositories {
maven { url "https://maven.dblsaiko.net/" }
maven { url "https://server.bbkr.space:8081/artifactory/libs-release/" }
maven { url "https://maven.fabricmc.net/" }
maven { url "https://maven.shedaniel.me/" }
maven { url 'https://maven.blamejared.com' }
maven { url 'https://jitpack.io' }
}
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}"
//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("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
}
}
}
}
configurations {
dev {
canBeResolved = false
canBeConsumed = true
}
}
artifacts {
dev jar
}

View file

@ -1,8 +1,164 @@
buildscript {
dependencies {
classpath 'org.kohsuke:github-api:1.114'
}
}
plugins { plugins {
id 'idea' id 'idea'
id 'eclipse' id 'eclipse'
id 'fabric-loom' version "${loom_version}" id 'fabric-loom' version '0.8-SNAPSHOT'
id 'maven-publish' id 'maven-publish'
} }
apply from: "bclib.gradle" sourceCompatibility = JavaVersion.VERSION_16
targetCompatibility = JavaVersion.VERSION_16
archivesBaseName = project.archives_base_name
version = project.mod_version
group = project.maven_group
repositories {
maven { url "https://maven.dblsaiko.net/" }
maven { url "https://server.bbkr.space:8081/artifactory/libs-release/" }
maven { url "https://maven.fabricmc.net/" }
maven { url 'https://maven.blamejared.com' }
maven { url "https://maven.shedaniel.me/" }
maven { url 'https://jitpack.io' }
}
dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings minecraft.officialMojangMappings()
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
//useApi "vazkii.patchouli:Patchouli:1.16.4-${project.patchouli_version}"
}
def useOptional(String dep) {
dependencies.modRuntime (dep) {
exclude group: "net.fabricmc.fabric-api"
exclude group: "net.fabricmc"
if (!dep.contains("me.shedaniel")) {
exclude group: "me.shedaniel"
}
}
dependencies.modCompileOnly (dep) {
exclude group: "net.fabricmc.fabric-api"
exclude group: "net.fabricmc"
if (!dep.contains("me.shedaniel")) {
exclude group: "me.shedaniel"
}
}
}
def useApi(String dep) {
dependencies.modApi (dep) {
exclude group: "net.fabricmc.fabric-api"
exclude group: "net.fabricmc"
if (!dep.contains("me.shedaniel")) {
exclude group: "me.shedaniel"
}
}
}
processResources {
inputs.property "version", project.version
duplicatesStrategy = 'EXCLUDE'
from(sourceSets.main.resources.srcDirs) {
include "fabric.mod.json"
expand "version": project.version
}
from(sourceSets.main.resources.srcDirs) {
exclude "fabric.mod.json"
}
}
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
javadoc {
options.tags = [ "reason" ]
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this task, sources will not be generated.
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
}
jar {
from "LICENSE"
}
artifacts {
archives sourcesJar
archives javadocJar
}
def env = System.getenv()
import org.kohsuke.github.GHReleaseBuilder
import org.kohsuke.github.GitHub
task release(dependsOn: [remapJar, sourcesJar, javadocJar]) {
onlyIf {
env.GITHUB_TOKEN
}
doLast {
def github = GitHub.connectUsingOAuth(env.GITHUB_TOKEN as String)
def repository = github.getRepository("paulevsGitch/BCLib")
def releaseBuilder = new GHReleaseBuilder(repository, version as String)
releaseBuilder.name("${archivesBaseName}-${version}")
releaseBuilder.body("A changelog can be found at https://github.com/paulevsGitch/BCLib/commits")
releaseBuilder.commitish("main")
def ghRelease = releaseBuilder.create()
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar"), "application/java-archive");
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-sources.jar"), "application/java-archive");
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-javadoc.jar"), "application/java-archive");
}
}
// configure the maven publication
publishing {
publications {
gpr(MavenPublication) {
artifactId archivesBaseName
artifact(remapJar) {
builtBy remapJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
}
}
// select the repositories you want to publish to
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/paulevsgitch/bclib")
credentials {
username = env.GITHUB_USER
password = env.GITHUB_TOKEN
}
}
}
}

View file

@ -1,19 +1,18 @@
# Done to increase the memory available to gradle. # Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx2G org.gradle.jvmargs=-Xmx2G
#Loom
loom_version=0.10-SNAPSHOT
# Fabric Properties # Fabric Properties
# check these on https://fabricmc.net/versions.html # check these on https://fabricmc.net/use
minecraft_version= 1.18 minecraft_version= 1.17
loader_version= 0.12.8 yarn_mappings= 6
fabric_version = 0.44.0+1.18 loader_version= 0.11.6
# Mod Properties # Mod Properties
mod_version = 1.0.0 mod_version = 0.2.0
maven_group = ru.bclib maven_group = ru.bclib
archives_base_name = bclib archives_base_name = bclib
# Dependencies # Dependencies
patchouli_version = 50-FABRIC # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
patchouli_version = 50-FABRIC
fabric_version = 0.36.0+1.17

Binary file not shown.

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

259
gradlew vendored Executable file → Normal file
View file

@ -1,7 +1,7 @@
#!/bin/sh #!/usr/bin/env sh
# #
# Copyright © 2015-2021 the original authors. # Copyright 2015 the original author or authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -17,113 +17,78 @@
# #
############################################################################## ##############################################################################
# ##
# Gradle start up script for POSIX generated by Gradle. ## Gradle start up script for UN*X
# ##
# 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 # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
app_path=$0 PRG="$0"
# Need this for relative symlinks.
# Need this for daisy-chained symlinks. while [ -h "$PRG" ] ; do
while ls=`ls -ld "$PRG"`
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path link=`expr "$ls" : '.*-> \(.*\)$'`
[ -h "$app_path" ] if expr "$link" : '/.*' > /dev/null; then
do PRG="$link"
ls=$( ls -ld "$app_path" ) else
link=${ls#*' -> '} PRG=`dirname "$PRG"`"/$link"
case $link in #( fi
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # 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"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD="maximum"
warn () { warn () {
echo "$*" echo "$*"
} >&2 }
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} >&2 }
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "$( uname )" in #( case "`uname`" in
CYGWIN* ) cygwin=true ;; #( CYGWIN* )
Darwin* ) darwin=true ;; #( cygwin=true
MSYS* | MINGW* ) msys=true ;; #( ;;
NONSTOP* ) nonstop=true ;; Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java JAVACMD="$JAVA_HOME/jre/sh/java"
else else
JAVACMD=$JAVA_HOME/bin/java JAVACMD="$JAVA_HOME/bin/java"
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -132,7 +97,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else 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. 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 Please set the JAVA_HOME variable in your environment to match the
@ -140,95 +105,79 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
case $MAX_FD in #( MAX_FD_LIMIT=`ulimit -H -n`
max*) if [ $? -eq 0 ] ; then
MAX_FD=$( ulimit -H -n ) || if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
warn "Could not query maximum file descriptor limit" MAX_FD="$MAX_FD_LIMIT"
esac fi
case $MAX_FD in #( ulimit -n $MAX_FD
'' | soft) :;; #( if [ $? -ne 0 ] ; then
*) warn "Could not set maximum file descriptor limit: $MAX_FD"
ulimit -n "$MAX_FD" || fi
warn "Could not set maximum file descriptor limit to $MAX_FD" else
esac warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi fi
# Collect all arguments for the java command, stacking in reverse order: # For Darwin, add options to specify how the application appears in the dock
# * args from the command line if $darwin; then
# * the main class name GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
# * -classpath fi
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
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`
# Now convert the arguments - kludge to limit ourselves to /bin/sh SEP=""
for arg do for dir in $ROOTDIRSRAW ; do
if ROOTDIRS="$ROOTDIRS$SEP$dir"
case $arg in #( SEP="|"
-*) 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 done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi fi
# Collect all arguments for the java command; # Escape application args
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of save () {
# shell script including quotes and variable substitutions, so put them in for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
# double quotes to make sure that they get re-expanded; and echo " "
# * put everything else in single quotes, so that it's not re-expanded. }
APP_ARGS=`save "$@"`
set -- \ # Collect all arguments for the java command, following the shell quoting and substitution rules
"-Dorg.gradle.appname=$APP_BASE_NAME" \ eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-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" "$@" exec "$JAVACMD" "$@"

22
gradlew.bat vendored
View file

@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if "%ERRORLEVEL%" == "0" goto init
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 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_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto init
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -64,14 +64,28 @@ echo location of your Java installation.
goto fail 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 :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell

View file

@ -1,903 +0,0 @@
/*
* Javadoc style sheet
*/
@import url("resources/fonts/dejavu.css");
/*
* Styles for individual HTML elements.
*
* These are styles that are specific to individual HTML elements. Changing them affects the style of a particular
* HTML element throughout the page.
*/
body {
background-color: #ffffff;
color: #353833;
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
font-size: 14px;
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
iframe {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow-y: scroll;
border: none;
}
a:link,
a:visited {
text-decoration: none;
color: #4a6782;
}
a[href]:hover,
a[href]:focus {
text-decoration: none;
color: #bb7a2a;
}
a[name] {
color: #353833;
}
pre {
font-family: "DejaVu Sans Mono", monospace;
font-size: 16px;
background-color: #fffadb;
border-radius: 5px;
}
h1 {
font-size: 20px;
}
h2 {
font-size: 18px;
}
h3 {
font-size: 16px;
}
h4 {
font-size: 13px;
}
h5 {
font-size: 12px;
}
h6 {
font-size: 11px;
}
ul {
list-style-type: disc;
}
code,
tt {
font-family: "DejaVu Sans Mono", monospace;
font-size: 14px;
padding-top: 4px;
margin-top: 8px;
line-height: 1.4em;
}
dt code {
font-family: "DejaVu Sans Mono", monospace;
font-size: 14px;
padding-top: 4px;
}
.summary-table dt code {
font-family: "DejaVu Sans Mono", monospace;
font-size: 14px;
vertical-align: top;
padding-top: 4px;
}
sup {
font-size: 8px;
}
button {
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
font-size: 14px;
}
/*
* Styles for HTML generated by javadoc.
*
* These are style classes that are used by the standard doclet to generate HTML documentation.
*/
/*
* Styles for document title and copyright.
*/
.clear {
clear: both;
height: 0;
overflow: hidden;
}
.about-language {
float: right;
padding: 0 21px 8px 8px;
font-size: 11px;
margin-top: -9px;
height: 2.9em;
}
.legal-copy {
margin-left: 0.5em;
}
.tab {
background-color: #0066ff;
color: #ffffff;
padding: 8px;
width: 5em;
font-weight: bold;
}
/*
* Styles for navigation bar.
*/
@media screen {
.flex-box {
position: fixed;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
.flex-header {
flex: 0 0 auto;
}
.flex-content {
flex: 1 1 auto;
overflow-y: auto;
}
}
.top-nav {
background-color: #4d7a97;
color: #ffffff;
float: left;
padding: 0;
width: 100%;
clear: right;
min-height: 2.8em;
padding-top: 10px;
overflow: hidden;
font-size: 12px;
}
.sub-nav {
background-color: #dee3e9;
float: left;
width: 100%;
overflow: hidden;
font-size: 12px;
}
.sub-nav div {
clear: left;
float: left;
padding: 0 0 5px 6px;
text-transform: uppercase;
}
.sub-nav .nav-list {
padding-top: 5px;
}
ul.nav-list {
display: block;
margin: 0 25px 0 0;
padding: 0;
}
ul.sub-nav-list {
float: left;
margin: 0 25px 0 0;
padding: 0;
}
ul.nav-list li {
list-style: none;
float: left;
padding: 5px 6px;
text-transform: uppercase;
}
.sub-nav .nav-list-search {
float: right;
margin: 0 0 0 0;
padding: 5px 6px;
clear: none;
}
.nav-list-search label {
position: relative;
right: -16px;
}
ul.sub-nav-list li {
list-style: none;
float: left;
padding-top: 10px;
}
.top-nav a:link,
.top-nav a:active,
.top-nav a:visited {
color: #ffffff;
text-decoration: none;
text-transform: uppercase;
}
.top-nav a:hover {
text-decoration: none;
color: #bb7a2a;
text-transform: uppercase;
}
.nav-bar-cell1-rev {
background-color: #f8981d;
color: #253441;
margin: auto 5px;
}
.skip-nav {
position: absolute;
top: auto;
left: -9999px;
overflow: hidden;
}
/*
* Hide navigation links and search box in print layout
*/
@media print {
ul.nav-list,
div.sub-nav {
display: none;
}
}
/*
* Styles for page header and footer.
*/
.title {
color: #2c4557;
margin: 10px 0;
}
.sub-title {
margin: 5px 0 0 0;
}
.header ul {
margin: 0 0 15px 0;
padding: 0;
}
.header ul li,
.footer ul li {
list-style: none;
font-size: 13px;
}
/*
* Styles for headings.
*/
body.class-declaration-page .summary h2,
body.class-declaration-page .details h2,
body.class-use-page h2,
body.module-declaration-page .block-list h2 {
font-style: italic;
padding: 0;
margin: 15px 0;
}
body.class-declaration-page .summary h3,
body.class-declaration-page .details h3,
body.class-declaration-page .summary .inherited-list h2 {
background-color: #dee3e9;
border: 1px solid #d0d9e0;
margin: 0 0 6px -8px;
padding: 7px 5px;
}
/*
* Styles for page layout containers.
*/
main {
clear: both;
padding: 10px 20px;
position: relative;
}
dl.notes > dt {
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
font-size: 12px;
font-weight: bold;
margin: 10px 0 0 0;
color: #4e4e4e;
}
dl.notes > dd {
margin: 5px 0 10px 0;
font-size: 15px;
font-family: "Roboto", "DejaVu Sans", "Helvetica Neue", Arial, Helvetica, sans-serif;
}
dl.name-value > dt {
margin-left: 1px;
font-size: 1.1em;
display: inline;
font-weight: bold;
}
dl.name-value > dd {
margin: 0 0 0 1px;
font-size: 1.1em;
display: inline;
}
/*
* Styles for lists.
*/
li.circle {
list-style: circle;
}
ul.horizontal li {
display: inline;
font-size: 0.9em;
}
div.inheritance {
margin: 0;
padding: 0;
}
div.inheritance div.inheritance {
margin-left: 2em;
}
ul.block-list,
ul.details-list,
ul.member-list,
ul.summary-list {
margin: 10px 0 10px 0;
padding: 0;
}
ul.block-list > li,
ul.details-list > li,
ul.member-list > li,
ul.summary-list > li {
list-style: none;
margin-bottom: 15px;
line-height: 1.4;
}
.summary-table dl,
.summary-table dl dt,
.summary-table dl dd {
margin-top: 0;
margin-bottom: 1px;
}
/*
* Styles for tables.
*/
.summary-table {
width: 100%;
border-spacing: 0;
border-left: 1px solid #eee;
border-right: 1px solid #eee;
border-bottom: 1px solid #eee;
}
.summary-table {
padding: 0;
}
.caption {
position: relative;
text-align: left;
background-repeat: no-repeat;
color: #253441;
font-weight: bold;
clear: none;
overflow: hidden;
padding: 0px;
padding-top: 10px;
padding-left: 1px;
margin: 0px;
white-space: pre;
}
.caption a:link,
.caption a:visited {
color: #1f389c;
}
.caption a:hover,
.caption a:active {
color: #ffffff;
}
.caption span {
white-space: nowrap;
padding-top: 5px;
padding-left: 12px;
padding-right: 12px;
padding-bottom: 7px;
display: inline-block;
float: left;
background-color: #f8981d;
border: none;
height: 16px;
}
div.table-tabs > button {
border: none;
cursor: pointer;
padding: 5px 12px 7px 12px;
font-weight: bold;
margin-right: 3px;
}
div.table-tabs > button.active-table-tab {
background: #f8981d;
color: #253441;
}
div.table-tabs > button.table-tab {
background: #4d7a97;
color: #ffffff;
}
.two-column-summary {
display: grid;
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
}
.three-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, auto);
}
.four-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto);
}
@media screen and (max-width: 600px) {
.two-column-summary {
display: grid;
grid-template-columns: 1fr;
}
}
@media screen and (max-width: 800px) {
.three-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(25%, auto);
}
.three-column-summary .col-last {
grid-column-end: span 2;
}
}
@media screen and (max-width: 1000px) {
.four-column-summary {
display: grid;
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
}
}
.summary-table > div {
text-align: left;
padding: 8px 3px 3px 7px;
}
.col-first,
.col-second,
.col-last,
.col-constructor-name,
.col-deprecated-item-name {
vertical-align: top;
padding-right: 0;
padding-top: 8px;
padding-bottom: 3px;
}
.table-header {
background: #dee3e9;
font-weight: bold;
}
.col-first,
.col-first {
font-size: 13px;
}
.col-second,
.col-second,
.col-last,
.col-constructor-name,
.col-deprecated-item-name,
.col-last {
font-size: 13px;
}
.col-first,
.col-second,
.col-constructor-name {
vertical-align: top;
overflow: auto;
}
.col-last {
white-space: normal;
}
.col-first a:link,
.col-first a:visited,
.col-second a:link,
.col-second a:visited,
.col-first a:link,
.col-first a:visited,
.col-second a:link,
.col-second a:visited,
.col-constructor-name a:link,
.col-constructor-name a:visited,
.col-deprecated-item-name a:link,
.col-deprecated-item-name a:visited,
.constant-values-container a:link,
.constant-values-container a:visited,
.all-classes-container a:link,
.all-classes-container a:visited,
.all-packages-container a:link,
.all-packages-container a:visited {
font-weight: bold;
}
.table-sub-heading-color {
background-color: #eeeeff;
}
.even-row-color,
.even-row-color .table-header {
background-color: #ffffff;
}
.odd-row-color,
.odd-row-color .table-header {
background-color: #eeeeef;
}
/*
* Styles for contents.
*/
.deprecated-content {
margin: 0;
padding: 10px 0;
}
div.block {
font-size: 15px;
font-family: "Roboto", "DejaVu Sans", "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.col-last div {
padding-top: 0;
}
.col-last a {
padding-bottom: 3px;
}
.module-signature,
.package-signature,
.type-signature,
.member-signature {
font-family: "DejaVu Sans Mono", monospace;
font-size: 14px;
margin: 14px 0;
white-space: pre-wrap;
}
.module-signature,
.package-signature,
.type-signature {
margin-top: 0;
}
.member-signature .type-parameters-long,
.member-signature .parameters,
.member-signature .exceptions {
display: inline-block;
vertical-align: top;
white-space: pre;
}
.member-signature .type-parameters {
white-space: normal;
}
/*
* Styles for formatting effect.
*/
.source-line-no {
color: green;
padding: 0 30px 0 0;
}
h1.hidden {
visibility: hidden;
overflow: hidden;
font-size: 10px;
}
.block {
display: block;
margin: 0 10px 5px 0;
color: #474747;
}
.deprecated-label,
.descfrm-type-label,
.implementation-label,
.member-name-label,
.member-name-link,
.module-label-in-package,
.module-label-in-type,
.override-specify-label,
.package-label-in-type,
.package-hierarchy-label,
.type-name-label,
.type-name-link,
.search-tag-link {
font-weight: bold;
}
.deprecation-comment,
.help-footnote,
.interface-name {
font-style: italic;
}
.deprecation-block {
font-size: 14px;
font-family: "DejaVu Serif", Georgia, "Times New Roman", Times, serif;
border-style: solid;
border-width: thin;
border-radius: 10px;
padding: 10px;
margin-bottom: 10px;
margin-right: 10px;
display: inline-block;
}
div.block div.deprecation-comment,
div.block div.block span.emphasized-phrase,
div.block div.block span.interface-name {
font-style: normal;
}
/*
* Styles specific to HTML5 elements.
*/
main,
nav,
header,
footer,
section {
display: block;
}
/*
* Styles for javadoc search.
*/
.ui-autocomplete-category {
font-weight: bold;
font-size: 15px;
padding: 7px 0 7px 3px;
background-color: #4d7a97;
color: #ffffff;
}
.result-item {
font-size: 13px;
}
.ui-autocomplete {
max-height: 85%;
max-width: 65%;
overflow-y: scroll;
overflow-x: scroll;
white-space: nowrap;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
}
ul.ui-autocomplete {
position: fixed;
z-index: 999999;
}
ul.ui-autocomplete li {
float: left;
clear: both;
width: 100%;
}
.result-highlight {
font-weight: bold;
}
#search {
background-image: url("resources/glass.png");
background-size: 13px;
background-repeat: no-repeat;
background-position: 2px 3px;
padding-left: 20px;
position: relative;
right: -18px;
width: 400px;
}
#reset {
background-color: rgb(255, 255, 255);
background-image: url("resources/x.png");
background-position: center;
background-repeat: no-repeat;
background-size: 12px;
border: 0 none;
width: 16px;
height: 16px;
position: relative;
left: -4px;
top: -4px;
font-size: 0px;
}
.watermark {
color: #545454;
}
.search-tag-desc-result {
font-style: italic;
font-size: 11px;
}
.search-tag-holder-result {
font-style: italic;
font-size: 12px;
}
.search-tag-result:target {
background-color: yellow;
}
.module-graph span {
display: none;
position: absolute;
}
.module-graph:hover span {
display: block;
margin: -100px 0 0 100px;
z-index: 1;
}
.inherited-list {
margin: 10px 0 10px 0;
}
section.description {
line-height: 1.4;
}
.summary section[class$="-summary"],
.details section[class$="-details"],
.class-uses .detail,
.serialized-class-details {
padding: 0px 20px 5px 10px;
border: 1px solid #ededed;
background-color: #f8f8f8;
}
.inherited-list,
section[class$="-details"] .detail {
padding: 0 0 5px 8px;
background-color: #ffffff;
border: none;
}
.vertical-separator {
padding: 0 5px;
}
ul.help-section-list {
margin: 0;
}
/*
* Indicator icon for external links.
*/
main a[href*="://"]::after
{
content: "";
display: inline-block;
background-image: url('data:image/svg+xml; utf8, \
<svg xmlns="http://www.w3.org/2000/svg" width="768" height="768">\
<path d="M584 664H104V184h216V80H0v688h688V448H584zM384 0l132 \
132-240 240 120 120 240-240 132 132V0z" fill="%234a6782"/>\
</svg>');
background-size: 100% 100%;
width: 7px;
height: 7px;
margin-left: 2px;
margin-bottom: 4px;
}
main a[href*="://"]:hover::after,
main a[href*="://"]:focus::after
{
background-image: url('data:image/svg+xml; utf8, \
<svg xmlns="http://www.w3.org/2000/svg" width="768" height="768">\
<path d="M584 664H104V184h216V80H0v688h688V448H584zM384 0l132 \
132-240 240 120 120 240-240 132 132V0z" fill="%23bb7a2a"/>\
</svg>');
}
/*
* Styles for user-provided tables.
*
* borderless:
* No borders, vertical margins, styled caption.
* This style is provided for use with existing doc comments.
* In general, borderless tables should not be used for layout purposes.
*
* plain:
* Plain borders around table and cells, vertical margins, styled caption.
* Best for small tables or for complex tables for tables with cells that span
* rows and columns, when the "striped" style does not work well.
*
* striped:
* Borders around the table and vertical borders between cells, striped rows,
* vertical margins, styled caption.
* Best for tables that have a header row, and a body containing a series of simple rows.
*/
table.borderless,
table.plain,
table.striped {
margin-top: 10px;
margin-bottom: 10px;
}
table.borderless > caption,
table.plain > caption,
table.striped > caption {
font-weight: bold;
font-size: smaller;
}
table.borderless th,
table.borderless td,
table.plain th,
table.plain td,
table.striped th,
table.striped td {
padding: 2px 5px;
}
table.borderless,
table.borderless > thead > tr > th,
table.borderless > tbody > tr > th,
table.borderless > tr > th,
table.borderless > thead > tr > td,
table.borderless > tbody > tr > td,
table.borderless > tr > td {
border: none;
}
table.borderless > thead > tr,
table.borderless > tbody > tr,
table.borderless > tr {
background-color: transparent;
}
table.plain {
border-collapse: collapse;
border: 1px solid black;
}
table.plain > thead > tr,
table.plain > tbody tr,
table.plain > tr {
background-color: transparent;
}
table.plain > thead > tr > th,
table.plain > tbody > tr > th,
table.plain > tr > th,
table.plain > thead > tr > td,
table.plain > tbody > tr > td,
table.plain > tr > td {
border: 1px solid black;
}
table.striped {
border-collapse: collapse;
border: 1px solid black;
}
table.striped > thead {
background-color: #e3e3e3;
}
table.striped > thead > tr > th,
table.striped > thead > tr > td {
border: 1px solid black;
}
table.striped > tbody > tr:nth-child(even) {
background-color: #eee;
}
table.striped > tbody > tr:nth-child(odd) {
background-color: #fff;
}
table.striped > tbody > tr > th,
table.striped > tbody > tr > td {
border-left: 1px solid black;
border-right: 1px solid black;
}
table.striped > tbody > tr > th {
font-weight: normal;
}
/**
* Tweak font sizes and paddings for small screens.
*/
@media screen and (max-width: 1050px) {
#search {
width: 300px;
}
}
@media screen and (max-width: 800px) {
#search {
width: 200px;
}
.top-nav,
.bottom-nav {
font-size: 11px;
padding-top: 6px;
}
.sub-nav {
font-size: 11px;
}
.about-language {
padding-right: 16px;
}
ul.nav-list li,
.sub-nav .nav-list-search {
padding: 6px;
}
ul.sub-nav-list li {
padding-top: 5px;
}
main {
padding: 10px;
}
.summary section[class$="-summary"],
.details section[class$="-details"],
.class-uses .detail,
.serialized-class-details {
padding: 0 8px 5px 8px;
}
body {
-webkit-text-size-adjust: none;
}
}
@media screen and (max-width: 500px) {
#search {
width: 150px;
}
.top-nav,
.bottom-nav {
font-size: 10px;
}
.sub-nav {
font-size: 10px;
}
.about-language {
font-size: 10px;
padding-right: 12px;
}
}

View file

@ -1,6 +0,0 @@
# From https://github.com/jitpack/jitpack.io/issues/4506#issuecomment-864562270
before_install:
- source "$HOME/.sdkman/bin/sdkman-init.sh"
- sdk update
- sdk install java 16.0.1.hs-adpt
- sdk use java 16.0.1.hs-adpt

View file

@ -1,11 +0,0 @@
package com.terraformersmc.modmenu.util;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
//This File was included from TerraformersMC/ModMenu
// to enable a ModMenu-Integration without having to
// compile against the actual plugin
@Environment(EnvType.CLIENT)
public interface ModMenuApiMarker {}

View file

@ -1,11 +0,0 @@
package org.anti_ad.mc.ipn.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// Included from "Inventory Profiles Next" (https://github.com/blackd/Inventory-Profiles)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IPNIgnore {}

View file

@ -5,23 +5,12 @@ import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import ru.bclib.api.TagAPI; import ru.bclib.api.TagAPI;
import ru.bclib.api.WorldDataAPI;
import ru.bclib.api.dataexchange.DataExchangeAPI;
import ru.bclib.api.dataexchange.handler.autosync.Chunker;
import ru.bclib.api.dataexchange.handler.autosync.HelloClient;
import ru.bclib.api.dataexchange.handler.autosync.HelloServer;
import ru.bclib.api.dataexchange.handler.autosync.RequestFiles;
import ru.bclib.api.dataexchange.handler.autosync.SendFiles;
import ru.bclib.config.Configs; import ru.bclib.config.Configs;
import ru.bclib.recipes.CraftingRecipes; import ru.bclib.recipes.CraftingRecipes;
import ru.bclib.registry.BaseBlockEntities; import ru.bclib.registry.BaseBlockEntities;
import ru.bclib.registry.BaseRegistry; import ru.bclib.registry.BaseRegistry;
import ru.bclib.util.Logger; import ru.bclib.util.Logger;
import ru.bclib.world.generator.BCLibEndBiomeSource; import ru.bclib.world.surface.BCLSurfaceBuilders;
import ru.bclib.world.generator.BCLibNetherBiomeSource;
import ru.bclib.world.generator.GeneratorOptions;
import java.util.List;
public class BCLib implements ModInitializer { public class BCLib implements ModInitializer {
public static final String MOD_ID = "bclib"; public static final String MOD_ID = "bclib";
@ -30,24 +19,10 @@ public class BCLib implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
BaseRegistry.register(); BaseRegistry.register();
GeneratorOptions.init();
BaseBlockEntities.register(); BaseBlockEntities.register();
BCLibEndBiomeSource.register(); BCLSurfaceBuilders.register();
BCLibNetherBiomeSource.register();
TagAPI.init(); TagAPI.init();
CraftingRecipes.init(); CraftingRecipes.init();
WorldDataAPI.registerModCache(MOD_ID);
DataExchangeAPI.registerMod(MOD_ID);
DataExchangeAPI.registerDescriptors(List.of(
HelloClient.DESCRIPTOR,
HelloServer.DESCRIPTOR,
RequestFiles.DESCRIPTOR,
SendFiles.DESCRIPTOR,
Chunker.DESCRIPTOR
));
BCLibPatch.register();
Configs.save(); Configs.save();
} }
@ -58,7 +33,7 @@ public class BCLib implements ModInitializer {
public static boolean isClient() { public static boolean isClient() {
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT; return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
} }
public static ResourceLocation makeID(String path) { public static ResourceLocation makeID(String path) {
return new ResourceLocation(MOD_ID, path); return new ResourceLocation(MOD_ID, path);
} }

View file

@ -1,82 +0,0 @@
package ru.bclib;
import net.minecraft.nbt.CompoundTag;
import ru.bclib.api.datafixer.DataFixerAPI;
import ru.bclib.api.datafixer.Patch;
import ru.bclib.interfaces.PatchFunction;
public final class BCLibPatch {
public static void register(){
DataFixerAPI.registerPatch(BiomeSourcePatch::new);
}
}
final class BiomeSourcePatch extends Patch {
private static final String NETHER_BIOME_SOURCE = "bclib:nether_biome_source";
private static final String END_BIOME_SOURCE = "bclib:end_biome_source";
protected BiomeSourcePatch() {
super(BCLib.MOD_ID, "0.4.0");
}
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() {
return (root, profile) -> {
CompoundTag worldGenSettings = root.getCompound("Data").getCompound("WorldGenSettings");
CompoundTag dimensions = worldGenSettings.getCompound("dimensions");
long seed = worldGenSettings.getLong("seed");
boolean result = false;
if (dimensions.contains("minecraft:the_nether")) {
CompoundTag dimRoot = dimensions.getCompound("minecraft:the_nether");
CompoundTag biomeSource = dimRoot.getCompound("generator").getCompound("biome_source");
if (!biomeSource.getString("type").equals(NETHER_BIOME_SOURCE)) {
BCLib.LOGGER.info("Applying Nether biome source patch");
dimRoot.put("generator", makeNetherGenerator(seed));
result = true;
}
}
if (dimensions.contains("minecraft:the_end")) {
CompoundTag dimRoot = dimensions.getCompound("minecraft:the_end");
CompoundTag biomeSource = dimRoot.getCompound("generator").getCompound("biome_source");
if (!biomeSource.getString("type").equals(END_BIOME_SOURCE)) {
BCLib.LOGGER.info("Applying End biome source patch");
dimRoot.put("generator", makeEndGenerator(seed));
result = true;
}
}
return result;
};
}
private 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;
}
private CompoundTag makeEndGenerator(long seed) {
CompoundTag generator = new CompoundTag();
generator.putString("type", "minecraft:noise");
generator.putString("settings", "minecraft:end");
generator.putLong("seed", seed);
CompoundTag biomeSource = new CompoundTag();
biomeSource.putString("type", END_BIOME_SOURCE);
biomeSource.putLong("seed", seed);
generator.put("biome_source", biomeSource);
return generator;
}
}

View file

@ -0,0 +1,158 @@
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<ResourceLocation, BCLBiome> ID_MAP = Maps.newHashMap();
private static final HashMap<Biome, BCLBiome> CLIENT = Maps.newHashMap();
private static Registry<Biome> 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<Biome> 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<Biome> 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<Biome> 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);
}
}

View file

@ -1,65 +1,53 @@
package ru.bclib.api; package ru.bclib.api;
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;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set; 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 { public class BonemealAPI {
private static final Map<ResourceLocation, Map<Block, WeightedList<Block>>> WATER_GRASS_BIOMES = Maps.newHashMap(); private static final Map<ResourceLocation, Map<Block, WeightedList<Block>>> WATER_GRASS_BIOMES = Maps.newHashMap();
private static final Map<ResourceLocation, Map<Block, WeightedList<Block>>> LAND_GRASS_BIOMES = Maps.newHashMap(); private static final Map<ResourceLocation, Map<Block, WeightedList<Block>>> LAND_GRASS_BIOMES = Maps.newHashMap();
private static final Map<Block, WeightedList<Block>> WATER_GRASS_TYPES = Maps.newHashMap(); private static final Map<Block, WeightedList<Block>> WATER_GRASS_TYPES = Maps.newHashMap();
private static final Map<Block, WeightedList<Block>> LAND_GRASS_TYPES = Maps.newHashMap(); private static final Map<Block, WeightedList<Block>> LAND_GRASS_TYPES = Maps.newHashMap();
private static final Map<Block, Block> SPREADABLE_BLOCKS = Maps.newHashMap(); private static final Set<Block> SPREADABLE_BLOCKS = Sets.newHashSet();
private static final Set<Block> TERRAIN_TO_SPREAD = Sets.newHashSet();
private static final Set<Block> TERRAIN = Sets.newHashSet();
public static void addSpreadableBlock(Block spreadableBlock, Block surfaceForSpread) { public static void addSpreadableBlock(Block block) {
SPREADABLE_BLOCKS.put(spreadableBlock, surfaceForSpread); SPREADABLE_BLOCKS.add(block);
TERRAIN_TO_SPREAD.add(surfaceForSpread);
TERRAIN.add(surfaceForSpread);
} }
public static boolean isTerrain(Block block) { public static boolean isSpreadable(Block block) {
return TERRAIN.contains(block); return SPREADABLE_BLOCKS.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) { public static void addLandGrass(Block plant, Block... terrain) {
for (Block block : terrain) { for (Block block: terrain) {
addLandGrass(plant, block, 1F); addLandGrass(block, plant, 1F);
} }
} }
public static void addLandGrass(ResourceLocation biome, Block plant, Block... terrain) { public static void addLandGrass(ResourceLocation biome, Block plant, Block... terrain) {
for (Block block : terrain) { for (Block block: terrain) {
addLandGrass(biome, plant, block, 1F); addLandGrass(biome, block, plant, 1F);
} }
} }
public static void addLandGrass(Block plant, Block terrain, float chance) { public static void addLandGrass(Block terrain, Block plant, float chance) {
WeightedList<Block> list = LAND_GRASS_TYPES.get(terrain); WeightedList<Block> list = LAND_GRASS_TYPES.get(terrain);
if (list == null) { if (list == null) {
list = new WeightedList<Block>(); list = new WeightedList<Block>();
LAND_GRASS_TYPES.put(terrain, list); LAND_GRASS_TYPES.put(terrain, list);
} }
TERRAIN.add(terrain);
list.add(plant, chance); list.add(plant, chance);
} }
public static void addLandGrass(ResourceLocation biome, Block plant, Block terrain, float chance) { public static void addLandGrass(ResourceLocation biome, Block terrain, Block plant, float chance) {
Map<Block, WeightedList<Block>> map = LAND_GRASS_BIOMES.get(biome); Map<Block, WeightedList<Block>> map = LAND_GRASS_BIOMES.get(biome);
if (map == null) { if (map == null) {
map = Maps.newHashMap(); map = Maps.newHashMap();
@ -70,33 +58,31 @@ public class BonemealAPI {
list = new WeightedList<Block>(); list = new WeightedList<Block>();
map.put(terrain, list); map.put(terrain, list);
} }
TERRAIN.add(terrain);
list.add(plant, chance); list.add(plant, chance);
} }
public static void addWaterGrass(Block plant, Block... terrain) { public static void addWaterGrass(Block plant, Block... terrain) {
for (Block block : terrain) { for (Block block: terrain) {
addWaterGrass(plant, block, 1F); addWaterGrass(block, plant, 1F);
} }
} }
public static void addWaterGrass(ResourceLocation biome, Block plant, Block... terrain) { public static void addWaterGrass(ResourceLocation biome, Block plant, Block... terrain) {
for (Block block : terrain) { for (Block block: terrain) {
addWaterGrass(biome, plant, block, 1F); addWaterGrass(biome, block, plant, 1F);
} }
} }
public static void addWaterGrass(Block plant, Block terrain, float chance) { public static void addWaterGrass(Block terrain, Block plant, float chance) {
WeightedList<Block> list = WATER_GRASS_TYPES.get(terrain); WeightedList<Block> list = WATER_GRASS_TYPES.get(terrain);
if (list == null) { if (list == null) {
list = new WeightedList<Block>(); list = new WeightedList<Block>();
WATER_GRASS_TYPES.put(terrain, list); WATER_GRASS_TYPES.put(terrain, list);
} }
TERRAIN.add(terrain);
list.add(plant, chance); list.add(plant, chance);
} }
public static void addWaterGrass(ResourceLocation biome, Block plant, Block terrain, float chance) { public static void addWaterGrass(ResourceLocation biome, Block terrain, Block plant, float chance) {
Map<Block, WeightedList<Block>> map = WATER_GRASS_BIOMES.get(biome); Map<Block, WeightedList<Block>> map = WATER_GRASS_BIOMES.get(biome);
if (map == null) { if (map == null) {
map = Maps.newHashMap(); map = Maps.newHashMap();
@ -107,7 +93,6 @@ public class BonemealAPI {
list = new WeightedList<Block>(); list = new WeightedList<Block>();
map.put(terrain, list); map.put(terrain, list);
} }
TERRAIN.add(terrain);
list.add(plant, chance); list.add(plant, chance);
} }
@ -127,16 +112,16 @@ public class BonemealAPI {
} }
public static Block getWaterGrass(ResourceLocation biomeID, Block terrain, Random random) { public static Block getWaterGrass(ResourceLocation biomeID, Block terrain, Random random) {
Map<Block, WeightedList<Block>> map = WATER_GRASS_BIOMES.get(biomeID); Map<Block, WeightedList<Block>> map = LAND_GRASS_BIOMES.get(biomeID);
WeightedList<Block> list = null; WeightedList<Block> list = null;
if (map != null) { if (map != null) {
list = map.get(terrain); list = map.get(terrain);
if (list == null) { if (list == null) {
list = WATER_GRASS_TYPES.get(terrain); list = LAND_GRASS_TYPES.get(terrain);
} }
} }
else { else {
list = WATER_GRASS_TYPES.get(terrain); list = LAND_GRASS_TYPES.get(terrain);
} }
return list == null ? null : list.get(random); return list == null ? null : list.get(random);
} }

View file

@ -1,22 +0,0 @@
package ru.bclib.api;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
import ru.bclib.mixin.common.ComposterBlockAccessor;
public class ComposterAPI {
public static Block allowCompost(float chance, Block block){
if (block != null) {
allowCompost(chance, block.asItem());
}
return block;
}
public static Item allowCompost(float chance, Item item){
if (item != null && item != Items.AIR) {
ComposterBlockAccessor.callAdd(chance, item);
}
return item;
}
}

View file

@ -0,0 +1,149 @@
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<String, String> REPLACEMENT = Maps.newHashMap();
private static final Map<String, Integer> 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<ModContainer> 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<File> 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<File> getAllRegions(File dir, List<File> 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);
}
}

View file

@ -1,144 +0,0 @@
package ru.bclib.api;
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.chunk.ChunkGenerator;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.ServerLevelData;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* provides some lifetime hooks for a Minecraft instance
*/
public class LifeCycleAPI {
private final static List<LevelLoadBiomesCall> onLoadLevelBiomes = new ArrayList<>(2);
private final static List<LevelLoadCall> onLoadLevel = new ArrayList<>(2);
private final static List<BeforeLevelLoadCall> beforeLoadLevel = new ArrayList<>(2);
/**
* A callback function that is used for each new ServerLevel instance
*/
public interface BeforeLevelLoadCall {
void beforeLoad();
}
/**
* A callback function that is used for each new ServerLevel instance
*/
public interface LevelLoadBiomesCall {
void onLoad(ServerLevel world, long seed, Registry<Biome> registry);
}
/**
* A callback function that is used for each new ServerLevel instance
*/
public interface LevelLoadCall {
void onLoad(
ServerLevel world,
MinecraftServer minecraftServer,
Executor executor,
LevelStorageSource.LevelStorageAccess levelStorageAccess,
ServerLevelData serverLevelData,
ResourceKey<Level> resourceKey,
DimensionType dimensionType,
ChunkProgressListener chunkProgressListener,
ChunkGenerator chunkGenerator,
boolean bl,
long l,
List<CustomSpawner> list,
boolean bl2);
}
/**
* Register a callback that is called before a level is loaded or created,
* but after the {@link WorldDataAPI} was initialized and patches from
* the {@link ru.bclib.api.datafixer.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 dimensionType
* @param chunkProgressListener
* @param chunkGenerator
* @param bl
* @param l
* @param list
* @param bl2
*/
public static void _runLevelLoad(ServerLevel world,
MinecraftServer minecraftServer,
Executor executor,
LevelStorageSource.LevelStorageAccess levelStorageAccess,
ServerLevelData serverLevelData,
ResourceKey<Level> resourceKey,
DimensionType dimensionType,
ChunkProgressListener chunkProgressListener,
ChunkGenerator chunkGenerator,
boolean bl,
long l,
List<CustomSpawner> list,
boolean bl2){
onLoadLevel.forEach(c -> c.onLoad(
world,
minecraftServer,
executor,
levelStorageAccess,
serverLevelData,
resourceKey,
dimensionType,
chunkProgressListener,
chunkGenerator,
bl,
l,
list,
bl2)
);
final long seed = world.getSeed();
final Registry<Biome> biomeRegistry = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
onLoadLevelBiomes.forEach(c -> c.onLoad(world, seed, biomeRegistry));
}
}

View file

@ -1,18 +1,16 @@
package ru.bclib.api; package ru.bclib.api;
import com.google.common.collect.Lists;
import net.fabricmc.loader.api.FabricLoader;
import ru.bclib.integration.ModIntegration;
import java.util.List; import java.util.List;
import com.google.common.collect.Lists;
import ru.bclib.integration.ModIntegration;
public class ModIntegrationAPI { public class ModIntegrationAPI {
private static final List<ModIntegration> INTEGRATIONS = Lists.newArrayList(); private static final List<ModIntegration> INTEGRATIONS = Lists.newArrayList();
private static final boolean HAS_CANVAS = FabricLoader.getInstance().isModLoaded("canvas");
/** /**
* Registers mod integration * Registers mod integration
*
* @param integration * @param integration
* @return * @return
*/ */
@ -23,7 +21,6 @@ public class ModIntegrationAPI {
/** /**
* Get all registered mod integrations. * Get all registered mod integrations.
*
* @return {@link List} of {@link ModIntegration}. * @return {@link List} of {@link ModIntegration}.
*/ */
public static List<ModIntegration> getIntegrations() { public static List<ModIntegration> getIntegrations() {
@ -40,8 +37,4 @@ public class ModIntegrationAPI {
} }
}); });
} }
public static boolean hasCanvas() {
return HAS_CANVAS;
}
} }

View file

@ -1,101 +0,0 @@
package ru.bclib.api;
import com.google.common.collect.Lists;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.Registry;
import net.minecraft.tags.Tag;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import ru.bclib.api.biomes.BiomeAPI;
import ru.bclib.blocks.BaseBarrelBlock;
import ru.bclib.blocks.BaseChestBlock;
import ru.bclib.blocks.BaseFurnaceBlock;
import ru.bclib.blocks.BaseSignBlock;
import ru.bclib.client.render.BCLRenderLayer;
import ru.bclib.client.render.BaseChestBlockEntityRenderer;
import ru.bclib.client.render.BaseSignBlockEntityRenderer;
import ru.bclib.interfaces.PostInitable;
import ru.bclib.interfaces.RenderLayerProvider;
import ru.bclib.interfaces.TagProvider;
import ru.bclib.registry.BaseBlockEntities;
import java.util.List;
import java.util.function.Consumer;
public class PostInitAPI {
private static List<Consumer<Boolean>> postInitFunctions = Lists.newArrayList();
private static List<Tag.Named<Block>> blockTags = Lists.newArrayList();
private static List<Tag.Named<Item>> itemTags = Lists.newArrayList();
/**
* Register a new function which will be called after all mods are initiated. Will be called on both client and server.
* @param function {@link Consumer} with {@code boolean} parameter ({@code true} for client, {@code false} for server).
*/
public static void register(Consumer<Boolean> function) {
postInitFunctions.add(function);
}
/**
* Called in proper BCLib entry points, for internal usage only.
* @param isClient {@code boolean}, {@code true} for client, {@code false} for server.
*/
public static void postInit(boolean isClient) {
if (postInitFunctions == null) {
return;
}
postInitFunctions.forEach(function -> function.accept(isClient));
Registry.BLOCK.forEach(block -> {
processBlockCommon(block);
if (isClient) {
processBlockClient(block);
}
});
postInitFunctions = null;
blockTags = null;
itemTags = null;
BiomeAPI.loadFabricAPIBiomes();
}
@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 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 TagProvider) {
TagProvider.class.cast(block).addTags(blockTags, itemTags);
blockTags.forEach(tag -> TagAPI.addTag(tag, block));
itemTags.forEach(tag -> TagAPI.addTag(tag, block));
blockTags.clear();
itemTags.clear();
}
}
}

View file

@ -1,13 +1,9 @@
package ru.bclib.api; package ru.bclib.api;
import com.google.common.collect.Maps; import java.util.function.Supplier;
import com.google.common.collect.Sets;
import net.fabricmc.fabric.api.tag.TagFactory;
import net.fabricmc.fabric.api.tag.TagRegistry; import net.fabricmc.fabric.api.tag.TagRegistry;
import net.fabricmc.fabric.impl.tag.extension.TagDelegate;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.tags.BlockTags; import net.minecraft.tags.BlockTags;
import net.minecraft.tags.ItemTags; import net.minecraft.tags.ItemTags;
import net.minecraft.tags.Tag; import net.minecraft.tags.Tag;
@ -15,77 +11,45 @@ import net.minecraft.tags.Tag.Named;
import net.minecraft.tags.TagCollection; import net.minecraft.tags.TagCollection;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import ru.bclib.BCLib; import ru.bclib.BCLib;
import ru.bclib.util.TagHelper;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
public class TagAPI { public class TagAPI {
private static final Map<ResourceLocation, Set<ResourceLocation>> TAGS_BLOCK = Maps.newConcurrentMap();
private static final Map<ResourceLocation, Set<ResourceLocation>> TAGS_ITEM = Maps.newConcurrentMap();
// Block Tags // Block Tags
public static final Tag.Named<Block> BLOCK_BOOKSHELVES = makeCommonBlockTag("bookshelves"); public static final Tag.Named<Block> BOOKSHELVES = makeCommonBlockTag("bookshelves");
public static final Tag.Named<Block> BLOCK_GEN_TERRAIN = makeBlockTag(BCLib.MOD_ID, "gen_terrain"); public static final Tag.Named<Block> GEN_TERRAIN = makeBlockTag(BCLib.MOD_ID, "gen_terrain");
public static final Tag.Named<Block> BLOCK_NETHER_GROUND = makeBlockTag(BCLib.MOD_ID, "nether_ground"); public static final Tag.Named<Block> NETHER_GROUND = makeBlockTag(BCLib.MOD_ID, "nether_ground");
public static final Tag.Named<Block> BLOCK_END_GROUND = makeBlockTag(BCLib.MOD_ID, "end_ground"); public static final Tag.Named<Block> END_GROUND = makeBlockTag(BCLib.MOD_ID, "end_ground");
public static final Tag.Named<Block> BLOCK_CHEST = makeCommonBlockTag("chest"); public static final Tag.Named<Block> BLOCK_CHEST = makeCommonBlockTag("chest");
public static final Tag.Named<Block> BLOCK_WOODEN_CHEST = makeCommonBlockTag("wooden_chests"); public static final Tag.Named<Block> END_STONES = makeCommonBlockTag("end_stones");
public static final Tag.Named<Block> BLOCK_BARREL = makeCommonBlockTag("barrel"); public static final Tag.Named<Block> NETHER_STONES = makeCommonBlockTag("nether_stones");
public static final Tag.Named<Block> BLOCK_WOODEN_BARREL = makeCommonBlockTag("wooden_barrels");
public static final Tag.Named<Block> BLOCK_END_STONES = makeCommonBlockTag("end_stones");
public static final Tag.Named<Block> BLOCK_NETHER_STONES = makeCommonBlockTag("nether_stones");
public static final Tag.Named<Block> BLOCK_NETHER_PORTAL_FRAME = makeCommonBlockTag("nether_pframe");
public static final Tag.Named<Block> BLOCK_WORKBENCHES = makeCommonBlockTag("workbench");
public static final Tag.Named<Block> BLOCK_SAPLINGS = makeCommonBlockTag("saplings");
public static final Tag.Named<Block> BLOCK_LEAVES = makeCommonBlockTag("leaves");
public static final Tag.Named<Block> BLOCK_IMMOBILE = makeCommonBlockTag("immobile");
public static final Tag.Named<Block> BLOCK_DRAGON_IMMUNE = getMCBlockTag("dragon_immune"); public static final Tag.Named<Block> DRAGON_IMMUNE = getMCBlockTag("dragon_immune");
public static final Tag.Named<Block> MINEABLE_AXE = getMCBlockTag("mineable/axe");
public static final Tag.Named<Block> MINEABLE_PICKAXE = getMCBlockTag("mineable/pickaxe");
public static final Tag.Named<Block> MINEABLE_SHOVEL = getMCBlockTag("mineable/shovel");
public static final Tag.Named<Block> MINEABLE_HOE = getMCBlockTag("mineable/hoe");
// Item Tags // Item Tags
public static final Tag.Named<Item> ITEM_CHEST = makeCommonItemTag("chest"); public static final Tag.Named<Item> ITEM_CHEST = makeCommonItemTag("chest");
public static final Tag.Named<Item> ITEM_WOODEN_CHEST = makeCommonItemTag("wooden_chests"); public static final Tag.Named<Item> IRON_INGOTS = makeCommonItemTag("iron_ingots");
public static final Tag.Named<Item> ITEM_BARREL = makeCommonItemTag("barrel"); public static final Tag.Named<Item> FURNACES = makeCommonItemTag("furnaces");
public static final Tag.Named<Item> ITEM_WOODEN_BARREL = makeCommonItemTag("wooden_barrels"); public final static Tag.Named<Item> HAMMERS = makeItemTag("fabric", "hammers");
public static final Tag.Named<Item> ITEM_IRON_INGOTS = makeCommonItemTag("iron_ingots");
public static final Tag.Named<Item> ITEM_FURNACES = makeCommonItemTag("furnaces");
public static final Tag.Named<Item> ITEM_WORKBENCHES = makeCommonItemTag("workbench");
public final static Tag.Named<Item> ITEM_HAMMERS = makeCommonItemTag("hammers");
public static final Tag.Named<Item> ITEM_SAPLINGS = makeCommonItemTag("saplings");
public static final Tag.Named<Item> ITEM_LEAVES = makeCommonItemTag("leaves");
public static final Tag.Named<Item> ITEM_SHEARS = getMCItemTag("shears");
public static final Tag.Named<Item> ITEM_COMMON_SHEARS = makeCommonItemTag("shears");
/** /**
* Get or create {@link Tag.Named}. * Get or create {@link Tag.Named}.
*
* @param containerSupplier - {@link TagCollection} {@link Supplier} tag collection; * @param containerSupplier - {@link TagCollection} {@link Supplier} tag collection;
* @param id - {@link ResourceLocation} tag id. * @param id - {@link ResourceLocation} tag id.
* @return {@link Tag.Named}. * @return {@link Tag.Named}.
*/ */
public static <T> Tag.Named<T> makeTag(Supplier<TagCollection<T>> containerSupplier, ResourceLocation id) { public static <T> Tag.Named<T> makeTag(Supplier<TagCollection<T>> containerSupplier, ResourceLocation id) {
Tag<T> tag = containerSupplier.get().getTag(id); Tag<T> tag = containerSupplier.get().getTag(id);
//return tag == null ? TagRegistry.create(id, containerSupplier) : (Named<T>) tag; return tag == null ? TagRegistry.create(id, containerSupplier) : (Named<T>) tag;
return tag == null ? new TagDelegate<>(id, containerSupplier) : (Named<T>) tag;
} }
/** /**
* Get or create {@link Block} {@link Tag.Named} with mod namespace. * Get or create {@link Block} {@link Tag.Named} with mod namespace.
*
* @param modID - {@link String} mod namespace (mod id); * @param modID - {@link String} mod namespace (mod id);
* @param name - {@link String} tag name. * @param name - {@link String} tag name.
* @return {@link Block} {@link Tag.Named}. * @return {@link Block} {@link Tag.Named}.
*/ */
public static Tag.Named<Block> makeBlockTag(String modID, String name) { public static Tag.Named<Block> makeBlockTag(String modID, String name) {
@ -94,9 +58,8 @@ public class TagAPI {
/** /**
* Get or create {@link Item} {@link Tag.Named} with mod namespace. * Get or create {@link Item} {@link Tag.Named} with mod namespace.
*
* @param modID - {@link String} mod namespace (mod id); * @param modID - {@link String} mod namespace (mod id);
* @param name - {@link String} tag name. * @param name - {@link String} tag name.
* @return {@link Item} {@link Tag.Named}. * @return {@link Item} {@link Tag.Named}.
*/ */
public static Tag.Named<Item> makeItemTag(String modID, String name) { public static Tag.Named<Item> makeItemTag(String modID, String name) {
@ -105,10 +68,9 @@ public class TagAPI {
/** /**
* Get or create {@link Block} {@link Tag.Named}. * Get or create {@link Block} {@link Tag.Named}.
* * @see <a href="https://fabricmc.net/wiki/tutorial:tags">Fabric Wiki (Tags)</a>
* @param name - {@link String} tag name. * @param name - {@link String} tag name.
* @return {@link Block} {@link Tag.Named}. * @return {@link Block} {@link Tag.Named}.
* @see <a href="https://fabricmc.net/wiki/tutorial:tags">Fabric Wiki (Tags)</a>
*/ */
public static Tag.Named<Block> makeCommonBlockTag(String name) { public static Tag.Named<Block> makeCommonBlockTag(String name) {
return makeTag(BlockTags::getAllTags, new ResourceLocation("c", name)); return makeTag(BlockTags::getAllTags, new ResourceLocation("c", name));
@ -116,10 +78,9 @@ public class TagAPI {
/** /**
* Get or create {@link Item} {@link Tag.Named}. * Get or create {@link Item} {@link Tag.Named}.
* * @see <a href="https://fabricmc.net/wiki/tutorial:tags">Fabric Wiki (Tags)</a>
* @param name - {@link String} tag name. * @param name - {@link String} tag name.
* @return {@link Item} {@link Tag.Named}. * @return {@link Item} {@link Tag.Named}.
* @see <a href="https://fabricmc.net/wiki/tutorial:tags">Fabric Wiki (Tags)</a>
*/ */
public static Tag.Named<Item> makeCommonItemTag(String name) { public static Tag.Named<Item> makeCommonItemTag(String name) {
return makeTag(ItemTags::getAllTags, new ResourceLocation("c", name)); return makeTag(ItemTags::getAllTags, new ResourceLocation("c", name));
@ -127,176 +88,44 @@ public class TagAPI {
/** /**
* Get or create Minecraft {@link Block} {@link Tag.Named}. * Get or create Minecraft {@link Block} {@link Tag.Named}.
*
* @param name - {@link String} tag name. * @param name - {@link String} tag name.
* @return {@link Block} {@link Tag.Named}. * @return {@link Block} {@link Tag.Named}.
*/ */
public static Tag.Named<Block> getMCBlockTag(String name) { public static Tag.Named<Block> getMCBlockTag(String name) {
ResourceLocation id = new ResourceLocation(name); ResourceLocation id = new ResourceLocation(name);
Tag<Block> tag = BlockTags.getAllTags().getTag(id); Tag<Block> tag = BlockTags.getAllTags().getTag(id);
//return tag == null ? (Named<Block>) TagRegistry.block(id) : (Named<Block>) tag; return tag == null ? (Named<Block>) TagRegistry.block(id) : (Named<Block>) tag;
return tag == null ? (Named<Block>) TagFactory.BLOCK.create(id): (Named<Block>) tag;
}
/**
* Get or create Minecraft {@link Item} {@link Tag.Named}.
*
* @param name - {@link String} tag name.
* @return {@link Item} {@link Tag.Named}.
*/
public static Tag.Named<Item> getMCItemTag(String name) {
ResourceLocation id = new ResourceLocation(name);
Tag<Item> tag = ItemTags.getAllTags().getTag(id);
return tag == null ? (Named<Item>) TagRegistry.item(id) : (Named<Item>) tag;
} }
/** /**
* Adds {@link Block} to NETHER_GROUND and GEN_TERRAIN tags to process it properly in terrain generators and block logic. * Adds {@link Block} to NETHER_GROUND and GEN_TERRAIN tags to process it properly in terrain generators and block logic.
*
* @param block - {@link Block}. * @param block - {@link Block}.
*/ */
public static void addNetherGround(Block block) { public static void addNetherGround(Block block) {
addTag(BLOCK_NETHER_GROUND, block); TagHelper.addTag(NETHER_GROUND, block);
addTag(BLOCK_GEN_TERRAIN, 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. * Adds {@link Block} to END_GROUND and GEN_TERRAIN tags to process it properly in terrain generators and block logic.
*
* @param block - {@link Block}. * @param block - {@link Block}.
*/ */
public static void addEndGround(Block block) { public static void addEndGround(Block block) {
addTag(BLOCK_GEN_TERRAIN, block); TagHelper.addTag(GEN_TERRAIN, block);
addTag(BLOCK_END_GROUND, block); TagHelper.addTag(END_GROUND, block);
} }
/** /**
* Initializes basic tags. Should be called only in BCLib main class. * Initializes basic tags. Should be called only in BCLib main class.
*/ */
public static void init() { public static void init() {
addTag(BLOCK_BOOKSHELVES, Blocks.BOOKSHELF); TagHelper.addTag(BOOKSHELVES, Blocks.BOOKSHELF);
addTag(BLOCK_GEN_TERRAIN, Blocks.END_STONE, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL); TagHelper.addTag(GEN_TERRAIN, Blocks.END_STONE, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL);
addTag(BLOCK_NETHER_GROUND, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL); TagHelper.addTag(NETHER_GROUND, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL);
addTag(BLOCK_END_GROUND, Blocks.END_STONE); TagHelper.addTag(END_GROUND, Blocks.END_STONE);
addTag(BLOCK_CHEST, Blocks.CHEST); TagHelper.addTag(BLOCK_CHEST, Blocks.CHEST);
addTag(ITEM_CHEST, Items.CHEST); TagHelper.addTag(ITEM_CHEST, Items.CHEST);
addTag(ITEM_IRON_INGOTS, Items.IRON_INGOT); TagHelper.addTag(IRON_INGOTS, Items.IRON_INGOT);
addTag(ITEM_FURNACES, Blocks.FURNACE); TagHelper.addTag(FURNACES, Blocks.FURNACE);
}
/**
* Adds one Tag to multiple Blocks.
* <p>
* Example:
* <pre>{@code Tag.Named<Block> DIMENSION_STONE = makeBlockTag("mymod", "dim_stone");
* addTag(DIMENSION_STONE, Blocks.END_STONE, Blocks.NETHERRACK);}</pre>
* <p>
* The call will reserve the Tag. The Tag is added to the blocks once
* {@link #apply(String, Map)} was executed.
*
* @param tag The new Tag
* @param blocks One or more blocks that should receive the Tag.
*/
public static void addTag(Tag.Named<Block> tag, Block... blocks) {
ResourceLocation tagID = tag.getName();
Set<ResourceLocation> 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);
}
}
}
/**
* Adds one Tag to multiple Items.
* <p>
* Example:
* <pre>{@code Tag.Named<Item> METALS = makeBlockTag("mymod", "metals");
* addTag(METALS, Items.IRON_INGOT, Items.GOLD_INGOT, Items.COPPER_INGOT);}</pre>
* <p>
* The call will reserve the Tag. The Tag is added to the items once
* {@link #apply(String, Map)} was executed.
*
* @param tag The new Tag
* @param items One or more item that should receive the Tag.
*/
public static void addTag(Tag.Named<Item> tag, ItemLike... items) {
ResourceLocation tagID = tag.getName();
Set<ResourceLocation> 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);
}
}
}
/**
* Adds multiple Tags to one Item.
* <p>
* The call will reserve the Tags. The Tags are added to the Item once
* * {@link #apply(String, Map)} was executed.
*
* @param item The Item that will receive all Tags
* @param tags One or more Tags
*/
@SafeVarargs
public static void addTags(ItemLike item, Tag.Named<Item>... tags) {
for (Tag.Named<Item> tag : tags) {
addTag(tag, item);
}
}
/**
* Adds multiple Tags to one Block.
* <p>
* The call will reserve the Tags. The Tags are added to the Block once
* * {@link #apply(String, Map)} was executed.
*
* @param block The Block that will receive all Tags
* @param tags One or more Tags
*/
@SafeVarargs
public static void addTags(Block block, Tag.Named<Block>... tags) {
for (Tag.Named<Block> tag : tags) {
addTag(tag, block);
}
}
/**
* Adds all {@code ids} to the {@code builder}.
*
* @param builder
* @param ids
* @return The Builder passed as {@code builder}.
*/
public static Tag.Builder apply(Tag.Builder builder, Set<ResourceLocation> ids) {
ids.forEach(value -> builder.addElement(value, "Better End Code"));
return builder;
}
/**
* Automatically called in {@link net.minecraft.tags.TagLoader#loadAndBuild(ResourceManager)}.
* <p>
* In most cases there is no need to call this Method manually.
*
* @param directory The name of the Tag-directory. Should be either <i>"tags/blocks"</i> or
* <i>"tags/items"</i>.
* @param tagsMap The map that will hold the registered Tags
* @return The {@code tagsMap} Parameter.
*/
public static Map<ResourceLocation, Tag.Builder> apply(String directory, Map<ResourceLocation, Tag.Builder> tagsMap) {
Map<ResourceLocation, Set<ResourceLocation>> 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;
} }
} }

View file

@ -1,30 +1,20 @@
package ru.bclib.api; package ru.bclib.api;
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 net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
import ru.bclib.BCLib;
import ru.bclib.api.datafixer.DataFixerAPI;
import ru.bclib.util.ModUtil;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
/** import com.google.common.collect.Lists;
* Mod-specifix data-storage for a world. import com.google.common.collect.Maps;
* <p>
* This class provides the ability for mod to store persistent data inside a world. The Storage for the world is import net.fabricmc.loader.api.FabricLoader;
* currently initialized as part of the {@link DataFixerAPI} in {@link DataFixerAPI#fixData(LevelStorageAccess, boolean, Consumer)} import net.fabricmc.loader.api.ModContainer;
* or {@link DataFixerAPI#initializeWorldData(File, boolean)} import net.minecraft.nbt.CompoundTag;
*/ import net.minecraft.nbt.NbtIo;
import ru.bclib.BCLib;
public class WorldDataAPI { public class WorldDataAPI {
private static final Map<String, CompoundTag> TAGS = Maps.newHashMap(); private static final Map<String, CompoundTag> TAGS = Maps.newHashMap();
private static final List<String> MODS = Lists.newArrayList(); private static final List<String> MODS = Lists.newArrayList();
@ -32,43 +22,36 @@ public class WorldDataAPI {
public static void load(File dataDir) { public static void load(File dataDir) {
WorldDataAPI.dataDir = dataDir; WorldDataAPI.dataDir = dataDir;
MODS.stream() MODS.stream().parallel().forEach(modID -> {
.parallel() File file = new File(dataDir, modID + ".nbt");
.forEach(modID -> { CompoundTag root = new CompoundTag();
File file = new File(dataDir, modID + ".nbt"); TAGS.put(modID, root);
CompoundTag root = new CompoundTag(); if (file.exists()) {
if (file.exists()) { try {
try { root = NbtIo.readCompressed(file);
root = NbtIo.readCompressed(file);
}
catch (IOException e) {
BCLib.LOGGER.error("World data loading failed", e);
}
TAGS.put(modID, root);
} }
else { catch (IOException e) {
Optional<ModContainer> optional = FabricLoader.getInstance() BCLib.LOGGER.error("World data loading failed", e);
.getModContainer(modID);
if (optional.isPresent()) {
ModContainer modContainer = optional.get();
if (BCLib.isDevEnvironment()) {
root.putString("version", "255.255.9999");
}
else {
root.putString("version", modContainer.getMetadata()
.getVersion()
.toString());
}
TAGS.put(modID, root);
saveFile(modID);
}
} }
}); }
else {
Optional<ModContainer> 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. * Register mod cache, world cache is located in world data folder.
*
* @param modID - {@link String} modID. * @param modID - {@link String} modID.
*/ */
public static void registerModCache(String modID) { public static void registerModCache(String modID) {
@ -77,7 +60,6 @@ public class WorldDataAPI {
/** /**
* Get root {@link CompoundTag} for mod cache in world data folder. * Get root {@link CompoundTag} for mod cache in world data folder.
*
* @param modID - {@link String} modID. * @param modID - {@link String} modID.
* @return {@link CompoundTag} * @return {@link CompoundTag}
*/ */
@ -92,14 +74,13 @@ public class WorldDataAPI {
/** /**
* Get {@link CompoundTag} with specified path from mod cache in world data folder. * Get {@link CompoundTag} with specified path from mod cache in world data folder.
*
* @param modID - {@link String} path to tag, dot-separated. * @param modID - {@link String} path to tag, dot-separated.
* @return {@link CompoundTag} * @return {@link CompoundTag}
*/ */
public static CompoundTag getCompoundTag(String modID, String path) { public static CompoundTag getCompoundTag(String modID, String path) {
String[] parts = path.split("\\."); String[] parts = path.split("\\.");
CompoundTag tag = getRootTag(modID); CompoundTag tag = getRootTag(modID);
for (String part : parts) { for (String part: parts) {
if (tag.contains(part)) { if (tag.contains(part)) {
tag = tag.getCompound(part); tag = tag.getCompound(part);
} }
@ -114,14 +95,10 @@ public class WorldDataAPI {
/** /**
* Forces mod cache file to be saved. * Forces mod cache file to be saved.
*
* @param modID {@link String} mod ID. * @param modID {@link String} mod ID.
*/ */
public static void saveFile(String modID) { public static void saveFile(String modID) {
try { try {
if (!dataDir.exists()) {
dataDir.mkdirs();
}
NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt")); NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt"));
} }
catch (IOException e) { catch (IOException e) {
@ -131,7 +108,6 @@ public class WorldDataAPI {
/** /**
* Get stored mod version (only for mods with registered cache). * Get stored mod version (only for mods with registered cache).
*
* @return {@link String} mod version. * @return {@link String} mod version.
*/ */
public static String getModVersion(String modID) { public static String getModVersion(String modID) {
@ -140,10 +116,9 @@ public class WorldDataAPI {
/** /**
* Get stored mod version as integer (only for mods with registered cache). * Get stored mod version as integer (only for mods with registered cache).
*
* @return {@code int} mod version. * @return {@code int} mod version.
*/ */
public static int getIntModVersion(String modID) { public static int getIntModVersion(String modID) {
return ModUtil.convertModVersion(getModVersion(modID)); return DataFixerAPI.getModVersion(getModVersion(modID));
} }
} }

View file

@ -1,599 +0,0 @@
package ru.bclib.api.biomes;
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.data.worldgen.BiomeDefaultFeatures;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.Music;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
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.BiomeBuilder;
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;
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.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.feature.ConfiguredStructureFeature;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import ru.bclib.util.ColorUtil;
import ru.bclib.world.biomes.BCLBiome;
import ru.bclib.world.features.BCLFeature;
import ru.bclib.world.structures.BCLStructureFeature;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
public class BCLBiomeBuilder {
private static final BCLBiomeBuilder INSTANCE = new BCLBiomeBuilder();
private static final SurfaceRules.ConditionSource SURFACE_NOISE = SurfaceRules.noiseCondition(Noises.SOUL_SAND_LAYER, -0.012);
private List<ConfiguredStructureFeature> structures = new ArrayList<>(16);
private BiomeGenerationSettings.Builder generationSettings;
private BiomeSpecialEffects.Builder effectsBuilder;
private MobSpawnSettings.Builder spawnSettings;
private SurfaceRules.RuleSource surfaceRule;
private Precipitation precipitation;
private ResourceLocation biomeID;
private BiomeCategory category;
private float temperature;
private float fogDensity;
private float downfall;
/**
* 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.category = BiomeCategory.NONE;
INSTANCE.generationSettings = null;
INSTANCE.effectsBuilder = null;
INSTANCE.spawnSettings = null;
INSTANCE.structures.clear();
INSTANCE.temperature = 1.0F;
INSTANCE.fogDensity = 1.0F;
INSTANCE.downfall = 1.0F;
return INSTANCE;
}
/**
* 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 category. Doesn't affect biome worldgen, but Fabric biome modifications can target biome by it.
* @param category {@link BiomeCategory}
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder category(BiomeCategory category) {
this.category = category;
return this;
}
/**
* Set biome temperature, affect plant color, biome generation and ice formation.
* @param temperature biome temperature.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder temperature(float temperature) {
this.temperature = temperature;
return this;
}
/**
* Set biome wetness (same as downfall). Affect plant color and biome generation.
* @param wetness biome wetness (downfall).
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder wetness(float wetness) {
this.downfall = wetness;
return this;
}
/**
* Adds mob spawning to biome.
* @param entityType {@link EntityType} mob type.
* @param weight spawn weight.
* @param minGroupCount minimum mobs in group.
* @param maxGroupCount maximum mobs in group.
* @return same {@link BCLBiomeBuilder} instance.
*/
public <M extends Mob> BCLBiomeBuilder spawn(EntityType<M> entityType, int weight, int minGroupCount, int maxGroupCount) {
getSpawns().addSpawn(entityType.getCategory(), new SpawnerData(entityType, weight, minGroupCount, maxGroupCount));
return this;
}
/**
* Adds 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 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, PlacedFeature feature) {
getGeneration().addFeature(decoration, feature);
return this;
}
/**
* Adds vanilla Mushrooms.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder defaultMushrooms() {
return feature(BiomeDefaultFeatures::addDefaultMushrooms);
}
/**
* Adds vanilla Nether Ores.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder netherDefaultOres() {
return feature(BiomeDefaultFeatures::addNetherDefaultOres);
}
/**
* Will add features into biome, used for vanilla feature adding functions.
* @param featureAdd {@link Consumer} with {@link BiomeGenerationSettings.Builder}.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder feature(Consumer<BiomeGenerationSettings.Builder> featureAdd) {
featureAdd.accept(getGeneration());
return this;
}
/**
* Adds new feature to the biome.
* @param feature {@link BCLFeature}.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder feature(BCLFeature feature) {
return feature(feature.getDecoration(), feature.getPlacedFeature());
}
/**
* Adds new structure feature into the biome.
* @param structure {@link ConfiguredStructureFeature} to add.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder structure(ConfiguredStructureFeature<?, ?> structure) {
structures.add(structure);
return this;
}
/**
* Adds new structure feature into thr biome. Will add building biome into the structure list.
* @param structure {@link BCLStructureFeature} to add.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder structure(BCLStructureFeature structure) {
structure.addInternalBiome(biomeID);
return structure(structure.getFeatureConfigured());
}
/**
* Adds new world carver into the biome.
* @param carver {@link ConfiguredWorldCarver} to add.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder carver(GenerationStep.Carving step, ConfiguredWorldCarver<?> carver) {
BuiltinRegistries.CONFIGURED_CARVER
.getResourceKey(carver)
.ifPresent(key -> BiomeModifications.addCarver(ctx -> ctx.getBiomeKey().location().equals(biomeID), step, key));
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(SurfaceRuleBuilder.start().surface(surfaceBlock.defaultBlockState()).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 surfaceRule {link SurfaceRules.RuleSource} surface rule.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder surface(SurfaceRules.RuleSource surfaceRule) {
this.surfaceRule = surfaceRule;
return this;
}
/**
* Finalize biome creation.
* @return created {@link BCLBiome} instance.
*/
public BCLBiome build() {
return build(BCLBiome::new);
}
/**
* Finalize biome creation.
* @param biomeConstructor {@link BiFunction} biome constructor.
* @return created {@link BCLBiome} instance.
*/
public <T extends BCLBiome> T build(BiFunction<ResourceLocation, Biome, T> biomeConstructor) {
BiomeBuilder builder = new BiomeBuilder()
.precipitation(precipitation)
.biomeCategory(category)
.temperature(temperature)
.downfall(downfall);
if (spawnSettings != null) {
builder.mobSpawnSettings(spawnSettings.build());
}
if (effectsBuilder != null) {
builder.specialEffects(effectsBuilder.build());
}
if (generationSettings != null) {
builder.generationSettings(generationSettings.build());
}
final T res = biomeConstructor.apply(biomeID, builder.build());
res.attachStructures(structures);
res.setSurface(surfaceRule);
res.setFogDensity(fogDensity);
return res;
}
/**
* 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;
}
}

View file

@ -1,728 +0,0 @@
package ru.bclib.api.biomes;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.impl.biome.NetherBiomeData;
import net.fabricmc.fabric.impl.biome.TheEndBiomeData;
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.level.ServerLevel;
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.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.biome.MobSpawnSettings.SpawnerData;
import net.minecraft.world.level.levelgen.GenerationStep.Carving;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import net.minecraft.world.level.levelgen.SurfaceRules;
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.StructureFeature;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import org.jetbrains.annotations.Nullable;
import ru.bclib.BCLib;
import ru.bclib.config.Configs;
import ru.bclib.interfaces.SurfaceRuleProvider;
import ru.bclib.mixin.common.BiomeGenerationSettingsAccessor;
import ru.bclib.mixin.common.MobSpawnSettingsAccessor;
import ru.bclib.mixin.common.StructureSettingsAccessor;
import ru.bclib.util.CollectionsUtil;
import ru.bclib.util.MHelper;
import ru.bclib.world.biomes.BCLBiome;
import ru.bclib.world.biomes.FabricBiomesData;
import ru.bclib.world.features.BCLFeature;
import ru.bclib.world.generator.BiomePicker;
import ru.bclib.world.structures.BCLStructureFeature;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
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());
public static final BiomePicker NETHER_BIOME_PICKER = new BiomePicker();
public static final BiomePicker END_LAND_BIOME_PICKER = new BiomePicker();
public static final BiomePicker END_VOID_BIOME_PICKER = new BiomePicker();
private static final Map<ResourceLocation, BCLBiome> ID_MAP = Maps.newHashMap();
private static final Map<Biome, BCLBiome> CLIENT = Maps.newHashMap();
private static Registry<Biome> biomeRegistry;
private static final Map<ResourceKey, List<BiConsumer<ResourceLocation, Biome>>> MODIFICATIONS = Maps.newHashMap();
private static final Map<ResourceLocation, SurfaceRules.RuleSource> SURFACE_RULES = Maps.newHashMap();
private static final Set<ResourceLocation> MODIFIED_BIOMES = Sets.newHashSet();
public static final BCLBiome NETHER_WASTES_BIOME = registerNetherBiome(getFromRegistry(Biomes.NETHER_WASTES));
public static final BCLBiome CRIMSON_FOREST_BIOME = registerNetherBiome(getFromRegistry(Biomes.CRIMSON_FOREST));
public static final BCLBiome WARPED_FOREST_BIOME = registerNetherBiome(getFromRegistry(Biomes.WARPED_FOREST));
public static final BCLBiome SOUL_SAND_VALLEY_BIOME = registerNetherBiome(getFromRegistry(Biomes.SOUL_SAND_VALLEY));
public static final BCLBiome BASALT_DELTAS_BIOME = registerNetherBiome(getFromRegistry(Biomes.BASALT_DELTAS));
public static final BCLBiome THE_END = registerEndLandBiome(getFromRegistry(Biomes.THE_END));
public static final BCLBiome END_MIDLANDS = registerSubBiome(THE_END, getFromRegistry(Biomes.END_MIDLANDS), 0.5F);
public static final BCLBiome END_HIGHLANDS = registerSubBiome(THE_END, getFromRegistry(Biomes.END_HIGHLANDS), 0.5F);
public static final BCLBiome END_BARRENS = registerEndVoidBiome(getFromRegistry(new ResourceLocation("end_barrens")));
public static final BCLBiome SMALL_END_ISLANDS = registerEndVoidBiome(getFromRegistry(new ResourceLocation("small_end_islands")));
/**
* Initialize registry for current server.
* @param biomeRegistry - {@link Registry} for {@link Biome}.
*/
public static void initRegistry(Registry<Biome> biomeRegistry) {
BiomeAPI.biomeRegistry = biomeRegistry;
CLIENT.clear();
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerBiome(BCLBiome biome) {
if (BuiltinRegistries.BIOME.get(biome.getID()) == null) {
Registry.register(BuiltinRegistries.BIOME, biome.getID(), biome.getBiome());
}
ID_MAP.put(biome.getID(), biome);
return biome;
}
public static BCLBiome registerSubBiome(BCLBiome parent, BCLBiome subBiome) {
registerBiome(subBiome);
parent.addSubBiome(subBiome);
return subBiome;
}
public static BCLBiome registerSubBiome(BCLBiome parent, Biome biome, float genChance) {
BCLBiome subBiome = new BCLBiome(biome).setGenChance(genChance);
return registerSubBiome(parent, subBiome);
}
/**
* 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}
*/
public static BCLBiome registerNetherBiome(BCLBiome biome) {
registerBiome(biome);
NETHER_BIOME_PICKER.addBiome(biome);
Random random = new Random(biome.getID().hashCode());
//TODO: 1.18 Check parameters, depth was previously called altitude
//temperature, humidity, continentalness, erosion, depth, weirdness, offset
Climate.ParameterPoint parameters = Climate.parameters(
MHelper.randRange(-1.5F, 1.5F, random),
MHelper.randRange(-1.5F, 1.5F, random),
MHelper.randRange(-1.5F, 1.5F, random), //new in 1.18
MHelper.randRange(-1.5F, 1.5F, random), //new in 1.18
MHelper.randRange(-1.5F, 1.5F, random),
MHelper.randRange(-1.5F, 1.5F, random),
random.nextFloat()
);
ResourceKey<Biome> key = BuiltinRegistries.BIOME.getResourceKey(biome.getBiome()).get();
NetherBiomeData.addNetherBiome(key, parameters);
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 biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerNetherBiome(Biome biome) {
BCLBiome bclBiome = new BCLBiome(biome);
configureBiome(bclBiome, 1.0F, 1.0F);
NETHER_BIOME_PICKER.addBiome(bclBiome);
registerBiome(bclBiome);
return bclBiome;
}
/**
* 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);
configureBiome(biome, 1.0F, 1.0F);
END_LAND_BIOME_PICKER.addBiome(biome);
float weight = biome.getGenChance();
ResourceKey<Biome> key = BuiltinRegistries.BIOME.getResourceKey(biome.getBiome()).get();
TheEndBiomeData.addEndBiomeReplacement(Biomes.END_HIGHLANDS, key, weight);
TheEndBiomeData.addEndBiomeReplacement(Biomes.END_MIDLANDS, key, weight);
return biome;
}
/**
* 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}
* @return {@link BCLBiome}
*/
public static BCLBiome registerEndLandBiome(Biome biome) {
BCLBiome bclBiome = new BCLBiome(biome);
configureBiome(bclBiome, 1.0F, 1.0F);
END_LAND_BIOME_PICKER.addBiome(bclBiome);
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 {@link BCLBiome};
* @param genChance float generation chance.
* @return {@link BCLBiome}
*/
public static BCLBiome registerEndLandBiome(Biome biome, float genChance) {
BCLBiome bclBiome = new BCLBiome(biome);
configureBiome(bclBiome, genChance, 1.0F);
END_LAND_BIOME_PICKER.addBiome(bclBiome);
registerBiome(bclBiome);
return bclBiome;
}
/**
* 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);
configureBiome(biome, 1.0F, 1.0F);
END_VOID_BIOME_PICKER.addBiome(biome);
float weight = biome.getGenChance();
ResourceKey<Biome> key = BuiltinRegistries.BIOME.getResourceKey(biome.getBiome()).get();
TheEndBiomeData.addEndBiomeReplacement(Biomes.SMALL_END_ISLANDS, 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(Biome biome) {
BCLBiome bclBiome = new BCLBiome(biome);
configureBiome(bclBiome, 1.0F, 1.0F);
END_VOID_BIOME_PICKER.addBiome(bclBiome);
registerBiome(bclBiome);
return bclBiome;
}
/**
* 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}
*/
public static BCLBiome registerEndVoidBiome(Biome biome, float genChance) {
ResourceKey<Biome> key = BuiltinRegistries.BIOME.getResourceKey(biome).get();
BCLBiome bclBiome = new BCLBiome(biome);
configureBiome(bclBiome, genChance, 1.0F);
END_VOID_BIOME_PICKER.addBiome(bclBiome);
registerBiome(bclBiome);
return bclBiome;
}
/**
* 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 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) {
ResourceKey key = BuiltinRegistries.BIOME.getResourceKey(biome).orElse(null);
return key != null ? key : biomeRegistry != null ? biomeRegistry.getResourceKey(biome).orElse(null) : 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 = BuiltinRegistries.BIOME.getKey(biome);
if (id == null && biomeRegistry != null) {
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);
}
/**
* 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);
}
/**
* Load biomes from Fabric API. For internal usage only.
*/
public static void loadFabricAPIBiomes() {
FabricBiomesData.NETHER_BIOMES.forEach((key) -> {
if (!hasBiome(key.location())) {
registerNetherBiome(BuiltinRegistries.BIOME.get(key.location()));
}
});
FabricBiomesData.END_LAND_BIOMES.forEach((key, weight) -> {
if (!hasBiome(key.location())) {
registerEndLandBiome(BuiltinRegistries.BIOME.get(key.location()), weight);
}
});
FabricBiomesData.END_VOID_BIOMES.forEach((key, weight) -> {
if (!hasBiome(key.location())) {
registerEndVoidBiome(BuiltinRegistries.BIOME.get(key.location()), weight);
}
});
}
@Nullable
public static Biome getFromRegistry(ResourceLocation key) {
return BuiltinRegistries.BIOME.get(key);
}
@Nullable
public static Biome getFromRegistry(ResourceKey<Biome> key) {
return BuiltinRegistries.BIOME.get(key);
}
public static boolean isDatapackBiome(ResourceLocation biomeID) {
return getFromRegistry(biomeID) == null;
}
public static boolean isNetherBiome(ResourceLocation biomeID) {
return pickerHasBiome(NETHER_BIOME_PICKER, biomeID);
}
public static boolean isEndBiome(ResourceLocation biomeID) {
return pickerHasBiome(END_LAND_BIOME_PICKER, biomeID) || pickerHasBiome(END_VOID_BIOME_PICKER, biomeID);
}
private static boolean pickerHasBiome(BiomePicker picker, ResourceLocation key) {
return picker.getBiomes().stream().filter(biome -> biome.getID().equals(key)).findFirst().isPresent();
}
/**
* 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<ResourceLocation, Biome> modification) {
List<BiConsumer<ResourceLocation, Biome>> modifications = MODIFICATIONS.get(dimensionID);
if (modifications == null) {
modifications = Lists.newArrayList();
MODIFICATIONS.put(dimensionID, modifications);
}
modifications.add(modification);
}
/**
* Registers new biome modification for the Overworld. Will work both for mod and datapack biomes.
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerOverworldBiomeModification(BiConsumer<ResourceLocation, Biome> modification) {
registerBiomeModification(Level.OVERWORLD, modification);
}
/**
* Registers new biome modification for the Nether. Will work both for mod and datapack biomes.
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerNetherBiomeModification(BiConsumer<ResourceLocation, Biome> modification) {
registerBiomeModification(Level.NETHER, modification);
}
/**
* Registers new biome modification for the End. Will work both for mod and datapack biomes.
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerEndBiomeModification(BiConsumer<ResourceLocation, Biome> modification) {
registerBiomeModification(Level.END, modification);
}
/**
* Will apply biome modifications to world, internal usage only.
* @param level
*/
public static void applyModifications(ServerLevel level) {
BiomeSource source = level.getChunkSource().getGenerator().getBiomeSource();
Set<Biome> biomes = source.possibleBiomes();
NoiseGeneratorSettings generator = null;
if (level.dimension() == Level.NETHER) {
generator = BuiltinRegistries.NOISE_GENERATOR_SETTINGS.get(NoiseGeneratorSettings.NETHER);
}
else if (level.dimension() == Level.END) {
generator = BuiltinRegistries.NOISE_GENERATOR_SETTINGS.get(NoiseGeneratorSettings.END);
}
if (generator != null) {
List<SurfaceRules.RuleSource> rules = getRuleSources(biomes, level.dimension());
SurfaceRuleProvider provider = SurfaceRuleProvider.class.cast(generator);
if (rules.size() > 0) {
rules.add(provider.getSurfaceRule());
provider.setSurfaceRule(SurfaceRules.sequence(rules.toArray(new SurfaceRules.RuleSource[rules.size()])));
}
else {
provider.setSurfaceRule(null);
}
}
List<BiConsumer<ResourceLocation, Biome>> modifications = MODIFICATIONS.get(level.dimension());
if (modifications == null) {
return;
}
biomes.forEach(biome -> {
ResourceLocation biomeID = getBiomeID(biome);
boolean modify = isDatapackBiome(biomeID);
if (biome != BuiltinRegistries.BIOME.get(biomeID)) {
modify = true;
}
else if (!modify && !MODIFIED_BIOMES.contains(biomeID)) {
MODIFIED_BIOMES.add(biomeID);
modify = true;
}
if (modify) {
modifications.forEach(consumer -> {
consumer.accept(biomeID, biome);
});
}
});
}
private static List<SurfaceRules.RuleSource> getRuleSources(Set<Biome> biomes, ResourceKey<Level> dimensionType) {
Set<ResourceLocation> biomeIDs = biomes.stream().map(biome -> getBiomeID(biome)).collect(Collectors.toSet());
List<SurfaceRules.RuleSource> rules = Lists.newArrayList();
SURFACE_RULES.forEach((biomeID, rule) -> {
if (biomeIDs.contains(biomeID)) {
rules.add(rule);
}
});
// Try handle biomes from other dimension, may work not as expected
// Will not work
/*Optional<Biome> optional = biomes
.stream()
.filter(biome -> biome.getBiomeCategory() != BiomeCategory.THEEND && biome.getBiomeCategory() != BiomeCategory.NETHER)
.findAny();
if (optional.isPresent()) {
rules.add(SurfaceRuleData.overworld());
}
if (dimensionType == Level.NETHER) {
optional = biomes.stream().filter(biome -> biome.getBiomeCategory() != BiomeCategory.THEEND).findAny();
if (optional.isPresent()) {
rules.add(SurfaceRuleData.end());
}
}
else if (dimensionType == Level.END) {
optional = biomes.stream().filter(biome -> biome.getBiomeCategory() != BiomeCategory.NETHER).findAny();
if (optional.isPresent()) {
rules.add(SurfaceRuleData.end());
}
}*/
return rules;
}
/**
* Adds new features to existing biome.
* @param biome {@link Biome} to add features in.
* @param feature {@link ConfiguredFeature} to add.
*
*/
public static void addBiomeFeature(Biome biome, BCLFeature feature) {
addBiomeFeature(biome, feature.getPlacedFeature(), feature.getDecoration());
}
/**
* Adds new features to existing biome.
* @param biome {@link Biome} to add features in.
* @param feature {@link ConfiguredFeature} to add.
* @param step a {@link Decoration} step for the feature.
*/
public static void addBiomeFeature(Biome biome, PlacedFeature feature, Decoration step) {
BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) biome.getGenerationSettings();
List<List<Supplier<PlacedFeature>>> allFeatures = CollectionsUtil.getMutable(accessor.bclib_getFeatures());
Set<PlacedFeature> set = CollectionsUtil.getMutable(accessor.bclib_getFeatureSet());
List<Supplier<PlacedFeature>> features = getFeaturesList(allFeatures, step);
features.add(() -> feature);
set.add(feature);
accessor.bclib_setFeatures(allFeatures);
accessor.bclib_setFeatureSet(set);
}
/**
* Adds new features to existing biome.
* @param biomeID {@link ResourceLocation} of the {@link Biome} to add features in.
* @param feature {@link ConfiguredFeature} to add.
* @param step a {@link Decoration} step for the feature.
*/
private static void addBiomeFeature(ResourceLocation biomeID, PlacedFeature feature, Decoration step) {
addBiomeFeature(BuiltinRegistries.BIOME.get(biomeID), feature, step);
}
/**
* Adds new features to existing biome.
* @param biome {@link Biome} to add features in.
* @param features array of {@link BCLFeature} to add.
*/
public static void addBiomeFeatures(Biome biome, BCLFeature... features) {
BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) biome.getGenerationSettings();
List<List<Supplier<PlacedFeature>>> allFeatures = CollectionsUtil.getMutable(accessor.bclib_getFeatures());
Set<PlacedFeature> set = CollectionsUtil.getMutable(accessor.bclib_getFeatureSet());
for (BCLFeature feature: features) {
List<Supplier<PlacedFeature>> featureList = getFeaturesList(allFeatures, feature.getDecoration());
featureList.add(() -> feature.getPlacedFeature());
set.add(feature.getPlacedFeature());
}
accessor.bclib_setFeatures(allFeatures);
accessor.bclib_setFeatureSet(set);
}
/**
* Adds new structure feature to existing biome.
* @param biomeKey {@link ResourceKey} for the {@link Biome} to add structure feature in.
* @param structure {@link ConfiguredStructureFeature} to add.
*/
public static void addBiomeStructure(ResourceKey biomeKey, ConfiguredStructureFeature structure) {
changeStructureStarts(structureMap -> {
Multimap<ConfiguredStructureFeature<?, ?>, ResourceKey<Biome>> configuredMap = structureMap.computeIfAbsent(structure.feature, k -> HashMultimap.create());
configuredMap.put(structure, biomeKey);
});
}
public static void addBiomeStructure(Biome biome, ConfiguredStructureFeature structure) {
changeStructureStarts(structureMap -> {
Multimap<ConfiguredStructureFeature<?, ?>, ResourceKey<Biome>> configuredMap = structureMap.computeIfAbsent(structure.feature, k -> HashMultimap.create());
var key = getBiomeKey(biome);
if (key!=null) {
configuredMap.put(structure, key);
} else {
BCLib.LOGGER.error("Unable to find Biome " + getBiomeID(biome));
}
});
}
private static void changeStructureStarts(Consumer<Map<StructureFeature<?>, Multimap<ConfiguredStructureFeature<?, ?>, ResourceKey<Biome>>>> modifier) {
Registry<NoiseGeneratorSettings> chunkGenSettingsRegistry = BuiltinRegistries.NOISE_GENERATOR_SETTINGS;
for (Map.Entry<ResourceKey<NoiseGeneratorSettings>, NoiseGeneratorSettings> entry : chunkGenSettingsRegistry.entrySet()) {
Map<StructureFeature<?>, Multimap<ConfiguredStructureFeature<?, ?>, ResourceKey<Biome>>> structureMap = getMutableStructureConfig(entry.getValue());
modifier.accept(structureMap);
setMutableStructureConfig(entry.getValue(), structureMap);
}
}
/**
* Adds new structure feature to existing biome.
* @param biomeKey {@link ResourceKey} for the {@link Biome} to add structure feature in.
* @param structure {@link BCLStructureFeature} to add.
*/
public static void addBiomeStructure(ResourceKey biomeKey, BCLStructureFeature structure) {
addBiomeStructure(biomeKey, structure.getFeatureConfigured());
}
/**
* Adds new structure features to existing biome.
* @param biomeKey {@link ResourceKey} for the {@link Biome} to add structure features in.
* @param structures array of {@link BCLStructureFeature} to add.
*/
public static void addBiomeStructures(ResourceKey biomeKey, BCLStructureFeature... structures) {
for (BCLStructureFeature structure: structures) {
addBiomeStructure(biomeKey, structure.getFeatureConfigured());
}
}
/**
* Adds new carver into existing biome.
* @param biome {@link Biome} to add carver in.
* @param carver {@link ConfiguredWorldCarver} to add.
* @param stage {@link Carving} stage.
*/
public static void addBiomeCarver(Biome biome, ConfiguredWorldCarver carver, Carving stage) {
BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) biome.getGenerationSettings();
Map<Carving, List<Supplier<ConfiguredWorldCarver<?>>>> carvers = CollectionsUtil.getMutable(accessor.bclib_getCarvers());
List<Supplier<ConfiguredWorldCarver<?>>> carverList = CollectionsUtil.getMutable(carvers.getOrDefault(stage, new ArrayList<>()));
carvers.put(stage, carverList);
carverList.add(() -> carver);
accessor.bclib_setCarvers(carvers);
}
/**
* Adds surface rule to specified biome.
* @param biomeID biome {@link ResourceLocation}.
* @param source {@link SurfaceRules.RuleSource}.
*/
public static void addSurfaceRule(ResourceLocation biomeID, SurfaceRules.RuleSource source) {
SURFACE_RULES.put(biomeID, source);
}
/**
* Get surface rule for the biome using its {@link ResourceLocation} ID as a key.
* @param biomeID {@link ResourceLocation} biome ID.
* @return {@link SurfaceRules.RuleSource}.
*/
@Nullable
public static SurfaceRules.RuleSource getSurfaceRule(ResourceLocation biomeID) {
return SURFACE_RULES.get(biomeID);
}
/**
* Adds mob spawning to specified biome.
* @param biome {@link Biome} to add mob spawning.
* @param entityType {@link EntityType} mob type.
* @param weight spawn weight.
* @param minGroupCount minimum mobs in group.
* @param maxGroupCount maximum mobs in group.
*/
public static <M extends Mob> void addBiomeMobSpawn(Biome biome, EntityType<M> entityType, int weight, int minGroupCount, int maxGroupCount) {
final MobCategory category = entityType.getCategory();
MobSpawnSettingsAccessor accessor = (MobSpawnSettingsAccessor) biome.getMobSettings();
Map<MobCategory, WeightedRandomList<SpawnerData>> spawners = CollectionsUtil.getMutable(accessor.bcl_getSpawners());
List<SpawnerData> mobs = spawners.containsKey(category) ? CollectionsUtil.getMutable(spawners.get(category).unwrap()) : Lists.newArrayList();
mobs.add(new SpawnerData(entityType, weight, minGroupCount, maxGroupCount));
spawners.put(category, WeightedRandomList.create(mobs));
accessor.bcl_setSpawners(spawners);
}
private static void configureBiome(BCLBiome biome, float chance, float fog) {
String group = biome.getID().getNamespace() + "." + biome.getID().getPath();
chance = Configs.BIOMES_CONFIG.getFloat(group, "generation_chance", chance);
fog = Configs.BIOMES_CONFIG.getFloat(group, "fog_density", fog);
biome.setGenChance(chance).setFogDensity(fog);
}
/**
* Getter for correct feature list from all biome feature list of lists.
* @param step feature {@link Decoration} step.
* @param lists biome accessor lists.
* @return mutable {@link ConfiguredFeature} list.
*/
private static List<Supplier<PlacedFeature>> getList(Decoration step, List<List<Supplier<PlacedFeature>>> lists) {
int index = step.ordinal();
if (lists.size() <= index) {
for (int i = lists.size(); i <= index; i++) {
lists.add(Lists.newArrayList());
}
}
List<Supplier<PlacedFeature>> list = CollectionsUtil.getMutable(lists.get(index));
lists.set(index, list);
return list;
}
private static List<Supplier<PlacedFeature>> getFeaturesList(List<List<Supplier<PlacedFeature>>> features, Decoration step) {
int index = step.ordinal();
while (features.size() <= index) {
features.add(Lists.newArrayList());
}
List<Supplier<PlacedFeature>> mutable = CollectionsUtil.getMutable(features.get(index));
features.set(index, mutable);
return mutable;
}
//inspired by net.fabricmc.fabric.impl.biome.modification.BiomeStructureStartsImpl
private static Map<StructureFeature<?>, Multimap<ConfiguredStructureFeature<?, ?>, ResourceKey<Biome>>> getMutableStructureConfig(NoiseGeneratorSettings settings) {
final StructureSettingsAccessor access = (StructureSettingsAccessor)settings.structureSettings();
ImmutableMap<StructureFeature<?>, ImmutableMultimap<ConfiguredStructureFeature<?, ?>, ResourceKey<Biome>>> immutableMap = access.bcl_getStructureConfig();
Map<StructureFeature<?>, Multimap<ConfiguredStructureFeature<?, ?>, ResourceKey<Biome>>> result = new HashMap<>(immutableMap.size());
for (Map.Entry<StructureFeature<?>, ImmutableMultimap<ConfiguredStructureFeature<?, ?>, ResourceKey<Biome>>> entry : immutableMap.entrySet()) {
result.put(entry.getKey(), HashMultimap.create(entry.getValue()));
}
return result;
}
//inspired by net.fabricmc.fabric.impl.biome.modification.BiomeStructureStartsImpl
private static void setMutableStructureConfig(NoiseGeneratorSettings settings, Map<StructureFeature<?>, Multimap<ConfiguredStructureFeature<?, ?>, ResourceKey<Biome>>> structureStarts) {
final StructureSettingsAccessor access = (StructureSettingsAccessor) settings.structureSettings();
access.bcl_setStructureConfig(
structureStarts
.entrySet()
.stream()
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, e -> ImmutableMultimap.copyOf(e.getValue())))
);
}
}

View file

@ -1,190 +0,0 @@
package ru.bclib.api.biomes;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
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 java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public class SurfaceRuleBuilder {
private static final Map<String, SurfaceRuleEntry> RULES_CACHE = Maps.newHashMap();
private static final SurfaceRuleBuilder INSTANCE = new SurfaceRuleBuilder();
private List<SurfaceRuleEntry> rules = Lists.newArrayList();
private SurfaceRuleEntry entryInstance;
private ResourceKey<Biome> 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<Biome> 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, 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.
* @param state {@link BlockState} for filling.
* @return same {@link SurfaceRuleBuilder} instance.
*/
public SurfaceRuleBuilder filler(BlockState state) {
entryInstance = getFromCache("fill_" + state.toString(), () -> new SurfaceRuleEntry(3, SurfaceRules.state(state)));
rules.add(entryInstance);
return this;
}
/**
* Set biome ceiling with specified {@link BlockState}. Example - block of sandstone in the Overworld desert in air pockets.
* @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(2, 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.
* @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, 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, 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);
}
/**
* Finalise rule building process.
* @return {@link SurfaceRules.RuleSource}.
*/
public SurfaceRules.RuleSource build() {
Collections.sort(rules);
List<SurfaceRules.RuleSource> 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<SurfaceRuleEntry> supplier) {
SurfaceRuleEntry entry = RULES_CACHE.get(name);
if (entry == null) {
entry = supplier.get();
RULES_CACHE.put(name, entry);
}
return entry;
}
}

View file

@ -1,24 +0,0 @@
package ru.bclib.api.biomes;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.levelgen.SurfaceRules;
import org.jetbrains.annotations.NotNull;
public class SurfaceRuleEntry<M extends Mob> implements Comparable<SurfaceRuleEntry> {
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);
}
}

View file

@ -1,99 +0,0 @@
package ru.bclib.api.dataexchange;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
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 org.jetbrains.annotations.NotNull;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
public abstract class BaseDataHandler {
private final boolean originatesOnServer;
@NotNull
private final ResourceLocation identifier;
protected BaseDataHandler(ResourceLocation identifier, boolean originatesOnServer) {
this.originatesOnServer = originatesOnServer;
this.identifier = identifier;
}
final public boolean getOriginatesOnServer() {
return originatesOnServer;
}
final public ResourceLocation getIdentifier() {
return identifier;
}
@Environment(EnvType.CLIENT)
abstract void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender);
private ServerPlayer lastMessageSender;
void receiveFromClient(MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender) {
lastMessageSender = player;
}
final protected boolean reply(BaseDataHandler message, MinecraftServer server) {
if (lastMessageSender == null) return false;
message.sendToClient(server, lastMessageSender);
return true;
}
abstract void sendToClient(MinecraftServer server);
abstract void sendToClient(MinecraftServer server, ServerPlayer player);
@Environment(EnvType.CLIENT)
abstract void sendToServer(Minecraft client);
protected boolean isBlocking() { return false; }
@Override
public String toString() {
return "BasDataHandler{" + "originatesOnServer=" + originatesOnServer + ", identifier=" + identifier + '}';
}
/**
* Write a String to a buffer (Convenience Method)
*
* @param buf The buffer to write to
* @param s The String you want to write
*/
public static void writeString(FriendlyByteBuf buf, String s) {
buf.writeByteArray(s.getBytes(StandardCharsets.UTF_8));
}
/**
* Read a string from a buffer (Convenience Method)
*
* @param buf Thea buffer to read from
* @return The received String
*/
public static String readString(FriendlyByteBuf buf) {
byte[] data = buf.readByteArray();
return new String(data, StandardCharsets.UTF_8);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BaseDataHandler)) return false;
BaseDataHandler that = (BaseDataHandler) o;
return originatesOnServer == that.originatesOnServer && identifier.equals(that.identifier);
}
@Override
public int hashCode() {
return Objects.hash(originatesOnServer, identifier);
}
}

View file

@ -1,18 +0,0 @@
package ru.bclib.api.dataexchange;
import ru.bclib.api.dataexchange.handler.DataExchange;
import java.util.Set;
abstract class Connector {
protected final DataExchange api;
Connector(DataExchange api) {
this.api = api;
}
public abstract boolean onClient();
protected Set<DataHandlerDescriptor> getDescriptors(){
return api.getDescriptors();
}
}

View file

@ -1,70 +0,0 @@
package ru.bclib.api.dataexchange;
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;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.FriendlyByteBuf;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.handler.DataExchange;
/**
* This is an internal class that handles a Clienetside players Connection to a Server
*/
@Environment(EnvType.CLIENT)
public class ConnectorClientside extends Connector {
private Minecraft client;
ConnectorClientside(DataExchange api) {
super(api);
this.client = null;
}
@Override
public boolean onClient() {
return true;
}
public void onPlayInit(ClientPacketListener handler, Minecraft client){
if (this.client!=null && this.client != client){
BCLib.LOGGER.warning("Client changed!");
}
this.client = client;
for(DataHandlerDescriptor desc : getDescriptors()){
ClientPlayNetworking.registerReceiver(desc.IDENTIFIER, (_client, _handler, _buf, _responseSender)->{
receiveFromServer(desc, _client, _handler, _buf, _responseSender);
});
}
}
public void onPlayReady(ClientPacketListener handler, PacketSender sender, Minecraft client){
for(DataHandlerDescriptor desc : getDescriptors()){
if (desc.sendOnJoin){
BaseDataHandler h = desc.JOIN_INSTANCE.get();
if (!h.getOriginatesOnServer()) {
h.sendToServer(client);
}
}
}
}
public void onPlayDisconnect(ClientPacketListener handler, Minecraft client){
for(DataHandlerDescriptor desc : getDescriptors()) {
ClientPlayNetworking.unregisterReceiver(desc.IDENTIFIER);
}
}
void receiveFromServer(DataHandlerDescriptor desc, Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender){
BaseDataHandler h = desc.INSTANCE.get();
h.receiveFromServer(client, handler, buf, responseSender);
}
public void sendToServer(BaseDataHandler h){
if (client==null){
throw new RuntimeException("[internal error] Client not initialized yet!");
}
h.sendToServer(this.client);
}
}

View file

@ -1,67 +0,0 @@
package ru.bclib.api.dataexchange;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.handler.DataExchange;
/**
* This is an internal class that handles a Serverside Connection to a Client-Player
*/
public class ConnectorServerside extends Connector {
private MinecraftServer server;
ConnectorServerside(DataExchange api) {
super(api);
server = null;
}
@Override
public boolean onClient() {
return false;
}
public void onPlayInit(ServerGamePacketListenerImpl handler, MinecraftServer server){
if (this.server!=null && this.server != server){
BCLib.LOGGER.warning("Server changed!");
}
this.server = server;
for(DataHandlerDescriptor desc : getDescriptors()){
ServerPlayNetworking.registerReceiver(handler, desc.IDENTIFIER, (_server, _player, _handler, _buf, _responseSender) -> {
receiveFromClient(desc, _server, _player, _handler, _buf, _responseSender);
});
}
}
public void onPlayReady(ServerGamePacketListenerImpl handler, PacketSender sender, MinecraftServer server){
for(DataHandlerDescriptor desc : getDescriptors()){
if (desc.sendOnJoin){
BaseDataHandler h = desc.JOIN_INSTANCE.get();
if (h.getOriginatesOnServer()) {
h.sendToClient(server, handler.player);
}
}
}
}
public void onPlayDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server){
for(DataHandlerDescriptor desc : getDescriptors()){
ServerPlayNetworking.unregisterReceiver(handler, desc.IDENTIFIER);
}
}
void receiveFromClient(DataHandlerDescriptor desc, MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender){
BaseDataHandler h = desc.INSTANCE.get();
h.receiveFromClient(server, player, handler, buf, responseSender);
}
public void sendToClient(BaseDataHandler h){
if (server==null){
throw new RuntimeException("[internal error] Server not initialized yet!");
}
h.sendToClient(this.server);
}
}

View file

@ -1,211 +0,0 @@
package ru.bclib.api.dataexchange;
import com.google.common.collect.Lists;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.network.FriendlyByteBuf;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.handler.DataExchange;
import ru.bclib.api.dataexchange.handler.autosync.AutoSync;
import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate;
import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID;
import ru.bclib.config.Config;
import ru.bclib.util.ModUtil;
import java.io.File;
import java.util.List;
import java.util.function.BiConsumer;
public class DataExchangeAPI extends DataExchange {
private final static List<String> MODS = Lists.newArrayList();
/**
* You should never need to create a custom instance of this Object.
*/
public DataExchangeAPI() {
super();
}
@Environment(EnvType.CLIENT)
protected ConnectorClientside clientSupplier(DataExchange api) {
return new ConnectorClientside(api);
}
protected ConnectorServerside serverSupplier(DataExchange api) {
return new ConnectorServerside(api);
}
/**
* Register a mod to participate in the DataExchange.
*
* @param modID - {@link String} modID.
*/
public static void registerMod(String modID) {
if (!MODS.contains(modID)) MODS.add(modID);
}
/**
* Register a mod dependency to participate in the DataExchange.
*
* @param modID - {@link String} modID.
*/
public static void registerModDependency(String modID) {
if (ModUtil.getModInfo(modID, false) != null && !"0.0.0".equals(ModUtil.getModVersion(modID))) {
registerMod(modID);
} else {
BCLib.LOGGER.info("Mod Dependency '" + modID + "' not found. This is probably OK.");
}
}
/**
* Returns the IDs of all registered Mods.
*
* @return List of modIDs
*/
public static List<String> registeredMods() {
return MODS;
}
/**
* Add a new Descriptor for a {@link DataHandler}.
*
* @param desc The Descriptor you want to add.
*/
public static void registerDescriptor(DataHandlerDescriptor desc) {
DataExchange api = DataExchange.getInstance();
api.getDescriptors()
.add(desc);
}
/**
* Bulk-Add a Descriptors for your {@link DataHandler}-Objects.
*
* @param desc The Descriptors you want to add.
*/
public static void registerDescriptors(List<DataHandlerDescriptor> desc) {
DataExchange api = DataExchange.getInstance();
api.getDescriptors()
.addAll(desc);
}
/**
* Sends the Handler.
* <p>
* Depending on what the result of {@link DataHandler#getOriginatesOnServer()}, the Data is sent from the server
* to the client (if {@code true}) or the other way around.
* <p>
* The method {@link DataHandler#serializeData(FriendlyByteBuf, boolean)} is called just before the data is sent. You should
* use this method to add the Data you need to the communication.
*
* @param h The Data that you want to send
*/
public static void send(BaseDataHandler h) {
if (h.getOriginatesOnServer()) {
DataExchangeAPI.getInstance().server.sendToClient(h);
}
else {
DataExchangeAPI.getInstance().client.sendToServer(h);
}
}
/**
* Registers a File for automatic client syncing.
*
* @param modID The ID of the calling Mod
* @param fileName The name of the File
*/
public static void addAutoSyncFile(String modID, File fileName) {
AutoSync.addAutoSyncFileData(modID, fileName, false, SyncFileHash.NEED_TRANSFER);
}
/**
* Registers a File for automatic client syncing.
* <p>
* The file is synced of the {@link SyncFileHash} on client and server are not equal. This method will not copy the
* configs content from the client to the server.
*
* @param modID The ID of the calling Mod
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
* Details
* @param fileName The name of the File
*/
public static void addAutoSyncFile(String modID, String uniqueID, File fileName) {
AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, false, SyncFileHash.NEED_TRANSFER);
}
/**
* Registers a File for automatic client syncing.
* <p>
* The content of the file is requested for comparison. This will copy the
* entire file from the client to the server.
* <p>
* You should only use this option, if you need to compare parts of the file in order to decide
* if the File needs to be copied. Normally using the {@link SyncFileHash}
* for comparison is sufficient.
*
* @param modID The ID of the calling Mod
* @param fileName The name of the File
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
*/
public static void addAutoSyncFile(String modID, File fileName, NeedTransferPredicate needTransfer) {
AutoSync.addAutoSyncFileData(modID, fileName, true, needTransfer);
}
/**
* Registers a File for automatic client syncing.
* <p>
* The content of the file is requested for comparison. This will copy the
* entire file from the client to the server.
* <p>
* You should only use this option, if you need to compare parts of the file in order to decide
* if the File needs to be copied. Normally using the {@link SyncFileHash}
* for comparison is sufficient.
*
* @param modID The ID of the calling Mod
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
* Details
* @param fileName The name of the File
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
*/
public static void addAutoSyncFile(String modID, String uniqueID, File fileName, NeedTransferPredicate needTransfer) {
AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, true, needTransfer);
}
/**
* Register a function that is called whenever the client receives a file from the server and replaced toe local
* file with the new content.
* <p>
* This callback is usefull if you need to reload the new content before the game is quit.
*
* @param callback A Function that receives the AutoSyncID as well as the Filename.
*/
public static void addOnWriteCallback(BiConsumer<AutoSyncID, File> callback) {
AutoSync.addOnWriteCallback(callback);
}
/**
* Returns the sync-folder for a given Mod.
* <p>
* BCLib will ensure that the contents of sync-folder on the client is the same as the one on the server.
*
* @param modID ID of the Mod
* @return The path to the sync-folder
*/
public static File getModSyncFolder(String modID) {
File fl = AutoSync.SYNC_FOLDER.localFolder.resolve(modID.replace(".", "-")
.replace(":", "-")
.replace("\\", "-")
.replace("/", "-"))
.normalize()
.toFile();
if (!fl.exists()) {
fl.mkdirs();
}
return fl;
}
static {
addOnWriteCallback(Config::reloadSyncedConfig);
}
}

View file

@ -1,274 +0,0 @@
package ru.bclib.api.dataexchange;
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 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 ru.bclib.BCLib;
import ru.bclib.api.dataexchange.handler.autosync.Chunker;
import ru.bclib.api.dataexchange.handler.autosync.Chunker.PacketChunkSender;
import java.util.Collection;
import java.util.List;
public abstract class DataHandler extends BaseDataHandler {
public abstract static class WithoutPayload extends DataHandler {
protected WithoutPayload(ResourceLocation identifier, boolean originatesOnServer) {
super(identifier, originatesOnServer);
}
@Override
protected boolean prepareData(boolean isClient) { return true; }
@Override
protected void serializeData(FriendlyByteBuf buf, boolean isClient) {
}
@Override
protected void deserializeIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient) {
}
}
protected DataHandler(ResourceLocation identifier, boolean originatesOnServer) {
super(identifier, originatesOnServer);
}
protected boolean prepareData(boolean isClient) { return true; }
abstract protected void serializeData(FriendlyByteBuf buf, boolean isClient);
abstract protected void deserializeIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient);
abstract protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient);
@Environment(EnvType.CLIENT)
@Override
void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender) {
deserializeIncomingData(buf, responseSender, true);
final Runnable runner = () -> runOnGameThread(client, null, true);
if (isBlocking()) client.executeBlocking(runner);
else client.execute(runner);
}
@Override
void receiveFromClient(MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender) {
super.receiveFromClient(server, player, handler, buf, responseSender);
deserializeIncomingData(buf, responseSender, false);
final Runnable runner = () -> runOnGameThread(null, server, false);
if (isBlocking()) server.executeBlocking(runner);
else server.execute(runner);
}
@Override
void sendToClient(MinecraftServer server) {
if (prepareData(false)) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeData(buf, false);
_sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
}
}
@Override
void sendToClient(MinecraftServer server, ServerPlayer player) {
if (prepareData(false)) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeData(buf, false);
_sendToClient(getIdentifier(), server, List.of(player), buf);
}
}
public static void _sendToClient(ResourceLocation identifier, MinecraftServer server, Collection<ServerPlayer> players, FriendlyByteBuf buf) {
if (buf.readableBytes()> Chunker.MAX_PACKET_SIZE) {
final PacketChunkSender sender = new 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, 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);
abstract protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, PacketSender responseSender);
abstract protected void runOnServerGameThread(MinecraftServer server);
@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, responseSender);
final Runnable runner = () -> runOnServerGameThread(server);
if (isBlocking()) server.executeBlocking(runner);
else server.execute(runner);
}
@Override
void sendToClient(MinecraftServer server) {
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
}
@Override
void sendToClient(MinecraftServer server, ServerPlayer player) {
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
}
@Environment(EnvType.CLIENT)
@Override
void sendToServer(Minecraft client) {
if (prepareDataOnClient()) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeDataOnClient(buf);
ClientPlayNetworking.send(getIdentifier(), buf);
}
}
}
/**
* A Message that always originates on the Server
*/
public abstract static class FromServer extends BaseDataHandler {
public abstract static class WithoutPayload extends FromServer {
protected WithoutPayload(ResourceLocation identifier) {
super(identifier);
}
@Override
protected boolean prepareDataOnServer() { return true; }
@Override
protected void serializeDataOnServer(FriendlyByteBuf buf) {
}
@Override
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
}
}
protected FromServer(ResourceLocation identifier) {
super(identifier, true);
}
protected boolean prepareDataOnServer() { return true; }
abstract protected void serializeDataOnServer(FriendlyByteBuf buf);
@Environment(EnvType.CLIENT)
abstract protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender);
@Environment(EnvType.CLIENT)
abstract protected void runOnClientGameThread(Minecraft client);
@Environment(EnvType.CLIENT)
@Override
final void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender) {
deserializeIncomingDataOnClient(buf, responseSender);
final Runnable runner = () -> runOnClientGameThread(client);
if (isBlocking()) client.executeBlocking(runner);
else client.execute(runner);
}
@Override
final void receiveFromClient(MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender) {
super.receiveFromClient(server, player, handler, buf, responseSender);
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
}
public void receiveFromMemory(FriendlyByteBuf buf){
receiveFromServer(Minecraft.getInstance(), null, buf, null);
}
@Override
final void sendToClient(MinecraftServer server) {
if (prepareDataOnServer()) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeDataOnServer(buf);
_sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
}
}
@Override
final void sendToClient(MinecraftServer server, ServerPlayer player) {
if (prepareDataOnServer()) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeDataOnServer(buf);
_sendToClient(getIdentifier(), server, List.of(player), buf);
}
}
@Environment(EnvType.CLIENT)
@Override
final void sendToServer(Minecraft client) {
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
}
}
}

View file

@ -1,49 +0,0 @@
package ru.bclib.api.dataexchange;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.function.Supplier;
public class DataHandlerDescriptor {
public DataHandlerDescriptor(@NotNull ResourceLocation identifier, @NotNull Supplier<BaseDataHandler> instancer){
this(identifier, instancer, instancer, false, false);
}
public DataHandlerDescriptor(@NotNull ResourceLocation identifier,@NotNull Supplier<BaseDataHandler> instancer, boolean sendOnJoin, boolean sendBeforeEnter){
this(identifier, instancer, instancer, sendOnJoin, sendBeforeEnter);
}
public DataHandlerDescriptor(@NotNull ResourceLocation identifier, @NotNull Supplier<BaseDataHandler> receiv_instancer, @NotNull Supplier<BaseDataHandler> join_instancer, boolean sendOnJoin, boolean sendBeforeEnter){
this.INSTANCE = receiv_instancer;
this.JOIN_INSTANCE = join_instancer;
this.IDENTIFIER = identifier;
this.sendOnJoin = sendOnJoin;
this.sendBeforeEnter = sendBeforeEnter;
}
public final boolean sendOnJoin;
public final boolean sendBeforeEnter;
@NotNull
public final ResourceLocation IDENTIFIER;
@NotNull
public final Supplier<BaseDataHandler> INSTANCE;
@NotNull
public final Supplier<BaseDataHandler> JOIN_INSTANCE;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof ResourceLocation){
return o.equals(IDENTIFIER);
}
if (!(o instanceof DataHandlerDescriptor that)) return false;
return IDENTIFIER.equals(that.IDENTIFIER);
}
@Override
public int hashCode() {
return Objects.hash(IDENTIFIER);
}
}

View file

@ -1,161 +0,0 @@
package ru.bclib.api.dataexchange;
import net.minecraft.network.FriendlyByteBuf;
import org.jetbrains.annotations.NotNull;
import ru.bclib.BCLib;
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;
public class FileHash {
private static int ERR_DOES_NOT_EXIST = -10;
private static int ERR_IO_ERROR = -20;
/**
* The md5-hash of the file
*/
@NotNull
public final byte[] md5;
/**
* The size (in bytes) of the input.
*/
public final int size;
/**
* a value that is directly calculated from defined byte positions.
*/
public final int value;
FileHash(byte[] md5, int size, int value) {
Objects.nonNull(md5);
this.md5 = md5;
this.size = size;
this.value = value;
}
static FileHash createForEmpty(int errCode) {
return new FileHash(new byte[0], 0, errCode);
}
public boolean noFile() {
return md5.length == 0;
}
/**
* Serializes the Object to a buffer
*
* @param buf The buffer to write to
*/
public void serialize(FriendlyByteBuf buf) {
buf.writeInt(size);
buf.writeInt(value);
buf.writeByteArray(md5);
}
/**
* Deserialize a Buffer to a new {@link SyncFileHash}-Object
*
* @param buf Thea buffer to read from
* @return The received String
*/
public static FileHash deserialize(FriendlyByteBuf buf) {
final int size = buf.readInt();
final int value = buf.readInt();
final byte[] md5 = buf.readByteArray();
return new FileHash(md5, size, value);
}
/**
* Convert the md5-hash to a human readable string
*
* @return The converted String
*/
public String getMd5String() {
return toHexString(md5);
}
/**
* Converts a byte-array to a hex-string representation
*
* @param bytes The source array
* @return The resulting string, or an empty String if the input was {@code null}
*/
public static String toHexString(byte[] bytes) {
if (bytes == null) return "";
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* Create a new {@link FileHash}.
*
* @param file The input file
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
* identical. Will return {@code null} when an error occurs or the File does not exist
*/
public static FileHash create(File file) {
if (!file.exists()) return createForEmpty(ERR_DOES_NOT_EXIST);
final Path path = file.toPath();
int size = 0;
byte[] md5 = new byte[0];
int value = 0;
try {
byte[] data = Files.readAllBytes(path);
size = data.length;
value = size > 0 ? (data[size / 3] | (data[size / 2] << 8) | (data[size / 5] << 16)) : -1;
if (size > 20) value |= data[20] << 24;
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(data);
md5 = md.digest();
return new FileHash(md5, size, value);
}
catch (IOException e) {
BCLib.LOGGER.error("Failed to read file: " + file);
return null;
}
catch (NoSuchAlgorithmException e) {
BCLib.LOGGER.error("Unable to build hash for file: " + file);
}
return createForEmpty(ERR_IO_ERROR);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FileHash)) return false;
FileHash fileHash = (FileHash) o;
return size == fileHash.size && value == fileHash.value && Arrays.equals(md5, fileHash.md5);
}
@Override
public int hashCode() {
int result = Objects.hash(size, value);
result = 31 * result + Arrays.hashCode(md5);
return result;
}
@Override
public String toString() {
return String.format("%08x", size) + "-" + String.format("%08x", value) + "-" + getMd5String();
}
}

View file

@ -1,108 +0,0 @@
package ru.bclib.api.dataexchange;
import net.minecraft.network.FriendlyByteBuf;
import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate;
import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID;
import java.io.File;
import java.util.Objects;
/**
* Calculates a hash based on the contents of a File.
* <p>
* A File-Hash contains the md5-sum of the File, as well as its size and byte-values from defined positions
* <p>
* You can compare instances using {@link #equals(Object)} to determine if two files are
* identical.
*/
public class SyncFileHash extends AutoSyncID {
public final FileHash hash;
SyncFileHash(String modID, File file, byte[] md5, int size, int value) {
this(modID, file.getName(), md5, size, value);
}
SyncFileHash(String modID, String uniqueID, byte[] md5, int size, int value) {
this(modID, uniqueID, new FileHash(md5, size, value));
}
SyncFileHash(String modID, File file, FileHash hash) {
this(modID, file.getName(), hash);
}
SyncFileHash(String modID, String uniqueID, FileHash hash) {
super(modID, uniqueID);
this.hash = hash;
}
final static NeedTransferPredicate NEED_TRANSFER = (clientHash, serverHash, content)-> !clientHash.equals(serverHash);
@Override
public String toString() {
return super.toString()+": "+hash.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SyncFileHash)) return false;
if (!super.equals(o)) return false;
SyncFileHash that = (SyncFileHash) o;
return hash.equals(that.hash);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), hash);
}
/**
* Serializes the Object to a buffer
* @param buf The buffer to write to
*/
public void serialize(FriendlyByteBuf buf) {
hash.serialize(buf);
DataHandler.writeString(buf, modID);
DataHandler.writeString(buf, uniqueID);
}
/**
*Deserialize a Buffer to a new {@link SyncFileHash}-Object
* @param buf Thea buffer to read from
* @return The received String
*/
public static SyncFileHash deserialize(FriendlyByteBuf buf){
final FileHash hash = FileHash.deserialize(buf);
final String modID = DataHandler.readString(buf);
final String uniqueID = DataHandler.readString(buf);
return new SyncFileHash(modID, uniqueID, hash);
}
/**
* Create a new {@link SyncFileHash}.
* <p>
* Will call {@link #create(String, File, String)} using the name of the File as {@code uniqueID}.
* @param modID ID of the calling Mod
* @param file The input file
*
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
* identical. Will return {@code null} when an error occurs or the File does not exist
*/
public static SyncFileHash create(String modID, File file){
return create(modID, file, file.getName());
}
/**
* Create a new {@link SyncFileHash}.
* @param modID ID of the calling Mod
* @param file The input file
* @param uniqueID The unique ID that is used for this File (see {@link SyncFileHash#uniqueID} for Details.
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
* identical. Will return {@code null} when an error occurs or the File does not exist
*/
public static SyncFileHash create(String modID, File file, String uniqueID){
return new SyncFileHash(modID, uniqueID, FileHash.create(file));
}
}

View file

@ -1,113 +0,0 @@
package ru.bclib.api.dataexchange.handler;
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 net.minecraft.resources.ResourceLocation;
import ru.bclib.api.dataexchange.BaseDataHandler;
import ru.bclib.api.dataexchange.ConnectorClientside;
import ru.bclib.api.dataexchange.ConnectorServerside;
import ru.bclib.api.dataexchange.DataExchangeAPI;
import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import java.util.HashSet;
import java.util.Set;
abstract public class DataExchange {
private static DataExchangeAPI instance;
protected static DataExchangeAPI getInstance() {
if (instance == null) {
instance = new DataExchangeAPI();
}
return instance;
}
protected ConnectorServerside server;
protected ConnectorClientside client;
protected final Set<DataHandlerDescriptor> descriptors;
private boolean didLoadSyncFolder = false;
abstract protected ConnectorClientside clientSupplier(DataExchange api);
abstract protected ConnectorServerside serverSupplier(DataExchange api);
protected DataExchange() {
descriptors = new HashSet<>();
}
public Set<DataHandlerDescriptor> getDescriptors() { return descriptors; }
public static DataHandlerDescriptor getDescriptor(ResourceLocation identifier){
return getInstance().descriptors.stream().filter(d -> d.equals(identifier)).findFirst().orElse(null);
}
@Environment(EnvType.CLIENT)
protected void initClientside() {
if (client != null) return;
client = clientSupplier(this);
ClientPlayConnectionEvents.INIT.register(client::onPlayInit);
ClientPlayConnectionEvents.JOIN.register(client::onPlayReady);
ClientPlayConnectionEvents.DISCONNECT.register(client::onPlayDisconnect);
}
protected void initServerSide() {
if (server != null) return;
server = serverSupplier(this);
ServerPlayConnectionEvents.INIT.register(server::onPlayInit);
ServerPlayConnectionEvents.JOIN.register(server::onPlayReady);
ServerPlayConnectionEvents.DISCONNECT.register(server::onPlayDisconnect);
}
/**
* Initializes all datastructures that need to exist in the client component.
* <p>
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
*/
@Environment(EnvType.CLIENT)
public static void prepareClientside() {
DataExchange api = DataExchange.getInstance();
api.initClientside();
}
/**
* Initializes all datastructures that need to exist in the server component.
* <p>
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
*/
public static void prepareServerside() {
DataExchange api = DataExchange.getInstance();
api.initServerSide();
}
/**
* Automatically called before the player enters the world.
* <p>
* This is automatically called by BCLib. It will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to*
* {@code true},
*/
@Environment(EnvType.CLIENT)
public static void sendOnEnter() {
getInstance().descriptors.forEach((desc) -> {
if (desc.sendBeforeEnter) {
BaseDataHandler h = desc.JOIN_INSTANCE.get();
if (!h.getOriginatesOnServer()) {
getInstance().client.sendToServer(h);
}
}
});
}
}

View file

@ -1,237 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
import net.minecraft.network.FriendlyByteBuf;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.SyncFileHash;
import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate;
import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile;
import ru.bclib.util.ModUtil;
import ru.bclib.util.ModUtil.ModInfo;
import ru.bclib.util.Pair;
import ru.bclib.util.PathUtil;
import ru.bclib.util.Triple;
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 NeedTransferPredicate needTransfer;
public final File fileName;
public final boolean requestContent;
private SyncFileHash hash;
AutoFileSyncEntry(String modID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) {
this(modID, fileName.getName(), fileName, requestContent, needTransfer);
}
AutoFileSyncEntry(String modID, String uniqueID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) {
super(modID, uniqueID);
this.needTransfer = needTransfer;
this.fileName = fileName;
this.requestContent = requestContent;
}
public SyncFileHash getFileHash() {
if (hash == null) {
hash = SyncFileHash.create(modID, fileName, uniqueID);
}
return hash;
}
public byte[] getContent() {
if (!fileName.exists()) return new byte[0];
final Path path = fileName.toPath();
try {
return Files.readAllBytes(path);
}
catch (IOException e) {
}
return new byte[0];
}
public int serializeContent(FriendlyByteBuf buf) {
DataHandler.writeString(buf, modID);
DataHandler.writeString(buf, uniqueID);
return serializeFileContent(buf);
}
public static Triple<AutoFileSyncEntry, byte[], AutoSyncID> deserializeContent(FriendlyByteBuf buf) {
final String modID = DataHandler.readString(buf);
final String uniqueID = DataHandler.readString(buf);
byte[] data = deserializeFileContent(buf);
AutoFileSyncEntry entry;
if (AutoSyncID.ForDirectFileRequest.MOD_ID.equals(modID)) {
entry = AutoFileSyncEntry.ForDirectFileRequest.finishDeserializeContent(uniqueID, buf);
}
else if (AutoSyncID.ForModFileRequest.UNIQUE_ID.equals(uniqueID)) {
entry = AutoFileSyncEntry.ForModFileRequest.finishDeserializeContent(modID, buf);
}
else {
entry = AutoFileSyncEntry.findMatching(modID, uniqueID);
}
return new Triple<>(entry, data, new AutoSyncID(modID, uniqueID));
}
public void serialize(FriendlyByteBuf buf) {
getFileHash().serialize(buf);
buf.writeBoolean(requestContent);
if (requestContent) {
serializeFileContent(buf);
}
}
public static AutoSync.AutoSyncTriple deserializeAndMatch(FriendlyByteBuf buf) {
Pair<SyncFileHash, byte[]> e = deserialize(buf);
AutoFileSyncEntry match = findMatching(e.first);
return new AutoSync.AutoSyncTriple(e.first, e.second, match);
}
public static Pair<SyncFileHash, byte[]> deserialize(FriendlyByteBuf buf) {
SyncFileHash hash = SyncFileHash.deserialize(buf);
boolean withContent = buf.readBoolean();
byte[] data = null;
if (withContent) {
data = deserializeFileContent(buf);
}
return new Pair(hash, data);
}
private int serializeFileContent(FriendlyByteBuf buf) {
if (!PathUtil.isChildOf(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) {
SubFile subFile = desc.getLocalSubFile(freq.relFile.toString());
if (subFile != null) {
final File absPath = desc.localFolder.resolve(subFile.relPath)
.normalize()
.toFile();
return new AutoFileSyncEntry.ForDirectFileRequest(freq.uniqueID, new File(subFile.relPath), absPath);
}
}
return null;
} else if (aid instanceof AutoSyncID.ForModFileRequest) {
AutoSyncID.ForModFileRequest mreq = (AutoSyncID.ForModFileRequest) aid;
return new AutoFileSyncEntry.ForModFileRequest(mreq.modID, true, null);
}
return findMatching(aid.modID, aid.uniqueID);
}
public static AutoFileSyncEntry findMatching(String modID, String uniqueID) {
return AutoSync.getAutoSyncFiles()
.stream()
.filter(asf -> asf.modID.equals(modID) && asf.uniqueID.equals(uniqueID))
.findFirst()
.orElse(null);
}
}

View file

@ -1,187 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
import net.fabricmc.loader.api.FabricLoader;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataExchangeAPI;
import ru.bclib.api.dataexchange.SyncFileHash;
import ru.bclib.config.Configs;
import ru.bclib.config.ServerConfig;
import ru.bclib.util.PathUtil;
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 {
public boolean test(SyncFileHash clientHash, SyncFileHash serverHash, FileContentWrapper content);
}
final static class AutoSyncTriple {
public final SyncFileHash serverHash;
public final byte[] serverContent;
public final AutoFileSyncEntry localMatch;
public AutoSyncTriple(SyncFileHash serverHash, byte[] serverContent, AutoFileSyncEntry localMatch) {
this.serverHash = serverHash;
this.serverContent = serverContent;
this.localMatch = localMatch;
}
@Override
public String toString() {
return serverHash.modID + "." + serverHash.uniqueID;
}
}
// ##### File Syncing
protected final static List<BiConsumer<AutoSyncID, File>> onWriteCallbacks = new ArrayList<>(2);
/**
* Register a function that is called whenever the client receives a file from the server and replaced toe local
* file with the new content.
* <p>
* This callback is usefull if you need to reload the new content before the game is quit.
*
* @param callback A Function that receives the AutoSyncID as well as the Filename.
*/
public static void addOnWriteCallback(BiConsumer<AutoSyncID, File> callback) {
onWriteCallbacks.add(callback);
}
private static final List<AutoFileSyncEntry> autoSyncFiles = new ArrayList<>(4);
public static List<AutoFileSyncEntry> getAutoSyncFiles() {
return autoSyncFiles;
}
/**
* Registers a File for automatic client syncing.
*
* @param modID The ID of the calling Mod
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
* @param fileName The name of the File
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
* entire file from the client to the server.
* <p>
* You should only use this option, if you need to compare parts of the file in order to decide
* If the File needs to be copied. Normally using the {@link SyncFileHash}
* for comparison is sufficient.
*/
public static void addAutoSyncFileData(String modID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) {
if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())){
BCLib.LOGGER.error(fileName + " is outside of Game Folder " + PathUtil.GAME_FOLDER);
} else {
autoSyncFiles.add(new AutoFileSyncEntry(modID, fileName, requestContent, needTransfer));
}
}
/**
* Registers a File for automatic client syncing.
*
* @param modID The ID of the calling Mod
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
* Details
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
* @param fileName The name of the File
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
* entire file from the client to the server.
* <p>
* You should only use this option, if you need to compare parts of the file in order to decide
* If the File needs to be copied. Normally using the {@link SyncFileHash}
* for comparison is sufficient.
*/
public static void addAutoSyncFileData(String modID, String uniqueID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) {
if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())){
BCLib.LOGGER.error(fileName + " is outside of Game Folder " + PathUtil.GAME_FOLDER);
} else {
autoSyncFiles.add(new AutoFileSyncEntry(modID, uniqueID, fileName, requestContent, needTransfer));
}
}
/**
* Called when {@code SendFiles} received a File on the Client and wrote it to the FileSystem.
* <p>
* This is the place where reload Code should go.
*
* @param aid The ID of the received File
* @param file The location of the FIle on the client
*/
static void didReceiveFile(AutoSyncID aid, File file) {
onWriteCallbacks.forEach(fkt -> fkt.accept(aid, file));
}
// ##### Folder Syncing
static final List<SyncFolderDescriptor> syncFolderDescriptions = Arrays.asList(SYNC_FOLDER);
private List<String> syncFolderContent;
protected List<String> getSyncFolderContent() {
if (syncFolderContent == null) {
return new ArrayList<>(0);
}
return syncFolderContent;
}
private static boolean didRegisterAdditionalMods = false;
//we call this from HelloClient on the Server to prepare transfer
protected static void loadSyncFolder() {
if (Configs.SERVER_CONFIG.isOfferingFiles()) {
syncFolderDescriptions.forEach(desc -> desc.loadCache());
}
if (!didRegisterAdditionalMods && Configs.SERVER_CONFIG.isOfferingMods()){
didRegisterAdditionalMods = true;
List<String> modIDs = Configs.SERVER_CONFIG.get(ServerConfig.ADDITIONAL_MODS);
if (modIDs != null){
modIDs.stream().forEach(modID -> DataExchangeAPI.registerModDependency(modID));
}
}
}
protected static SyncFolderDescriptor getSyncFolderDescriptor(String folderID) {
return syncFolderDescriptions.stream()
.filter(d -> d.equals(folderID))
.findFirst()
.orElse(null);
}
protected static Path localBasePathForFolderID(String folderID) {
final SyncFolderDescriptor desc = getSyncFolderDescriptor(folderID);
if (desc != null) {
return desc.localFolder;
}
else {
BCLib.LOGGER.warning("Unknown Sync-Folder ID '" + folderID + "'");
return null;
}
}
public static void registerSyncFolder(String folderID, Path localBaseFolder, boolean removeAdditionalFiles) {
localBaseFolder = localBaseFolder.normalize();
if (PathUtil.isChildOf(PathUtil.GAME_FOLDER, localBaseFolder)) {
final SyncFolderDescriptor desc = new SyncFolderDescriptor(folderID, localBaseFolder, removeAdditionalFiles);
if (syncFolderDescriptions.contains(desc)) {
BCLib.LOGGER.warning("Tried to override Folder Sync '" + folderID + "' again.");
}
else {
syncFolderDescriptions.add(desc);
}
}
else {
BCLib.LOGGER.error(localBaseFolder + " (from " + folderID + ") is outside the game directory " + PathUtil.GAME_FOLDER + ". Sync is not allowed.");
}
}
}

View file

@ -1,142 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
import net.minecraft.network.FriendlyByteBuf;
import org.jetbrains.annotations.NotNull;
import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.config.Config;
import ru.bclib.util.ModUtil;
import java.io.File;
import java.util.Objects;
public class AutoSyncID {
static class WithContentOverride extends AutoSyncID {
final FileContentWrapper contentWrapper;
final File localFile;
WithContentOverride(String modID, String uniqueID, FileContentWrapper contentWrapper, File localFile) {
super(modID, uniqueID);
this.contentWrapper = contentWrapper;
this.localFile = localFile;
}
@Override
public String toString() {
return super.toString() + " (Content override)";
}
}
static class ForDirectFileRequest extends AutoSyncID {
public final static String MOD_ID = "bclib::FILE";
final File relFile;
ForDirectFileRequest(String syncID, File relFile) {
super(ForDirectFileRequest.MOD_ID, syncID);
this.relFile = relFile;
}
@Override
void serializeData(FriendlyByteBuf buf) {
super.serializeData(buf);
DataHandler.writeString(buf, relFile.toString());
}
static ForDirectFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf){
final File fl = new File(DataHandler.readString(buf));
return new ForDirectFileRequest(uniqueID, fl);
}
@Override
public String toString() {
return super.uniqueID + " (" + this.relFile + ")";
}
}
static class ForModFileRequest extends AutoSyncID {
public final static String UNIQUE_ID = "bclib::MOD";
private final String version;
ForModFileRequest(String modID, String version) {
super(modID, ForModFileRequest.UNIQUE_ID);
this.version = version;
}
@Override
void serializeData(FriendlyByteBuf buf) {
super.serializeData(buf);
buf.writeInt(ModUtil.convertModVersion(version));
}
static ForModFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf){
final String version = ModUtil.convertModVersion(buf.readInt());
return new ForModFileRequest(modID, version);
}
@Override
public String toString() {
return super.modID + " (v" + this.version + ")";
}
}
/**
* A Unique ID for the referenced File.
* <p>
* Files with the same {@link #modID} need to have a unique IDs. Normally the filename from FileHash(String, File, byte[], int, int)
* is used to generated that ID, but you can directly specify one using FileHash(String, String, byte[], int, int).
*/
@NotNull
public final String uniqueID;
/**
* The ID of the Mod that is registering the File
*/
@NotNull
public final String modID;
public AutoSyncID(String modID, String uniqueID) {
Objects.nonNull(modID);
Objects.nonNull(uniqueID);
this.modID = modID;
this.uniqueID = uniqueID;
}
@Override
public String toString() {
return modID + "." + uniqueID;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AutoSyncID)) return false;
AutoSyncID that = (AutoSyncID) o;
return uniqueID.equals(that.uniqueID) && modID.equals(that.modID);
}
@Override
public int hashCode() {
return Objects.hash(uniqueID, modID);
}
void serializeData(FriendlyByteBuf buf) {
DataHandler.writeString(buf, modID);
DataHandler.writeString(buf, uniqueID);
}
static AutoSyncID deserializeData(FriendlyByteBuf buf){
String modID = DataHandler.readString(buf);
String uID = DataHandler.readString(buf);
if (ForDirectFileRequest.MOD_ID.equals(modID)){
return ForDirectFileRequest.finishDeserialize(modID, uID, buf);
} else if (ForModFileRequest.UNIQUE_ID.equals(uID)){
return ForModFileRequest.finishDeserialize(modID, uID, buf);
} else{
return new AutoSyncID(modID, uID);
}
}
public boolean isConfigFile(){
return this.uniqueID.startsWith(Config.CONFIG_SYNC_PREFIX);
}
}

View file

@ -1,270 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
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 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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.BaseDataHandler;
import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.api.dataexchange.handler.DataExchange;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
* Used to seperate large data transfers into multiple smaller messages.
* <p>
* {@link DataHandler} will automatically convert larger messages into Chunks on the Server
* and assemble the original message from those chunks on the client.
*/
public class Chunker extends DataHandler.FromServer {
/**
* Responsible for assembling the original ByteBuffer created by {@link PacketChunkSender} on the
* receiving end. Automatically created from the header {@link Chunker}-Message (where the serialNo==-1)
*/
static class PacketChunkReceiver {
@NotNull
public final UUID uuid;
public final int chunkCount;
@NotNull
private final FriendlyByteBuf networkedBuf;
@Nullable
private final DataHandlerDescriptor descriptor;
private static List<PacketChunkReceiver> active = new ArrayList<>(1);
private static PacketChunkReceiver newReceiver(@NotNull UUID uuid, int chunkCount, ResourceLocation origin){
DataHandlerDescriptor desc = DataExchange.getDescriptor(origin);
final PacketChunkReceiver r = new PacketChunkReceiver(uuid, chunkCount, desc);
active.add(r);
return r;
}
private static PacketChunkReceiver getOrCreate(@NotNull UUID uuid, int chunkCount, ResourceLocation origin){
return active.stream().filter(r -> r.uuid.equals(uuid)).findFirst().orElse(newReceiver(uuid, chunkCount, origin));
}
public static PacketChunkReceiver get(@NotNull UUID uuid){
return active.stream().filter(r -> r.uuid.equals(uuid)).findFirst().orElse(null);
}
private PacketChunkReceiver(@NotNull UUID uuid, int chunkCount, @Nullable DataHandlerDescriptor descriptor){
this.uuid = uuid;
this.chunkCount = chunkCount;
networkedBuf = PacketByteBufs.create();
this.descriptor = descriptor;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PacketChunkReceiver)) return false;
PacketChunkReceiver that = (PacketChunkReceiver) o;
return uuid.equals(that.uuid);
}
@Override
public int hashCode() {
return Objects.hash(uuid);
}
public boolean testFinished(){
ProgressListener listener = ChunkerProgress.getProgressListener();
if (listener!=null){
listener.progressStagePercentage((100*receivedCount)/chunkCount);
}
if (incomingBuffer == null){
return true;
} if (lastReadSerial>=chunkCount-1){
onFinish();
return true;
}
return false;
}
private void addBuffer(FriendlyByteBuf input){
final int size = input.readableBytes();
final int cap = networkedBuf.capacity()-networkedBuf.writerIndex();
if (cap < size){
networkedBuf.capacity(networkedBuf.writerIndex() + size);
}
input.readBytes(networkedBuf, size);
input.clear();
}
protected void onFinish(){
incomingBuffer.clear();
incomingBuffer = null;
final BaseDataHandler baseHandler = descriptor.INSTANCE.get();
if (baseHandler instanceof DataHandler.FromServer handler){
handler.receiveFromMemory(networkedBuf);
}
}
Map<Integer, FriendlyByteBuf> incomingBuffer = new HashMap<>();
int lastReadSerial = -1;
int receivedCount = 0;
public void processReceived(FriendlyByteBuf buf, int serialNo, int size){
receivedCount++;
if (lastReadSerial == serialNo-1){
addBuffer(buf);
lastReadSerial = serialNo;
} else {
//not sure if order is guaranteed by the underlying system!
boolean haveAll = true;
for (int nr = lastReadSerial+1; nr < serialNo-1; nr++){
if (incomingBuffer.get(nr) == null){
haveAll = false;
break;
}
}
if (haveAll){
for (int nr = lastReadSerial+1; nr < serialNo-1; nr++){
addBuffer(incomingBuffer.get(nr));
incomingBuffer.put(nr, null);
}
addBuffer(buf);
lastReadSerial = serialNo;
} else {
incomingBuffer.put(serialNo, buf);
}
}
}
}
/**
* Responsible for splitting an outgoing ByteBuffer into several smaller Chunks and
* send them as seperate messages to the {@link Chunker}-Channel
*/
public static class PacketChunkSender {
private final FriendlyByteBuf networkedBuf;
public final UUID uuid;
public final int chunkCount;
public final int size;
public final ResourceLocation origin;
public PacketChunkSender(FriendlyByteBuf buf, ResourceLocation origin){
networkedBuf = buf;
size = buf.readableBytes();
chunkCount = (int)Math.ceil((double)size / MAX_PAYLOAD_SIZE);
uuid = UUID.randomUUID();
this.origin = origin;
}
public void sendChunks(Collection<ServerPlayer> players){
BCLib.LOGGER.info("Sending Request in " + chunkCount + " Packet-Chunks");
for (int i=-1; i<chunkCount; i++){
Chunker c = new Chunker(i, uuid, networkedBuf, chunkCount, origin);
FriendlyByteBuf buf = PacketByteBufs.create();
c.serializeDataOnServer(buf);
for (ServerPlayer player : players){
ServerPlayNetworking.send(player, DESCRIPTOR.IDENTIFIER, buf);
}
}
}
}
//header = version + UUID + serialNo + size, see serializeDataOnServer
private static final int HEADER_SIZE = 1 + 16 + 4 + 4;
public static final int MAX_PACKET_SIZE = 1024*1024;
private static final int MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "chunker"), Chunker::new, false, false);
private int serialNo;
private UUID uuid;
private int chunkCount;
private FriendlyByteBuf networkedBuf;
private ResourceLocation origin;
protected Chunker(int serialNo, UUID uuid, FriendlyByteBuf networkedBuf, int chunkCount, ResourceLocation origin) {
super(DESCRIPTOR.IDENTIFIER);
this.serialNo = serialNo;
this.uuid = uuid;
this.networkedBuf = networkedBuf;
this.chunkCount = chunkCount;
this.origin = origin;
}
protected Chunker(){
super(DESCRIPTOR.IDENTIFIER);
}
@Override
protected void serializeDataOnServer(FriendlyByteBuf buf) {
//Sending Header. Make sure to change HEADER_SIZE if you change this!
buf.writeByte(0);
buf.writeLong(uuid.getMostSignificantBits());
buf.writeLong(uuid.getLeastSignificantBits());
buf.writeInt(serialNo);
//sending Payload
if (serialNo == -1){
//this is our header-Chunk that transports status information
buf.writeInt(chunkCount);
writeString(buf, origin.getNamespace());
writeString(buf, origin.getPath());
} else {
//this is an actual payload chunk
buf.capacity(MAX_PACKET_SIZE);
final int size = Math.min(MAX_PAYLOAD_SIZE, networkedBuf.readableBytes());
buf.writeInt(size);
networkedBuf.readBytes(buf, size);
}
}
private PacketChunkReceiver receiver;
@Override
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
final int version = buf.readByte();
uuid = new UUID(buf.readLong(), buf.readLong());
serialNo = buf.readInt();
if (serialNo == -1){
chunkCount = buf.readInt();
final String namespace = readString(buf);
final String path = readString(buf);
ResourceLocation ident = new ResourceLocation(namespace, path);
BCLib.LOGGER.info("Receiving " + chunkCount + " + Packet-Chunks for " + ident);
receiver = PacketChunkReceiver.getOrCreate(uuid, chunkCount, ident);
} else {
receiver = PacketChunkReceiver.get(uuid);
if (receiver!=null) {
final int size = buf.readInt();
receiver.processReceived(buf, serialNo, size);
} else {
BCLib.LOGGER.error("Unknown Packet-Chunk Transfer for " + uuid);
}
}
}
@Override
protected void runOnClientGameThread(Minecraft client) {
if (receiver!=null){
receiver.testFinished();
}
}
}

View file

@ -1,26 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.util.ProgressListener;
import ru.bclib.gui.screens.ProgressScreen;
@Environment(EnvType.CLIENT)
public class ChunkerProgress {
private static ProgressScreen progressScreen;
@Environment(EnvType.CLIENT)
public static void setProgressScreen(ProgressScreen scr){
progressScreen = scr;
}
@Environment(EnvType.CLIENT)
public static ProgressScreen getProgressScreen(){
return progressScreen;
}
@Environment(EnvType.CLIENT)
public static ProgressListener getProgressListener(){
return progressScreen;
}
}

View file

@ -1,77 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
import ru.bclib.BCLib;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class FileContentWrapper {
private byte[] rawContent;
private ByteArrayOutputStream outputStream;
FileContentWrapper(byte[] content) {
this.rawContent = content;
this.outputStream = null;
}
public byte[] getOriginalContent() {
return rawContent;
}
public byte[] getRawContent() {
if (outputStream != null) {
return outputStream.toByteArray();
}
return rawContent;
}
private void invalidateOutputStream() {
if (this.outputStream != null) {
try {
this.outputStream.close();
}
catch (IOException e) {
BCLib.LOGGER.debug(e);
}
}
this.outputStream = null;
}
public void setRawContent(byte[] rawContent) {
this.rawContent = rawContent;
invalidateOutputStream();
}
public void syncWithOutputStream() {
if (outputStream != null) {
try {
outputStream.flush();
}
catch (IOException e) {
BCLib.LOGGER.error(e.getMessage());
e.printStackTrace();
}
setRawContent(getRawContent());
invalidateOutputStream();
}
}
public ByteArrayInputStream getInputStream() {
if (rawContent == null) return new ByteArrayInputStream(new byte[0]);
return new ByteArrayInputStream(rawContent);
}
public ByteArrayOutputStream getOrCreateOutputStream() {
if (this.outputStream == null) {
return this.getEmptyOutputStream();
}
return this.outputStream;
}
public ByteArrayOutputStream getEmptyOutputStream() {
invalidateOutputStream();
this.outputStream = new ByteArrayOutputStream(this.rawContent.length);
return this.outputStream;
}
}

View file

@ -1,497 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
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 net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.resources.ResourceLocation;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataExchangeAPI;
import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID.WithContentOverride;
import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile;
import ru.bclib.config.Configs;
import ru.bclib.config.ServerConfig;
import ru.bclib.gui.screens.ModListScreen;
import ru.bclib.gui.screens.ProgressScreen;
import ru.bclib.gui.screens.SyncFilesScreen;
import ru.bclib.gui.screens.WarnBCLibVersionMismatch;
import ru.bclib.util.ModUtil;
import ru.bclib.util.ModUtil.ModInfo;
import ru.bclib.util.PathUtil;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Sent from the Server to the Client.
* <p>
* For Details refer to {@link HelloServer}
*/
public class HelloClient extends DataHandler.FromServer {
public record OfferedModInfo(String version, int size, boolean canDownload) {
}
public interface IServerModMap extends Map<String, OfferedModInfo> {}
public static class ServerModMap extends HashMap<String, OfferedModInfo> implements IServerModMap {}
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_client"), HelloClient::new, false, false);
public HelloClient() {
super(DESCRIPTOR.IDENTIFIER);
}
static String getBCLibVersion() {
return ModUtil.getModVersion(BCLib.MOD_ID);
}
@Override
protected boolean prepareDataOnServer() {
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
return false;
}
AutoSync.loadSyncFolder();
return true;
}
@Override
protected void serializeDataOnServer(FriendlyByteBuf buf) {
final String vbclib = getBCLibVersion();
BCLib.LOGGER.info("Sending Hello to Client. (server=" + vbclib + ")");
//write BCLibVersion (=protocol version)
buf.writeInt(ModUtil.convertModVersion(vbclib));
if (Configs.SERVER_CONFIG.isOfferingMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()) {
List<String> mods = DataExchangeAPI.registeredMods();
final List<String> inmods = mods;
if (Configs.SERVER_CONFIG.isOfferingAllMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()){
mods = new ArrayList<>(inmods.size());
mods.addAll(inmods);
mods.addAll(ModUtil
.getMods()
.entrySet()
.stream()
.filter(entry -> entry.getValue().metadata.getEnvironment()!= ModEnvironment.SERVER && !inmods.contains(entry.getKey()))
.map(entry -> entry.getKey())
.collect(Collectors.toList())
);
}
mods = mods
.stream()
.filter(entry -> !Configs.SERVER_CONFIG.get(ServerConfig.EXCLUDED_MODS).contains(entry))
.collect(Collectors.toList());
//write Plugin Versions
buf.writeInt(mods.size());
for (String modID : mods) {
final String ver = ModUtil.getModVersion(modID);
int size = 0;
final ModInfo mi = ModUtil.getModInfo(modID);
if (mi != null) {
try {
size = (int) Files.size(mi.jarPath);
} catch (IOException e) {
BCLib.LOGGER.error("Unable to get File Size: " + e.getMessage());
}
}
writeString(buf, modID);
buf.writeInt(ModUtil.convertModVersion(ver));
buf.writeInt(size);
final boolean canDownload = size>0 && Configs.SERVER_CONFIG.isOfferingMods() && (Configs.SERVER_CONFIG.isOfferingAllMods() || inmods.contains(modID));
buf.writeBoolean(canDownload);
BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver + " (size: " + PathUtil.humanReadableFileSize(size) + ", download="+canDownload+")");
}
}
else {
BCLib.LOGGER.info("Server will not list Mods.");
buf.writeInt(0);
}
if (Configs.SERVER_CONFIG.isOfferingFiles() || Configs.SERVER_CONFIG.isOfferingConfigs()) {
//do only include files that exist on the server
final List<AutoFileSyncEntry> existingAutoSyncFiles = AutoSync.getAutoSyncFiles()
.stream()
.filter(e -> e.fileName.exists())
.filter(e -> (e.isConfigFile() && Configs.SERVER_CONFIG.isOfferingConfigs()) || (e instanceof AutoFileSyncEntry.ForDirectFileRequest && Configs.SERVER_CONFIG.isOfferingFiles()))
.collect(Collectors.toList());
//send config Data
buf.writeInt(existingAutoSyncFiles.size());
for (AutoFileSyncEntry entry : existingAutoSyncFiles) {
entry.serialize(buf);
BCLib.LOGGER.info(" - Offering " + (entry.isConfigFile() ? "Config " : "File ") + entry);
}
}
else {
BCLib.LOGGER.info("Server will neither offer Files nor Configs.");
buf.writeInt(0);
}
if (Configs.SERVER_CONFIG.isOfferingFiles()) {
buf.writeInt(AutoSync.syncFolderDescriptions.size());
AutoSync.syncFolderDescriptions.forEach(desc -> {
BCLib.LOGGER.info(" - Offering Folder " + desc.localFolder + " (allowDelete=" + desc.removeAdditionalFiles + ")");
desc.serialize(buf);
});
}
else {
BCLib.LOGGER.info("Server will not offer Sync Folders.");
buf.writeInt(0);
}
buf.writeBoolean(Configs.SERVER_CONFIG.isOfferingInfosForMods());
}
String bclibVersion = "0.0.0";
IServerModMap modVersion = new ServerModMap();
List<AutoSync.AutoSyncTriple> autoSyncedFiles = null;
List<SyncFolderDescriptor> autoSynFolders = null;
boolean serverPublishedModInfo = false;
@Environment(EnvType.CLIENT)
@Override
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
//read BCLibVersion (=protocol version)
bclibVersion = ModUtil.convertModVersion(buf.readInt());
final boolean protocolVersion_0_4_1 = ModUtil.isLargerOrEqualVersion(bclibVersion, "0.4.1");
//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
if (protocolVersion_0_4_1) {
size = buf.readInt();
canDownload = buf.readBoolean();
}
else {
size = 0;
canDownload = true;
}
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
if (protocolVersion_0_4_1) {
final int folderCount = buf.readInt();
for (int i = 0; i < folderCount; i++) {
SyncFolderDescriptor desc = SyncFolderDescriptor.deserialize(buf);
autoSynFolders.add(desc);
}
serverPublishedModInfo = buf.readBoolean();
}
}
@Environment(EnvType.CLIENT)
private void processAutoSyncFolder(final List<AutoSyncID> filesToRequest, final List<AutoSyncID.ForDirectFileRequest> filesToRemove) {
if (!Configs.CLIENT_CONFIG.isAcceptingFiles()) {
return;
}
if (autoSynFolders.size() > 0) {
BCLib.LOGGER.info("Folders offered by Server:");
}
autoSynFolders.forEach(desc -> {
//desc contains the fileCache sent from the server, load the local version to get hold of the actual file cache on the client
SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(desc.folderID);
if (localDescriptor != null) {
BCLib.LOGGER.info(" - " + desc.folderID + " (" + desc.localFolder + ", allowRemove=" + desc.removeAdditionalFiles + ")");
localDescriptor.invalidateCache();
desc.relativeFilesStream()
.filter(desc::discardChildElements)
.forEach(subFile -> {
BCLib.LOGGER.warning(" * " + subFile.relPath + " (REJECTED)");
});
if (desc.removeAdditionalFiles) {
List<AutoSyncID.ForDirectFileRequest> additionalFiles = localDescriptor.relativeFilesStream()
.filter(subFile -> !desc.hasRelativeFile(subFile))
.map(desc::mapAbsolute)
.filter(desc::acceptChildElements)
.map(absPath -> new AutoSyncID.ForDirectFileRequest(desc.folderID, absPath.toFile()))
.collect(Collectors.toList());
additionalFiles.forEach(aid -> BCLib.LOGGER.info(" * " + desc.localFolder.relativize(aid.relFile.toPath()) + " (missing on server)"));
filesToRemove.addAll(additionalFiles);
}
desc.relativeFilesStream()
.filter(desc::acceptChildElements)
.forEach(subFile -> {
SubFile localSubFile = localDescriptor.getLocalSubFile(subFile.relPath);
if (localSubFile != null) {
//the file exists locally, check if the hashes match
if (!localSubFile.hash.equals(subFile.hash)) {
BCLib.LOGGER.info(" * " + subFile.relPath + " (changed)");
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(desc.folderID, new File(subFile.relPath)));
}
else {
BCLib.LOGGER.info(" * " + subFile.relPath);
}
}
else {
//the file is missing locally
BCLib.LOGGER.info(" * " + subFile.relPath + " (missing on client)");
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(desc.folderID, new File(subFile.relPath)));
}
});
//free some memory
localDescriptor.invalidateCache();
}
else {
BCLib.LOGGER.info(" - " + desc.folderID + " (Failed to find)");
}
});
}
@Environment(EnvType.CLIENT)
private void processSingleFileSync(final List<AutoSyncID> filesToRequest) {
final boolean debugHashes = Configs.CLIENT_CONFIG.shouldPrintDebugHashes();
if (autoSyncedFiles.size() > 0) {
BCLib.LOGGER.info("Files offered by Server:");
}
//Handle single sync files
//Single files need to be registered for sync on both client and server
//There are no restrictions to the target folder, but the client decides the final
//location.
for (AutoSync.AutoSyncTriple e : autoSyncedFiles) {
String actionString = "";
FileContentWrapper contentWrapper = new FileContentWrapper(e.serverContent);
if (e.localMatch == null) {
actionString = "(unknown source -> omitting)";
//filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
}
else if (e.localMatch.needTransfer.test(e.localMatch.getFileHash(), e.serverHash, contentWrapper)) {
actionString = "(prepare update)";
//we did not yet receive the new content
if (contentWrapper.getRawContent() == null) {
filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
}
else {
filesToRequest.add(new AutoSyncID.WithContentOverride(e.serverHash.modID, e.serverHash.uniqueID, contentWrapper, e.localMatch.fileName));
}
}
BCLib.LOGGER.info(" - " + e + ": " + actionString);
if (debugHashes) {
BCLib.LOGGER.info(" * " + e.serverHash + " (Server)");
BCLib.LOGGER.info(" * " + e.localMatch.getFileHash() + " (Client)");
BCLib.LOGGER.info(" * local Content " + (contentWrapper.getRawContent() == null));
}
}
}
@Environment(EnvType.CLIENT)
private void processModFileSync(final List<AutoSyncID> filesToRequest, final Set<String> mismatchingMods) {
for (Entry<String, OfferedModInfo> e : modVersion.entrySet()) {
final String localVersion = ModUtil.getModVersion(e.getKey());
final OfferedModInfo serverInfo = e.getValue();
final boolean requestMod = !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")+ ")");
if (requestMod) {
filesToRequest.add(new AutoSyncID.ForModFileRequest(e.getKey(), serverInfo.version));
}
if (!serverInfo.version.equals(localVersion)) {
mismatchingMods.add(e.getKey());
}
}
mismatchingMods.addAll(ModListScreen.localMissing(modVersion));
mismatchingMods.addAll(ModListScreen.serverMissing(modVersion));
}
@Override
protected boolean isBlocking() {
return true;
}
@Environment(EnvType.CLIENT)
@Override
protected void runOnClientGameThread(Minecraft client) {
if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
return;
}
final String localBclibVersion = getBCLibVersion();
BCLib.LOGGER.info("Received Hello from Server. (client=" + localBclibVersion + ", server=" + bclibVersion + ")");
if (ModUtil.convertModVersion(localBclibVersion) != ModUtil.convertModVersion(bclibVersion)){
showBCLibError(client);
return;
}
final List<AutoSyncID> filesToRequest = new ArrayList<>(2);
final List<AutoSyncID.ForDirectFileRequest> filesToRemove = new ArrayList<>(2);
final Set<String> mismatchingMods = new HashSet<>(2);
processModFileSync(filesToRequest, mismatchingMods);
processSingleFileSync(filesToRequest);
processAutoSyncFolder(filesToRequest, filesToRemove);
//Handle folder sync
//Both client and server need to know about the folder you want to sync
//Files can only get placed within that folder
if ((filesToRequest.size() > 0 || filesToRemove.size() > 0) && ( Configs.CLIENT_CONFIG.isAcceptingMods() || Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles())) {
showSyncFilesScreen(client, filesToRequest, filesToRemove);
return;
} else if (serverPublishedModInfo && mismatchingMods.size()>0 && Configs.CLIENT_CONFIG.isShowingModInfo()) {
client.setScreen(new ModListScreen(client.screen, new TranslatableComponent("title.bclib.modmissmatch"), new TranslatableComponent("message.bclib.modmissmatch"), CommonComponents.GUI_PROCEED, ModUtil.getMods(), modVersion));
return;
}
}
@Environment(EnvType.CLIENT)
protected void showBCLibError(Minecraft client) {
BCLib.LOGGER.error("BCLib differs on client and server.");
client.setScreen(new WarnBCLibVersionMismatch((download) -> {
if (download) {
requestBCLibDownload();
this.onCloseSyncFilesScreen();
} else {
Minecraft.getInstance()
.setScreen(null);
}
}));
}
@Environment(EnvType.CLIENT)
protected void showSyncFilesScreen(Minecraft client, List<AutoSyncID> files, final List<AutoSyncID.ForDirectFileRequest> filesToRemove) {
int configFiles = 0;
int singleFiles = 0;
int folderFiles = 0;
int modFiles = 0;
for (AutoSyncID aid : files) {
if (aid.isConfigFile()) {
configFiles++;
}
else if (aid instanceof AutoSyncID.ForModFileRequest) {
modFiles++;
}
else if (aid instanceof AutoSyncID.ForDirectFileRequest) {
folderFiles++;
}
else {
singleFiles++;
}
}
client.setScreen(new SyncFilesScreen(modFiles, configFiles, singleFiles, folderFiles, filesToRemove.size(), modVersion, (downloadMods, downloadConfigs, downloadFiles, removeFiles) -> {
if (downloadMods || downloadConfigs || downloadFiles) {
BCLib.LOGGER.info("Updating local Files:");
List<AutoSyncID.WithContentOverride> localChanges = new ArrayList<>(files.toArray().length);
List<AutoSyncID> requestFiles = new ArrayList<>(files.toArray().length);
files.forEach(aid -> {
if (aid.isConfigFile() && downloadConfigs) {
processOfferedFile(requestFiles, aid);
}
else if (aid instanceof AutoSyncID.ForModFileRequest && downloadMods) {
processOfferedFile(requestFiles, aid);
}
else if (downloadFiles) {
processOfferedFile(requestFiles, aid);
}
});
requestFileDownloads(requestFiles);
}
if (removeFiles) {
filesToRemove.forEach(aid -> {
BCLib.LOGGER.info(" - " + aid.relFile + " (removing)");
aid.relFile.delete();
});
}
this.onCloseSyncFilesScreen();
}));
}
@Environment(EnvType.CLIENT)
private void onCloseSyncFilesScreen(){
Minecraft.getInstance()
.setScreen(ChunkerProgress.getProgressScreen());
}
private void processOfferedFile(List<AutoSyncID> requestFiles, AutoSyncID aid) {
if (aid instanceof WithContentOverride) {
final WithContentOverride aidc = (WithContentOverride) aid;
BCLib.LOGGER.info(" - " + aid + " (updating Content)");
SendFiles.writeSyncedFile(aid, aidc.contentWrapper.getRawContent(), aidc.localFile);
}
else {
requestFiles.add(aid);
BCLib.LOGGER.info(" - " + aid + " (requesting)");
}
}
private void requestBCLibDownload() {
BCLib.LOGGER.warning("Starting download of BCLib");
requestFileDownloads(List.of(new AutoSyncID.ForModFileRequest(BCLib.MOD_ID, bclibVersion)));
}
@Environment(EnvType.CLIENT)
private void requestFileDownloads(List<AutoSyncID> files) {
BCLib.LOGGER.info("Starting download of Files:" + files.size());
final ProgressScreen progress = new ProgressScreen(null, new TranslatableComponent("title.bclib.filesync.progress"), new TranslatableComponent("message.bclib.filesync.progress"));
progress.progressStart(new TranslatableComponent("message.bclib.filesync.progress.stage.empty"));
ChunkerProgress.setProgressScreen(progress);
DataExchangeAPI.send(new RequestFiles(files));
}
}

View file

@ -1,108 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataExchangeAPI;
import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.config.Configs;
import ru.bclib.util.ModUtil;
import java.io.File;
/**
* This message is sent once a player enters the world. It initiates a sequence of Messages that will sync files between both
* client and server.
* <table>
* <caption>Description</caption>
* <tr>
* <th>Server</th>
* <th></th>
* <th>Client</th>
* <th></th>
* </tr>
* <tr>
* <td colspan="4">Player enters World</td>
* </tr>
* <tr>
* <td></td>
* <td>&lt;--</td>
* <td>{@link HelloServer}</td>
* <td>Sends the current BLib-Version installed on the Client</td>
* </tr>
* <tr>
* <td>{@link HelloClient}</td>
* <td>--&gt;</td>
* <td></td>
* <td>Sends the current BClIb-Version, the Version of all Plugins and data for all AutpoSync-Files
* ({@link DataExchangeAPI#addAutoSyncFile(String, File)} on the Server</td>
* </tr>
* <tr>
* <td></td>
* <td>&lt;--</td>
* <td>{@link RequestFiles}</td>
* <td>Request missing or out of sync Files from the Server</td>
* </tr>
* <tr>
* <td>{@link SendFiles}</td>
* <td>--&gt;</td>
* <td></td>
* <td>Send Files from the Server to the Client</td>
* </tr>
* </table>
*/
public class HelloServer extends DataHandler.FromClient {
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_server"), HelloServer::new, true, false);
protected String bclibVersion = "0.0.0";
public HelloServer() {
super(DESCRIPTOR.IDENTIFIER);
}
@Environment(EnvType.CLIENT)
@Override
protected boolean prepareDataOnClient() {
if (! Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
return false;
}
return true;
}
@Environment(EnvType.CLIENT)
@Override
protected void serializeDataOnClient(FriendlyByteBuf buf) {
BCLib.LOGGER.info("Sending hello to server.");
buf.writeInt(ModUtil.convertModVersion(HelloClient.getBCLibVersion()));
}
@Override
protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, PacketSender responseSender) {
bclibVersion = ModUtil.convertModVersion(buf.readInt());
}
@Override
protected void runOnServerGameThread(MinecraftServer server) {
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
return;
}
String localBclibVersion = HelloClient.getBCLibVersion();
BCLib.LOGGER.info("Received Hello from Client. (server=" + localBclibVersion + ", client=" + bclibVersion + ")");
if (!server.isPublished()) {
BCLib.LOGGER.info("Auto-Sync is disabled for Singleplayer worlds.");
return;
}
reply(new HelloClient(), server);
}
}

View file

@ -1,98 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.config.Configs;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class RequestFiles extends DataHandler.FromClient {
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "request_files"), RequestFiles::new, false, false);
static String currentToken = "";
protected List<AutoSyncID> files;
private RequestFiles() {
this(null);
}
public RequestFiles(List<AutoSyncID> files) {
super(DESCRIPTOR.IDENTIFIER);
this.files = files;
}
@Environment(EnvType.CLIENT)
@Override
protected boolean prepareDataOnClient() {
if (! Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
return false;
}
return true;
}
@Environment(EnvType.CLIENT)
@Override
protected void serializeDataOnClient(FriendlyByteBuf buf) {
newToken();
writeString(buf, currentToken);
buf.writeInt(files.size());
for (AutoSyncID a : files) {
a.serializeData(buf);
}
}
String receivedToken = "";
@Override
protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, 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) {
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
return;
}
List<AutoFileSyncEntry> syncEntries = files.stream()
.map(asid -> AutoFileSyncEntry.findMatching(asid))
.filter(e -> e != null)
.collect(Collectors.toList());
reply(new SendFiles(syncEntries, receivedToken), server);
}
public static void newToken() {
currentToken = UUID.randomUUID()
.toString();
}
static {
newToken();
}
}

View file

@ -1,216 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.config.Configs;
import ru.bclib.gui.screens.ConfirmRestartScreen;
import ru.bclib.util.Pair;
import ru.bclib.util.PathUtil;
import ru.bclib.util.Triple;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class SendFiles extends DataHandler.FromServer {
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "send_files"), SendFiles::new, false, false);
protected List<AutoFileSyncEntry> files;
private String token;
public SendFiles() {
this(null, "");
}
public SendFiles(List<AutoFileSyncEntry> files, String token) {
super(DESCRIPTOR.IDENTIFIER);
this.files = files;
this.token = token;
}
@Override
protected boolean prepareDataOnServer() {
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
return false;
}
return true;
}
@Override
protected void serializeDataOnServer(FriendlyByteBuf buf) {
List<AutoFileSyncEntry> existingFiles = files.stream()
.filter(e -> e!=null &&e.fileName!=null && e.fileName.exists())
.collect(Collectors.toList());
/*
//this will try to send a file that was not registered or requested by the client
existingFiles.add(new AutoFileSyncEntry("none", new File("D:\\MinecraftPlugins\\BetterNether\\run\\server.properties"),true,(a, b, content) -> {
System.out.println("Got Content:" + content.length);
return true;
}));*/
/*//this will try to send a folder-file that was not registered or requested by the client
existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("test.json"), DataExchange.SYNC_FOLDER.mapAbsolute("test.json").toFile()));*/
/*//this will try to send a folder-file that was not registered or requested by the client and is outside the base-folder
existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("../breakout.json"), DataExchange.SYNC_FOLDER.mapAbsolute("../breakout.json").toFile()));*/
writeString(buf, token);
buf.writeInt(existingFiles.size());
BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:");
for (AutoFileSyncEntry entry : existingFiles) {
int length = entry.serializeContent(buf);
BCLib.LOGGER.info(" - " + entry + " (" + PathUtil.humanReadableFileSize(length) + ")");
}
}
private List<Pair<AutoFileSyncEntry, byte[]>> receivedFiles;
@Environment(EnvType.CLIENT)
@Override
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
if ( Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) {
token = readString(buf);
if (!token.equals(RequestFiles.currentToken)) {
RequestFiles.newToken();
BCLib.LOGGER.error("Unrequested File Transfer!");
receivedFiles = new ArrayList<>(0);
return;
}
RequestFiles.newToken();
int size = buf.readInt();
receivedFiles = new ArrayList<>(size);
BCLib.LOGGER.info("Server sent " + size + " Files:");
for (int i = 0; i < size; i++) {
Triple<AutoFileSyncEntry, byte[], AutoSyncID> p = AutoFileSyncEntry.deserializeContent(buf);
if (p.first != null) {
final String type;
if (p.first.isConfigFile() && Configs.CLIENT_CONFIG.isAcceptingConfigs()) {
receivedFiles.add(p);
type = "Accepted Config ";
} else if (p.first instanceof AutoFileSyncEntry.ForModFileRequest && Configs.CLIENT_CONFIG.isAcceptingMods()){
receivedFiles.add(p);
type = "Accepted Mod ";
} else if ( Configs.CLIENT_CONFIG.isAcceptingFiles()){
receivedFiles.add(p);
type = "Accepted File ";
} else {
type = "Ignoring ";
}
BCLib.LOGGER.info(" - " + type + p.first + " (" + PathUtil.humanReadableFileSize(p.second.length) + ")");
}
else {
BCLib.LOGGER.error(" - Failed to receive File " + p.third + ", possibly sent from a Mod that is not installed on the client.");
}
}
}
}
@Environment(EnvType.CLIENT)
@Override
protected void runOnClientGameThread(Minecraft client) {
if ( Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) {
BCLib.LOGGER.info("Writing Files:");
//TODO: Reject files that were not in the last RequestFiles.
for (Pair<AutoFileSyncEntry, byte[]> entry : receivedFiles) {
final AutoFileSyncEntry e = entry.first;
final byte[] data = entry.second;
writeSyncedFile(e, data, e.fileName);
}
showConfirmRestart(client);
}
}
@Environment(EnvType.CLIENT)
static void writeSyncedFile(AutoSyncID e, byte[] data, File fileName) {
if (fileName!=null && !PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())){
BCLib.LOGGER.error(fileName + " is not within game folder " + PathUtil.GAME_FOLDER);
return;
}
if (!PathUtil.MOD_BAK_FOLDER.toFile().exists()){
PathUtil.MOD_BAK_FOLDER.toFile().mkdirs();
}
Path path = fileName!=null?fileName.toPath():null;
Path removeAfter = null;
if (e instanceof AutoFileSyncEntry.ForModFileRequest mase){
removeAfter = path;
int count = 0;
final String prefix = "_bclib_synced_";
String name = prefix + mase.modID + "_" + mase.version.replace(".", "_") + ".jar";
do {
if (path != null) {
//move to the same directory as the existing Mod
path = path.getParent()
.resolve(name);
}
else {
//move to the default mode location
path = PathUtil.MOD_FOLDER.resolve(name);
}
count++;
name = prefix + mase.modID + "_" + mase.version.replace(".", "_") + "__" + String.format("%03d", count) + ".jar";
} while (path.toFile().exists());
}
BCLib.LOGGER.info(" - Writing " + path + " (" + PathUtil.humanReadableFileSize(data.length) + ")");
try {
final File parentFile = path.getParent()
.toFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
Files.write(path, data);
if (removeAfter != null){
final String bakFileName = removeAfter.toFile().getName();
String collisionFreeName = bakFileName;
Path targetPath;
int count = 0;
do {
targetPath = PathUtil.MOD_BAK_FOLDER.resolve(collisionFreeName);
count++;
collisionFreeName = String.format("%03d", count) + "_" + bakFileName;
} while (targetPath.toFile().exists());
BCLib.LOGGER.info(" - Moving " + removeAfter + " to " +targetPath);
removeAfter.toFile().renameTo(targetPath.toFile());
}
AutoSync.didReceiveFile(e, fileName);
}
catch (IOException ioException) {
BCLib.LOGGER.error(" --> Writing " + fileName + " failed: " + ioException);
}
}
@Environment(EnvType.CLIENT)
protected void showConfirmRestart(Minecraft client) {
client.setScreen(new ConfirmRestartScreen(() -> {
Minecraft.getInstance()
.setScreen(null);
client.stop();
}));
}
}

View file

@ -1,206 +0,0 @@
package ru.bclib.api.dataexchange.handler.autosync;
import net.minecraft.network.FriendlyByteBuf;
import org.jetbrains.annotations.NotNull;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.FileHash;
import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID.ForDirectFileRequest;
import ru.bclib.config.Configs;
import ru.bclib.util.PathUtil;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class SyncFolderDescriptor {
static class SubFile {
public final String relPath;
public final FileHash hash;
SubFile(String relPath, FileHash hash) {
this.relPath = relPath;
this.hash = hash;
}
@Override
public String toString() {
return relPath;
}
public void serialize(FriendlyByteBuf buf) {
DataHandler.writeString(buf, relPath);
hash.serialize(buf);
}
public static SubFile deserialize(FriendlyByteBuf buf) {
final String relPath = DataHandler.readString(buf);
FileHash hash = FileHash.deserialize(buf);
return new SubFile(relPath, hash);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof String) return relPath.equals(o);
if (!(o instanceof SubFile)) return false;
SubFile subFile = (SubFile) o;
return relPath.equals(subFile.relPath);
}
@Override
public int hashCode() {
return relPath.hashCode();
}
}
@NotNull
public final String folderID;
public final boolean removeAdditionalFiles;
@NotNull
public final Path localFolder;
private List<SubFile> fileCache;
public SyncFolderDescriptor(String folderID, Path localFolder, boolean removeAdditionalFiles) {
this.removeAdditionalFiles = removeAdditionalFiles;
this.folderID = folderID;
this.localFolder = localFolder;
fileCache = null;
}
@Override
public String toString() {
return "SyncFolderDescriptor{" + "folderID='" + folderID + '\'' + ", removeAdditionalFiles=" + removeAdditionalFiles + ", localFolder=" + localFolder + ", files=" + (fileCache == null ? "?" : fileCache.size()) + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof String) {
return folderID.equals(o);
}
if (o instanceof ForDirectFileRequest) {
return folderID.equals(((ForDirectFileRequest) o).uniqueID);
}
if (!(o instanceof SyncFolderDescriptor)) return false;
SyncFolderDescriptor that = (SyncFolderDescriptor) o;
return folderID.equals(that.folderID);
}
@Override
public int hashCode() {
return folderID.hashCode();
}
public int fileCount() {
return fileCache == null ? 0 : fileCache.size();
}
public void invalidateCache() {
fileCache = null;
}
public void loadCache() {
if (fileCache == null) {
fileCache = new ArrayList<>(8);
PathUtil.fileWalker(localFolder.toFile(), p -> fileCache.add(new SubFile(localFolder.relativize(p)
.toString(), FileHash.create(p.toFile()))));
/*//this tests if we can trick the system to load files that are not beneath the base-folder
if (!BCLib.isClient()) {
fileCache.add(new SubFile("../breakout.json", FileHash.create(mapAbsolute("../breakout.json").toFile())));
}*/
}
}
public void serialize(FriendlyByteBuf buf) {
final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(AutoSync.SYNC_CATEGORY, "debugHashes", false);
loadCache();
DataHandler.writeString(buf, folderID);
buf.writeBoolean(removeAdditionalFiles);
buf.writeInt(fileCache.size());
fileCache.forEach(fl -> {
BCLib.LOGGER.info(" - " + fl.relPath);
if (debugHashes) {
BCLib.LOGGER.info(" " + fl.hash);
}
fl.serialize(buf);
});
}
public static SyncFolderDescriptor deserialize(FriendlyByteBuf buf) {
final String folderID = DataHandler.readString(buf);
final boolean remAddFiles = buf.readBoolean();
final int count = buf.readInt();
SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(folderID);
final SyncFolderDescriptor desc;
if (localDescriptor != null) {
desc = new SyncFolderDescriptor(folderID, localDescriptor.localFolder, localDescriptor.removeAdditionalFiles && remAddFiles);
desc.fileCache = new ArrayList<>(count);
}
else {
BCLib.LOGGER.warning(BCLib.isClient() ? "Client" : "Server" + " does not know Sync-Folder ID '" + folderID + "'");
desc = null;
}
for (int i = 0; i < count; i++) {
SubFile relPath = SubFile.deserialize(buf);
if (desc != null) desc.fileCache.add(relPath);
}
return desc;
}
//Note: make sure loadCache was called before using this
boolean hasRelativeFile(String relFile) {
return fileCache.stream()
.filter(sf -> sf.equals(relFile))
.findFirst()
.isPresent();
}
//Note: make sure loadCache was called before using this
boolean hasRelativeFile(SubFile subFile) {
return hasRelativeFile(subFile.relPath);
}
//Note: make sure loadCache was called before using this
SubFile getLocalSubFile(String relPath) {
return fileCache.stream()
.filter(sf -> sf.relPath.equals(relPath))
.findFirst()
.orElse(null);
}
Stream<SubFile> relativeFilesStream() {
loadCache();
return fileCache.stream();
}
public Path mapAbsolute(String relPath) {
return this.localFolder.resolve(relPath)
.normalize();
}
public Path mapAbsolute(SubFile subFile) {
return this.localFolder.resolve(subFile.relPath)
.normalize();
}
public boolean acceptChildElements(Path absPath) {
return PathUtil.isChildOf(this.localFolder, absPath);
}
public boolean acceptChildElements(SubFile subFile) {
return acceptChildElements(mapAbsolute(subFile));
}
public boolean discardChildElements(SubFile subFile) {
return !acceptChildElements(subFile);
}
}

View file

@ -1,618 +0,0 @@
package ru.bclib.api.datafixer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.toasts.SystemToast;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.worldselection.EditWorldScreen;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
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 org.jetbrains.annotations.NotNull;
import ru.bclib.BCLib;
import ru.bclib.api.WorldDataAPI;
import ru.bclib.config.Configs;
import ru.bclib.gui.screens.AtomicProgressListener;
import ru.bclib.gui.screens.ConfirmFixScreen;
import ru.bclib.gui.screens.LevelFixErrorScreen;
import ru.bclib.gui.screens.LevelFixErrorScreen.Listener;
import ru.bclib.gui.screens.ProgressScreen;
import ru.bclib.util.Logger;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
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;
/**
* API to manage Patches that need to get applied to a world
*/
public class DataFixerAPI {
static final Logger LOGGER = new Logger("DataFixerAPI");
static class State {
public boolean didFail = false;
protected ArrayList<String> errors = new ArrayList<>();
public void addError(String s){
errors.add(s);
}
public boolean hasError(){
return errors.size()>0;
}
public String getErrorMessage(){
return errors.stream().reduce("", (a, b) -> a + " - " + b + "\n");
}
public String[] getErrorMessages(){
String[] res = new String[errors.size()];
return errors.toArray(res);
}
}
@FunctionalInterface
public static interface Callback {
public void call();
}
private static boolean wrapCall(LevelStorageSource levelSource, String levelID, Function<LevelStorageAccess, Boolean> runWithLevel) {
LevelStorageSource.LevelStorageAccess levelStorageAccess;
try {
levelStorageAccess = levelSource.createAccess(levelID);
}
catch (IOException e) {
BCLib.LOGGER.warning("Failed to read level {} data", levelID, e);
SystemToast.onWorldAccessFailure(Minecraft.getInstance(), levelID);
Minecraft.getInstance().setScreen(null);
return true;
}
boolean returnValue = runWithLevel.apply(levelStorageAccess);
try {
levelStorageAccess.close();
}
catch (IOException e) {
BCLib.LOGGER.warning("Failed to unlock access to level {}", levelID, e);
}
return returnValue;
}
/**
* Will apply necessary Patches to the world.
*
* @param levelSource The SourceStorage for this Minecraft instance, You can get this using
* {@code Minecraft.getInstance().getLevelSource()}
* @param levelID The ID of the Level you want to patch
* @param showUI {@code true}, if you want to present the user with a Screen that offers to backup the world
* before applying the patches
* @param onResume When this method retursn {@code true}, this function will be called when the world is ready
* @return {@code true} if the UI was displayed. The UI is only displayed if {@code showUI} was {@code true} and
* patches were enabled in the config and the Guardian did find any patches that need to be applied to the world.
*
*/
public static boolean fixData(LevelStorageSource levelSource, String levelID, boolean showUI, Consumer<Boolean> onResume) {
return wrapCall(levelSource, levelID, (levelStorageAccess) -> fixData(levelStorageAccess, showUI, onResume));
}
/**
* Will apply necessary Patches to the world.
*
* @param levelStorageAccess The access class of the level you want to patch
* @param showUI {@code true}, if you want to present the user with a Screen that offers to backup the world
* before applying the patches
* @param onResume When this method retursn {@code true}, this function will be called when the world is ready
* @return {@code true} if the UI was displayed. The UI is only displayed if {@code showUI} was {@code true} and
* patches were enabled in the config and the Guardian did find any patches that need to be applied to the world.
*
*/
public static boolean fixData(LevelStorageSource.LevelStorageAccess levelStorageAccess, boolean showUI, Consumer<Boolean> onResume){
File levelPath = levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile();
File levelDat = levelStorageAccess.getLevelPath(LevelResource.LEVEL_DATA_FILE).toFile();
boolean newWorld = false;
if (!levelDat.exists()) {
BCLib.LOGGER.info("Creating a new World, no fixes needed");
newWorld = true;
}
initializeWorldData(levelPath, newWorld);
if (newWorld) return false;
return fixData(levelPath, levelStorageAccess.getLevelId(), showUI, onResume);
}
/**
* Initializes the DataStorage for this world. If the world is new, the patch registry is initialized to the
* current versions of the plugins.
* <p>
* This implementation will create a new {@link LevelStorageAccess} and call {@link #initializeWorldData(File, boolean)}
* using the provided root path.
* @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 newWorld {@code true} if this is a fresh world
*/
public static void initializeWorldData(LevelStorageSource levelSource, String levelID, boolean newWorld) {
wrapCall(levelSource, levelID, (levelStorageAccess) -> {
initializeWorldData(levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile(), newWorld);
return true;
});
}
/**
* Initializes the DataStorage for this world. If the world is new, the patch registry is initialized to the
* current versions of the plugins.
* @param levelBaseDir Folder of the world
* @param newWorld {@code true} if this is a fresh world
*
*/
public static void initializeWorldData(File levelBaseDir, boolean newWorld){
WorldDataAPI.load(new File(levelBaseDir, "data"));
if (newWorld){
getMigrationProfile().markApplied();
WorldDataAPI.saveFile(BCLib.MOD_ID);
}
}
@Environment(EnvType.CLIENT)
private static AtomicProgressListener showProgressScreen(){
ProgressScreen ps = new ProgressScreen(Minecraft.getInstance().screen, new TranslatableComponent("title.bclib.datafixer.progress"), new TranslatableComponent("message.bclib.datafixer.progress"));
Minecraft.getInstance().setScreen(ps);
return ps;
}
private static boolean fixData(File dir, String levelID, boolean showUI, Consumer<Boolean> onResume) {
MigrationProfile profile = loadProfileIfNeeded(dir);
BiConsumer<Boolean, Boolean> runFixes = (createBackup, applyFixes) -> {
final AtomicProgressListener progress;
if (applyFixes) {
if (showUI) {
progress = showProgressScreen();
} else {
progress = new AtomicProgressListener() {
private long timeStamp = Util.getMillis();
private AtomicInteger counter = new AtomicInteger(0);
@Override
public void incAtomic(int maxProgress) {
int percentage = (100 * counter.incrementAndGet()) / maxProgress;
if (Util.getMillis() - this.timeStamp >= 1000L) {
this.timeStamp = Util.getMillis();
BCLib.LOGGER.info((String) "Patching... {}%", percentage);
}
}
@Override
public void resetAtomic() {
counter = new AtomicInteger(0);
}
public void stop() {
}
public void progressStage(Component component) {
BCLib.LOGGER.info((String) "Patcher Stage... {}%", component.getString());
}
};
}
} else {
progress = null;
}
Supplier<State> runner = () -> {
if (createBackup) {
progress.progressStage(new TranslatableComponent("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.getBoolean(Configs.MAIN_PATCH_CATEGORY, "applyPatches", true)) {
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 = WorldDataAPI.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
MigrationProfile profile = Patch.createMigrationData(patchConfig);
return profile;
}
@Environment(EnvType.CLIENT)
static void showBackupWarning(String levelID, BiConsumer<Boolean, Boolean> whenFinished){
Minecraft.getInstance().setScreen(new ConfirmFixScreen((Screen) null, whenFinished::accept));
}
private static State runDataFixes(File dir, MigrationProfile profile, AtomicProgressListener progress) {
State state = new State();
progress.resetAtomic();
progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.reading"));
List<File> players = getAllPlayers(dir);
List<File> regions = getAllRegions(dir, null);
final int maxProgress = players.size()+regions.size()+4;
progress.incAtomic(maxProgress);
progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.players"));
players.parallelStream().forEach((file) -> {
fixPlayer(profile, state, file);
progress.incAtomic(maxProgress);
});
progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.level"));
fixLevel(profile, state, dir);
progress.incAtomic(maxProgress);
progress.progressStage(new TranslatableComponent("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(new TranslatableComponent("message.bclib.datafixer.progress.regions"));
regions.parallelStream().forEach((file) -> {
fixRegion(profile, state, file);
progress.incAtomic(maxProgress);
});
if (!state.didFail) {
progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.saving"));
profile.markApplied();
WorldDataAPI.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 = WorldDataAPI.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
}
return patchConfTag;
}
static void fixItemArrayWithID(ListTag items, boolean[] changed, MigrationProfile data, boolean recursive) {
items.forEach(inTag -> {
fixID((CompoundTag) inTag, changed, data, recursive);
});
}
static void fixID(CompoundTag inTag, boolean[] changed, MigrationProfile data, boolean recursive) {
final CompoundTag tag = inTag;
changed[0] |= data.replaceStringFromIDs(tag, "id");
if (tag.contains("Item")) {
CompoundTag item = (CompoundTag)tag.get("Item");
fixID(item, changed, data, recursive);
}
if (recursive && tag.contains("Items")) {
fixItemArrayWithID(tag.getList("Items", Tag.TAG_COMPOUND), changed, data, true);
}
if (recursive && tag.contains("Inventory")) {
ListTag inventory = tag.getList("Inventory", Tag.TAG_COMPOUND);
fixItemArrayWithID(inventory, changed, data, true);
}
if (tag.contains("tag")) {
CompoundTag entityTag = (CompoundTag)tag.get("tag");
if (entityTag.contains("BlockEntityTag")){
CompoundTag blockEntityTag = (CompoundTag)entityTag.get("BlockEntityTag");
fixID(blockEntityTag, changed, data, recursive);
/*ListTag items = blockEntityTag.getList("Items", Tag.TAG_COMPOUND);
fixItemArrayWithID(items, changed, data, recursive);*/
}
}
}
private static List<File> getAllPlayers(File dir) {
List<File> list = new ArrayList<>();
dir = new File(dir, "playerdata");
if (!dir.exists() || !dir.isDirectory()) {
return list;
}
for (File file : dir.listFiles()) {
if (file.isFile() && file.getName().endsWith(".dat")) {
list.add(file);
}
}
return list;
}
private static List<File> getAllRegions(File dir, List<File> list) {
if (list == null) {
list = new ArrayList<>();
}
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
getAllRegions(file, list);
} else if (file.isFile() && file.getName().endsWith(".mca")) {
list.add(file);
}
}
return list;
}
/**
* register a new Patch
*
* @param patch A #Supplier that will instantiate the new Patch Object
*/
public static void registerPatch(Supplier<Patch> patch) {
Patch.getALL().add(patch.get());
}
private static CompoundTag readNbt(File file) throws IOException {
try {
return NbtIo.readCompressed(file);
} catch (ZipException | EOFException e){
return NbtIo.read(file);
}
}
}

View file

@ -1,49 +0,0 @@
package ru.bclib.api.datafixer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import org.jetbrains.annotations.NotNull;
import ru.bclib.interfaces.PatchBiFunction;
import ru.bclib.interfaces.PatchFunction;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A Patch for level.dat that is always executed no matter what Patchlevel is set in a world.
*/
public abstract class ForcedLevelPatch extends Patch {
protected ForcedLevelPatch(@NotNull String modID, String version) {
super(modID, version, true);
}
@Override
public final Map<String, String> getIDReplacements() {
return new HashMap<String, String>();
}
@Override
public final PatchFunction<CompoundTag, Boolean> getWorldDataPatcher() { return null; }
@Override
public final PatchBiFunction<ListTag, ListTag, Boolean> getBlockStatePatcher() { return null; }
@Override
public final List<String> getWorldDataIDPaths() {
return null;
}
@Override
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() { return this::runLevelDatPatch; }
/**
* Called with the contents of level.dat in {@code root}
* @param root The contents of level.dat
* @param profile The active migration profile
* @return true, if the run did change the contents of root
*/
abstract protected Boolean runLevelDatPatch(CompoundTag root, MigrationProfile profile);
}

View file

@ -1,322 +0,0 @@
package ru.bclib.api.datafixer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.Tag;
import org.jetbrains.annotations.NotNull;
import ru.bclib.BCLib;
import ru.bclib.api.WorldDataAPI;
import ru.bclib.interfaces.PatchBiFunction;
import ru.bclib.interfaces.PatchFunction;
import ru.bclib.util.ModUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class MigrationProfile {
final Set<String> mods;
final Map<String, String> idReplacements;
final List<PatchFunction<CompoundTag, Boolean>> levelPatchers;
final List<PatchBiFunction<ListTag, ListTag, Boolean>> statePatchers;
final List<Patch> worldDataPatchers;
final Map<String, List<String>> worldDataIDPaths;
private final CompoundTag config;
private CompoundTag level;
private File levelBaseDir;
private boolean prePatchChangedLevelDat;
private boolean didRunPrePatch;
private Exception prePatchException;
MigrationProfile(CompoundTag config, boolean applyAll) {
this.config = config;
this.mods = Collections.unmodifiableSet(Patch.getALL()
.stream()
.map(p -> p.modID)
.collect(Collectors.toSet()));
HashMap<String, String> replacements = new HashMap<String, String>();
List<PatchFunction<CompoundTag, Boolean>> levelPatches = new LinkedList<>();
List<Patch> worldDataPatches = new LinkedList<>();
List<PatchBiFunction<ListTag, ListTag, Boolean>> statePatches = new LinkedList<>();
HashMap<String, List<String>> worldDataIDPaths = new HashMap<>();
for (String modID : mods) {
Patch.getALL()
.stream()
.filter(p -> p.modID.equals(modID))
.forEach(patch -> {
List<String> paths = patch.getWorldDataIDPaths();
if (paths!=null) worldDataIDPaths.put(modID, paths);
if (applyAll || currentPatchLevel(modID) < patch.level || patch.alwaysApply) {
replacements.putAll(patch.getIDReplacements());
if (patch.getLevelDatPatcher()!=null)
levelPatches.add(patch.getLevelDatPatcher());
if (patch.getWorldDataPatcher()!=null)
worldDataPatches.add(patch);
if (patch.getBlockStatePatcher()!=null)
statePatches.add(patch.getBlockStatePatcher());
DataFixerAPI.LOGGER.info("Applying " + patch);
}
else {
DataFixerAPI.LOGGER.info("Ignoring " + patch);
}
});
}
this.worldDataIDPaths = Collections.unmodifiableMap(worldDataIDPaths);
this.idReplacements = Collections.unmodifiableMap(replacements);
this.levelPatchers = Collections.unmodifiableList(levelPatches);
this.worldDataPatchers = Collections.unmodifiableList(worldDataPatches);
this.statePatchers = Collections.unmodifiableList(statePatches);
}
/**
* This method is supposed to be used by developers to apply id-patches to custom nbt structures. It is only
* available in Developer-Mode
*
*/
public static void fixCustomFolder(File dir){
if (!BCLib.isDevEnvironment()) return;
MigrationProfile profile = Patch.createMigrationData();
List<File> nbts = getAllNbts(dir, null);
nbts.parallelStream().forEach((file) -> {
DataFixerAPI.LOGGER.info("Loading NBT " + file);
try {
CompoundTag root = NbtIo.readCompressed(file);
boolean[] changed = {false};
if (root.contains("palette")){
ListTag items = root.getList("palette", Tag.TAG_COMPOUND);
items.forEach(inTag -> {
CompoundTag tag = (CompoundTag)inTag;
changed[0] |= profile.replaceStringFromIDs(tag, "Name");
});
}
if (changed[0]){
DataFixerAPI.LOGGER.info("Writing NBT " + file);
NbtIo.writeCompressed(root, file);
}
}
catch (IOException e) {
e.printStackTrace();
}
});
}
private static List<File> getAllNbts(File dir, List<File> list) {
if (list == null) {
list = new ArrayList<>();
}
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
getAllNbts(file, list);
} else if (file.isFile() && file.getName().endsWith(".nbt")) {
list.add(file);
}
}
return list;
}
final public CompoundTag getLevelDat(File levelBaseDir){
if (level == null || this.levelBaseDir==null || !this.levelBaseDir.equals(levelBaseDir)){
runPrePatches(levelBaseDir);
}
return level;
}
final public boolean isLevelDatChanged(){
return prePatchChangedLevelDat;
}
final public File getLevelDatFile(){
return new File(levelBaseDir, "level.dat");
}
final public Exception getPrePatchException(){
return prePatchException;
}
final public void runPrePatches(File levelBaseDir){
if (didRunPrePatch){
BCLib.LOGGER.warning("Already did run PrePatches for " + this.levelBaseDir + ".");
}
BCLib.LOGGER.info("Running Pre Patchers on " + levelBaseDir);
this.levelBaseDir = levelBaseDir;
this.level = null;
this.prePatchException = null;
didRunPrePatch = true;
this.prePatchChangedLevelDat = runPreLevelPatches(getLevelDatFile());
}
private boolean runPreLevelPatches(File levelDat){
try {
level = NbtIo.readCompressed(levelDat);
boolean changed = patchLevelDat(level);
return changed;
}
catch (IOException | PatchDidiFailException e) {
prePatchException = e;
return false;
}
}
final public void markApplied() {
for (String modID : mods) {
DataFixerAPI.LOGGER.info("Updating Patch-Level for '{}' from {} to {}", modID, ModUtil.convertModVersion(currentPatchLevel(modID)), ModUtil.convertModVersion(Patch.maxPatchLevel(modID)));
if (config!=null)
config.putString(modID, Patch.maxPatchVersion(modID));
}
}
public String currentPatchVersion(@NotNull String modID) {
if (config==null || !config.contains(modID)) return "0.0.0";
return config.getString(modID);
}
public int currentPatchLevel(@NotNull String modID) {
return ModUtil.convertModVersion(currentPatchVersion(modID));
}
public boolean hasAnyFixes() {
boolean hasLevelDatPatches;
if (didRunPrePatch != false) {
hasLevelDatPatches = prePatchChangedLevelDat;
} else {
hasLevelDatPatches = levelPatchers.size()>0;
}
return idReplacements.size() > 0 || hasLevelDatPatches || worldDataPatchers.size() > 0;
}
public String replaceStringFromIDs(@NotNull String val) {
final String replace = idReplacements.get(val);
return replace;
}
public boolean replaceStringFromIDs(@NotNull CompoundTag tag, @NotNull String key) {
if (!tag.contains(key)) return false;
final String val = tag.getString(key);
final String replace = idReplacements.get(val);
if (replace != null) {
DataFixerAPI.LOGGER.warning("Replacing ID '{}' with '{}'.", val, replace);
tag.putString(key, replace);
return true;
}
return false;
}
private boolean replaceIDatPath(@NotNull ListTag list, @NotNull String[] parts, int level){
boolean[] changed = {false};
if (level == parts.length-1) {
DataFixerAPI.fixItemArrayWithID(list, changed, this, true);
} else {
list.forEach(inTag -> changed[0] |= replaceIDatPath((CompoundTag)inTag, parts, level+1));
}
return changed[0];
}
private boolean replaceIDatPath(@NotNull CompoundTag tag, @NotNull String[] parts, int level){
boolean changed = false;
for (int i=level; i<parts.length-1; i++) {
final String part = parts[i];
if (tag.contains(part)) {
final byte type = tag.getTagType(part);
if (type == Tag.TAG_LIST) {
ListTag list = tag.getList(part, Tag.TAG_COMPOUND);
return replaceIDatPath(list, parts, i);
} else if (type == Tag.TAG_COMPOUND) {
tag = tag.getCompound(part);
}
} else {
return false;
}
}
if (tag!=null && parts.length>0) {
final String key = parts[parts.length-1];
final byte type = tag.getTagType(key);
if (type == Tag.TAG_LIST) {
final ListTag list = tag.getList(key, Tag.TAG_COMPOUND);
final boolean[] _changed = {false};
if (list.size()==0) {
_changed[0] = DataFixerAPI.fixStringIDList(tag, key, this);
} else {
DataFixerAPI.fixItemArrayWithID(list, _changed, this, true);
}
return _changed[0];
} else if (type == Tag.TAG_STRING) {
return replaceStringFromIDs(tag, key);
} else if (type == Tag.TAG_COMPOUND) {
final CompoundTag cTag = tag.getCompound(key);
boolean[] _changed = {false};
DataFixerAPI.fixID(cTag, _changed, this, true);
return _changed[0];
}
}
return false;
}
public boolean replaceIDatPath(@NotNull CompoundTag root, @NotNull String path){
String[] parts = path.split("\\.");
return replaceIDatPath(root, parts, 0);
}
public boolean patchLevelDat(@NotNull CompoundTag level) throws PatchDidiFailException {
boolean changed = false;
for (PatchFunction<CompoundTag, Boolean> f : levelPatchers) {
changed |= f.apply(level, this);
}
return changed;
}
public void patchWorldData() throws PatchDidiFailException {
for (Patch patch : worldDataPatchers) {
CompoundTag root = WorldDataAPI.getRootTag(patch.modID);
boolean changed = patch.getWorldDataPatcher().apply(root, this);
if (changed) {
WorldDataAPI.saveFile(patch.modID);
}
}
for (Map.Entry<String, List<String>> entry : worldDataIDPaths.entrySet()){
CompoundTag root = WorldDataAPI.getRootTag(entry.getKey());
boolean[] changed = {false};
entry.getValue().forEach(path -> {
changed[0] |= replaceIDatPath(root, path);
});
if (changed[0]){
WorldDataAPI.saveFile(entry.getKey());
}
}
}
public boolean patchBlockState(ListTag palette, ListTag states) throws PatchDidiFailException{
boolean changed = false;
for (PatchBiFunction<ListTag, ListTag, Boolean> f : statePatchers) {
changed |= f.apply(palette, states, this);
}
return changed;
}
}

View file

@ -1,228 +0,0 @@
package ru.bclib.api.datafixer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import org.jetbrains.annotations.NotNull;
import ru.bclib.interfaces.PatchBiFunction;
import ru.bclib.interfaces.PatchFunction;
import ru.bclib.util.ModUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class Patch {
private static final List<Patch> ALL = new ArrayList<>(10);
/**
* The Patch-Level derived from {@link #version}
*/
public final int level;
/**
* The Patch-Version string
*/
public final String version;
/**
* The Mod-ID that registered this Patch
*/
@NotNull
public final String modID;
/**
* This Mod is tested for each level start
*/
public final boolean alwaysApply;
static List<Patch> getALL() {
return ALL;
}
/**
* Returns the highest Patch-Version that is available for the given mod. If no patches were
* registerd for the mod, this will return 0.0.0
*
* @param modID The ID of the mod you want to query
* @return The highest Patch-Version that was found
*/
public static String maxPatchVersion(@NotNull String modID) {
return ALL.stream().filter(p -> p.modID.equals(modID)).map(p -> p.version).reduce((p, c) -> c).orElse("0.0.0");
}
/**
* Returns the highest patch-level that is available for the given mod. If no patches were
* registerd for the mod, this will return 0
*
* @param modID The ID of the mod you want to query
* @return The highest Patch-Level that was found
*/
public static int maxPatchLevel(@NotNull String modID) {
return ALL.stream().filter(p -> p.modID.equals(modID)).mapToInt(p -> p.level).max().orElse(0);
}
/**
* Called by inheriting classes.
* <p>
* Performs some sanity checks on the values and might throw a #RuntimeException if any
* inconsistencies are found.
*
* @param modID The ID of the Mod you want to register a patch for. This should be your
* ModID only. The ModID can not be {@code null} or an empty String.
* @param version The mod-version that introduces the patch. This needs Semantic-Version String
* like x.x.x. Developers are responsible for registering their patches in the correct
* order (with increasing versions). You are not allowed to register a new
* Patch with a version lower or equal than
* {@link Patch#maxPatchVersion(String)}
*/
protected Patch(@NotNull String modID, String version) {
this(modID, version, false);
}
/**
* Internal Constructor used to create patches that can allways run (no matter what patchlevel a level has)
* @param modID The ID of the Mod
* @param version The mod-version that introduces the patch. When {@Code runAllways} is set, this version will
* determine the patchlevel that is written to the level
* @param alwaysApply When true, this patch is always active, no matter the patchlevel of the world.
* This should be used sparingly and just for patches that apply to level.dat (as they only take
* effect when changes are detected). Use {@link ForcedLevelPatch} to instatiate.
*/
Patch(@NotNull String modID, String version, boolean alwaysApply) {
//Patchlevels need to be unique and registered in ascending order
if (modID == null || modID.isEmpty()) {
throw new RuntimeException("[INTERNAL ERROR] Patches need a valid modID!");
}
if (version == null || version.isEmpty()) {
throw new RuntimeException("Invalid Mod-Version");
}
this.version = version;
this.alwaysApply = alwaysApply;
this.level = ModUtil.convertModVersion(version);
if (!ALL.stream().filter(p -> p.modID.equals(modID)).noneMatch(p -> p.level >= this.level) || this.level <= 0) {
throw new RuntimeException("[INTERNAL ERROR] Patch-levels need to be created in ascending order beginning with 1.");
}
this.modID = modID;
}
@Override
public String toString() {
return "Patch{" + modID + ':' +version + ':' + level + '}';
}
/**
* Return block data fixes. Fixes will be applied on world load if current patch-level for
* the linked mod is lower than the {@link #level}.
* <p>
* The default implementation of this method returns an empty map.
*
* @return The returned Map should contain the replacements. All occurences of the
* {@code KeySet} are replaced with the associated value.
*/
public Map<String, String> getIDReplacements() {
return new HashMap<String, String>();
}
/**
* Return a {@link PatchFunction} that is called with the content of <i>level.dat</i>.
* <p>
* The function needs to return {@code true}, if changes were made to the data.
* If an error occurs, the method should throw a {@link PatchDidiFailException}
*
* The default implementation of this method returns null.
*
* @return {@code true} if changes were applied and we need to save the data
*/
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() { return null; }
/**
* Return a {@link PatchFunction} that is called with the content from the
* {@link ru.bclib.api.WorldDataAPI} 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<CompoundTag, Boolean> getWorldDataPatcher() { return null; }
/**
* Return a {@link PatchBiFunction} that is called with pallette and blockstate of
* each chunk in every region. This method is called AFTER all ID replacements
* from {@link #getIDReplacements()} were applied to the pallete.
*
* 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<ListTag, ListTag, Boolean> getBlockStatePatcher() { return null; }
/**
* Generates ready to use data for all currently registered patches. The list of
* patches is selected by the current patch-level of the world.
* <p>
* A {@link #Patch} with a given {@link #level} is only included if the patch-level of the
* world is less
* @param config The current patch-level configuration*
* @return a new {@link MigrationProfile} Object.
*/
static MigrationProfile createMigrationData(CompoundTag config) {
return new MigrationProfile(config, false);
}
/**
* This method is supposed to be used by developers to apply id-patches to custom nbt structures. It is only
* available in Developer-Mode
*
*/
static MigrationProfile createMigrationData() {
return new MigrationProfile(null, true);
}
/**
* Returns a list of paths where your mod stores IDs in your {@link ru.bclib.api.WorldDataAPI}-File.
* <p>
* {@link DataFixerAPI} will use information from the latest patch that returns a non-null-result. This list is used
* to automatically fix changed IDs from all active patches (see {@link Patch#getIDReplacements()}
* <p>
* The end of the path can either be a {@link net.minecraft.nbt.StringTag}, a {@link net.minecraft.nbt.ListTag} or
* a {@link CompoundTag}. If the Path contains a non-leaf {@link net.minecraft.nbt.ListTag}, all members of that
* list will be processed. For example:
* <pre>
* - global +
* | - key (String)
* | - items (List) +
* | - { id (String) }
* | - { id (String) }
* </pre>
* The path <b>global.items.id</b> will fix all <i>id</i>-entries in the <i>items</i>-list, while the path
* <b>global.key</b> will only fix the <i>key</i>-entry.
* <p>
* if the leaf-entry (= the last part of the path, which would be <i>items</i> in <b>global.items</b>) is a
* {@link CompoundTag}, the system will fix any <i>id</i> entry. If the {@link CompoundTag} contains an <i>item</i>
* or <i>tag.BlockEntityTag</i> entry, the system will recursivley continue with those. If an <i>items</i>
* or <i>inventory</i>-{@link net.minecraft.nbt.ListTag} was found, the system will continue recursivley with
* every item of that list.
* <p>
* if the leaf-entry is a {@link net.minecraft.nbt.ListTag}, it is handle the same as a child <i>items</i> entry
* of a {@link CompoundTag}.
*
* @return {@code null} if nothing changes or a list of Paths in your {@link ru.bclib.api.WorldDataAPI}-File.
* Paths are dot-seperated (see {@link ru.bclib.api.WorldDataAPI#getCompoundTag(String, String)}).
*/
public List<String> getWorldDataIDPaths() {
return null;
}
}

View file

@ -1,10 +0,0 @@
package ru.bclib.api.datafixer;
public class PatchDidiFailException extends Exception {
public PatchDidiFailException(){
super();
}
public PatchDidiFailException(Exception e){
super(e);
}
}

View file

@ -1,308 +0,0 @@
package ru.bclib.api.spawning;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.fabricmc.fabric.mixin.object.builder.SpawnRestrictionAccessor;
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 ru.bclib.interfaces.SpawnRule;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public class SpawnRuleBuilder<M extends Mob> {
private static final Map<String, SpawnRuleEntry> RULES_CACHE = Maps.newHashMap();
private static final SpawnRuleBuilder INSTANCE = new SpawnRuleBuilder();
private List<SpawnRuleEntry> rules = Lists.newArrayList();
private SpawnRuleEntry entryInstance;
private EntityType<M> entityType;
private SpawnRuleBuilder() {}
/**
* Starts new rule building process.
* @return prepared {@link SpawnRuleBuilder} instance.
*/
public static SpawnRuleBuilder start(EntityType<? extends Mob> entityType) {
INSTANCE.entityType = entityType;
INSTANCE.rules.clear();
return INSTANCE;
}
/**
* 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<? extends Entity> 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<SpawnRuleEntry> rulesCopy = Lists.newArrayList(this.rules);
Collections.sort(rulesCopy);
SpawnPredicate<M> 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<SpawnRuleEntry> supplier) {
SpawnRuleEntry entry = RULES_CACHE.get(name);
if (entry == null) {
entry = supplier.get();
RULES_CACHE.put(name, entry);
}
return entry;
}
}

View file

@ -1,30 +0,0 @@
package ru.bclib.api.spawning;
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 org.jetbrains.annotations.NotNull;
import ru.bclib.interfaces.SpawnRule;
import java.util.Random;
public class SpawnRuleEntry<M extends Mob> implements Comparable<SpawnRuleEntry> {
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<M> 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);
}
}

View file

@ -26,26 +26,25 @@ import ru.bclib.registry.BaseBlockEntities;
public class BaseBarrelBlockEntity extends RandomizableContainerBlockEntity { public class BaseBarrelBlockEntity extends RandomizableContainerBlockEntity {
private NonNullList<ItemStack> inventory; private NonNullList<ItemStack> inventory;
private int viewerCount; private int viewerCount;
private BaseBarrelBlockEntity(BlockEntityType<?> type, BlockPos blockPos, BlockState blockState) { private BaseBarrelBlockEntity(BlockEntityType<?> type, BlockPos blockPos, BlockState blockState) {
super(type, blockPos, blockState); super(type, blockPos, blockState);
this.inventory = NonNullList.withSize(27, ItemStack.EMPTY); this.inventory = NonNullList.withSize(27, ItemStack.EMPTY);
} }
public BaseBarrelBlockEntity(BlockPos blockPos, BlockState blockState) { public BaseBarrelBlockEntity(BlockPos blockPos, BlockState blockState) {
this(BaseBlockEntities.BARREL, blockPos, blockState); this(BaseBlockEntities.BARREL, blockPos, blockState);
} }
@Override public CompoundTag save(CompoundTag tag) {
public void saveAdditional(CompoundTag tag) { super.save(tag);
super.saveAdditional(tag);
if (!this.trySaveLootTable(tag)) { if (!this.trySaveLootTable(tag)) {
ContainerHelper.saveAllItems(tag, this.inventory); ContainerHelper.saveAllItems(tag, this.inventory);
} }
//return tag; return tag;
} }
public void load(CompoundTag tag) { public void load(CompoundTag tag) {
super.load(tag); super.load(tag);
this.inventory = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); this.inventory = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
@ -53,57 +52,56 @@ public class BaseBarrelBlockEntity extends RandomizableContainerBlockEntity {
ContainerHelper.loadAllItems(tag, this.inventory); ContainerHelper.loadAllItems(tag, this.inventory);
} }
} }
public int getContainerSize() { public int getContainerSize() {
return 27; return 27;
} }
protected NonNullList<ItemStack> getItems() { protected NonNullList<ItemStack> getItems() {
return this.inventory; return this.inventory;
} }
protected void setItems(NonNullList<ItemStack> list) { protected void setItems(NonNullList<ItemStack> list) {
this.inventory = list; this.inventory = list;
} }
protected Component getDefaultName() { protected Component getDefaultName() {
return new TranslatableComponent("container.barrel"); return new TranslatableComponent("container.barrel");
} }
protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) {
return ChestMenu.threeRows(syncId, playerInventory, this); return ChestMenu.threeRows(syncId, playerInventory, this);
} }
public void startOpen(Player player) { public void startOpen(Player player) {
if (!player.isSpectator()) { if (!player.isSpectator()) {
if (viewerCount < 0) { if (viewerCount < 0) {
viewerCount = 0; viewerCount = 0;
} }
++viewerCount; ++viewerCount;
BlockState blockState = this.getBlockState(); BlockState blockState = this.getBlockState();
if (!blockState.getValue(BarrelBlock.OPEN)) { if (!blockState.getValue(BarrelBlock.OPEN)) {
playSound(blockState, SoundEvents.BARREL_OPEN); playSound(blockState, SoundEvents.BARREL_OPEN);
setOpen(blockState, true); setOpen(blockState, true);
} }
if (level != null) { if (level != null) {
scheduleUpdate(); scheduleUpdate();
} }
} }
} }
private void scheduleUpdate() { private void scheduleUpdate() {
level.scheduleTick(getBlockPos(), getBlockState().getBlock(), 5); level.getBlockTicks().scheduleTick(getBlockPos(), getBlockState().getBlock(), 5);
} }
public void tick() { public void tick() {
if (level != null) { if (level != null) {
viewerCount = ChestBlockEntity.getOpenCount(level, worldPosition); viewerCount = ChestBlockEntity.getOpenCount(level, worldPosition);
if (viewerCount > 0) { if (viewerCount > 0) {
scheduleUpdate(); scheduleUpdate();
} } else {
else {
BlockState blockState = getBlockState(); BlockState blockState = getBlockState();
if (!(blockState.getBlock() instanceof BaseBarrelBlock)) { if (!(blockState.getBlock() instanceof BaseBarrelBlock)) {
setRemoved(); setRemoved();
@ -116,35 +114,27 @@ public class BaseBarrelBlockEntity extends RandomizableContainerBlockEntity {
} }
} }
} }
public void stopOpen(Player player) { public void stopOpen(Player player) {
if (!player.isSpectator()) { if (!player.isSpectator()) {
--this.viewerCount; --this.viewerCount;
} }
} }
private void setOpen(BlockState state, boolean open) { private void setOpen(BlockState state, boolean open) {
if (level != null) { if (level != null) {
level.setBlock(this.getBlockPos(), state.setValue(BarrelBlock.OPEN, open), 3); level.setBlock(this.getBlockPos(), state.setValue(BarrelBlock.OPEN, open), 3);
} }
} }
private void playSound(BlockState blockState, SoundEvent soundEvent) { private void playSound(BlockState blockState, SoundEvent soundEvent) {
if (level != null) { if (level != null) {
Vec3i vec3i = blockState.getValue(BarrelBlock.FACING).getNormal(); Vec3i vec3i = blockState.getValue(BarrelBlock.FACING).getNormal();
double d = (double) this.worldPosition.getX() + 0.5D + (double) vec3i.getX() / 2.0D; 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 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; double f = (double) this.worldPosition.getZ() + 0.5D + (double) vec3i.getZ() / 2.0D;
level.playSound( level.playSound(null, d, e, f, soundEvent, SoundSource.BLOCKS, 0.5F,
null, this.level.random.nextFloat() * 0.1F + 0.9F);
d,
e,
f,
soundEvent,
SoundSource.BLOCKS,
0.5F,
this.level.random.nextFloat() * 0.1F + 0.9F
);
} }
} }
} }

View file

@ -15,11 +15,11 @@ public class BaseFurnaceBlockEntity extends AbstractFurnaceBlockEntity {
public BaseFurnaceBlockEntity(BlockPos blockPos, BlockState blockState) { public BaseFurnaceBlockEntity(BlockPos blockPos, BlockState blockState) {
super(BaseBlockEntities.FURNACE, blockPos, blockState, RecipeType.SMELTING); super(BaseBlockEntities.FURNACE, blockPos, blockState, RecipeType.SMELTING);
} }
protected Component getDefaultName() { protected Component getDefaultName() {
return new TranslatableComponent("container.furnace"); return new TranslatableComponent("container.furnace");
} }
protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) {
return new FurnaceMenu(syncId, playerInventory, this, this.dataAccess); return new FurnaceMenu(syncId, playerInventory, this, this.dataAccess);
} }

View file

@ -10,7 +10,7 @@ public class BaseSignBlockEntity extends SignBlockEntity {
public BaseSignBlockEntity(BlockPos blockPos, BlockState blockState) { public BaseSignBlockEntity(BlockPos blockPos, BlockState blockState) {
super(blockPos, blockState); super(blockPos, blockState);
} }
@Override @Override
public BlockEntityType<?> getType() { public BlockEntityType<?> getType() {
return BaseBlockEntities.SIGN; return BaseBlockEntities.SIGN;

View file

@ -1,6 +1,11 @@
package ru.bclib.blockentities; package ru.bclib.blockentities;
import java.util.Collections;
import java.util.Set;
import java.util.function.Supplier;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
@ -8,36 +13,33 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Set;
public class DynamicBlockEntityType<T extends BlockEntity> extends BlockEntityType<T> { public class DynamicBlockEntityType<T extends BlockEntity> extends BlockEntityType<T> {
private final Set<Block> validBlocks = Sets.newHashSet(); private final Set<Block> validBlocks = Sets.newHashSet();
private final BlockEntitySupplier<? extends T> factory; private final BlockEntitySupplier<? extends T> factory;
public DynamicBlockEntityType(BlockEntitySupplier<? extends T> supplier) { public DynamicBlockEntityType(BlockEntitySupplier<? extends T> supplier) {
super(null, Collections.emptySet(), null); super(null, Collections.emptySet(), null);
this.factory = supplier; this.factory = supplier;
} }
@Override @Override
@Nullable @Nullable public T create(BlockPos blockPos, BlockState blockState) {
public T create(BlockPos blockPos, BlockState blockState) {
return factory.create(blockPos, blockState); return factory.create(blockPos, blockState);
} }
@Override @Override
public boolean isValid(BlockState blockState) { public boolean isValid(BlockState blockState) {
return validBlocks.contains(blockState.getBlock()); return validBlocks.contains(blockState.getBlock());
} }
public void registerBlock(Block block) { public void registerBlock(Block block) {
validBlocks.add(block); validBlocks.add(block);
} }
@FunctionalInterface @FunctionalInterface
public interface BlockEntitySupplier<T extends BlockEntity> { public
interface BlockEntitySupplier<T extends BlockEntity> {
T create(BlockPos blockPos, BlockState blockState); T create(BlockPos blockPos, BlockState blockState);
} }
} }

View file

@ -1,71 +1,75 @@
package ru.bclib.blocks; 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.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; 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.AnvilBlock;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks; 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.BlockState;
import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty; import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.MaterialColor; import net.minecraft.world.level.material.MaterialColor;
import net.minecraft.world.level.storage.loot.LootContext; 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.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import ru.bclib.interfaces.CustomItemProvider;
import ru.bclib.items.BaseAnvilItem; import ru.bclib.items.BaseAnvilItem;
import java.util.Collections; public abstract class BaseAnvilBlock extends AnvilBlock implements BlockModelProvider {
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
public abstract class BaseAnvilBlock extends AnvilBlock implements BlockModelProvider, CustomItemProvider {
public static final IntegerProperty DESTRUCTION = BlockProperties.DESTRUCTION; public static final IntegerProperty DESTRUCTION = BlockProperties.DESTRUCTION;
public IntegerProperty durability;
public BaseAnvilBlock(MaterialColor color) { public BaseAnvilBlock(MaterialColor color) {
this(FabricBlockSettings.copyOf(Blocks.ANVIL).color(color)); super(FabricBlockSettings.copyOf(Blocks.ANVIL).mapColor(color));
}
public BaseAnvilBlock(BlockBehaviour.Properties properties) {
super(properties);
} }
@Override @Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) { protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder); super.createBlockStateDefinition(builder);
if (getMaxDurability() != 3) { builder.add(DESTRUCTION);
durability = IntegerProperty.create("durability", 0, getMaxDurability());
}
else {
durability = BlockProperties.DEFAULT_ANVIL_DURABILITY;
}
builder.add(DESTRUCTION, durability);
} }
@Override
@SuppressWarnings("deprecation")
public List<ItemStack> 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 @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
return getBlockModel(blockId, defaultBlockState()); return getBlockModel(blockId, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
@ -78,7 +82,7 @@ public abstract class BaseAnvilBlock extends AnvilBlock implements BlockModelPro
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_ANVIL, textures); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_ANVIL, textures);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
@ -89,47 +93,4 @@ public abstract class BaseAnvilBlock extends AnvilBlock implements BlockModelPro
registerBlockModel(stateId, modelLocation, blockState, modelCache); registerBlockModel(stateId, modelLocation, blockState, modelCache);
return ModelsHelper.createFacingModel(modelLocation, blockState.getValue(FACING), false, false); 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<ItemStack> 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;
}
} }

View file

@ -45,14 +45,14 @@ public abstract class BaseAttachedBlock extends BaseBlockNotFull {
} }
return null; return null;
} }
@Override @Override
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
Direction direction = state.getValue(FACING); Direction direction = state.getValue(FACING);
BlockPos blockPos = pos.relative(direction.getOpposite()); BlockPos blockPos = pos.relative(direction.getOpposite());
return canSupportCenter(world, blockPos, direction) || world.getBlockState(blockPos).is(BlockTags.LEAVES); return canSupportCenter(world, blockPos, direction) || world.getBlockState(blockPos).is(BlockTags.LEAVES);
} }
@Override @Override
public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
if (!canSurvive(state, world, pos)) { if (!canSurvive(state, world, pos)) {
@ -62,13 +62,13 @@ public abstract class BaseAttachedBlock extends BaseBlockNotFull {
return state; return state;
} }
} }
@Override @Override
public BlockState rotate(BlockState state, Rotation rotation) { public BlockState rotate(BlockState state, Rotation rotation) {
return BlocksHelper.rotateHorizontal(state, rotation, FACING); return BlocksHelper.rotateHorizontal(state, rotation, FACING);
} }
@Override @Override
public BlockState mirror(BlockState state, Mirror mirror) { public BlockState mirror(BlockState state, Mirror mirror) {
return BlocksHelper.mirrorHorizontal(state, mirror, FACING); return BlocksHelper.mirrorHorizontal(state, mirror, FACING);

View file

@ -1,23 +1,23 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import java.util.Optional;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import java.util.Optional;
public class BaseBarkBlock extends BaseRotatedPillarBlock { public class BaseBarkBlock extends BaseRotatedPillarBlock {
public BaseBarkBlock(Properties settings) { public BaseBarkBlock(Properties settings) {
super(settings); super(settings);
} }
@Override @Override
protected Optional<String> createBlockPattern(ResourceLocation blockId) { protected Optional<String> createBlockPattern(ResourceLocation blockId) {
blockId = Registry.BLOCK.getKey(this); blockId = Registry.BLOCK.getKey(this);
return PatternsHelper.createJson(BasePatterns.BLOCK_BASE, replacePath(blockId)); return PatternsHelper.createJson(BasePatterns.BLOCK_BASE, replacePath(blockId));
} }
private ResourceLocation replacePath(ResourceLocation blockId) { private ResourceLocation replacePath(ResourceLocation blockId) {
String newPath = blockId.getPath().replace("_bark", "_log_side"); String newPath = blockId.getPath().replace("_bark", "_log_side");
return new ResourceLocation(blockId.getNamespace(), newPath); return new ResourceLocation(blockId.getNamespace(), newPath);

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -17,42 +24,32 @@ import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.monster.piglin.PiglinAi; import net.minecraft.world.entity.monster.piglin.PiglinAi;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BarrelBlock; import net.minecraft.world.level.block.BarrelBlock;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape; import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity; 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.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.Nullable;
import ru.bclib.blockentities.BaseBarrelBlockEntity; import ru.bclib.blockentities.BaseBarrelBlockEntity;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import ru.bclib.registry.BaseBlockEntities; import ru.bclib.registry.BaseBlockEntities;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
public class BaseBarrelBlock extends BarrelBlock implements BlockModelProvider { public class BaseBarrelBlock extends BarrelBlock implements BlockModelProvider {
public BaseBarrelBlock(Block source) { public BaseBarrelBlock(Block source) {
this(FabricBlockSettings.copyOf(source).noOcclusion()); super(FabricBlockSettings.copyOf(source).noOcclusion());
} }
public BaseBarrelBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override @Override
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return BaseBlockEntities.BARREL.create(blockPos, blockState); return BaseBlockEntities.BARREL.create(blockPos, blockState);
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
@ -60,24 +57,24 @@ public class BaseBarrelBlock extends BarrelBlock implements BlockModelProvider {
drop.add(new ItemStack(this.asItem())); drop.add(new ItemStack(this.asItem()));
return drop; return drop;
} }
@Override @Override
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand,
BlockHitResult hit) {
if (world.isClientSide) { if (world.isClientSide) {
return InteractionResult.SUCCESS; return InteractionResult.SUCCESS;
} } else {
else {
BlockEntity blockEntity = world.getBlockEntity(pos); BlockEntity blockEntity = world.getBlockEntity(pos);
if (blockEntity instanceof BaseBarrelBlockEntity) { if (blockEntity instanceof BaseBarrelBlockEntity) {
player.openMenu((BaseBarrelBlockEntity) blockEntity); player.openMenu((BaseBarrelBlockEntity) blockEntity);
player.awardStat(Stats.OPEN_BARREL); player.awardStat(Stats.OPEN_BARREL);
PiglinAi.angerNearbyPiglins(player, true); PiglinAi.angerNearbyPiglins(player, true);
} }
return InteractionResult.CONSUME; return InteractionResult.CONSUME;
} }
} }
@Override @Override
public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) {
BlockEntity blockEntity = world.getBlockEntity(pos); BlockEntity blockEntity = world.getBlockEntity(pos);
@ -85,14 +82,15 @@ public class BaseBarrelBlock extends BarrelBlock implements BlockModelProvider {
((BaseBarrelBlockEntity) blockEntity).tick(); ((BaseBarrelBlockEntity) blockEntity).tick();
} }
} }
@Override @Override
public RenderShape getRenderShape(BlockState state) { public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL; return RenderShape.MODEL;
} }
@Override @Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) { public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer,
ItemStack itemStack) {
if (itemStack.hasCustomHoverName()) { if (itemStack.hasCustomHoverName()) {
BlockEntity blockEntity = world.getBlockEntity(pos); BlockEntity blockEntity = world.getBlockEntity(pos);
if (blockEntity instanceof BaseBarrelBlockEntity) { if (blockEntity instanceof BaseBarrelBlockEntity) {
@ -100,52 +98,41 @@ public class BaseBarrelBlock extends BarrelBlock implements BlockModelProvider {
} }
} }
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
return getBlockModel(blockId, defaultBlockState()); return getBlockModel(blockId, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
Optional<String> pattern; Optional<String> pattern;
if (blockState.getValue(OPEN)) { if (blockState.getValue(OPEN)) {
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARREL_OPEN, blockId); pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARREL_OPEN, blockId);
} } else {
else {
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BOTTOM_TOP, blockId); pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BOTTOM_TOP, blockId);
} }
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
String open = blockState.getValue(OPEN) ? "_open" : ""; String open = blockState.getValue(OPEN) ? "_open" : "";
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + open); ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(),
"block/" + stateId.getPath() + open);
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
Direction facing = blockState.getValue(FACING); Direction facing = blockState.getValue(FACING);
BlockModelRotation rotation = BlockModelRotation.X0_Y0; BlockModelRotation rotation = BlockModelRotation.X0_Y0;
switch (facing) { switch (facing) {
case NORTH: case NORTH: rotation = BlockModelRotation.X90_Y0; break;
rotation = BlockModelRotation.X90_Y0; case EAST: rotation = BlockModelRotation.X90_Y90; break;
break; case SOUTH: rotation = BlockModelRotation.X90_Y180; break;
case EAST: case WEST: rotation = BlockModelRotation.X90_Y270; break;
rotation = BlockModelRotation.X90_Y90; case DOWN: rotation = BlockModelRotation.X180_Y0; break;
break; default: 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); return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false);
} }

View file

@ -1,73 +1,29 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import java.util.Collections;
import java.util.List;
import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState; 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.LootContext;
import ru.bclib.interfaces.BlockModelProvider; import ru.bclib.client.models.BlockModelProvider;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/**
* Base class for a default Block.
* <p>
* This Block-Type will:
* <ul>
* <li>Drop itself</li>
* <li>Automatically create an Item-Model from the Block-Model</li>
* </ul>
*/
public class BaseBlock extends Block implements BlockModelProvider { 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) { public BaseBlock(Properties settings) {
super(settings); super(settings);
} }
/**
* {@inheritDoc}
* <p>
* This implementation will drop the Block itself
*/
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
/**
* {@inheritDoc}
* <p>
* This implementation will load the Block-Model and return it as the Item-Model
*/
@Override @Override
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
return getBlockModel(blockId, defaultBlockState()); return getBlockModel(blockId, defaultBlockState());
} }
/**
* This method is used internally.
* <p>
* It is called from Block-Contructors, to allow the augmentation of the blocks
* preset properties.
* <p>
* 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<FabricBlockSettings> customizeProperties, FabricBlockSettings settings) {
customizeProperties.accept(settings);
return settings;
}
} }

View file

@ -6,18 +6,19 @@ import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
public class BaseBlockNotFull extends BaseBlock { public class BaseBlockNotFull extends BaseBlock {
public BaseBlockNotFull(Properties settings) { public BaseBlockNotFull(Properties settings) {
super(settings); super(settings);
} }
public boolean canSuffocate(BlockState state, BlockGetter view, BlockPos pos) { public boolean canSuffocate(BlockState state, BlockGetter view, BlockPos pos) {
return false; return false;
} }
public boolean isSimpleFullBlock(BlockState state, BlockGetter view, BlockPos pos) { public boolean isSimpleFullBlock(BlockState state, BlockGetter view, BlockPos pos) {
return false; return false;
} }
public boolean allowsSpawning(BlockState state, BlockGetter view, BlockPos pos, EntityType<?> type) { public boolean allowsSpawning(BlockState state, BlockGetter view, BlockPos pos, EntityType<?> type) {
return false; return false;
} }

View file

@ -1,25 +1,26 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import java.util.Collections;
import java.util.List;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.item.ItemStack; 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.BaseEntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import java.util.Collections;
import java.util.List;
public class BaseBlockWithEntity extends BaseEntityBlock { public class BaseBlockWithEntity extends BaseEntityBlock {
public BaseBlockWithEntity(Properties settings) { public BaseBlockWithEntity(Properties settings) {
super(settings); super(settings);
} }
@Override @Override
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return null; return null;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {

View file

@ -1,5 +1,11 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -10,26 +16,16 @@ import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.block.Block; 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.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public class BaseBookshelfBlock extends BaseBlock { public class BaseBookshelfBlock extends BaseBlock {
public BaseBookshelfBlock(Block source) { public BaseBookshelfBlock(Block source) {
this(FabricBlockSettings.copyOf(source)); super(FabricBlockSettings.copyOf(source));
}
public BaseBookshelfBlock(BlockBehaviour.Properties properties) {
super(properties);
} }
@Override @Override
@ -43,14 +39,14 @@ public class BaseBookshelfBlock extends BaseBlock {
} }
return Collections.singletonList(new ItemStack(Items.BOOK, 3)); return Collections.singletonList(new ItemStack(Items.BOOK, 3));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BOOKSHELF, replacePath(blockId)); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BOOKSHELF, replacePath(blockId));
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
private ResourceLocation replacePath(ResourceLocation blockId) { private ResourceLocation replacePath(ResourceLocation blockId) {
String newPath = blockId.getPath().replace("_bookshelf", ""); String newPath = blockId.getPath().replace("_bookshelf", "");
return new ResourceLocation(blockId.getNamespace(), newPath); return new ResourceLocation(blockId.getNamespace(), newPath);

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.block.model.BlockModel;
@ -13,31 +20,26 @@ import net.minecraft.world.level.block.ButtonBlock;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.AttachFace; import net.minecraft.world.level.block.state.properties.AttachFace;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public abstract class BaseButtonBlock extends ButtonBlock implements BlockModelProvider { public abstract class BaseButtonBlock extends ButtonBlock implements BlockModelProvider {
private final Block parent; private final Block parent;
protected BaseButtonBlock(Block parent, Properties properties, boolean sensitive) { protected BaseButtonBlock(Block parent, Properties properties, boolean sensitive) {
super(sensitive, properties); super(sensitive, properties);
this.parent = parent; this.parent = parent;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
@ -45,56 +47,38 @@ public abstract class BaseButtonBlock extends ButtonBlock implements BlockModelP
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_BUTTON, parentId); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_BUTTON, parentId);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
ResourceLocation parentId = Registry.BLOCK.getKey(parent); ResourceLocation parentId = Registry.BLOCK.getKey(parent);
Optional<String> pattern = blockState.getValue(POWERED) ? PatternsHelper.createJson( Optional<String> pattern = blockState.getValue(POWERED) ?
BasePatterns.BLOCK_BUTTON_PRESSED, PatternsHelper.createJson(BasePatterns.BLOCK_BUTTON_PRESSED, parentId) :
parentId PatternsHelper.createJson(BasePatterns.BLOCK_BUTTON, parentId);
) : PatternsHelper.createJson(BasePatterns.BLOCK_BUTTON, parentId);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
String powered = blockState.getValue(POWERED) ? "_powered" : ""; String powered = blockState.getValue(POWERED) ? "_powered" : "";
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + powered); ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(),
"block/" + stateId.getPath() + powered);
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
AttachFace face = blockState.getValue(FACE); AttachFace face = blockState.getValue(FACE);
boolean isCeiling = face == AttachFace.CEILING; boolean isCeiling = face == AttachFace.CEILING;
int x = 0, y = 0; int x = 0, y = 0;
switch (face) { switch (face) {
case CEILING: case CEILING: x = 180; break;
x = 180; case WALL: x = 90; break;
break; default: break;
case WALL:
x = 90;
break;
default:
break;
} }
switch (blockState.getValue(FACING)) { switch (blockState.getValue(FACING)) {
case NORTH: case NORTH: if (isCeiling) { y = 180; } break;
if (isCeiling) { case EAST: y = isCeiling ? 270 : 90; break;
y = 180; case SOUTH: if(!isCeiling) { y = 180; } break;
} case WEST: y = isCeiling ? 90 : 270; break;
break; default: 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); BlockModelRotation rotation = BlockModelRotation.by(x, y);
return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), face == AttachFace.WALL); return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), face == AttachFace.WALL);

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -10,60 +17,50 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.ChainBlock; 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.block.state.BlockState;
import net.minecraft.world.level.material.MaterialColor; import net.minecraft.world.level.material.MaterialColor;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.client.render.BCLRenderLayer; import ru.bclib.client.render.BCLRenderLayer;
import ru.bclib.interfaces.BlockModelProvider; import ru.bclib.interfaces.IRenderTyped;
import ru.bclib.interfaces.RenderLayerProvider;
import java.util.Collections; public class BaseChainBlock extends ChainBlock implements BlockModelProvider, IRenderTyped {
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseChainBlock extends ChainBlock implements BlockModelProvider, RenderLayerProvider {
public BaseChainBlock(MaterialColor color) { public BaseChainBlock(MaterialColor color) {
this(FabricBlockSettings.copyOf(Blocks.CHAIN).color(color)); super(FabricBlockSettings.copyOf(Blocks.CHAIN).mapColor(color));
} }
public BaseChainBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
return ModelsHelper.createItemModel(blockId); return ModelsHelper.createItemModel(blockId);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CHAIN, blockId); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CHAIN, blockId);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
Direction.Axis axis = blockState.getValue(AXIS); Direction.Axis axis = blockState.getValue(AXIS);
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(),
"block/" + stateId.getPath());
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
return ModelsHelper.createRotatedModel(modelId, axis); return ModelsHelper.createRotatedModel(modelId, axis);
} }
@Override @Override
public BCLRenderLayer getRenderLayer() { public BCLRenderLayer getRenderLayer() {
return BCLRenderLayer.CUTOUT; return BCLRenderLayer.CUTOUT;

View file

@ -1,28 +1,30 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; 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.Block;
import net.minecraft.world.level.block.ChestBlock; import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import ru.bclib.registry.BaseBlockEntities; import ru.bclib.registry.BaseBlockEntities;
import java.util.List;
import java.util.Optional;
public class BaseChestBlock extends ChestBlock implements BlockModelProvider { public class BaseChestBlock extends ChestBlock implements BlockModelProvider {
private final Block parent; private final Block parent;
@ -35,22 +37,23 @@ public class BaseChestBlock extends ChestBlock implements BlockModelProvider {
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return BaseBlockEntities.CHEST.create(blockPos, blockState); return BaseBlockEntities.CHEST.create(blockPos, blockState);
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder)
{
List<ItemStack> drop = super.getDrops(state, builder); List<ItemStack> drop = super.getDrops(state, builder);
drop.add(new ItemStack(this.asItem())); drop.add(new ItemStack(this.asItem()));
return drop; return drop;
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_CHEST, blockId); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_CHEST, blockId);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -11,63 +18,56 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ComposterBlock; import net.minecraft.world.level.block.ComposterBlock;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.ModelsHelper.MultiPartBuilder; import ru.bclib.client.models.ModelsHelper.MultiPartBuilder;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseComposterBlock extends ComposterBlock implements BlockModelProvider { public class BaseComposterBlock extends ComposterBlock implements BlockModelProvider {
public BaseComposterBlock(Block source) { public BaseComposterBlock(Block source) {
super(FabricBlockSettings.copyOf(source)); super(FabricBlockSettings.copyOf(source));
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this.asItem())); return Collections.singletonList(new ItemStack(this.asItem()));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return getBlockModel(resourceLocation, defaultBlockState()); return getBlockModel(resourceLocation, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_COMPOSTER, blockId); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_COMPOSTER, blockId);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath());
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
MultiPartBuilder builder = MultiPartBuilder.create(stateDefinition); MultiPartBuilder builder = MultiPartBuilder.create(stateDefinition);
LEVEL.getPossibleValues().forEach(level -> { LEVEL.getPossibleValues().forEach(level -> {
if (level > 0) { if (level > 0) {
ResourceLocation contentId; ResourceLocation contentId;
if (level > 7) { if (level > 7) {
contentId = new ResourceLocation("block/composter_contents_ready"); contentId = new ResourceLocation("block/composter_contents_ready");
} } else {
else {
contentId = new ResourceLocation("block/composter_contents" + level); contentId = new ResourceLocation("block/composter_contents" + level);
} }
builder.part(contentId).setCondition(state -> state.getValue(LEVEL).equals(level)).add(); builder.part(contentId).setCondition(state -> state.getValue(LEVEL).equals(level)).add();
} }
}); });
builder.part(modelId).add(); builder.part(modelId).add();
return builder.build(); return builder.build();
} }
} }

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -8,48 +15,36 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.CraftingTableBlock; 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.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
public class BaseCraftingTableBlock extends CraftingTableBlock implements BlockModelProvider { public class BaseCraftingTableBlock extends CraftingTableBlock implements BlockModelProvider {
public BaseCraftingTableBlock(Block source) { public BaseCraftingTableBlock(Block source) {
this(FabricBlockSettings.copyOf(source)); super(FabricBlockSettings.copyOf(source));
} }
public BaseCraftingTableBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this.asItem())); return Collections.singletonList(new ItemStack(this.asItem()));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return getBlockModel(resourceLocation, defaultBlockState()); return getBlockModel(resourceLocation, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
String blockName = blockId.getPath(); String blockName = blockId.getPath();
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_SIDED, new HashMap<String, String>() { Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_SIDED, new HashMap<String, String>() {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
{ {
put("%modid%", blockId.getNamespace()); put("%modid%", blockId.getNamespace());
put("%particle%", blockName + "_front"); put("%particle%", blockName + "_front");

View file

@ -1,7 +1,13 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; 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.BlockPos;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
@ -25,30 +31,20 @@ import net.minecraft.world.phys.shapes.VoxelShape;
import ru.bclib.util.BlocksHelper; import ru.bclib.util.BlocksHelper;
import ru.bclib.util.MHelper; import ru.bclib.util.MHelper;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class BaseCropBlock extends BasePlantBlock { public class BaseCropBlock extends BasePlantBlock {
public static final IntegerProperty AGE = IntegerProperty.create("age", 0, 3);
private static final VoxelShape SHAPE = Block.box(2, 0, 2, 14, 14, 14); 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 Block[] terrain;
private final Item drop; private final Item drop;
public BaseCropBlock(Item drop, Block... terrain) { public BaseCropBlock(Item drop, Block... terrain) {
this( super(FabricBlockSettings.of(Material.PLANT)
FabricBlockSettings.of(Material.PLANT) .breakByTool(FabricToolTags.HOES)
.breakByHand(true) .breakByHand(true)
.sound(SoundType.GRASS) .sound(SoundType.GRASS)
.randomTicks() .randomTicks()
.noCollission(), .noCollission());
drop, terrain
);
}
public BaseCropBlock(BlockBehaviour.Properties properties, Item drop, Block... terrain) {
super(properties);
this.drop = drop; this.drop = drop;
this.terrain = terrain; this.terrain = terrain;
this.registerDefaultState(defaultBlockState().setValue(AGE, 0)); this.registerDefaultState(defaultBlockState().setValue(AGE, 0));
@ -61,7 +57,7 @@ public class BaseCropBlock extends BasePlantBlock {
@Override @Override
protected boolean isTerrain(BlockState state) { protected boolean isTerrain(BlockState state) {
for (Block block : terrain) { for (Block block: terrain) {
if (state.is(block)) { if (state.is(block)) {
return true; return true;
} }
@ -110,7 +106,7 @@ public class BaseCropBlock extends BasePlantBlock {
public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) {
return state.getValue(AGE) < 3; return state.getValue(AGE) < 3;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) {

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -12,46 +19,36 @@ import net.minecraft.util.StringRepresentable;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.DoorBlock; 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.BlockState;
import net.minecraft.world.level.block.state.properties.DoorHingeSide; import net.minecraft.world.level.block.state.properties.DoorHingeSide;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.client.render.BCLRenderLayer; import ru.bclib.client.render.BCLRenderLayer;
import ru.bclib.interfaces.BlockModelProvider; import ru.bclib.interfaces.IRenderTyped;
import ru.bclib.interfaces.RenderLayerProvider;
import java.util.Collections; public class BaseDoorBlock extends DoorBlock implements IRenderTyped, BlockModelProvider {
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseDoorBlock extends DoorBlock implements RenderLayerProvider, BlockModelProvider {
public BaseDoorBlock(Block source) { public BaseDoorBlock(Block source) {
this(FabricBlockSettings.copyOf(source).strength(3F, 3F).noOcclusion()); super(FabricBlockSettings.copyOf(source).strength(3F, 3F).noOcclusion());
} }
public BaseDoorBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
if (state.getValue(HALF) == DoubleBlockHalf.LOWER) if (state.getValue(HALF) == DoubleBlockHalf.LOWER)
return Collections.singletonList(new ItemStack(this.asItem())); return Collections.singletonList(new ItemStack(this.asItem()));
else return Collections.emptyList(); else
return Collections.emptyList();
} }
@Override @Override
public BCLRenderLayer getRenderLayer() { public BCLRenderLayer getRenderLayer() {
return BCLRenderLayer.CUTOUT; return BCLRenderLayer.CUTOUT;
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
@ -67,12 +64,11 @@ public class BaseDoorBlock extends DoorBlock implements RenderLayerProvider, Blo
case TOP: case TOP:
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_TOP, resourceLocation); pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_TOP, resourceLocation);
break; break;
default: default: break;
break;
} }
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
@ -85,27 +81,23 @@ public class BaseDoorBlock extends DoorBlock implements RenderLayerProvider, Blo
case EAST: case EAST:
if (hinge && open) { if (hinge && open) {
rotation = BlockModelRotation.X0_Y90; rotation = BlockModelRotation.X0_Y90;
} } else if (open) {
else if (open) {
rotation = BlockModelRotation.X0_Y270; rotation = BlockModelRotation.X0_Y270;
} }
break; break;
case SOUTH: case SOUTH:
if (!hinge && !open || hinge && !open) { if (!hinge && !open || hinge && !open) {
rotation = BlockModelRotation.X0_Y90; rotation = BlockModelRotation.X0_Y90;
} } else if (hinge) {
else if (hinge) {
rotation = BlockModelRotation.X0_Y180; rotation = BlockModelRotation.X0_Y180;
} }
break; break;
case WEST: case WEST:
if (!hinge && !open || hinge && !open) { if (!hinge && !open || hinge && !open) {
rotation = BlockModelRotation.X0_Y180; rotation = BlockModelRotation.X0_Y180;
} } else if (hinge) {
else if (hinge) {
rotation = BlockModelRotation.X0_Y270; rotation = BlockModelRotation.X0_Y270;
} } else {
else {
rotation = BlockModelRotation.X0_Y90; rotation = BlockModelRotation.X0_Y90;
} }
break; break;
@ -113,20 +105,17 @@ public class BaseDoorBlock extends DoorBlock implements RenderLayerProvider, Blo
default: default:
if (!hinge && !open || hinge && !open) { if (!hinge && !open || hinge && !open) {
rotation = BlockModelRotation.X0_Y270; rotation = BlockModelRotation.X0_Y270;
} } else if (!hinge) {
else if (!hinge) {
rotation = BlockModelRotation.X0_Y180; rotation = BlockModelRotation.X0_Y180;
} }
break; break;
} }
ResourceLocation modelId = new ResourceLocation( ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(),
stateId.getNamespace(), "block/" + stateId.getPath() + "_" + doorType);
"block/" + stateId.getPath() + "_" + doorType
);
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false);
} }
protected DoorType getDoorType(BlockState blockState) { protected DoorType getDoorType(BlockState blockState) {
boolean isHinge = isHinge(blockState.getValue(HINGE), blockState.getValue(OPEN)); boolean isHinge = isHinge(blockState.getValue(HINGE), blockState.getValue(OPEN));
switch (blockState.getValue(HALF)) { switch (blockState.getValue(HALF)) {
@ -139,30 +128,34 @@ public class BaseDoorBlock extends DoorBlock implements RenderLayerProvider, Blo
} }
return DoorType.BOTTOM; return DoorType.BOTTOM;
} }
private boolean isHinge(DoorHingeSide hingeSide, boolean open) { private boolean isHinge(DoorHingeSide hingeSide, boolean open) {
boolean isHinge = hingeSide == DoorHingeSide.RIGHT; boolean isHinge = hingeSide == DoorHingeSide.RIGHT;
return isHinge && !open || !isHinge && open; return isHinge && !open || !isHinge && open;
} }
protected enum DoorType implements StringRepresentable { protected enum DoorType implements StringRepresentable {
BOTTOM_HINGE("bottom_hinge"), TOP_HINGE("top_hinge"), BOTTOM("bottom"), TOP("top"); BOTTOM_HINGE("bottom_hinge"),
TOP_HINGE("top_hinge"),
BOTTOM("bottom"),
TOP("top");
private final String name; private final String name;
DoorType(String name) { DoorType(String name) {
this.name = name; this.name = name;
} }
public boolean isHinge() { public boolean isHinge() {
return this == BOTTOM_HINGE || this == TOP_HINGE; return this == BOTTOM_HINGE ||
this == TOP_HINGE;
} }
@Override @Override
public String toString() { public String toString() {
return getSerializedName(); return getSerializedName();
} }
@Override @Override
public String getSerializedName() { public String getSerializedName() {
return name; return name;

View file

@ -1,6 +1,10 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import java.util.List;
import java.util.Random;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -31,38 +35,31 @@ import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShape;
import ru.bclib.client.render.BCLRenderLayer; import ru.bclib.client.render.BCLRenderLayer;
import ru.bclib.interfaces.RenderLayerProvider; import ru.bclib.interfaces.IRenderTyped;
import ru.bclib.util.BlocksHelper; import ru.bclib.util.BlocksHelper;
import java.util.List; @SuppressWarnings("deprecation")
import java.util.Random; public abstract class BaseDoublePlantBlock extends BaseBlockNotFull implements IRenderTyped, BonemealableBlock {
public abstract class BaseDoublePlantBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock {
private static final VoxelShape SHAPE = Block.box(4, 2, 4, 12, 16, 12); private static final VoxelShape SHAPE = Block.box(4, 2, 4, 12, 16, 12);
public static final IntegerProperty ROTATION = BlockProperties.ROTATION; public static final IntegerProperty ROTATION = BlockProperties.ROTATION;
public static final BooleanProperty TOP = BooleanProperty.create("top"); public static final BooleanProperty TOP = BooleanProperty.create("top");
public BaseDoublePlantBlock() { public BaseDoublePlantBlock() {
this( super(FabricBlockSettings.of(Material.PLANT)
FabricBlockSettings.of(Material.PLANT) .breakByTool(FabricToolTags.SHEARS)
.breakByHand(true) .breakByHand(true)
.sound(SoundType.GRASS) .sound(SoundType.WET_GRASS)
.noCollission() .noCollission());
); this.registerDefaultState(this.stateDefinition.any().setValue(TOP, false));
} }
public BaseDoublePlantBlock(int light) { public BaseDoublePlantBlock(int light) {
this( super(FabricBlockSettings.of(Material.PLANT)
FabricBlockSettings.of(Material.PLANT) .breakByTool(FabricToolTags.SHEARS)
.breakByHand(true) .breakByHand(true)
.sound(SoundType.GRASS) .sound(SoundType.WET_GRASS)
.lightLevel((state) -> state.getValue(TOP) ? light : 0) .lightLevel((state) -> state.getValue(TOP) ? light : 0)
.noCollission() .noCollission());
);
}
public BaseDoublePlantBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(TOP, false)); this.registerDefaultState(this.stateDefinition.any().setValue(TOP, false));
} }
@ -70,21 +67,19 @@ public abstract class BaseDoublePlantBlock extends BaseBlockNotFull implements R
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) { protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) {
stateManager.add(TOP, ROTATION); stateManager.add(TOP, ROTATION);
} }
@Override @Override
@SuppressWarnings("deprecation")
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
Vec3 vec3d = state.getOffset(view, pos); Vec3 vec3d = state.getOffset(view, pos);
return SHAPE.move(vec3d.x, vec3d.y, vec3d.z); return SHAPE.move(vec3d.x, vec3d.y, vec3d.z);
} }
@Override @Override
public BlockBehaviour.OffsetType getOffsetType() { public BlockBehaviour.OffsetType getOffsetType() {
return BlockBehaviour.OffsetType.XZ; return BlockBehaviour.OffsetType.XZ;
} }
@Override @Override
@SuppressWarnings("deprecation")
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
BlockState down = world.getBlockState(pos.below()); BlockState down = world.getBlockState(pos.below());
BlockState up = world.getBlockState(pos.above()); BlockState up = world.getBlockState(pos.above());
@ -96,11 +91,10 @@ public abstract class BaseDoublePlantBlock extends BaseBlockNotFull implements R
BlockState up = world.getBlockState(pos.above()); BlockState up = world.getBlockState(pos.above());
return state.getValue(TOP) ? down.getBlock() == this : isTerrain(down) && (up.getBlock() == this); return state.getValue(TOP) ? down.getBlock() == this : isTerrain(down) && (up.getBlock() == this);
} }
protected abstract boolean isTerrain(BlockState state); protected abstract boolean isTerrain(BlockState state);
@Override @Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
if (!canStayAt(state, world, pos)) { if (!canStayAt(state, world, pos)) {
return Blocks.AIR.defaultBlockState(); return Blocks.AIR.defaultBlockState();
@ -117,10 +111,7 @@ public abstract class BaseDoublePlantBlock extends BaseBlockNotFull implements R
} }
ItemStack tool = builder.getParameter(LootContextParams.TOOL); ItemStack tool = builder.getParameter(LootContextParams.TOOL);
if (tool != null && FabricToolTags.SHEARS.contains(tool.getItem()) || EnchantmentHelper.getItemEnchantmentLevel( if (tool != null && FabricToolTags.SHEARS.contains(tool.getItem()) || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) {
Enchantments.SILK_TOUCH,
tool
) > 0) {
return Lists.newArrayList(new ItemStack(this)); return Lists.newArrayList(new ItemStack(this));
} }
else { else {
@ -132,26 +123,20 @@ public abstract class BaseDoublePlantBlock extends BaseBlockNotFull implements R
public BCLRenderLayer getRenderLayer() { public BCLRenderLayer getRenderLayer() {
return BCLRenderLayer.CUTOUT; return BCLRenderLayer.CUTOUT;
} }
@Override @Override
public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) {
return true; return true;
} }
@Override @Override
public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) {
return true; return true;
} }
@Override @Override
public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) { public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) {
ItemEntity item = new ItemEntity( ItemEntity item = new ItemEntity(world, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, new ItemStack(this));
world,
pos.getX() + 0.5,
pos.getY() + 0.5,
pos.getZ() + 0.5,
new ItemStack(this)
);
world.addFreshEntity(item); world.addFreshEntity(item);
} }

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -13,17 +20,11 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FenceBlock; import net.minecraft.world.level.block.FenceBlock;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.ModelsHelper.MultiPartBuilder; import ru.bclib.client.models.ModelsHelper.MultiPartBuilder;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseFenceBlock extends FenceBlock implements BlockModelProvider { public class BaseFenceBlock extends FenceBlock implements BlockModelProvider {
private final Block parent; private final Block parent;
@ -32,13 +33,13 @@ public class BaseFenceBlock extends FenceBlock implements BlockModelProvider {
super(FabricBlockSettings.copyOf(source).noOcclusion()); super(FabricBlockSettings.copyOf(source).noOcclusion());
this.parent = source; this.parent = source;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
@ -46,7 +47,7 @@ public class BaseFenceBlock extends FenceBlock implements BlockModelProvider {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_FENCE, parentId); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_FENCE, parentId);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
@ -61,34 +62,27 @@ public class BaseFenceBlock extends FenceBlock implements BlockModelProvider {
} }
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_post"); ResourceLocation postId = new ResourceLocation(stateId.getNamespace(),
ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_side"); "block/" + stateId.getPath() + "_post");
ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(),
"block/" + stateId.getPath() + "_side");
registerBlockModel(postId, postId, blockState, modelCache); registerBlockModel(postId, postId, blockState, modelCache);
registerBlockModel(sideId, sideId, blockState, modelCache); registerBlockModel(sideId, sideId, blockState, modelCache);
MultiPartBuilder builder = MultiPartBuilder.create(stateDefinition); MultiPartBuilder builder = MultiPartBuilder.create(stateDefinition);
builder.part(sideId).setCondition(state -> state.getValue(NORTH)).setUVLock(true).add(); builder.part(sideId).setCondition(state -> state.getValue(NORTH)).setUVLock(true).add();
builder.part(sideId) builder.part(sideId).setCondition(state -> state.getValue(EAST))
.setCondition(state -> state.getValue(EAST)) .setTransformation(BlockModelRotation.X0_Y90.getRotation()).setUVLock(true).add();
.setTransformation(BlockModelRotation.X0_Y90.getRotation()) builder.part(sideId).setCondition(state -> state.getValue(SOUTH))
.setUVLock(true) .setTransformation(BlockModelRotation.X0_Y180.getRotation()).setUVLock(true).add();
.add(); builder.part(sideId).setCondition(state -> state.getValue(WEST))
builder.part(sideId) .setTransformation(BlockModelRotation.X0_Y270.getRotation()).setUVLock(true).add();
.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(); builder.part(postId).add();
return builder.build(); return builder.build();
} }
} }

View file

@ -20,33 +20,28 @@ import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType; 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.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import ru.bclib.blockentities.BaseFurnaceBlockEntity; import ru.bclib.blockentities.BaseFurnaceBlockEntity;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.client.render.BCLRenderLayer; import ru.bclib.client.render.BCLRenderLayer;
import ru.bclib.interfaces.BlockModelProvider; import ru.bclib.interfaces.IRenderTyped;
import ru.bclib.interfaces.RenderLayerProvider;
import ru.bclib.registry.BaseBlockEntities; import ru.bclib.registry.BaseBlockEntities;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class BaseFurnaceBlock extends FurnaceBlock implements BlockModelProvider, RenderLayerProvider { public class BaseFurnaceBlock extends FurnaceBlock implements BlockModelProvider, IRenderTyped {
public BaseFurnaceBlock(Block source) { public BaseFurnaceBlock(Block source) {
this(FabricBlockSettings.copyOf(source).lightLevel(state -> state.getValue(LIT) ? 13 : 0)); super(FabricBlockSettings.copyOf(source).luminance(state -> state.getValue(LIT) ? 13 : 0));
} }
public BaseFurnaceBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override @Override
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return new BaseFurnaceBlockEntity(blockPos, blockState); return new BaseFurnaceBlockEntity(blockPos, blockState);
@ -60,7 +55,7 @@ public class BaseFurnaceBlock extends FurnaceBlock implements BlockModelProvider
player.awardStat(Stats.INTERACT_WITH_FURNACE); player.awardStat(Stats.INTERACT_WITH_FURNACE);
} }
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
@ -74,34 +69,34 @@ public class BaseFurnaceBlock extends FurnaceBlock implements BlockModelProvider
textures.put("%front%", blockName + "_front_on"); textures.put("%front%", blockName + "_front_on");
textures.put("%glow%", blockName + "_glow"); textures.put("%glow%", blockName + "_glow");
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FURNACE_LIT, textures); pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FURNACE_LIT, textures);
} } else {
else {
textures.put("%front%", blockName + "_front"); textures.put("%front%", blockName + "_front");
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FURNACE, textures); pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FURNACE, textures);
} }
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return getBlockModel(resourceLocation, defaultBlockState()); return getBlockModel(resourceLocation, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
String lit = blockState.getValue(LIT) ? "_lit" : ""; String lit = blockState.getValue(LIT) ? "_lit" : "";
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + lit); ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(),
"block/" + stateId.getPath() + lit);
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), false, true); return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), false, true);
} }
@Override @Override
public BCLRenderLayer getRenderLayer() { public BCLRenderLayer getRenderLayer() {
return BCLRenderLayer.CUTOUT; return BCLRenderLayer.CUTOUT;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
@ -115,19 +110,15 @@ public class BaseFurnaceBlock extends FurnaceBlock implements BlockModelProvider
} }
return drop; return drop;
} }
@Override @Override
@Nullable @Nullable
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState blockState, BlockEntityType<T> blockEntityType) { public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState blockState, BlockEntityType<T> blockEntityType) {
return createFurnaceTicker(level, blockEntityType, BaseBlockEntities.FURNACE); return createFurnaceTicker(level, blockEntityType, BaseBlockEntities.FURNACE);
} }
@Nullable @Nullable
protected static <T extends BlockEntity> BlockEntityTicker<T> createFurnaceTicker(Level level, BlockEntityType<T> blockEntityType, BlockEntityType<? extends AbstractFurnaceBlockEntity> blockEntityType2) { protected static <T extends BlockEntity> BlockEntityTicker<T> createFurnaceTicker(Level level, BlockEntityType<T> blockEntityType, BlockEntityType<? extends AbstractFurnaceBlockEntity> blockEntityType2) {
return level.isClientSide ? null : createTickerHelper( return level.isClientSide ? null : createTickerHelper(blockEntityType, blockEntityType2, AbstractFurnaceBlockEntity::serverTick);
blockEntityType,
blockEntityType2,
AbstractFurnaceBlockEntity::serverTick
);
} }
} }

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -12,16 +19,10 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FenceGateBlock; import net.minecraft.world.level.block.FenceGateBlock;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseGateBlock extends FenceGateBlock implements BlockModelProvider { public class BaseGateBlock extends FenceGateBlock implements BlockModelProvider {
private final Block parent; private final Block parent;
@ -30,19 +31,19 @@ public class BaseGateBlock extends FenceGateBlock implements BlockModelProvider
super(FabricBlockSettings.copyOf(source).noOcclusion()); super(FabricBlockSettings.copyOf(source).noOcclusion());
this.parent = source; this.parent = source;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return getBlockModel(resourceLocation, defaultBlockState()); return getBlockModel(resourceLocation, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
@ -51,27 +52,23 @@ public class BaseGateBlock extends FenceGateBlock implements BlockModelProvider
ResourceLocation parentId = Registry.BLOCK.getKey(parent); ResourceLocation parentId = Registry.BLOCK.getKey(parent);
Optional<String> pattern; Optional<String> pattern;
if (inWall) { if (inWall) {
pattern = isOpen ? PatternsHelper.createJson( pattern = isOpen ? PatternsHelper.createJson(BasePatterns.BLOCK_GATE_OPEN_WALL, parentId) :
BasePatterns.BLOCK_GATE_OPEN_WALL, PatternsHelper.createJson(BasePatterns.BLOCK_GATE_CLOSED_WALL, parentId);
parentId } else {
) : PatternsHelper.createJson(BasePatterns.BLOCK_GATE_CLOSED_WALL, parentId); pattern = isOpen ? PatternsHelper.createJson(BasePatterns.BLOCK_GATE_OPEN, parentId) :
} PatternsHelper.createJson(BasePatterns.BLOCK_GATE_CLOSED, parentId);
else {
pattern = isOpen ? PatternsHelper.createJson(
BasePatterns.BLOCK_GATE_OPEN,
parentId
) : PatternsHelper.createJson(BasePatterns.BLOCK_GATE_CLOSED, parentId);
} }
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
boolean inWall = blockState.getValue(IN_WALL); boolean inWall = blockState.getValue(IN_WALL);
boolean isOpen = blockState.getValue(OPEN); boolean isOpen = blockState.getValue(OPEN);
String state = "" + (inWall ? "_wall" : "") + (isOpen ? "_open" : "_closed"); String state = "" + (inWall ? "_wall" : "") + (isOpen ? "_open" : "_closed");
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state); ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(),
"block/" + stateId.getPath() + state);
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), true, false); return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), true, false);
} }

View file

@ -1,57 +1,159 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.UnbakedModel; 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.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; 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.Block;
import net.minecraft.world.level.block.LadderBlock; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockBehaviour; 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.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.block.state.StateDefinition;
import org.jetbrains.annotations.Nullable; 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.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.client.render.BCLRenderLayer; import ru.bclib.client.render.BCLRenderLayer;
import ru.bclib.interfaces.BlockModelProvider; import ru.bclib.interfaces.IRenderTyped;
import ru.bclib.interfaces.RenderLayerProvider; import ru.bclib.util.BlocksHelper;
import java.util.Collections; @SuppressWarnings("deprecation")
import java.util.List; public class BaseLadderBlock extends BaseBlockNotFull implements IRenderTyped, BlockModelProvider {
import java.util.Map; public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING;
import java.util.Optional; 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 class BaseLadderBlock extends LadderBlock implements RenderLayerProvider, BlockModelProvider {
public BaseLadderBlock(Block block) { public BaseLadderBlock(Block block) {
this(FabricBlockSettings.copyOf(block).noOcclusion()); super(FabricBlockSettings.copyOf(block).noOcclusion());
} }
public BaseLadderBlock(BlockBehaviour.Properties properties) { @Override
super(properties); protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> 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 @Override
public BCLRenderLayer getRenderLayer() { public BCLRenderLayer getRenderLayer() {
return BCLRenderLayer.CUTOUT; return BCLRenderLayer.CUTOUT;
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
return ModelsHelper.createBlockItem(blockId); return ModelsHelper.createBlockItem(blockId);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_LADDER, blockId); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_LADDER, blockId);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
@ -59,10 +161,4 @@ public class BaseLadderBlock extends LadderBlock implements RenderLayerProvider,
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), false, true); return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), false, true);
} }
@Override
@SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this));
}
} }

View file

@ -1,13 +1,14 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import net.fabricmc.fabric.api.mininglevel.v1.FabricMineableTags;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; 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.renderer.block.model.BlockModel;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.Tag.Named;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.item.enchantment.Enchantments;
@ -18,64 +19,49 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.MaterialColor; import net.minecraft.world.level.material.MaterialColor;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import ru.bclib.api.TagAPI; import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.render.BCLRenderLayer; import ru.bclib.client.render.BCLRenderLayer;
import ru.bclib.interfaces.BlockModelProvider; import ru.bclib.interfaces.IRenderTyped;
import ru.bclib.interfaces.RenderLayerProvider;
import ru.bclib.interfaces.TagProvider;
import ru.bclib.util.MHelper; import ru.bclib.util.MHelper;
import java.util.Collections; public class BaseLeavesBlock extends LeavesBlock implements BlockModelProvider, IRenderTyped {
import java.util.List; private final Block sapling;
import java.util.function.Consumer;
public class BaseLeavesBlock extends LeavesBlock implements BlockModelProvider, RenderLayerProvider, TagProvider {
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<FabricBlockSettings> customizeProperties) {
super(BaseBlock.acceptAndReturn(customizeProperties, makeLeaves(color)));
this.sapling = sapling;
}
public BaseLeavesBlock(Block sapling, MaterialColor color, int light, Consumer<FabricBlockSettings> customizeProperties) {
super(BaseBlock.acceptAndReturn(customizeProperties, makeLeaves(color).luminance(light)));
this.sapling = sapling;
}
public BaseLeavesBlock(Block sapling, MaterialColor color) { public BaseLeavesBlock(Block sapling, MaterialColor color) {
super(makeLeaves(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; this.sapling = sapling;
} }
public BaseLeavesBlock(Block sapling, MaterialColor color, int light) { public BaseLeavesBlock(Block sapling, MaterialColor color, int light) {
super(makeLeaves(color).lightLevel(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; this.sapling = sapling;
} }
@Override @Override
public BCLRenderLayer getRenderLayer() { public BCLRenderLayer getRenderLayer() {
return BCLRenderLayer.CUTOUT; return BCLRenderLayer.CUTOUT;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
ItemStack tool = builder.getParameter(LootContextParams.TOOL); ItemStack tool = builder.getParameter(LootContextParams.TOOL);
if (tool != null) { if (tool != null) {
if (tool.isCorrectToolForDrops(state) || EnchantmentHelper.getItemEnchantmentLevel( if (FabricToolTags.SHEARS.contains(tool.getItem()) || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) {
Enchantments.SILK_TOUCH,
tool
) > 0) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
int fortune = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool); int fortune = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool);
@ -86,16 +72,9 @@ public class BaseLeavesBlock extends LeavesBlock implements BlockModelProvider,
} }
return MHelper.RANDOM.nextInt(16) == 0 ? Lists.newArrayList(new ItemStack(sapling)) : Lists.newArrayList(); return MHelper.RANDOM.nextInt(16) == 0 ? Lists.newArrayList(new ItemStack(sapling)) : Lists.newArrayList();
} }
@Override @Override
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return getBlockModel(resourceLocation, defaultBlockState()); return getBlockModel(resourceLocation, defaultBlockState());
} }
@Override
public void addTags(List<Named<Block>> blockTags, List<Named<Item>> itemTags) {
blockTags.add(FabricMineableTags.SHEARS_MINEABLE);
blockTags.add(TagAPI.MINEABLE_HOE);
blockTags.add(BlockTags.LEAVES);
}
} }

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -12,37 +19,26 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.IronBarsBlock; 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.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.client.render.BCLRenderLayer; import ru.bclib.client.render.BCLRenderLayer;
import ru.bclib.interfaces.BlockModelProvider; import ru.bclib.interfaces.IRenderTyped;
import ru.bclib.interfaces.RenderLayerProvider;
import java.util.Collections; public class BaseMetalBarsBlock extends IronBarsBlock implements BlockModelProvider, IRenderTyped {
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseMetalBarsBlock extends IronBarsBlock implements BlockModelProvider, RenderLayerProvider {
public BaseMetalBarsBlock(Block source) { public BaseMetalBarsBlock(Block source) {
this(FabricBlockSettings.copyOf(source).strength(5.0F, 6.0F).noOcclusion()); super(FabricBlockSettings.copyOf(source).strength(5.0F, 6.0F).noOcclusion());
} }
public BaseMetalBarsBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
public Optional<String> getModelString(String block) { public Optional<String> getModelString(String block) {
ResourceLocation blockId = Registry.BLOCK.getKey(this); ResourceLocation blockId = Registry.BLOCK.getKey(this);
if (block.contains("item")) { if (block.contains("item")) {
@ -55,13 +51,13 @@ public class BaseMetalBarsBlock extends IronBarsBlock implements BlockModelProvi
return PatternsHelper.createJson(BasePatterns.BLOCK_BARS_SIDE, blockId); return PatternsHelper.createJson(BasePatterns.BLOCK_BARS_SIDE, blockId);
} }
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return ModelsHelper.createBlockItem(resourceLocation); return ModelsHelper.createBlockItem(resourceLocation);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
@ -76,40 +72,32 @@ public class BaseMetalBarsBlock extends IronBarsBlock implements BlockModelProvi
} }
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_post"); ResourceLocation postId = new ResourceLocation(stateId.getNamespace(),
ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_side"); "block/" + stateId.getPath() + "_post");
ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(),
"block/" + stateId.getPath() + "_side");
registerBlockModel(postId, postId, blockState, modelCache); registerBlockModel(postId, postId, blockState, modelCache);
registerBlockModel(sideId, sideId, blockState, modelCache); registerBlockModel(sideId, sideId, blockState, modelCache);
ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition); ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition);
builder.part(postId) builder.part(postId).setCondition(state ->
.setCondition(state -> !state.getValue(NORTH) && !state.getValue(EAST) && !state.getValue(SOUTH) && !state !state.getValue(NORTH) && !state.getValue(EAST) &&
.getValue(WEST)) !state.getValue(SOUTH) && !state.getValue(WEST)).add();
.add();
builder.part(sideId).setCondition(state -> state.getValue(NORTH)).setUVLock(true).add(); builder.part(sideId).setCondition(state -> state.getValue(NORTH)).setUVLock(true).add();
builder.part(sideId) builder.part(sideId).setCondition(state -> state.getValue(EAST))
.setCondition(state -> state.getValue(EAST)) .setTransformation(BlockModelRotation.X0_Y90.getRotation()).setUVLock(true).add();
.setTransformation(BlockModelRotation.X0_Y90.getRotation()) builder.part(sideId).setCondition(state -> state.getValue(SOUTH))
.setUVLock(true) .setTransformation(BlockModelRotation.X0_Y180.getRotation()).setUVLock(true).add();
.add(); builder.part(sideId).setCondition(state -> state.getValue(WEST))
builder.part(sideId) .setTransformation(BlockModelRotation.X0_Y270.getRotation()).setUVLock(true).add();
.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(); return builder.build();
} }
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public boolean skipRendering(BlockState state, BlockState stateFrom, Direction direction) { public boolean skipRendering(BlockState state, BlockState stateFrom, Direction direction) {
if (direction.getAxis().isVertical() && stateFrom.getBlock() == this && !stateFrom.equals(state)) { if (direction.getAxis().isVertical() && stateFrom.getBlock() == this && !stateFrom.equals(state)) {
@ -117,7 +105,7 @@ public class BaseMetalBarsBlock extends IronBarsBlock implements BlockModelProvi
} }
return super.skipRendering(state, stateFrom, direction); return super.skipRendering(state, stateFrom, direction);
} }
@Override @Override
public BCLRenderLayer getRenderLayer() { public BCLRenderLayer getRenderLayer() {
return BCLRenderLayer.CUTOUT; return BCLRenderLayer.CUTOUT;

View file

@ -1,19 +1,18 @@
package ru.bclib.blocks; 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.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags;
import net.fabricmc.fabric.impl.object.builder.FabricBlockInternals;
import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.Tag.Named;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.util.valueproviders.UniformInt; import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments; 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.OreBlock; import net.minecraft.world.level.block.OreBlock;
import net.minecraft.world.level.block.SoundType; import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
@ -21,63 +20,32 @@ import net.minecraft.world.level.material.Material;
import net.minecraft.world.level.material.MaterialColor; import net.minecraft.world.level.material.MaterialColor;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import ru.bclib.api.TagAPI; import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.interfaces.BlockModelProvider;
import ru.bclib.interfaces.TagProvider;
import ru.bclib.util.MHelper; import ru.bclib.util.MHelper;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
public class BaseOreBlock extends OreBlock implements BlockModelProvider { public class BaseOreBlock extends OreBlock implements BlockModelProvider {
private final Supplier<Item> dropItem; private final Item dropItem;
private final int minCount; private final int minCount;
private final int maxCount; private final int maxCount;
public BaseOreBlock(Supplier<Item> drop, int minCount, int maxCount, int experience) { public BaseOreBlock(Item drop, int minCount, int maxCount, int experience) {
this(drop, minCount, maxCount, experience, 0); super(FabricBlockSettings.of(Material.STONE, MaterialColor.SAND)
} .hardness(3F)
.resistance(9F)
public BaseOreBlock(Supplier<Item> drop, int minCount, int maxCount, int experience, int miningLevel) { .requiresCorrectToolForDrops()
this( .sound(SoundType.STONE), UniformInt.of(1, experience));
FabricBlockSettings
.of(Material.STONE, MaterialColor.SAND)
.requiresTool()
.destroyTime(3F)
.explosionResistance(9F)
.sound(SoundType.STONE),
drop, minCount, maxCount, experience, miningLevel
);
}
public BaseOreBlock(Properties properties, Supplier<Item> drop, int minCount, int maxCount, int experience) {
this(properties, drop, minCount, maxCount, experience, 0);
}
public BaseOreBlock(Properties properties, Supplier<Item> drop, int minCount, int maxCount, int experience, int miningLevel) {
super(makeProps(properties, miningLevel), UniformInt.of(experience>0?1:0, experience));
this.dropItem = drop; this.dropItem = drop;
this.minCount = minCount; this.minCount = minCount;
this.maxCount = maxCount; this.maxCount = maxCount;
} }
private static Properties makeProps(Properties properties, int level){
FabricBlockInternals.computeExtraData(properties).addMiningLevel(FabricToolTags.PICKAXES, level);
return properties;
}
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return getDroppedItems(this, dropItem.get(), maxCount, minCount, state, builder);
}
public static List<ItemStack> getDroppedItems(ItemLike block, Item dropItem, int maxCount, int minCount, BlockState state, LootContext.Builder builder) {
ItemStack tool = builder.getParameter(LootContextParams.TOOL); ItemStack tool = builder.getParameter(LootContextParams.TOOL);
if (tool != null && tool.isCorrectToolForDrops(state)) { if (tool != null && tool.isCorrectToolForDrops(state)) {
if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) {
return Collections.singletonList(new ItemStack(block)); return Collections.singletonList(new ItemStack(this));
} }
int count; int count;
int enchantment = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool); int enchantment = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool);
@ -88,15 +56,14 @@ public class BaseOreBlock extends OreBlock implements BlockModelProvider {
return Collections.singletonList(new ItemStack(dropItem, max)); return Collections.singletonList(new ItemStack(dropItem, max));
} }
count = MHelper.randRange(min, max, MHelper.RANDOM); count = MHelper.randRange(min, max, MHelper.RANDOM);
} } else {
else {
count = MHelper.randRange(minCount, maxCount, MHelper.RANDOM); count = MHelper.randRange(minCount, maxCount, MHelper.RANDOM);
} }
return Collections.singletonList(new ItemStack(dropItem, count)); return Collections.singletonList(new ItemStack(dropItem, count));
} }
return Collections.emptyList(); return Collections.emptyList();
} }
@Override @Override
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return getBlockModel(resourceLocation, defaultBlockState()); return getBlockModel(resourceLocation, defaultBlockState());

View file

@ -1,6 +1,14 @@
package ru.bclib.blocks; 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 com.google.common.collect.Maps;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -20,21 +28,16 @@ import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import java.util.Collections; @SuppressWarnings("deprecation")
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BasePathBlock extends BaseBlockNotFull { public class BasePathBlock extends BaseBlockNotFull {
private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 15, 16); private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 15, 16);
private Block baseBlock; private Block baseBlock;
public BasePathBlock(Block source) { public BasePathBlock(Block source) {
super(FabricBlockSettings.copyOf(source).isValidSpawn((state, world, pos, type) -> false)); super(FabricBlockSettings.copyOf(source).isValidSpawn((state, world, pos, type) -> false));
this.baseBlock = Blocks.DIRT; this.baseBlock = Blocks.DIRT;
@ -55,13 +58,11 @@ public class BasePathBlock extends BaseBlockNotFull {
} }
@Override @Override
@SuppressWarnings("deprecation")
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
return SHAPE; return SHAPE;
} }
@Override @Override
@SuppressWarnings("deprecation")
public VoxelShape getCollisionShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { public VoxelShape getCollisionShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
return SHAPE; return SHAPE;
} }
@ -71,7 +72,7 @@ public class BasePathBlock extends BaseBlockNotFull {
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
return getBlockModel(blockId, defaultBlockState()); return getBlockModel(blockId, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
@ -86,7 +87,7 @@ public class BasePathBlock extends BaseBlockNotFull {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PATH, textures); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PATH, textures);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {

View file

@ -1,19 +1,16 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import java.util.List;
import java.util.Random;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.mininglevel.v1.FabricMineableTags;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.Tag.Named;
import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.item.enchantment.Enchantments;
@ -33,20 +30,11 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import ru.bclib.api.TagAPI;
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.client.render.BCLRenderLayer;
import ru.bclib.interfaces.RenderLayerProvider; import ru.bclib.interfaces.IRenderTyped;
import ru.bclib.interfaces.TagProvider;
import java.util.List; @SuppressWarnings("deprecation")
import java.util.Optional; public abstract class BasePlantBlock extends BaseBlockNotFull implements IRenderTyped, BonemealableBlock {
import java.util.Random;
public abstract class BasePlantBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock, TagProvider {
private static final VoxelShape SHAPE = Block.box(4, 0, 4, 12, 14, 12); private static final VoxelShape SHAPE = Block.box(4, 0, 4, 12, 14, 12);
public BasePlantBlock() { public BasePlantBlock() {
@ -58,53 +46,46 @@ public abstract class BasePlantBlock extends BaseBlockNotFull implements RenderL
} }
public BasePlantBlock(boolean replaceable) { public BasePlantBlock(boolean replaceable) {
this( super(FabricBlockSettings.of(replaceable ? Material.REPLACEABLE_PLANT : Material.PLANT)
FabricBlockSettings .breakByTool(FabricToolTags.SHEARS)
.of(replaceable ? Material.REPLACEABLE_PLANT : Material.PLANT)
.breakByHand(true) .breakByHand(true)
.sound(SoundType.GRASS) .sound(SoundType.GRASS)
.noCollission() .noCollission());
);
} }
public BasePlantBlock(boolean replaceable, int light) { public BasePlantBlock(boolean replaceable, int light) {
this( super(FabricBlockSettings.of(replaceable ? Material.REPLACEABLE_PLANT : Material.PLANT)
FabricBlockSettings .breakByTool(FabricToolTags.SHEARS)
.of(replaceable ? Material.REPLACEABLE_PLANT : Material.PLANT)
.breakByHand(true) .breakByHand(true)
.luminance(light) .luminance(light)
.sound(SoundType.GRASS) .sound(SoundType.GRASS)
.noCollission() .noCollission());
);
} }
public BasePlantBlock(Properties settings) { public BasePlantBlock(Properties settings) {
super(settings); super(settings);
} }
protected abstract boolean isTerrain(BlockState state); protected abstract boolean isTerrain(BlockState state);
@Override @Override
@SuppressWarnings("deprecation")
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
Vec3 vec3d = state.getOffset(view, pos); Vec3 vec3d = state.getOffset(view, pos);
return SHAPE.move(vec3d.x, vec3d.y, vec3d.z); return SHAPE.move(vec3d.x, vec3d.y, vec3d.z);
} }
@Override @Override
public BlockBehaviour.OffsetType getOffsetType() { public BlockBehaviour.OffsetType getOffsetType() {
return BlockBehaviour.OffsetType.XZ; return BlockBehaviour.OffsetType.XZ;
} }
@Override @Override
@SuppressWarnings("deprecation")
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
BlockState down = world.getBlockState(pos.below()); BlockState down = world.getBlockState(pos.below());
return isTerrain(down); return isTerrain(down);
} }
@Override @Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
if (!canSurvive(state, world, pos)) { if (!canSurvive(state, world, pos)) {
return Blocks.AIR.defaultBlockState(); return Blocks.AIR.defaultBlockState();
@ -117,10 +98,7 @@ public abstract class BasePlantBlock extends BaseBlockNotFull implements RenderL
@Override @Override
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
ItemStack tool = builder.getParameter(LootContextParams.TOOL); ItemStack tool = builder.getParameter(LootContextParams.TOOL);
if (tool != null && FabricToolTags.SHEARS.contains(tool.getItem()) || EnchantmentHelper.getItemEnchantmentLevel( if (tool != null && FabricToolTags.SHEARS.contains(tool.getItem()) || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) {
Enchantments.SILK_TOUCH,
tool
) > 0) {
return Lists.newArrayList(new ItemStack(this)); return Lists.newArrayList(new ItemStack(this));
} }
else { else {
@ -132,46 +110,20 @@ public abstract class BasePlantBlock extends BaseBlockNotFull implements RenderL
public BCLRenderLayer getRenderLayer() { public BCLRenderLayer getRenderLayer() {
return BCLRenderLayer.CUTOUT; return BCLRenderLayer.CUTOUT;
} }
@Override @Override
public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) {
return true; return true;
} }
@Override @Override
public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) {
return true; return true;
} }
@Override @Override
public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) { public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) {
ItemEntity item = new ItemEntity( ItemEntity item = new ItemEntity(world, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, new ItemStack(this));
world,
pos.getX() + 0.5,
pos.getY() + 0.5,
pos.getZ() + 0.5,
new ItemStack(this)
);
world.addFreshEntity(item); world.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<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CROSS, resourceLocation);
return ModelsHelper.fromPattern(pattern);
}
@Override
public void addTags(List<Named<Block>> blockTags, List<Named<Item>> itemTags) {
blockTags.add(FabricMineableTags.SHEARS_MINEABLE);
blockTags.add(TagAPI.MINEABLE_HOE);
}
} }

View file

@ -1,6 +1,9 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import java.util.Random;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; 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.BlockPos;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
@ -12,19 +15,16 @@ import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty; import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Material; import net.minecraft.world.level.material.Material;
import java.util.Random;
public abstract class BasePlantWithAgeBlock extends BasePlantBlock { public abstract class BasePlantWithAgeBlock extends BasePlantBlock {
public static final IntegerProperty AGE = BlockProperties.AGE; public static final IntegerProperty AGE = BlockProperties.AGE;
public BasePlantWithAgeBlock() { public BasePlantWithAgeBlock() {
this( this(FabricBlockSettings.of(Material.PLANT)
FabricBlockSettings.of(Material.PLANT) .breakByTool(FabricToolTags.SHEARS)
.breakByHand(true) .breakByHand(true)
.sound(SoundType.GRASS) .sound(SoundType.GRASS)
.randomTicks() .randomTicks()
.noCollission() .noCollission());
);
} }
public BasePlantWithAgeBlock(Properties settings) { public BasePlantWithAgeBlock(Properties settings) {
@ -53,7 +53,7 @@ public abstract class BasePlantWithAgeBlock extends BasePlantBlock {
public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) {
return true; return true;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) {

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -12,16 +19,10 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.PressurePlateBlock; import net.minecraft.world.level.block.PressurePlateBlock;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BasePressurePlateBlock extends PressurePlateBlock implements BlockModelProvider { public class BasePressurePlateBlock extends PressurePlateBlock implements BlockModelProvider {
private final Block parent; private final Block parent;
@ -30,19 +31,19 @@ public class BasePressurePlateBlock extends PressurePlateBlock implements BlockM
super(rule, FabricBlockSettings.copyOf(source).noCollission().noOcclusion().strength(0.5F)); super(rule, FabricBlockSettings.copyOf(source).noCollission().noOcclusion().strength(0.5F));
this.parent = source; this.parent = source;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return getBlockModel(resourceLocation, defaultBlockState()); return getBlockModel(resourceLocation, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
@ -50,18 +51,18 @@ public class BasePressurePlateBlock extends PressurePlateBlock implements BlockM
Optional<String> pattern; Optional<String> pattern;
if (blockState.getValue(POWERED)) { if (blockState.getValue(POWERED)) {
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_DOWN, parentId); pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_DOWN, parentId);
} } else {
else {
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_UP, parentId); pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_UP, parentId);
} }
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
String state = blockState.getValue(POWERED) ? "_down" : "_up"; String state = blockState.getValue(POWERED) ? "_down" : "_up";
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state); ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(),
"block/" + stateId.getPath() + state);
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
return ModelsHelper.createBlockSimple(modelId); return ModelsHelper.createBlockSimple(modelId);
} }

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -11,15 +18,9 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RotatedPillarBlock; import net.minecraft.world.level.block.RotatedPillarBlock;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable; import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseRotatedPillarBlock extends RotatedPillarBlock implements BlockModelProvider { public class BaseRotatedPillarBlock extends RotatedPillarBlock implements BlockModelProvider {
public BaseRotatedPillarBlock(Properties settings) { public BaseRotatedPillarBlock(Properties settings) {
@ -27,28 +28,28 @@ public class BaseRotatedPillarBlock extends RotatedPillarBlock implements BlockM
} }
public BaseRotatedPillarBlock(Block block) { public BaseRotatedPillarBlock(Block block) {
this(FabricBlockSettings.copyOf(block)); super(FabricBlockSettings.copyOf(block));
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
return getBlockModel(blockId, defaultBlockState()); return getBlockModel(blockId, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
Optional<String> pattern = createBlockPattern(blockId); Optional<String> pattern = createBlockPattern(blockId);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
@ -56,7 +57,7 @@ public class BaseRotatedPillarBlock extends RotatedPillarBlock implements BlockM
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
return ModelsHelper.createRotatedModel(modelId, blockState.getValue(AXIS)); return ModelsHelper.createRotatedModel(modelId, blockState.getValue(AXIS));
} }
protected Optional<String> createBlockPattern(ResourceLocation blockId) { protected Optional<String> createBlockPattern(ResourceLocation blockId) {
return PatternsHelper.createBlockPillar(blockId); return PatternsHelper.createBlockPillar(blockId);
} }

View file

@ -1,8 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -14,7 +18,6 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.BlockGetter;
@ -39,18 +42,14 @@ import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import ru.bclib.blockentities.BaseSignBlockEntity; import ru.bclib.blockentities.BaseSignBlockEntity;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.interfaces.BlockModelProvider; import ru.bclib.interfaces.ISpetialItem;
import ru.bclib.interfaces.CustomItemProvider;
import ru.bclib.util.BlocksHelper; import ru.bclib.util.BlocksHelper;
import java.util.Collections;
import java.util.List;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class BaseSignBlock extends SignBlock implements BlockModelProvider, CustomItemProvider { public class BaseSignBlock extends SignBlock implements BlockModelProvider, ISpetialItem {
public static final IntegerProperty ROTATION = BlockStateProperties.ROTATION_16; public static final IntegerProperty ROTATION = BlockStateProperties.ROTATION_16;
public static final BooleanProperty FLOOR = BooleanProperty.create("floor"); public static final BooleanProperty FLOOR = BooleanProperty.create("floor");
private static final VoxelShape[] WALL_SHAPES = new VoxelShape[] { private static final VoxelShape[] WALL_SHAPES = new VoxelShape[] {
@ -59,7 +58,7 @@ public class BaseSignBlock extends SignBlock implements BlockModelProvider, Cust
Block.box(0.0D, 4.5D, 0.0D, 16.0D, 12.5D, 2.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) Block.box(14.0D, 4.5D, 0.0D, 16.0D, 12.5D, 16.0D)
}; };
private final Block parent; private final Block parent;
public BaseSignBlock(Block source) { public BaseSignBlock(Block source) {
@ -67,17 +66,17 @@ public class BaseSignBlock extends SignBlock implements BlockModelProvider, Cust
this.registerDefaultState(this.stateDefinition.any().setValue(ROTATION, 0).setValue(FLOOR, false).setValue(WATERLOGGED, false)); this.registerDefaultState(this.stateDefinition.any().setValue(ROTATION, 0).setValue(FLOOR, false).setValue(WATERLOGGED, false));
this.parent = source; this.parent = source;
} }
@Override @Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) { protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(ROTATION, FLOOR, WATERLOGGED); builder.add(ROTATION, FLOOR, WATERLOGGED);
} }
@Override @Override
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
return state.getValue(FLOOR) ? SHAPE : WALL_SHAPES[state.getValue(ROTATION) >> 2]; return state.getValue(FLOOR) ? SHAPE : WALL_SHAPES[state.getValue(ROTATION) >> 2];
} }
@Override @Override
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return new BaseSignBlockEntity(blockPos, blockState); return new BaseSignBlockEntity(blockPos, blockState);
@ -91,26 +90,24 @@ public class BaseSignBlock extends SignBlock implements BlockModelProvider, Cust
if (!world.isClientSide) { if (!world.isClientSide) {
sign.setAllowedPlayerEditor(placer.getUUID()); sign.setAllowedPlayerEditor(placer.getUUID());
((ServerPlayer) placer).connection.send(new ClientboundOpenSignEditorPacket(pos)); ((ServerPlayer) placer).connection.send(new ClientboundOpenSignEditorPacket(pos));
} } else {
else {
sign.setEditable(true); sign.setEditable(true);
} }
} }
} }
} }
@Override @Override
public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
if (state.getValue(WATERLOGGED)) { if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
} }
if (!canSurvive(state, world, pos)) { if (!canSurvive(state, world, pos)) {
return state.getValue(WATERLOGGED) ? state.getFluidState() return state.getValue(WATERLOGGED) ? state.getFluidState().createLegacyBlock() : Blocks.AIR.defaultBlockState();
.createLegacyBlock() : Blocks.AIR.defaultBlockState();
} }
return super.updateShape(state, facing, neighborState, world, pos, neighborPos); return super.updateShape(state, facing, neighborState, world, pos, neighborPos);
} }
@Override @Override
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
if (!state.getValue(FLOOR)) { if (!state.getValue(FLOOR)) {
@ -121,51 +118,48 @@ public class BaseSignBlock extends SignBlock implements BlockModelProvider, Cust
return world.getBlockState(pos.below()).getMaterial().isSolid(); return world.getBlockState(pos.below()).getMaterial().isSolid();
} }
} }
@Override @Override
public BlockState getStateForPlacement(BlockPlaceContext ctx) { public BlockState getStateForPlacement(BlockPlaceContext ctx) {
if (ctx.getClickedFace() == Direction.UP) { if (ctx.getClickedFace() == Direction.UP) {
FluidState fluidState = ctx.getLevel().getFluidState(ctx.getClickedPos()); FluidState fluidState = ctx.getLevel().getFluidState(ctx.getClickedPos());
return this.defaultBlockState() return this.defaultBlockState().setValue(FLOOR, true)
.setValue(FLOOR, true) .setValue(ROTATION, Mth.floor((180.0 + ctx.getRotation() * 16.0 / 360.0) + 0.5 - 12) & 15)
.setValue(ROTATION, Mth.floor((180.0 + ctx.getRotation() * 16.0 / 360.0) + 0.5 - 12) & 15) .setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER);
.setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER); } else if (ctx.getClickedFace() != Direction.DOWN) {
}
else if (ctx.getClickedFace() != Direction.DOWN) {
BlockState blockState = this.defaultBlockState(); BlockState blockState = this.defaultBlockState();
FluidState fluidState = ctx.getLevel().getFluidState(ctx.getClickedPos()); FluidState fluidState = ctx.getLevel().getFluidState(ctx.getClickedPos());
LevelReader worldView = ctx.getLevel(); LevelReader worldView = ctx.getLevel();
BlockPos blockPos = ctx.getClickedPos(); BlockPos blockPos = ctx.getClickedPos();
Direction[] directions = ctx.getNearestLookingDirections(); Direction[] directions = ctx.getNearestLookingDirections();
for (Direction direction : directions) { for (Direction direction : directions) {
if (direction.getAxis().isHorizontal()) { if (direction.getAxis().isHorizontal()) {
Direction dir = direction.getOpposite(); Direction dir = direction.getOpposite();
int rot = Mth.floor((180.0 + dir.toYRot() * 16.0 / 360.0) + 0.5 + 4) & 15; int rot = Mth.floor((180.0 + dir.toYRot() * 16.0 / 360.0) + 0.5 + 4) & 15;
blockState = blockState.setValue(ROTATION, rot); blockState = blockState.setValue(ROTATION, rot);
if (blockState.canSurvive(worldView, blockPos)) { if (blockState.canSurvive(worldView, blockPos)) {
return blockState.setValue(FLOOR, false) return blockState.setValue(FLOOR, false).setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER);
.setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER);
} }
} }
} }
} }
return null; return null;
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
ResourceLocation parentId = Registry.BLOCK.getKey(parent); ResourceLocation parentId = Registry.BLOCK.getKey(parent);
return ModelsHelper.createBlockEmpty(parentId); return ModelsHelper.createBlockEmpty(parentId);
} }
@Override @Override
public BlockState rotate(BlockState state, Rotation rotation) { public BlockState rotate(BlockState state, Rotation rotation) {
return state.setValue(ROTATION, rotation.rotate((Integer) state.getValue(ROTATION), 16)); return state.setValue(ROTATION, rotation.rotate((Integer) state.getValue(ROTATION), 16));
} }
@Override @Override
public BlockState mirror(BlockState state, Mirror mirror) { public BlockState mirror(BlockState state, Mirror mirror) {
return state.setValue(ROTATION, mirror.mirror((Integer) state.getValue(ROTATION), 16)); return state.setValue(ROTATION, mirror.mirror((Integer) state.getValue(ROTATION), 16));
@ -175,19 +169,26 @@ public class BaseSignBlock extends SignBlock implements BlockModelProvider, Cust
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
@Override @Override
public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) {
// TODO Auto-generated method stub
return super.canPlaceLiquid(world, pos, state, fluid); return super.canPlaceLiquid(world, pos, state, fluid);
} }
@Override @Override
public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) {
// TODO Auto-generated method stub
return super.placeLiquid(world, pos, state, fluidState); return super.placeLiquid(world, pos, state, fluidState);
} }
@Override @Override
public BlockItem getCustomItem(ResourceLocation blockID, FabricItemSettings settings) { public int getStackSize() {
return new BlockItem(this, settings.stacksTo(16)); return 16;
}
@Override
public boolean canPlaceOnWater() {
return false;
} }
} }

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -14,16 +21,10 @@ import net.minecraft.world.level.block.SlabBlock;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.SlabType; import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseSlabBlock extends SlabBlock implements BlockModelProvider { public class BaseSlabBlock extends SlabBlock implements BlockModelProvider {
private final Block parent; private final Block parent;
@ -32,20 +33,19 @@ public class BaseSlabBlock extends SlabBlock implements BlockModelProvider {
super(FabricBlockSettings.copyOf(source)); super(FabricBlockSettings.copyOf(source));
this.parent = source; this.parent = source;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
int count = state.getValue(TYPE) == SlabType.DOUBLE ? 2 : 1; return Collections.singletonList(new ItemStack(this));
return Collections.singletonList(new ItemStack(this, count));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return getBlockModel(resourceLocation, defaultBlockState()); return getBlockModel(resourceLocation, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
@ -53,21 +53,18 @@ public class BaseSlabBlock extends SlabBlock implements BlockModelProvider {
Optional<String> pattern; Optional<String> pattern;
if (blockState.getValue(TYPE) == SlabType.DOUBLE) { if (blockState.getValue(TYPE) == SlabType.DOUBLE) {
pattern = PatternsHelper.createBlockSimple(parentId); pattern = PatternsHelper.createBlockSimple(parentId);
} } else {
else {
pattern = PatternsHelper.createJson(BasePatterns.BLOCK_SLAB, parentId); pattern = PatternsHelper.createJson(BasePatterns.BLOCK_SLAB, parentId);
} }
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
SlabType type = blockState.getValue(TYPE); SlabType type = blockState.getValue(TYPE);
ResourceLocation modelId = new ResourceLocation( ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(),
stateId.getNamespace(), "block/" + stateId.getPath() + "_" + type);
"block/" + stateId.getPath() + "_" + type
);
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
if (type == SlabType.TOP) { if (type == SlabType.TOP) {
return ModelsHelper.createMultiVariant(modelId, BlockModelRotation.X180_Y0.getRotation(), true); return ModelsHelper.createMultiVariant(modelId, BlockModelRotation.X180_Y0.getRotation(), true);

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -15,18 +22,13 @@ 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.Half;
import net.minecraft.world.level.block.state.properties.StairsShape; import net.minecraft.world.level.block.state.properties.StairsShape;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseStairsBlock extends StairBlock implements BlockModelProvider { public class BaseStairsBlock extends StairBlock implements BlockModelProvider {
private final Block parent; private final Block parent;
public BaseStairsBlock(Block source) { public BaseStairsBlock(Block source) {
@ -39,13 +41,13 @@ public class BaseStairsBlock extends StairBlock implements BlockModelProvider {
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return getBlockModel(resourceLocation, defaultBlockState()); return getBlockModel(resourceLocation, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
@ -66,7 +68,7 @@ public class BaseStairsBlock extends StairBlock implements BlockModelProvider {
} }
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
@ -75,21 +77,21 @@ public class BaseStairsBlock extends StairBlock implements BlockModelProvider {
switch (shape) { switch (shape) {
case INNER_LEFT: case INNER_LEFT:
case INNER_RIGHT: case INNER_RIGHT:
state = "_inner"; state = "_inner"; break;
break;
case OUTER_LEFT: case OUTER_LEFT:
case OUTER_RIGHT: case OUTER_RIGHT:
state = "_outer"; state = "_outer"; break;
break;
default: default:
state = ""; state = "";
} }
ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state); ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state);
registerBlockModel(stateId, modelId, blockState, modelCache); registerBlockModel(stateId, modelId, blockState, modelCache);
boolean isTop = blockState.getValue(HALF) == Half.TOP; boolean isTop = blockState.getValue(HALF) == Half.TOP;
boolean isLeft = shape == StairsShape.INNER_LEFT || shape == StairsShape.OUTER_LEFT; boolean isLeft = shape == StairsShape.INNER_LEFT ||
boolean isRight = shape == StairsShape.INNER_RIGHT || shape == StairsShape.OUTER_RIGHT; shape == StairsShape.OUTER_LEFT;
boolean isRight = shape == StairsShape.INNER_RIGHT ||
shape == StairsShape.OUTER_RIGHT;
int y = 0; int y = 0;
int x = isTop ? 180 : 0; int x = isTop ? 180 : 0;
switch (blockState.getValue(FACING)) { switch (blockState.getValue(FACING)) {

View file

@ -6,10 +6,11 @@ import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
public class BaseStoneButtonBlock extends BaseButtonBlock { public class BaseStoneButtonBlock extends BaseButtonBlock {
public BaseStoneButtonBlock(Block source) { public BaseStoneButtonBlock(Block source) {
super(source, FabricBlockSettings.copyOf(source).noOcclusion(), false); super(source, FabricBlockSettings.copyOf(source).noOcclusion(), false);
} }
@Override @Override
protected SoundEvent getSound(boolean clicked) { protected SoundEvent getSound(boolean clicked) {
return clicked ? SoundEvents.STONE_BUTTON_CLICK_ON : SoundEvents.STONE_BUTTON_CLICK_OFF; return clicked ? SoundEvents.STONE_BUTTON_CLICK_ON : SoundEvents.STONE_BUTTON_CLICK_OFF;

View file

@ -20,7 +20,7 @@ public class BaseStripableLogBlock extends BaseRotatedPillarBlock {
private final Block striped; private final Block striped;
public BaseStripableLogBlock(MaterialColor color, Block striped) { public BaseStripableLogBlock(MaterialColor color, Block striped) {
super(FabricBlockSettings.copyOf(striped).color(color)); super(FabricBlockSettings.copyOf(striped).mapColor(color));
this.striped = striped; this.striped = striped;
} }
@ -30,11 +30,7 @@ public class BaseStripableLogBlock extends BaseRotatedPillarBlock {
if (FabricToolTags.AXES.contains(player.getMainHandItem().getItem())) { if (FabricToolTags.AXES.contains(player.getMainHandItem().getItem())) {
world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F);
if (!world.isClientSide) { if (!world.isClientSide) {
world.setBlock(pos, world.setBlock(pos, striped.defaultBlockState().setValue(RotatedPillarBlock.AXIS, state.getValue(RotatedPillarBlock.AXIS)), 11);
striped.defaultBlockState()
.setValue(RotatedPillarBlock.AXIS, state.getValue(RotatedPillarBlock.AXIS)),
11
);
if (!player.isCreative()) { if (!player.isCreative()) {
player.getMainHandItem().hurt(1, world.random, (ServerPlayer) player); player.getMainHandItem().hurt(1, world.random, (ServerPlayer) player);
} }

View file

@ -1,6 +1,15 @@
package ru.bclib.blocks; 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 com.google.common.collect.Maps;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -32,41 +41,30 @@ import net.minecraft.world.level.material.MaterialColor;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.client.sound.BlockSounds; import ru.bclib.client.sound.BlockSounds;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class BaseTerrainBlock extends BaseBlock { public class BaseTerrainBlock extends BaseBlock {
private final Block baseBlock; private final Block baseBlock;
private Block pathBlock; private Block pathBlock;
public BaseTerrainBlock(Block baseBlock, MaterialColor color) { public BaseTerrainBlock(Block baseBlock, MaterialColor color) {
super(FabricBlockSettings super(FabricBlockSettings.copyOf(baseBlock).materialColor(color).sound(BlockSounds.TERRAIN_SOUND).randomTicks());
.copyOf(baseBlock)
.materialColor(color)
.sound(BlockSounds.TERRAIN_SOUND)
.randomTicks()
);
this.baseBlock = baseBlock; this.baseBlock = baseBlock;
} }
public void setPathBlock(Block roadBlock) { public void setPathBlock(Block roadBlock) {
this.pathBlock = roadBlock; this.pathBlock = roadBlock;
} }
public Block getBaseBlock() { public Block getBaseBlock() {
return baseBlock; return baseBlock;
} }
@Override @Override
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (pathBlock != null && FabricToolTags.SHOVELS.contains(player.getMainHandItem().getItem())) { if (pathBlock != null && FabricToolTags.SHOVELS.contains(player.getMainHandItem().getItem())) {
@ -88,16 +86,16 @@ public class BaseTerrainBlock extends BaseBlock {
if (tool != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { if (tool != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
return Collections.singletonList(new ItemStack(getBaseBlock())); return Collections.singletonList(new ItemStack(Blocks.END_STONE));
} }
@Override @Override
public void randomTick(BlockState state, ServerLevel world, BlockPos pos, Random random) { public void randomTick(BlockState state, ServerLevel world, BlockPos pos, Random random) {
if (random.nextInt(16) == 0 && !canStay(state, world, pos)) { if (random.nextInt(16) == 0 && !canStay(state, world, pos)) {
world.setBlockAndUpdate(pos, getBaseBlock().defaultBlockState()); world.setBlockAndUpdate(pos, Blocks.END_STONE.defaultBlockState());
} }
} }
public boolean canStay(BlockState state, LevelReader worldView, BlockPos pos) { public boolean canStay(BlockState state, LevelReader worldView, BlockPos pos) {
BlockPos blockPos = pos.above(); BlockPos blockPos = pos.above();
BlockState blockState = worldView.getBlockState(blockPos); BlockState blockState = worldView.getBlockState(blockPos);
@ -108,15 +106,7 @@ public class BaseTerrainBlock extends BaseBlock {
return false; return false;
} }
else { else {
int i = LayerLightEngine.getLightBlockInto( int i = LayerLightEngine.getLightBlockInto(worldView, state, pos, blockState, blockPos, Direction.UP, blockState.getLightBlock(worldView, blockPos));
worldView,
state,
pos,
blockState,
blockPos,
Direction.UP,
blockState.getLightBlock(worldView, blockPos)
);
return i < 5; return i < 5;
} }
} }
@ -126,11 +116,11 @@ public class BaseTerrainBlock extends BaseBlock {
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
return getBlockModel(blockId, defaultBlockState()); return getBlockModel(blockId, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
ResourceLocation baseId = Registry.BLOCK.getKey(getBaseBlock()); ResourceLocation baseId = Registry.BLOCK.getKey(baseBlock);
String modId = blockId.getNamespace(); String modId = blockId.getNamespace();
String path = blockId.getPath(); String path = blockId.getPath();
String bottom = baseId.getNamespace() + ":block/" + baseId.getPath(); String bottom = baseId.getNamespace() + ":block/" + baseId.getPath();
@ -141,7 +131,7 @@ public class BaseTerrainBlock extends BaseBlock {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_TOP_SIDE_BOTTOM, textures); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_TOP_SIDE_BOTTOM, textures);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {

View file

@ -1,5 +1,13 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -10,33 +18,21 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.TrapDoorBlock; 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.BlockState;
import net.minecraft.world.level.block.state.properties.Half; import net.minecraft.world.level.block.state.properties.Half;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.client.render.BCLRenderLayer; import ru.bclib.client.render.BCLRenderLayer;
import ru.bclib.interfaces.BlockModelProvider; import ru.bclib.interfaces.IRenderTyped;
import ru.bclib.interfaces.RenderLayerProvider;
import java.util.Collections; public class BaseTrapdoorBlock extends TrapDoorBlock implements IRenderTyped, BlockModelProvider {
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseTrapdoorBlock extends TrapDoorBlock implements RenderLayerProvider, BlockModelProvider {
public BaseTrapdoorBlock(Block source) { public BaseTrapdoorBlock(Block source) {
this(FabricBlockSettings.copyOf(source).strength(3.0F, 3.0F).noOcclusion()); super(FabricBlockSettings.copyOf(source).strength(3.0F, 3.0F).noOcclusion());
} }
public BaseTrapdoorBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
@ -47,32 +43,28 @@ public class BaseTrapdoorBlock extends TrapDoorBlock implements RenderLayerProvi
public BCLRenderLayer getRenderLayer() { public BCLRenderLayer getRenderLayer() {
return BCLRenderLayer.CUTOUT; return BCLRenderLayer.CUTOUT;
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation resourceLocation) { public BlockModel getItemModel(ResourceLocation resourceLocation) {
return getBlockModel(resourceLocation, defaultBlockState()); return getBlockModel(resourceLocation, defaultBlockState());
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) {
String name = resourceLocation.getPath(); String name = resourceLocation.getPath();
Optional<String> pattern = PatternsHelper.createJson( Optional<String> pattern = PatternsHelper.createJson(BasePatterns.BLOCK_TRAPDOOR, new HashMap<String, String>() {
BasePatterns.BLOCK_TRAPDOOR, private static final long serialVersionUID = 1L;
new HashMap<String, String>() { {
private static final long serialVersionUID = 1L; put("%modid%", resourceLocation.getNamespace());
put("%texture%", name);
{ put("%side%", name.replace("trapdoor", "door_side"));
put("%modid%", resourceLocation.getNamespace());
put("%texture%", name);
put("%side%", name.replace("trapdoor", "door_side"));
}
} }
); });
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
@ -95,8 +87,7 @@ public class BaseTrapdoorBlock extends TrapDoorBlock implements RenderLayerProvi
case WEST: case WEST:
y = (isTop && isOpen) ? 90 : 270; y = (isTop && isOpen) ? 90 : 270;
break; break;
default: default: break;
break;
} }
BlockModelRotation rotation = BlockModelRotation.by(x, y); BlockModelRotation rotation = BlockModelRotation.by(x, y);
return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false);

View file

@ -1,6 +1,7 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; 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.BlockPos;
import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelAccessor;
@ -14,25 +15,22 @@ import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.material.Material; import net.minecraft.world.level.material.Material;
public abstract class BaseUnderwaterWallPlantBlock extends BaseWallPlantBlock implements LiquidBlockContainer { public abstract class BaseUnderwaterWallPlantBlock extends BaseWallPlantBlock implements LiquidBlockContainer {
public BaseUnderwaterWallPlantBlock() { public BaseUnderwaterWallPlantBlock() {
this( super(FabricBlockSettings.of(Material.WATER_PLANT)
FabricBlockSettings .breakByTool(FabricToolTags.SHEARS)
.of(Material.WATER_PLANT)
.breakByHand(true) .breakByHand(true)
.sound(SoundType.WET_GRASS) .sound(SoundType.WET_GRASS)
.noCollission() .noCollission());
);
} }
public BaseUnderwaterWallPlantBlock(int light) { public BaseUnderwaterWallPlantBlock(int light) {
this( super(FabricBlockSettings.of(Material.WATER_PLANT)
FabricBlockSettings .breakByTool(FabricToolTags.SHEARS)
.of(Material.WATER_PLANT)
.breakByHand(true) .breakByHand(true)
.luminance(light) .luminance(light)
.sound(SoundType.WET_GRASS) .sound(SoundType.WET_GRASS)
.noCollission() .noCollission());
);
} }
public BaseUnderwaterWallPlantBlock(Properties settings) { public BaseUnderwaterWallPlantBlock(Properties settings) {
@ -43,7 +41,7 @@ public abstract class BaseUnderwaterWallPlantBlock extends BaseWallPlantBlock im
public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) {
return false; return false;
} }
@Override @Override
public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) {
return false; return false;

View file

@ -1,6 +1,10 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import java.util.List;
import java.util.Random;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -30,14 +34,11 @@ import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShape;
import ru.bclib.blocks.BlockProperties.TripleShape; import ru.bclib.blocks.BlockProperties.TripleShape;
import ru.bclib.client.render.BCLRenderLayer; import ru.bclib.client.render.BCLRenderLayer;
import ru.bclib.interfaces.RenderLayerProvider; import ru.bclib.interfaces.IRenderTyped;
import ru.bclib.util.BlocksHelper; import ru.bclib.util.BlocksHelper;
import java.util.List;
import java.util.Random;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock { public class BaseVineBlock extends BaseBlockNotFull implements IRenderTyped, BonemealableBlock {
public static final EnumProperty<TripleShape> SHAPE = BlockProperties.TRIPLE_SHAPE; public static final EnumProperty<TripleShape> SHAPE = BlockProperties.TRIPLE_SHAPE;
private static final VoxelShape VOXEL_SHAPE = Block.box(2, 0, 2, 14, 16, 14); private static final VoxelShape VOXEL_SHAPE = Block.box(2, 0, 2, 14, 16, 14);
@ -50,18 +51,12 @@ public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvid
} }
public BaseVineBlock(int light, boolean bottomOnly) { public BaseVineBlock(int light, boolean bottomOnly) {
this( super(FabricBlockSettings.of(Material.PLANT)
FabricBlockSettings .breakByTool(FabricToolTags.SHEARS)
.of(Material.PLANT)
.breakByHand(true) .breakByHand(true)
.sound(SoundType.GRASS) .sound(SoundType.GRASS)
.lightLevel((state) -> bottomOnly ? state.getValue(SHAPE) == TripleShape.BOTTOM ? light : 0 : light) .lightLevel((state) -> bottomOnly ? state.getValue(SHAPE) == TripleShape.BOTTOM ? light : 0 : light)
.noCollission() .noCollission());
);
}
public BaseVineBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(SHAPE, TripleShape.BOTTOM)); this.registerDefaultState(this.stateDefinition.any().setValue(SHAPE, TripleShape.BOTTOM));
} }
@ -69,13 +64,13 @@ public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvid
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) { protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) {
stateManager.add(SHAPE); stateManager.add(SHAPE);
} }
@Override @Override
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
Vec3 vec3d = state.getOffset(view, pos); Vec3 vec3d = state.getOffset(view, pos);
return VOXEL_SHAPE.move(vec3d.x, vec3d.y, vec3d.z); return VOXEL_SHAPE.move(vec3d.x, vec3d.y, vec3d.z);
} }
@Override @Override
public BlockBehaviour.OffsetType getOffsetType() { public BlockBehaviour.OffsetType getOffsetType() {
return BlockBehaviour.OffsetType.XZ; return BlockBehaviour.OffsetType.XZ;
@ -84,7 +79,7 @@ public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvid
public boolean canGenerate(BlockState state, LevelReader world, BlockPos pos) { public boolean canGenerate(BlockState state, LevelReader world, BlockPos pos) {
return isSupport(state, world, pos); return isSupport(state, world, pos);
} }
@Override @Override
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
return isSupport(state, world, pos); return isSupport(state, world, pos);
@ -94,15 +89,17 @@ public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvid
BlockState up = world.getBlockState(pos.above()); BlockState up = world.getBlockState(pos.above());
return up.is(this) || up.is(BlockTags.LEAVES) || canSupportCenter(world, pos.above(), Direction.DOWN); return up.is(this) || up.is(BlockTags.LEAVES) || canSupportCenter(world, pos.above(), Direction.DOWN);
} }
@Override @Override
public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
if (!canSurvive(state, world, pos)) { if (!canSurvive(state, world, pos)) {
return Blocks.AIR.defaultBlockState(); return Blocks.AIR.defaultBlockState();
} }
else { else {
if (world.getBlockState(pos.below()).getBlock() != this) return state.setValue(SHAPE, TripleShape.BOTTOM); if (world.getBlockState(pos.below()).getBlock() != this)
else if (world.getBlockState(pos.above()).getBlock() != this) return state.setValue(SHAPE, TripleShape.TOP); 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); return state.setValue(SHAPE, TripleShape.MIDDLE);
} }
} }
@ -110,10 +107,7 @@ public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvid
@Override @Override
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
ItemStack tool = builder.getParameter(LootContextParams.TOOL); ItemStack tool = builder.getParameter(LootContextParams.TOOL);
if (tool != null && FabricToolTags.SHEARS.contains(tool.getItem()) || EnchantmentHelper.getItemEnchantmentLevel( if (tool != null && FabricToolTags.SHEARS.contains(tool.getItem()) || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) {
Enchantments.SILK_TOUCH,
tool
) > 0) {
return Lists.newArrayList(new ItemStack(this)); return Lists.newArrayList(new ItemStack(this));
} }
else { else {
@ -125,7 +119,7 @@ public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvid
public BCLRenderLayer getRenderLayer() { public BCLRenderLayer getRenderLayer() {
return BCLRenderLayer.CUTOUT; return BCLRenderLayer.CUTOUT;
} }
@Override @Override
public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) {
while (world.getBlockState(pos).getBlock() == this) { while (world.getBlockState(pos).getBlock() == this) {
@ -133,7 +127,7 @@ public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvid
} }
return world.getBlockState(pos).isAir(); return world.getBlockState(pos).isAir();
} }
@Override @Override
public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) {
while (world.getBlockState(pos).getBlock() == this) { while (world.getBlockState(pos).getBlock() == this) {
@ -141,7 +135,7 @@ public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvid
} }
return world.isEmptyBlock(pos); return world.isEmptyBlock(pos);
} }
@Override @Override
public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) { public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) {
while (world.getBlockState(pos).getBlock() == this) { while (world.getBlockState(pos).getBlock() == this) {

View file

@ -1,5 +1,12 @@
package ru.bclib.blocks; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
@ -14,31 +21,26 @@ import net.minecraft.world.level.block.WallBlock;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.WallSide; import net.minecraft.world.level.block.state.properties.WallSide;
import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.Nullable;
import ru.bclib.client.models.BasePatterns; import ru.bclib.client.models.BasePatterns;
import ru.bclib.client.models.BlockModelProvider;
import ru.bclib.client.models.ModelsHelper; import ru.bclib.client.models.ModelsHelper;
import ru.bclib.client.models.PatternsHelper; import ru.bclib.client.models.PatternsHelper;
import ru.bclib.interfaces.BlockModelProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BaseWallBlock extends WallBlock implements BlockModelProvider { public class BaseWallBlock extends WallBlock implements BlockModelProvider {
private final Block parent; private final Block parent;
public BaseWallBlock(Block source) { public BaseWallBlock(Block source) {
super(FabricBlockSettings.copyOf(source).noOcclusion()); super(FabricBlockSettings.copyOf(source).noOcclusion());
this.parent = source; this.parent = source;
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) { public List<ItemStack> getDrops(BlockState state, LootContext.Builder builder) {
return Collections.singletonList(new ItemStack(this)); return Collections.singletonList(new ItemStack(this));
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public BlockModel getItemModel(ResourceLocation blockId) { public BlockModel getItemModel(ResourceLocation blockId) {
@ -46,7 +48,7 @@ public class BaseWallBlock extends WallBlock implements BlockModelProvider {
Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_WALL, parentId); Optional<String> pattern = PatternsHelper.createJson(BasePatterns.ITEM_WALL, parentId);
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) {
@ -64,58 +66,37 @@ public class BaseWallBlock extends WallBlock implements BlockModelProvider {
} }
return ModelsHelper.fromPattern(pattern); return ModelsHelper.fromPattern(pattern);
} }
@Override @Override
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) { public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map<ResourceLocation, UnbakedModel> modelCache) {
ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_post"); ResourceLocation postId = new ResourceLocation(stateId.getNamespace(),
ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_side"); "block/" + stateId.getPath() + "_post");
ResourceLocation sideTallId = new ResourceLocation( ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(),
stateId.getNamespace(), "block/" + stateId.getPath() + "_side");
"block/" + stateId.getPath() + "_side_tall" ResourceLocation sideTallId = new ResourceLocation(stateId.getNamespace(),
); "block/" + stateId.getPath() + "_side_tall");
registerBlockModel(postId, postId, blockState, modelCache); registerBlockModel(postId, postId, blockState, modelCache);
registerBlockModel(sideId, sideId, blockState, modelCache); registerBlockModel(sideId, sideId, blockState, modelCache);
registerBlockModel(sideTallId, sideTallId, blockState, modelCache); registerBlockModel(sideTallId, sideTallId, blockState, modelCache);
ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition); 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(NORTH_WALL) == WallSide.LOW).setUVLock(true).add();
builder.part(sideId) builder.part(sideId).setCondition(state -> state.getValue(EAST_WALL) == WallSide.LOW)
.setCondition(state -> state.getValue(EAST_WALL) == WallSide.LOW) .setTransformation(BlockModelRotation.X0_Y90.getRotation()).setUVLock(true).add();
.setTransformation(BlockModelRotation.X0_Y90.getRotation()) builder.part(sideId).setCondition(state -> state.getValue(SOUTH_WALL) == WallSide.LOW)
.setUVLock(true) .setTransformation(BlockModelRotation.X0_Y180.getRotation()).setUVLock(true).add();
.add(); builder.part(sideId).setCondition(state -> state.getValue(WEST_WALL) == WallSide.LOW)
builder.part(sideId) .setTransformation(BlockModelRotation.X0_Y270.getRotation()).setUVLock(true).add();
.setCondition(state -> state.getValue(SOUTH_WALL) == WallSide.LOW) builder.part(sideTallId).setCondition(state -> state.getValue(NORTH_WALL) == WallSide.TALL).setUVLock(true).add();
.setTransformation(BlockModelRotation.X0_Y180.getRotation()) builder.part(sideTallId).setCondition(state -> state.getValue(EAST_WALL) == WallSide.TALL)
.setUVLock(true) .setTransformation(BlockModelRotation.X0_Y90.getRotation()).setUVLock(true).add();
.add(); builder.part(sideTallId).setCondition(state -> state.getValue(SOUTH_WALL) == WallSide.TALL)
builder.part(sideId) .setTransformation(BlockModelRotation.X0_Y180.getRotation()).setUVLock(true).add();
.setCondition(state -> state.getValue(WEST_WALL) == WallSide.LOW) builder.part(sideTallId).setCondition(state -> state.getValue(WEST_WALL) == WallSide.TALL)
.setTransformation(BlockModelRotation.X0_Y270.getRotation()) .setTransformation(BlockModelRotation.X0_Y270.getRotation()).setUVLock(true).add();
.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(); builder.part(postId).setCondition(state -> state.getValue(UP)).add();
return builder.build(); return builder.build();
} }
} }

View file

@ -1,8 +1,12 @@
package ru.bclib.blocks; package ru.bclib.blocks;
import java.util.EnumMap;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; 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.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.item.context.BlockPlaceContext;
@ -24,36 +28,29 @@ import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShape;
import ru.bclib.util.BlocksHelper; import ru.bclib.util.BlocksHelper;
import java.util.EnumMap;
public abstract class BaseWallPlantBlock extends BasePlantBlock { public abstract class BaseWallPlantBlock extends BasePlantBlock {
private static final EnumMap<Direction, VoxelShape> SHAPES = Maps.newEnumMap(ImmutableMap.of( private static final EnumMap<Direction, VoxelShape> SHAPES = Maps.newEnumMap(ImmutableMap.of(
Direction.NORTH, Block.box(1, 1, 8, 15, 15, 16), Direction.NORTH, Block.box(1, 1, 8, 15, 15, 16),
Direction.SOUTH, Block.box(1, 1, 0, 15, 15, 8), Direction.SOUTH, Block.box(1, 1, 0, 15, 15, 8),
Direction.WEST, Block.box(8, 1, 1, 16, 15, 15), Direction.WEST, Block.box(8, 1, 1, 16, 15, 15),
Direction.EAST, Block.box(0, 1, 1, 8, 15, 15) Direction.EAST, Block.box(0, 1, 1, 8, 15, 15)));
));
public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING; public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING;
public BaseWallPlantBlock() { public BaseWallPlantBlock() {
this( this(FabricBlockSettings.of(Material.PLANT)
FabricBlockSettings .breakByTool(FabricToolTags.SHEARS)
.of(Material.PLANT)
.breakByHand(true) .breakByHand(true)
.sound(SoundType.GRASS) .sound(SoundType.GRASS)
.noCollission() .noCollission());
);
} }
public BaseWallPlantBlock(int light) { public BaseWallPlantBlock(int light) {
this( this(FabricBlockSettings.of(Material.PLANT)
FabricBlockSettings .breakByTool(FabricToolTags.SHEARS)
.of(Material.PLANT)
.breakByHand(true) .breakByHand(true)
.luminance(light) .luminance(light)
.sound(SoundType.GRASS) .sound(SoundType.GRASS)
.noCollission() .noCollission());
);
} }
public BaseWallPlantBlock(Properties settings) { public BaseWallPlantBlock(Properties settings) {
@ -64,17 +61,17 @@ public abstract class BaseWallPlantBlock extends BasePlantBlock {
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) { protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> stateManager) {
stateManager.add(FACING); stateManager.add(FACING);
} }
@Override @Override
public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) {
return SHAPES.get(state.getValue(FACING)); return SHAPES.get(state.getValue(FACING));
} }
@Override @Override
public BlockBehaviour.OffsetType getOffsetType() { public BlockBehaviour.OffsetType getOffsetType() {
return BlockBehaviour.OffsetType.NONE; return BlockBehaviour.OffsetType.NONE;
} }
@Override @Override
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
Direction direction = state.getValue(FACING); Direction direction = state.getValue(FACING);
@ -104,7 +101,7 @@ public abstract class BaseWallPlantBlock extends BasePlantBlock {
} }
return null; return null;
} }
@Override @Override
public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
if (!canSurvive(state, world, pos)) { if (!canSurvive(state, world, pos)) {
@ -120,7 +117,7 @@ public abstract class BaseWallPlantBlock extends BasePlantBlock {
public BlockState rotate(BlockState state, Rotation rotation) { public BlockState rotate(BlockState state, Rotation rotation) {
return BlocksHelper.rotateHorizontal(state, rotation, FACING); return BlocksHelper.rotateHorizontal(state, rotation, FACING);
} }
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirror) { public BlockState mirror(BlockState state, Mirror mirror) {

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