Compare commits

...

48 Commits

Author SHA1 Message Date
octarine-noise
178a014a6b bump version 2021-07-30 15:57:56 +02:00
octarine-noise
0b802663dc fix ignored snowed leaves config 2021-07-26 23:32:02 +02:00
octarine-noise
d2b9326ced mod support: BOP, BYG, Environmental, Desolation 2021-07-26 23:29:58 +02:00
octarine-noise
31eddf682d get leaf particle type from rules 2021-07-26 22:43:42 +02:00
octarine-noise
e689a44687 simpler parser, allow for negated conditions 2021-07-26 22:42:54 +02:00
octarine-noise
b4824b77ae fix leaf block & leaf particle colors 2021-07-26 14:52:26 +02:00
octarine-noise
4c08354d74 Leaf shader wind integration 2021-07-26 12:15:38 +02:00
octarine-noise
0518b01b50 Optifine compatibility for mixin 2021-07-26 12:14:57 +02:00
octarine-noise
563a67f213 fix wrong package name for Refs 2021-07-26 12:13:06 +02:00
octarine-noise
4637e282ce cleaner rule matching implementation based on Either 2021-07-15 00:53:24 +02:00
octarine-noise
8ef84718b5 get tint index directly from model 2021-07-13 20:06:45 +02:00
octarine-noise
1e69081a2f calculate average texture color without atlas 2021-07-13 20:05:15 +02:00
octarine-noise
c418e001b0 change overlay layer to use Maps as storage 2021-07-13 17:07:06 +02:00
octarine-noise
4ce7eda78b fix snowed short grass color 2021-07-13 17:05:57 +02:00
octarine-noise
145e07363c support for Nylium blocks 2021-07-12 21:47:06 +02:00
octarine-noise
54e245bcd4 add rules for crops 2021-07-12 20:49:45 +02:00
octarine-noise
d2485cd323 remove old block config 2021-07-12 20:27:55 +02:00
octarine-noise
257593d231 use new config parser
+BakeWrapperManager no longer singleton
2021-07-12 20:21:26 +02:00
octarine-noise
c8e79c22ff [WIP] Config parser 2021-07-12 19:13:42 +02:00
octarine-noise
29ab544269 Multi-layer rendering support 2021-07-06 00:06:11 +02:00
octarine-noise
78c7b53595 bump version to 2.6.5 2021-06-02 16:41:26 +02:00
octarine-noise
3a04099fc2 fix chinese lang file 2021-05-29 23:47:13 +02:00
octarine-noise
d6038dd072 add PackNameDecorator to avoid NPE
Forge allows pack hiding, but just in case
2021-05-29 23:43:14 +02:00
octarine-noise
4ebb7f2d35 fix blocks with flat lighting 2021-05-29 23:27:20 +02:00
octarine-noise
14a8600552 fix rising soul particle texture 2021-05-29 16:24:05 +02:00
octarine-noise
b3ffb7e4d6 Merge remote-tracking branch 'local/forge-1.15' into forge-1.16
# Conflicts:
#	src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt
2021-05-22 19:22:10 +02:00
octarine-noise
fc7f2be15c language key fixes 2021-05-22 19:20:34 +02:00
octarine-noise
b7bdd438e4 upgrade to Cloth Config v4 2021-05-22 19:00:54 +02:00
octarine-noise
65c9596a14 Merge remote-tracking branch 'local/forge-1.15' into forge-1.16
# Conflicts:
#	build.gradle.kts
#	src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt
#	src/main/kotlin/mods/betterfoliage/config/Config.kt
#	src/main/resources/assets/betterfoliage/lang/en_us.lang
2021-05-22 18:38:10 +02:00
octarine-noise
25b8896a25 log config GUI registration 2021-05-22 18:34:18 +02:00
octarine-noise
f1f811219e Merge remote-tracking branch 'local/forge-1.14' into forge-1.15
# Conflicts:
#	build.gradle.kts
#	gradle.properties
#	src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt
#	src/main/kotlin/mods/betterfoliage/client/Client.kt
#	src/main/kotlin/mods/betterfoliage/client/integration/ForestryIntegration.kt
#	src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt
#	src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt
#	src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt
#	src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt
#	src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt
#	src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt
#	src/main/kotlin/mods/betterfoliage/client/texture/Utils.kt
#	src/main/kotlin/mods/betterfoliage/config/Config.kt
#	src/main/kotlin/mods/betterfoliage/config/DelegatingConfig.kt
#	src/main/kotlin/mods/betterfoliage/config/MainConfig.kt
#	src/main/kotlin/mods/betterfoliage/resource/discovery/Delegate.kt
#	src/main/kotlin/mods/betterfoliage/resource/generated/CenteringTextureGenerator.kt
#	src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedGrass.kt
#	src/main/kotlin/mods/octarinecore/client/resource/AsyncSpriteProviderManager.kt
#	src/main/kotlin/mods/octarinecore/client/resource/ModelDiscovery.kt
#	src/main/kotlin/mods/octarinecore/client/resource/ResourceGeneration.kt
#	src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt
2021-05-22 18:07:32 +02:00
octarine-noise
57dc83f1af config GUI misc fixes 2021-05-22 17:49:57 +02:00
octarine-noise
180c2bf230 bump version to 2.5.2 2021-05-22 17:29:13 +02:00
octarine-noise
ff89aa7a13 add client-only version tester 2021-05-22 17:27:51 +02:00
octarine-noise
25cea8633c Integrate ClothConfig for Forge 2021-05-22 17:27:28 +02:00
octarine-noise
eeabc1922e translations JSON conversion
+ some cleanup
2021-05-22 17:25:48 +02:00
XiLaiTL
d7e16d603f Add files via upload
(cherry picked from commit ce8afacb019336962848ade302e7fe76c2654ebd)
2021-05-22 16:39:09 +02:00
octarine-noise
b1a08ab500 Merge remote-tracking branch 'local/forge-1.15' into forge-1.16
# Conflicts:
#	gradle.properties
#	src/main/kotlin/mods/betterfoliage/integration/ShadersModIntegration.kt
#	src/main/kotlin/mods/betterfoliage/model/HalfBaked.kt
#	src/main/kotlin/mods/betterfoliage/render/lighting/VanillaAoCalculation.kt
2021-05-16 12:30:07 +02:00
octarine-noise
fae9e9dfa9 adopt AO calculation tweak from Indigo 2021-05-16 12:02:42 +02:00
octarine-noise
512cd786f7 fix diffuse shading being too dark with shaders 2021-05-16 12:01:59 +02:00
octarine-noise
b96a17fdb9 clean up access transformer 2021-05-16 11:49:04 +02:00
octarine-noise
3d78ecce22 fix unbaked block model coords not being zero-centered 2021-05-16 11:48:34 +02:00
octarine-noise
6219e9353d fix crash with Abnormals Core
change random display tick mixin injection type from @Redirect to @Inject
2021-05-14 18:55:10 +02:00
octarine-noise
e3eb222d93 deburr build script 2021-05-14 18:52:22 +02:00
octarine-noise
8f82fefbb7 Port to 1.16.5
Kottle -> KotlinForForge
2021-05-14 14:44:29 +02:00
octarine-noise
8a303a1a29 update Gradle wrapper to 6.8.1
ForgeGradle dependency for FG4 onwards
2021-05-14 14:40:16 +02:00
octarine-noise
a97a575dd5 fix missing Optifine shader wind effects
+minor cleanup
2021-05-14 14:36:48 +02:00
octarine-noise
49d4f8aa31 remove some Fabric aliases 2021-04-30 12:28:34 +02:00
103 changed files with 3091 additions and 1953 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ build/
classes/
temp/
logs
src/main/javacc/mods

View File

@@ -1,31 +1,27 @@
plugins {
kotlin("jvm").version("1.3.61")
id("net.minecraftforge.gradle").version("3.0.194")
kotlin("jvm").version("1.4.20")
id("net.minecraftforge.gradle").version("4.1.12")
id("org.spongepowered.mixin").version("0.7-SNAPSHOT")
id("com.intershop.gradle.javacc").version("4.0.0")
}
apply(plugin = "org.spongepowered.mixin")
repositories {
maven("http://files.minecraftforge.net/maven")
maven("https://files.minecraftforge.net/maven")
maven("https://repo.spongepowered.org/maven")
maven("https://minecraft.curseforge.com/api/maven")
maven("https://maven.shedaniel.me/")
maven("https://www.cursemaven.com")
maven("https://thedarkcolour.github.io/KotlinForForge/")
}
dependencies {
"minecraft"("net.minecraftforge:forge:${properties["mcVersion"]}-${properties["forgeVersion"]}")
"api"(fg.deobf("curse.maven:clothconfig-348521:2938583"))
"implementation"(fg.deobf("curse.maven:biomesoplenty-220318:2988999"))
"implementation"("kottle:Kottle:${properties["kottleVersion"]}")
// "implementation"("org.spongepowered:mixin:0.8-SNAPSHOT")
"implementation"("thedarkcolour:kotlinforforge:1.7.0")
"api"(fg.deobf("curse.maven:clothconfig-348521:3311352"))
}
configurations["annotationProcessor"].extendsFrom(configurations["implementation"])
sourceSets {
get("main").ext["refMap"] = "betterfoliage.refmap.json"
get("main").java.srcDir("src/main/javacc/")
}
minecraft {
@@ -42,6 +38,17 @@ minecraft {
}
}
javacc {
configs {
create("blockconfig") {
staticParam = "false"
inputFile = file("src/main/javacc/BlockConfig.jj")
outputDir = file("src/main/javacc/")
packageName = "mods.betterfoliage.config.match.parser"
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
@@ -51,7 +58,6 @@ kotlin {
target.compilations.configureEach {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.freeCompilerArgs += listOf("-Xno-param-assertions", "-Xno-call-assertions")
}
}

View File

@@ -4,11 +4,11 @@ org.gradle.daemon=false
group = com.github.octarine-noise
jarName = BetterFoliage-Forge
version = 2.6.0
version = 2.7.0
mcVersion = 1.15.2
forgeVersion = 31.2.44
mappingsChannel = snapshot
mappingsVersion = 20200514-1.15.1
mcVersion = 1.16.5
forgeVersion = 36.1.17
mappingsChannel = official
mappingsVersion = 1.16.5
kottleVersion = 1.4.0
#kottleVersion = 1.4.0

Binary file not shown.

View File

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

53
gradlew vendored
View File

@@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -66,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -109,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -138,19 +156,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
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" ;;
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
@@ -159,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

43
gradlew.bat vendored
View File

@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -1,6 +1,6 @@
pluginManagement {
repositories {
maven("http://files.minecraftforge.net/maven")
maven("https://files.minecraftforge.net/maven")
maven("https://repo.spongepowered.org/maven")
gradlePluginPortal()
}

View File

@@ -21,7 +21,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
*/
@Mixin(Block.class)
public class MixinBlock {
private static final String shouldSideBeRendered = "Lnet/minecraft/block/Block;shouldSideBeRendered(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Z";
private static final String shouldSideBeRendered = "Lnet/minecraft/block/Block;shouldRenderFace(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Z";
private static final String getVoxelShape = "Lnet/minecraft/block/BlockState;func_215702_a(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
private static final String getFaceOcclusionShape = "Lnet/minecraft/block/BlockState;getFaceOcclusionShape(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
private static final String isOpaqueCube = "Lnet/minecraft/block/Block;isOpaqueCube(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)Z";

View File

@@ -8,7 +8,7 @@ import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.BlockModelRenderer;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ILightReader;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraftforge.client.model.data.IModelData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -19,12 +19,12 @@ import java.util.Random;
@Mixin(BlockModelRenderer.class)
public class MixinBlockModelRenderer {
private static final String renderModel = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModel(Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelFlat = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModelFlat(Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelSmooth = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModelSmooth(Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModel = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModel(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelFlat = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModelFlat(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelSmooth = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModelSmooth(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
@Redirect(method = renderModel, at = @At(value = "INVOKE", target = renderModelSmooth), remap = false)
public boolean onRenderModelSmooth(BlockModelRenderer renderer, ILightReader world, IBakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, IVertexBuilder buffer, boolean checkSides, Random random, long rand, int combinedOverlay, IModelData modelData) {
public boolean onRenderModelSmooth(BlockModelRenderer renderer, IBlockDisplayReader world, IBakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, IVertexBuilder buffer, boolean checkSides, Random random, long rand, int combinedOverlay, IModelData modelData) {
if (model instanceof SpecialRenderModel)
return RenderCtxVanilla.render(renderer, world, (SpecialRenderModel) model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData, true);
else
@@ -32,10 +32,10 @@ public class MixinBlockModelRenderer {
}
@Redirect(method = renderModel, at = @At(value = "INVOKE", target = renderModelFlat), remap = false)
public boolean onRenderModelFlat(BlockModelRenderer renderer, ILightReader world, IBakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, IVertexBuilder buffer, boolean checkSides, Random random, long rand, int combinedOverlay, IModelData modelData) {
public boolean onRenderModelFlat(BlockModelRenderer renderer, IBlockDisplayReader world, IBakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, IVertexBuilder buffer, boolean checkSides, Random random, long rand, int combinedOverlay, IModelData modelData) {
if (model instanceof SpecialRenderModel)
return RenderCtxVanilla.render(renderer, world, (SpecialRenderModel) model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData, false);
else
return renderer.renderModelSmooth(world, model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData);
return renderer.renderModelFlat(world, model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData);
}
}

View File

@@ -1,12 +1,15 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.Hooks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader;
import org.spongepowered.asm.mixin.Debug;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Redirect;
/**
@@ -14,14 +17,15 @@ import org.spongepowered.asm.mixin.injection.Redirect;
*
* Needed to avoid excessive darkening of Round Logs at the corners, now that they are not full blocks.
*/
@Mixin(BlockState.class)
@Mixin(AbstractBlock.AbstractBlockState.class)
@SuppressWarnings({"deprecation"})
public class MixinBlockState {
private static final String callFrom = "Lnet/minecraft/block/BlockState;getAmbientOcclusionLightValue(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";
private static final String callTo = "Lnet/minecraft/block/Block;getAmbientOcclusionLightValue(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";
private static final String callFrom = "Lnet/minecraft/block/AbstractBlock$AbstractBlockState;getShadeBrightness(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";
// why is the INVOKEVIRTUAL target class Block in the bytecode, not AbstractBlock?
private static final String callTo = "Lnet/minecraft/block/Block;getShadeBrightness(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";
@Redirect(method = callFrom, at = @At(value = "INVOKE", target = callTo))
float getAmbientOcclusionValue(Block block, BlockState state, IBlockReader reader, BlockPos pos) {
return Hooks.getAmbientOcclusionLightValueOverride(block.getAmbientOcclusionLightValue(state, reader, pos), state);
return Hooks.getAmbientOcclusionLightValueOverride(block.getShadeBrightness(state, reader, pos), state);
}
}

View File

@@ -1,35 +1,34 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.Hooks;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Random;
@Mixin(ClientWorld.class)
public class MixinClientWorld {
private static final String worldAnimateTick = "Lnet/minecraft/client/world/ClientWorld;animateTick(IIIILjava/util/Random;ZLnet/minecraft/util/math/BlockPos$Mutable;)V";
private static final String worldAnimateTick = "Lnet/minecraft/client/world/ClientWorld;doAnimateTick(IIIILjava/util/Random;ZLnet/minecraft/util/math/BlockPos$Mutable;)V";
private static final String blockAnimateTick = "Lnet/minecraft/block/Block;animateTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V";
private static final String worldNotify = "Lnet/minecraft/client/world/ClientWorld;notifyBlockUpdate(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V";
private static final String rendererNotify = "Lnet/minecraft/client/renderer/WorldRenderer;notifyBlockUpdate(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V";
private static final String worldNotify = "Lnet/minecraft/client/world/ClientWorld;sendBlockUpdated(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V";
private static final String rendererNotify = "Lnet/minecraft/client/renderer/WorldRenderer;blockChanged(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V";
/**
* Inject a callback to call for every random display tick. Used for adding custom particle effects to blocks.
*/
@Redirect(method = worldAnimateTick, at = @At(value = "INVOKE", target = blockAnimateTick))
void onAnimateTick(Block block, BlockState state, World world, BlockPos pos, Random random) {
Hooks.onRandomDisplayTick(block, state, world, pos, random);
block.animateTick(state, world, pos, random);
@Inject(method = worldAnimateTick, at = @At(value = "INVOKE", target = blockAnimateTick))
void onAnimateTick(int x, int y, int z, int range, Random random, boolean doBarrier, BlockPos.Mutable pos, CallbackInfo ci) {
Hooks.onRandomDisplayTick((ClientWorld) (Object) this, pos, random);
}
/**
@@ -39,6 +38,6 @@ public class MixinClientWorld {
@Redirect(method = worldNotify, at = @At(value = "INVOKE", target = rendererNotify))
void onClientBlockChanged(WorldRenderer renderer, IBlockReader world, BlockPos pos, BlockState oldState, BlockState newState, int flags) {
Hooks.onClientBlockChanged((ClientWorld) world, pos, oldState, newState, flags);
renderer.notifyBlockUpdate(world, pos, oldState, newState, flags);
renderer.blockChanged(world, pos, oldState, newState, flags);
}
}

View File

@@ -1,12 +1,12 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import mods.betterfoliage.render.pipeline.RenderCtxForge;
import mods.betterfoliage.model.SpecialRenderModel;
import mods.betterfoliage.render.pipeline.RenderCtxForge;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ILightReader;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.client.model.pipeline.ForgeBlockModelRenderer;
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
@@ -19,14 +19,14 @@ import java.util.Random;
@Mixin(ForgeBlockModelRenderer.class)
public class MixinForgeBlockModelRenderer {
private static final String renderModelFlat = "renderModelFlat(Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelSmooth = "renderModelSmooth(Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String render = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;render(Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;ZLjava/util/Random;JLnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelFlat = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;renderModelFlat(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelSmooth = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;renderModelSmooth(Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String render = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;render(Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;Lnet/minecraft/world/IBlockDisplayReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;ZLjava/util/Random;JLnet/minecraftforge/client/model/data/IModelData;)Z";
@Redirect(method = {renderModelFlat, renderModelSmooth}, at = @At(value = "INVOKE", target = render), remap = false)
public boolean render(
VertexLighterFlat lighter,
ILightReader world,
IBlockDisplayReader world,
IBakedModel model,
BlockState state,
BlockPos pos,

View File

@@ -1,5 +1,6 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.BetterFoliageMod;
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
import mods.betterfoliage.resource.discovery.ModelDefinitionsLoadedEvent;
@@ -20,13 +21,13 @@ abstract public class MixinModelBakery {
private static final String processLoading = "Lnet/minecraft/client/renderer/model/ModelBakery;processLoading(Lnet/minecraft/profiler/IProfiler;I)V";
private static final String stitch = "Lnet/minecraft/client/renderer/texture/AtlasTexture;stitch(Lnet/minecraft/resources/IResourceManager;Ljava/util/stream/Stream;Lnet/minecraft/profiler/IProfiler;I)Lnet/minecraft/client/renderer/texture/AtlasTexture$SheetData;";
private static final String profilerSection = "Lnet/minecraft/profiler/IProfiler;endStartSection(Ljava/lang/String;)V";
private static final String profilerSection = "Lnet/minecraft/profiler/IProfiler;popPush(Ljava/lang/String;)V";
private static final String getBakedModel = "Lnet/minecraft/client/renderer/model/ModelBakery;getBakedModel(Lnet/minecraft/util/ResourceLocation;Lnet/minecraft/client/renderer/model/IModelTransform;Ljava/util/function/Function;)Lnet/minecraft/client/renderer/model/IBakedModel;";
private static final String bakeModel = "Lnet/minecraft/client/renderer/model/IUnbakedModel;bakeModel(Lnet/minecraft/client/renderer/model/ModelBakery;Ljava/util/function/Function;Lnet/minecraft/client/renderer/model/IModelTransform;Lnet/minecraft/util/ResourceLocation;)Lnet/minecraft/client/renderer/model/IBakedModel;";
private static final String bakeModel = "Lnet/minecraft/client/renderer/model/IUnbakedModel;bake(Lnet/minecraft/client/renderer/model/ModelBakery;Ljava/util/function/Function;Lnet/minecraft/client/renderer/model/IModelTransform;Lnet/minecraft/util/ResourceLocation;)Lnet/minecraft/client/renderer/model/IBakedModel;";
@Inject(method = processLoading, at = @At(value = "INVOKE", target = profilerSection, ordinal = 4))
void onBeforeTextures(IProfiler profiler, int maxMipmapLevel, CallbackInfo ci) {
profiler.endStartSection("betterfoliage");
profiler.popPush("betterfoliage");
BetterFoliageMod.INSTANCE.getBus().post(new ModelDefinitionsLoadedEvent(ModelBakery.class.cast(this)));
}
@@ -34,10 +35,10 @@ abstract public class MixinModelBakery {
IBakedModel onBakeModel(
IUnbakedModel unbaked,
ModelBakery bakery,
Function<Material, TextureAtlasSprite> spriteGetter,
Function<RenderMaterial, TextureAtlasSprite> spriteGetter,
IModelTransform transform,
ResourceLocation locationIn
) {
return BakeWrapperManager.INSTANCE.onBake(unbaked, bakery, spriteGetter, transform, locationIn);
return BetterFoliage.INSTANCE.getModelManager().onBake(unbaked, bakery, spriteGetter, transform, locationIn);
}
}

View File

@@ -0,0 +1,57 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import mods.betterfoliage.render.pipeline.RenderCtxBase;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
import net.minecraft.client.renderer.chunk.VisGraph;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
@Mixin(targets = "net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask")
public class MixinOptifineChunkRendererDispatcher {
private static final String compile = "Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$ChunkRender$RebuildTask;compile(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$CompiledChunk;Lnet/minecraft/client/renderer/RegionRenderCacheBuilder;)Ljava/util/Set;";
private static final String getBlockStateMcp = "Lnet/optifine/override/ChunkCacheOF;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;";
private static final String getBlockStateSrg = "Lnet/optifine/override/ChunkCacheOF;func_180495_p(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;";
@Inject(method = compile, locals = LocalCapture.CAPTURE_FAILHARD, require = 1, at = {
@At(value = "INVOKE", target = getBlockStateMcp),
@At(value = "INVOKE", target = getBlockStateSrg),
})
void onStartBlockRender(
float xIn, float yIn, float zIn,
ChunkRenderDispatcher.CompiledChunk compiledChunkIn, RegionRenderCacheBuilder builderIn,
CallbackInfoReturnable<Set> cir,
int i, BlockPos blockpos, BlockPos blockpos1, VisGraph visgraph, Set set, MatrixStack matrixstack,
@Coerce IBlockDisplayReader chunkrendercache, RenderType[] singleLayer,
boolean shaders, boolean shadersMidBlock, Random random,
BlockRendererDispatcher blockrendererdispatcher, Iterator var18,
@Coerce BlockPos blockpos2
) {
RenderCtxBase.reset(chunkrendercache, blockrendererdispatcher, blockpos2, random);
}
// @Inject(method = compile, locals = LocalCapture.PRINT, require = 1, at = {
// @At(value = "INVOKE", target = getBlockStateMcp),
// @At(value = "INVOKE", target = getBlockStateSrg),
// })
// void printLocals(
// float p_228940_1_, float p_228940_2_, float p_228940_3_,
// ChunkRenderDispatcher.CompiledChunk p_228940_4_, RegionRenderCacheBuilder p_228940_5_,
// CallbackInfoReturnable<BlockState> ci) {
// }
}

View File

@@ -0,0 +1,46 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import mods.betterfoliage.render.pipeline.RenderCtxBase;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
import net.minecraft.client.renderer.chunk.ChunkRenderCache;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
import net.minecraft.client.renderer.chunk.VisGraph;
import net.minecraft.util.math.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
@Mixin(targets = "net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask")
public class MixinVanillaChunkRendererDispatcher {
private static final String compile = "Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$ChunkRender$RebuildTask;compile(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$CompiledChunk;Lnet/minecraft/client/renderer/RegionRenderCacheBuilder;)Ljava/util/Set;";
private static final String getBlockState = "Lnet/minecraft/client/renderer/chunk/ChunkRenderCache;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;";
@Inject(method = compile, at = @At(value = "INVOKE", target = getBlockState), locals = LocalCapture.CAPTURE_FAILHARD)
void onStartBlockRender(
float p_228940_1_, float p_228940_2_, float p_228940_3_,
ChunkRenderDispatcher.CompiledChunk p_228940_4_, RegionRenderCacheBuilder p_228940_5_,
CallbackInfoReturnable ci,
int i, BlockPos blockpos, BlockPos blockpos1, VisGraph visgraph, Set set,
ChunkRenderCache chunkrendercache, MatrixStack matrixstack,
Random random,
BlockRendererDispatcher blockrendererdispatcher, Iterator var15,
BlockPos blockpos2) {
RenderCtxBase.reset(chunkrendercache, blockrendererdispatcher, blockpos2, random);
}
// @Inject(method = compile, at = @At(value = "INVOKE", target = getBlockState), locals = LocalCapture.PRINT)
// void printLocals(
// float p_228940_1_, float p_228940_2_, float p_228940_3_,
// ChunkRenderDispatcher.CompiledChunk p_228940_4_, RegionRenderCacheBuilder p_228940_5_,
// CallbackInfoReturnable ci) {
// }
}

View File

@@ -0,0 +1,101 @@
PARSER_BEGIN(BlockConfigParser)
package mods.betterfoliage.config.match.parser;
import java.util.List;
import java.util.LinkedList;
import mods.betterfoliage.config.match.*;
public class BlockConfigParser {
public String configFile;
ConfigSource getSource(Token t) {
return new ConfigSource(configFile, t.beginLine, t.beginColumn);
}
}
PARSER_END(BlockConfigParser)
// Whitespace definition
SKIP : { " " | "\n" | "\t" | "\r" }
// Single-line comment
SPECIAL_TOKEN : { <lineComment: "//" (~["\n","\r"])* ("\n"|"\r"|"\r\n")> }
// Lexical state for string literal in quotes
SPECIAL_TOKEN : { < quoteStart : "\"" > : withinQuotes }
<withinQuotes> SPECIAL_TOKEN : { < quoteEnd : "\"" > : DEFAULT }
<withinQuotes> TOKEN : { < stringLiteral : (["a"-"z"] | ["0"-"9"] | "/" | "." | "_" | "-" | ":" )* > }
// Symbol tokens
TOKEN : {
< parenStart : "(" > |
< parenEnd : ")" > |
< dot : "." > |
< comma : "," > |
< exclamation : "!" >
}
List<Node.MatchAll> matchFile() : {
Token t; Node n; List<Node.MatchAll> rules = new LinkedList<Node.MatchAll>();
} {
(
t = "match"
{ List<Node> nodes = new LinkedList<Node>(); }
(n = match() { nodes.add(n); })*
"end"
{ rules.add(new Node.MatchAll(getSource(t), nodes)); }
)*
{ return rules; }
}
Node match() : {
Token t; Token t2; MatchMethod mm; List<Node.Value> values; Node.Value v; Node n;
} {
<exclamation> n = match() { return new Node.Negate(n); }
|
t = "block.class." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
{ return new Node.MatchValueList(Node.MatchSource.BLOCK_CLASS, mm, getSource(t), values); }
|
t = "block.name." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
{ return new Node.MatchValueList(Node.MatchSource.BLOCK_NAME, mm, getSource(t), values); }
|
t = "model." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
{ return new Node.MatchValueList(Node.MatchSource.MODEL_LOCATION, mm, getSource(t), values); }
|
t = "isParam" <parenStart> t2 = <stringLiteral> <comma> values = matchValueList() <parenEnd>
{ return new Node.MatchParam(t2.image, values, getSource(t)); }
|
t = "setParam" <parenStart> t2 = <stringLiteral> <comma> v = matchValue() <parenEnd>
{ return new Node.SetParam(t2.image, v, getSource(t)); }
}
MatchMethod matchMethod() : {} {
"matches" { return MatchMethod.EXACT_MATCH; } |
"extends" { return MatchMethod.EXTENDS; } |
"contains" { return MatchMethod.CONTAINS; }
}
List<Node.Value> matchValueList() : {
List<Node.Value> values = new LinkedList<Node.Value>();
Node.Value v;
} {
v = matchValue() { values.add(v); }
(<comma> v = matchValue() { values.add(v); } )*
{ return values; }
}
Node.Value matchValue() : {
Token t;
} {
t = <stringLiteral>
{ return new Node.Value.Literal(getSource(t), t.image); }
|
"classOf" <parenStart> t = <stringLiteral> <parenEnd>
{ return new Node.Value.ClassOf(getSource(t), t.image); }
|
"model.texture" <parenStart> t = <stringLiteral> <parenEnd>
{ return new Node.Value.Texture(getSource(t), t.image); }
|
"model.tint" <parenStart> t = <stringLiteral> <parenEnd>
{ return new Node.Value.Tint(getSource(t), t.image); }
}

View File

@@ -15,26 +15,24 @@ import mods.betterfoliage.render.block.vanilla.StandardLeafDiscovery
import mods.betterfoliage.render.block.vanilla.StandardLeafModel
import mods.betterfoliage.render.block.vanilla.StandardLilypadDiscovery
import mods.betterfoliage.render.block.vanilla.StandardLilypadModel
import mods.betterfoliage.render.block.vanilla.StandardRoundLogDiscovery
import mods.betterfoliage.render.block.vanilla.StandardMyceliumDiscovery
import mods.betterfoliage.render.block.vanilla.StandardMyceliumModel
import mods.betterfoliage.render.block.vanilla.StandardNetherrackDiscovery
import mods.betterfoliage.render.block.vanilla.StandardNetherrackModel
import mods.betterfoliage.render.block.vanilla.StandardRoundLogDiscovery
import mods.betterfoliage.render.block.vanilla.StandardRoundLogModel
import mods.betterfoliage.render.block.vanilla.StandardSandDiscovery
import mods.betterfoliage.render.block.vanilla.StandardSandModel
import mods.betterfoliage.render.lighting.AoSideHelper
import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.particle.LeafWindTracker
import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.BlockTypeCache
import mods.betterfoliage.resource.discovery.ModelDefinitionsLoadedEvent
import mods.betterfoliage.resource.discovery.RuleBasedDiscovery
import mods.betterfoliage.resource.generated.GeneratedTexturePack
import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.util.resourceManager
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.resources.IReloadableResourceManager
import net.minecraftforge.eventbus.api.SubscribeEvent
/**
* Object responsible for initializing (and holding a reference to) all the infrastructure of the mod
@@ -47,32 +45,41 @@ object BetterFoliage {
/** List of recognized [BlockState]s */
var blockTypes = BlockTypeCache()
val blockConfig = BlockConfig()
val standardModelSupport = RuleBasedDiscovery().apply {
discoverers["cactus"] = StandardCactusDiscovery
discoverers["dirt"] = StandardDirtDiscovery
discoverers["grass"] = StandardGrassDiscovery
discoverers["leaf"] = StandardLeafDiscovery
discoverers["lilypad"] = StandardLilypadDiscovery
discoverers["mycelium"] = StandardMyceliumDiscovery
discoverers["netherrack"] = StandardNetherrackDiscovery
discoverers["round-log"] = StandardRoundLogDiscovery
discoverers["sand"] = StandardSandDiscovery
}
val modelManager = BakeWrapperManager().apply {
discoverers.add(standardModelSupport)
}
fun init() {
// discoverers
BetterFoliageMod.bus.register(BakeWrapperManager)
BetterFoliageMod.bus.register(modelManager)
BetterFoliageMod.bus.register(LeafParticleRegistry)
(Minecraft.getInstance().resourceManager as IReloadableResourceManager).addReloadListener(LeafParticleRegistry)
resourceManager.registerReloadListener(LeafParticleRegistry)
ChunkOverlayManager.layers.add(RoundLogOverlayLayer)
listOf(
StandardLeafDiscovery,
StandardGrassDiscovery,
StandardDirtDiscovery,
StandardMyceliumDiscovery,
StandardSandDiscovery,
StandardLilypadDiscovery,
StandardCactusDiscovery,
StandardNetherrackDiscovery,
StandardRoundLogDiscovery
StandardRoundLogDiscovery,
).forEach {
BakeWrapperManager.discoverers.add(it)
}
// init singletons
val singletons = listOf(
AoSideHelper,
BlockConfig,
ChunkOverlayManager,
LeafWindTracker
)

View File

@@ -1,25 +1,36 @@
package mods.betterfoliage
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import net.alexwells.kottle.FMLKotlinModLoadingContext
import mods.betterfoliage.config.MainConfig
import mods.betterfoliage.config.clothGuiRoot
import mods.betterfoliage.config.forgeSpecRoot
import mods.betterfoliage.util.tryDefault
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.screen.Screen
import net.minecraft.util.ResourceLocation
import net.minecraftforge.fml.ExtensionPoint.CONFIGGUIFACTORY
import net.minecraftforge.fml.ExtensionPoint.DISPLAYTEST
import net.minecraftforge.fml.ModLoadingContext
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.config.ModConfig
import org.apache.commons.lang3.tuple.Pair
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil
import thedarkcolour.kotlinforforge.forge.MOD_BUS
import java.io.File
import java.io.PrintStream
import java.util.Properties
import java.util.function.BiFunction
import java.util.function.BiPredicate
import java.util.function.Supplier
@Mod(BetterFoliageMod.MOD_ID)
object BetterFoliageMod {
const val MOD_ID = "betterfoliage"
val bus = FMLKotlinModLoadingContext.get().modEventBus
val bus = MOD_BUS
val config = MainConfig()
val detailLogStream = PrintStream(File("logs/betterfoliage.log").apply {
parentFile.mkdirs()
@@ -32,9 +43,34 @@ object BetterFoliageMod {
)
init {
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.build())
Minecraft.getInstance().resourcePackList.addPackFinder(BetterFoliage.generatedPack.finder)
bus.register(BlockConfig)
val ctx = ModLoadingContext.get()
val configSpec = config.forgeSpecRoot()
ctx.registerConfig(ModConfig.Type.CLIENT, configSpec)
// Add config GUI extension if Cloth Config is available
val clothLoaded = tryDefault(false) { Class.forName("me.shedaniel.clothconfig2.forge.api.ConfigBuilder"); true }
if (clothLoaded) {
logger(this).log(Level.INFO, "Cloth Config found, registering GUI")
ctx.registerExtensionPoint(CONFIGGUIFACTORY) { BiFunction<Minecraft, Screen, Screen> { client, parent ->
config.clothGuiRoot(
parentScreen = parent,
prefix = listOf(MOD_ID),
background = ResourceLocation("minecraft:textures/block/spruce_log.png"),
saveAction = { configSpec.save() }
)
} }
}
// Accept-all version tester (we are client-only)
ctx.registerExtensionPoint(DISPLAYTEST) {
Pair.of(
Supplier { "Honk if you see this!" },
BiPredicate<String, Boolean> { _, _ -> true }
)
}
Minecraft.getInstance().resourcePackRepository.addPackFinder(BetterFoliage.generatedPack.finder)
BetterFoliage.init()
}
}

View File

@@ -1,4 +1,4 @@
package mods.octarinecore
package mods.betterfoliage
import mods.betterfoliage.util.ClassRef
import mods.betterfoliage.util.ClassRef.Companion.float
@@ -10,6 +10,7 @@ import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.model.IUnbakedModel
@@ -17,11 +18,15 @@ import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader
import net.minecraft.world.IBlockReader
import net.minecraft.world.ILightReader
import net.minecraftforge.client.model.pipeline.BlockInfo
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
import java.util.*
import net.minecraftforge.registries.IRegistryDelegate
import java.util.Random
import java.util.function.Predicate
typealias Sprite = TextureAtlasSprite
// Java
val String = ClassRef<String>("java.lang.String")
@@ -32,7 +37,7 @@ fun <K, V> mapRefMutable() = ClassRef<MutableMap<K, V>>("java.util.Map")
// Minecraft
val IBlockReader = ClassRef<IBlockReader>("net.minecraft.world.IBlockReader")
val ILightReader = ClassRef<ILightReader>("net.minecraft.world.ILightReader")
val ILightReader = ClassRef<IBlockDisplayReader>("net.minecraft.world.IBlockDisplayReader")
val BlockState = ClassRef<BlockState>("net.minecraft.block.BlockState")
val BlockPos = ClassRef<BlockPos>("net.minecraft.util.math.BlockPos")
val Block = ClassRef<Block>("net.minecraft.block.Block")
@@ -59,6 +64,10 @@ object ModelBakery : ClassRef<ModelBakery>("net.minecraft.client.renderer.model.
val topUnbakedModels = FieldRef(this, "topUnbakedModels", mapRefMutable<ResourceLocation, IUnbakedModel>())
}
object RenderTypeLookup : ClassRef<RenderTypeLookup>("net.minecraft.client.renderer.RenderTypeLookup") {
val blockRenderChecks = FieldRef(this, "blockRenderChecks", mapRefMutable<IRegistryDelegate<Block>, Predicate<RenderType>>())
}
// Optifine
val OptifineClassTransformer = ClassRef<Any>("optifine.OptiFineClassTransformer")
val BlockPosM = ClassRef<Any>("net.optifine.BlockPosM")
@@ -77,6 +86,13 @@ object CustomColors : ClassRef<Any>("net.optifine.CustomColors") {
}
// Optifine shaders
object Shaders : ClassRef<Any>("net.optifine.shaders.Shaders") {
val shaderPackLoaded = FieldRef(this, "shaderPackLoaded", boolean)
val blockLightLevel05 = FieldRef(this, "blockLightLevel05", float)
val blockLightLevel06 = FieldRef(this, "blockLightLevel06", float)
val blockLightLevel08 = FieldRef(this, "blockLightLevel08", float)
}
object SVertexBuilder : ClassRef<Any>("net.optifine.shaders.SVertexBuilder") {
val pushState = MethodRef(this, "pushEntity", void, long)
val popState = MethodRef(this, "popEntity", void)

View File

@@ -20,8 +20,8 @@ import net.minecraft.util.Direction.UP
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.shapes.VoxelShape
import net.minecraft.util.math.shapes.VoxelShapes
import net.minecraft.world.IBlockDisplayReader
import net.minecraft.world.IBlockReader
import net.minecraft.world.ILightReader
import net.minecraft.world.World
import java.util.Random
@@ -35,11 +35,12 @@ fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: Bloc
ChunkOverlayManager.onBlockChange(worldClient, pos)
}
fun onRandomDisplayTick(block: Block, state: BlockState, world: World, pos: BlockPos, random: Random) {
fun onRandomDisplayTick(world: ClientWorld, pos: BlockPos, random: Random) {
val state = world.getBlockState(pos)
if (Config.enabled &&
Config.risingSoul.enabled &&
state.block == Blocks.SOUL_SAND &&
world.isAirBlock(pos.offset(UP)) &&
world.getBlockState(pos.relative(UP)).isAir &&
Math.random() < Config.risingSoul.chance) {
RisingSoulParticle(world, pos).addIfValid()
}
@@ -47,7 +48,7 @@ fun onRandomDisplayTick(block: Block, state: BlockState, world: World, pos: Bloc
if (Config.enabled &&
Config.fallingLeaves.enabled &&
random.nextDouble() < Config.fallingLeaves.chance &&
world.isAirBlock(pos.offset(DOWN))
world.getBlockState(pos.relative(DOWN)).isAir
) {
(getActualRenderModel(world, pos, state, random) as? LeafBlockModel)?.let { leafModel ->
val blockColor = Minecraft.getInstance().blockColors.getColor(state, world, pos, 0)
@@ -63,13 +64,13 @@ fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos
}
fun shouldForceSideRenderOF(state: BlockState, world: IBlockReader, pos: BlockPos, face: Direction) =
world.getBlockState(pos.offset(face)).let { neighbor -> BetterFoliage.blockTypes.hasTyped<RoundLogKey>(neighbor) }
world.getBlockState(pos.relative(face)).let { neighbor -> BetterFoliage.blockTypes.hasTyped<RoundLogKey>(neighbor) }
fun getActualRenderModel(world: ILightReader, pos: BlockPos, state: BlockState, random: Random): SpecialRenderModel? {
val model = Minecraft.getInstance().blockRendererDispatcher.blockModelShapes.getModel(state) as? SpecialRenderModel
fun getActualRenderModel(world: IBlockDisplayReader, pos: BlockPos, state: BlockState, random: Random): SpecialRenderModel? {
val model = Minecraft.getInstance().blockRenderer.blockModelShaper.getBlockModel(state) as? SpecialRenderModel
?: return null
if (model is WeightedModelWrapper) {
random.setSeed(state.getPositionRandom(pos))
random.setSeed(state.getSeed(pos))
return model.getModel(random).model
}
return model

View File

@@ -1,7 +1,6 @@
package mods.betterfoliage.chunk
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.semiRandom
@@ -10,7 +9,7 @@ import net.minecraft.block.BlockState
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.IBlockDisplayReader
import net.minecraft.world.IWorldReader
import net.minecraft.world.biome.Biome
import net.minecraft.world.level.ColorResolver
@@ -20,9 +19,11 @@ import net.minecraft.world.level.ColorResolver
* block-relative coordinates.
*/
interface BlockCtx {
val world: ILightReader
val world: IBlockDisplayReader
val pos: BlockPos
val seed: Long get() = state.getSeed(pos)
fun offset(dir: Direction) = offset(dir.offset)
fun offset(offset: Int3): BlockCtx
@@ -35,13 +36,13 @@ interface BlockCtx {
val biome: Biome? get() =
(world as? IWorldReader)?.getBiome(pos) ?:
(world as? ChunkRenderCache)?.world?.getBiome(pos)
(world as? ChunkRenderCache)?.level?.getBiome(pos)
val isNormalCube: Boolean get() = state.isNormalCube(world, pos)
val isFullBlock: Boolean get() = state.isCollisionShapeFullBlock(world, pos)
fun isNeighborSolid(dir: Direction) = offset(dir).let { it.state.isSolidSide(it.world, it.pos, dir.opposite) }
fun isNeighborSturdy(dir: Direction) = offset(dir).let { it.state.isFaceSturdy(it.world, it.pos, dir.opposite) }
fun shouldSideBeRendered(side: Direction) = Block.shouldSideBeRendered(state, world, pos, side)
fun shouldSideBeRendered(side: Direction) = Block.shouldRenderFace(state, world, pos, side)
/** Get a semi-random value based on the block coordinate and the given seed. */
fun semiRandom(seed: Int) = pos.semiRandom(seed)
@@ -49,11 +50,11 @@ interface BlockCtx {
/** Get an array of semi-random values based on the block coordinate. */
fun semiRandomArray(num: Int): Array<Int> = Array(num) { semiRandom(it) }
fun color(resolver: ColorResolver) = world.getBlockColor(pos, resolver)
fun color(resolver: ColorResolver) = world.getBlockTint(pos, resolver)
}
class BasicBlockCtx(
override val world: ILightReader,
override val world: IBlockDisplayReader,
override val pos: BlockPos
) : BlockCtx {
override val state: BlockState = world.getBlockState(pos)

View File

@@ -1,124 +1,95 @@
package mods.betterfoliage.chunk
import mods.octarinecore.ChunkCacheOF
import mods.betterfoliage.util.get
import mods.betterfoliage.util.isInstance
import mods.betterfoliage.ChunkCacheOF
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.ChunkPos
import net.minecraft.world.ILightReader
import net.minecraft.world.DimensionType
import net.minecraft.world.IBlockDisplayReader
import net.minecraft.world.IWorldReader
import net.minecraft.world.dimension.DimensionType
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.world.ChunkEvent
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import java.util.*
import kotlin.collections.List
import kotlin.collections.MutableMap
import kotlin.collections.associateWith
import kotlin.collections.forEach
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.set
val ILightReader.dimType: DimensionType get() = when {
this is IWorldReader -> dimension.type
this is ChunkRenderCache -> world.dimension.type
this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache].world.dimension.type
val IBlockDisplayReader.dimType: DimensionType get() = when {
this is IWorldReader -> dimensionType()
this is ChunkRenderCache -> level.dimensionType()
this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache].level.dimensionType()
else -> throw IllegalArgumentException("DimensionType of world with class ${this::class.qualifiedName} cannot be determined!")
}
/**
* Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position
*/
interface ChunkOverlayLayer<T> {
fun calculate(ctx: BlockCtx): T
fun onBlockUpdate(world: ILightReader, pos: BlockPos)
abstract class ChunkOverlayLayer<T> {
val dimData = IdentityHashMap<DimensionType, SparseChunkedMap<T>>()
abstract fun calculate(ctx: BlockCtx): T
abstract fun onBlockUpdate(world: IBlockDisplayReader, pos: BlockPos)
operator fun get(ctx: BlockCtx): T {
return dimData
.getOrPut(ctx.world.dimType) { SparseChunkedMap() }
.getOrPut(ctx.pos) { calculate(ctx) }
}
fun remove(world: IBlockDisplayReader, pos: BlockPos) {
dimData[world.dimType]?.remove(pos)
}
}
/**
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
* Event forwarder for multiple layers of chunk overlay data.
*/
object ChunkOverlayManager {
var tempCounter = 0
init {
MinecraftForge.EVENT_BUS.register(this)
}
val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>()
init { MinecraftForge.EVENT_BUS.register(this) }
val layers = mutableListOf<ChunkOverlayLayer<*>>()
/**
* Get the overlay data for a given layer and position
*
* @param layer Overlay layer to query
* @param reader World to use if calculation of overlay value is necessary
* @param pos Block position
*/
fun <T> get(layer: ChunkOverlayLayer<T>, ctx: BlockCtx): T? {
val data = chunkData[ctx.world.dimType]?.get(ChunkPos(ctx.pos)) ?: return null
data.get(layer, ctx.pos).let { value ->
if (value !== ChunkOverlayData.UNCALCULATED) return value
val newValue = layer.calculate(ctx)
data.set(layer, ctx.pos, newValue)
return newValue
}
}
/**
* Clear the overlay data for a given layer and position
*
* @param layer Overlay layer to clear
* @param pos Block position
*/
fun <T> clear(dimension: DimensionType, layer: ChunkOverlayLayer<T>, pos: BlockPos) {
chunkData[dimension]?.get(ChunkPos(pos))?.clear(layer, pos)
}
fun onBlockChange(world: ClientWorld, pos: BlockPos) {
if (chunkData[world.dimType]?.containsKey(ChunkPos(pos)) == true) {
layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
}
}
@SubscribeEvent
fun handleLoadWorld(event: WorldEvent.Load) = (event.world as? ClientWorld)?.let { world ->
chunkData[world.dimType] = mutableMapOf()
layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
}
@SubscribeEvent
fun handleUnloadWorld(event: WorldEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
chunkData.remove(world.dimType)
}
@SubscribeEvent
fun handleLoadChunk(event: ChunkEvent.Load) = (event.world as? ClientWorld)?.let { world ->
chunkData[world.dimType]?.let { chunks ->
// check for existence first because Optifine fires a TON of these
if (event.chunk.pos !in chunks.keys) chunks[event.chunk.pos] = ChunkOverlayData(layers)
}
layers.forEach { layer -> layer.dimData.remove(world.dimType) }
}
@SubscribeEvent
fun handleUnloadChunk(event: ChunkEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
chunkData[world.dimType]?.remove(event.chunk.pos)
layers.forEach { layer -> layer.dimData[world.dimType]?.removeChunk(event.chunk.pos) }
}
}
class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
val BlockPos.isValid: Boolean get() = y in validYRange
val rawData = layers.associateWith { emptyOverlay() }
fun <T> get(layer: ChunkOverlayLayer<T>, pos: BlockPos): T? = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T? else null
fun <T> set(layer: ChunkOverlayLayer<T>, pos: BlockPos, data: T) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data) else null
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, UNCALCULATED) else null
interface DoubleMap<K1, K2, V> {
val map1: MutableMap<K1, MutableMap<K2, V>>
fun createMap2(): MutableMap<K2, V>
companion object {
val UNCALCULATED = object {}
fun emptyOverlay() = Array(16) { Array(16) { Array<Any?>(256) { UNCALCULATED }}}
val validYRange = 0 until 256
fun remove(key1: K1) {
map1.remove(key1)
}
fun remove(key1: K1, key2: K2) {
map1[key1]?.remove(key2)
}
fun contains(key1: K1) = map1.contains(key1)
fun getOrSet(key1: K1, key2: K2, factory: () -> V) =
(map1[key1] ?: createMap2().apply { map1[key1] = this }).let { subMap ->
subMap[key2] ?: factory().apply { subMap[key2] = this }
}
}
class SparseChunkedMap<V> {
val map = object : DoubleMap<ChunkPos, BlockPos, V> {
override val map1 = mutableMapOf<ChunkPos, MutableMap<BlockPos, V>>()
override fun createMap2() = mutableMapOf<BlockPos, V>()
}
fun getOrPut(pos: BlockPos, factory: () -> V) = map.getOrSet(ChunkPos(pos), pos, factory)
fun remove(pos: BlockPos) = map.remove(ChunkPos(pos), pos)
fun removeChunk(pos: ChunkPos) = map.map1.remove(pos)
}

View File

@@ -0,0 +1,40 @@
package mods.betterfoliage.config
import mods.betterfoliage.config.match.Node
import mods.betterfoliage.config.match.parser.BlockConfigParser
import mods.betterfoliage.config.match.parser.ParseException
import mods.betterfoliage.config.match.parser.TokenMgrError
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.stripStart
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Level.ERROR
class BlockConfig : HasLogger() {
lateinit var rules: List<Node.MatchAll>
fun readConfig(manager: IResourceManager) {
val configs = manager.listResources("config/betterfoliage") { it.endsWith(".rules") }
rules = configs.flatMap { configLocation ->
val resource = manager.getResource(configLocation)
val parser = BlockConfigParser(resource.inputStream)
.apply { configFile = configLocation.stripStart("config/betterfoliage/").toString() }
try {
parser.matchFile()
} catch (e: ParseException) {
parseError(e, configLocation)
} catch (e: TokenMgrError) {
parseError(e, configLocation)
}
}
}
fun parseError(e: Throwable, location: ResourceLocation): List<Node.MatchAll> {
"Error parsing block config $location: ${e.message}".let {
logger.log(ERROR, it)
detailLogger.log(ERROR, it)
}
return emptyList()
}
}

View File

@@ -1,179 +0,0 @@
package mods.betterfoliage.config
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ModelTextureListConfiguration
import net.minecraft.util.ResourceLocation
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
import java.util.Random
private fun featureEnable() = boolean(true).lang("enabled")
abstract class PopulationConfigCategory() : ConfigCategory() {
abstract val enabled: Boolean
abstract val population: Int
fun enabled(random: Random) = random.nextInt(64) < population && enabled
}
// Config singleton
object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.MOD_ID) {
val enabled by boolean(true)
val nVidia by boolean(false)
object leaves : ConfigCategory() {
val enabled by featureEnable()
val snowEnabled by boolean(true)
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val vOffset by double(max=0.4, default=0.1).lang("vOffset")
val size by double(min=0.75, max=2.5, default=1.4).lang("size")
val dense by boolean(false)
val hideInternal by boolean(true)
val saturationThreshold by double(default=0.1)
}
object shortGrass : PopulationConfigCategory(){
override val enabled by featureEnable()
val myceliumEnabled by boolean(true)
val snowEnabled by boolean(true)
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=0.1, max=2.5, default=0.6).lang("heightMin")
val heightMax by double(min=0.1, max=2.5, default=0.8).lang("heightMax")
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
override val population by int(max=64, default=64).lang("population")
val useGenerated by boolean(false)
val shaderWind by boolean(true).lang("shaderWind")
val saturationThreshold by double(default=0.1)
}
object connectedGrass : ConfigCategory(){
val enabled by featureEnable()
val snowEnabled by boolean(false)
}
object roundLogs : ConfigCategory(){
val enabled by featureEnable()
val radiusSmall by double(max=0.5, default=0.25)
val radiusLarge by double(max=0.5, default=0.44)
val dimming by double(default = 0.7)
val connectSolids by boolean(false)
val lenientConnect by boolean(true)
val connectPerpendicular by boolean(true)
val connectGrass by boolean(true)
val defaultY by boolean(false)
val zProtection by double(min = 0.9, default = 0.99)
}
object cactus : ConfigCategory(){
val enabled by featureEnable()
val size by double(min=1.0, max=2.0, default=1.3).lang("size")
val sizeVariation by double(max=0.5, default=0.1)
val hOffset by double(max=0.5, default=0.1).lang("hOffset")
}
object lilypad : PopulationConfigCategory(){
override val enabled by featureEnable()
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
override val population by int(max=64, default=16, min=0)
val shaderWind by boolean(true).lang("shaderWind")
}
object reed : PopulationConfigCategory(){
override val enabled by featureEnable()
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=1.5, max=3.5, default=1.7).lang("heightMin")
val heightMax by double(min=1.5, max=3.5, default=2.2).lang("heightMax")
override val population by int(max=64, default=32).lang("population")
val minBiomeTemp by double(default=0.4)
val minBiomeRainfall by double(default=0.4)
// val biomes by biomeList { it.filterTemp(0.4f, null) && it.filterRain(0.4f, null) }
val shaderWind by boolean(true).lang("shaderWind")
}
object algae : PopulationConfigCategory(){
override val enabled by featureEnable()
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
val heightMin by double(min=0.1, max=1.5, default=0.5).lang("heightMin")
val heightMax by double(min=0.1, max=1.5, default=1.0).lang("heightMax")
override val population by int(max=64, default=48).lang("population")
// val biomes by biomeList { it.filterClass("river", "ocean") }
val shaderWind by boolean(true).lang("shaderWind")
}
object coral : PopulationConfigCategory(){
override val enabled by featureEnable()
val shallowWater by boolean(false)
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val vOffset by double(max=0.4, default=0.1).lang("vOffset")
val size by double(min=0.5, max=1.5, default=0.7).lang("size")
val crustSize by double(min=0.5, max=1.5, default=1.4)
val chance by int(max=64, default=32)
override val population by int(max=64, default=48).lang("population")
// val biomes by biomeList { it.filterClass("river", "ocean", "beach") }
}
object netherrack : ConfigCategory(){
val enabled by featureEnable()
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=0.1, max=1.5, default=0.2).lang("heightMin")
val heightMax by double(min=0.1, max=1.5, default=0.5).lang("heightMax")
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
}
object fallingLeaves : ConfigCategory(){
val enabled by featureEnable()
val speed by double(min=0.01, max=0.15, default=0.05)
val windStrength by double(min=0.1, max=2.0, default=0.5)
val stormStrength by double(min=0.1, max=2.0, default=0.8)
val size by double(min=0.25, max=1.5, default=0.75).lang("size")
val chance by double(min=0.001, max=1.0, default=0.02)
val perturb by double(min=0.01, max=1.0, default=0.25)
val lifetime by double(min=1.0, max=15.0, default=5.0)
val opacityHack by boolean(true)
}
object risingSoul : ConfigCategory(){
val enabled by featureEnable()
val chance by double(min=0.001, max=1.0, default=0.02)
val perturb by double(min=0.01, max=0.25, default=0.05)
val headSize by double(min=0.25, max=1.5, default=1.0)
val trailSize by double(min=0.25, max=1.5, default=0.75)
val opacity by double(min=0.05, max=1.0, default=0.5)
val sizeDecay by double(min=0.5, max=1.0, default=0.97)
val opacityDecay by double(min=0.5, max=1.0, default=0.97)
val lifetime by double(min=1.0, max=15.0, default=4.0)
val trailLength by int(min=2, max=128, default=48)
val trailDensity by int(min=1, max=16, default=3)
}
}
object BlockConfig {
private val list = mutableListOf<Any>()
val leafBlocks = blocks("leaves_blocks_default.cfg")
val leafModels = models("leaves_models_default.cfg")
val grassBlocks = blocks("grass_blocks_default.cfg")
val grassModels = models("grass_models_default.cfg")
val mycelium = blocks("mycelium_blocks_default.cfg")
// val dirt = blocks("dirt_default.cfg")
val crops = blocks("crop_default.cfg")
val logBlocks = blocks("log_blocks_default.cfg")
val logModels = models("log_models_default.cfg")
val lilypad = blocks("lilypad_default.cfg")
init { BetterFoliageMod.bus.register(this) }
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
@SubscribeEvent
fun onConfig(event: ModConfig.ModConfigEvent) {
list.forEach { when(it) {
is ConfigurableBlockMatcher -> it.readDefaults()
is ModelTextureListConfiguration -> it.readDefaults()
} }
}
}

View File

@@ -0,0 +1,170 @@
package mods.betterfoliage.config
import me.shedaniel.clothconfig2.forge.api.AbstractConfigListEntry
import me.shedaniel.clothconfig2.forge.api.ConfigBuilder
import me.shedaniel.clothconfig2.forge.api.ConfigEntryBuilder
import me.shedaniel.clothconfig2.forge.gui.entries.SubCategoryListEntry
import mods.betterfoliage.util.asText
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.resources.I18n
import net.minecraft.util.ResourceLocation
import net.minecraftforge.common.ForgeConfigSpec
import java.util.Optional
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
const val MAX_LINE_LEN = 30
fun DelegatingConfigGroup.forgeSpecRoot() =
ForgeConfigSpec.Builder()
.also { createForgeNode(it) }
.build()
fun DelegatingConfigGroup.clothGuiRoot(
parentScreen: Screen,
prefix: List<String>,
background: ResourceLocation,
saveAction: ()->Unit
) = ConfigBuilder.create()
.setParentScreen(parentScreen)
.setTitle(I18n.get((prefix + "title").joinToString(".")).asText())
.setDefaultBackgroundTexture(background)
.setSavingRunnable(saveAction)
.also { builder ->
createClothNode(prefix).value.forEach { rootCategory ->
builder.getOrCreateCategory("main".asText()).addEntry(rootCategory)
}
}
.build()
sealed class DelegatingConfigNode {
abstract fun createClothNode(path: List<String>): AbstractConfigListEntry<*>
}
abstract class DelegatingConfigValue<T> : DelegatingConfigNode(), ReadOnlyProperty<DelegatingConfigGroup, T> {
lateinit var forgeValue: ForgeConfigSpec.ConfigValue<T>
abstract fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String)
}
open class DelegatingConfigGroup : DelegatingConfigNode() {
val children = mutableMapOf<String, DelegatingConfigNode>()
fun createForgeNode(builder: ForgeConfigSpec.Builder) {
children.forEach { (name, node) ->
when(node) {
is DelegatingConfigGroup -> {
builder.push(name)
node.createForgeNode(builder)
builder.pop()
}
is DelegatingConfigValue<*> -> node.createForgeNode(builder, name)
}
}
}
override fun createClothNode(path: List<String>): SubCategoryListEntry {
val builder = ConfigEntryBuilder.create()
.startSubCategory(path.joinToString(".").translate())
.setTooltip(*path.joinToString(".").translateTooltip())
.setExpanded(false)
children.forEach { (name, node) -> builder.add(node.createClothNode(path + name)) }
return builder.build()
}
}
interface DelegatingConfigGroupFactory<T> {
operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T>
}
fun <T: DelegatingConfigGroup> subNode(factory: ()->T) = object : DelegatingConfigGroupFactory<T> {
override operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> {
val child = factory()
parent.children[property.name] = child
return ReadOnlyProperty { _, _ -> child }
}
}
interface DelegatingConfigValueFactory<T> {
fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String): ForgeConfigSpec.ConfigValue<T>
fun createClothNode(prop: CachingConfigProperty<T>, path: List<String>): AbstractConfigListEntry<T>
operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> {
return object : CachingConfigProperty<T>(parent, property) {
override fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String) {
forgeValue = this@DelegatingConfigValueFactory.createForgeNode(builder, name)
}
override fun createClothNode(path: List<String>): AbstractConfigListEntry<*> = createClothNode(this, path)
}.apply { parent.children[property.name] = this }
}
}
abstract class CachingConfigProperty<T>(parent: DelegatingConfigGroup, property: KProperty<*>) : DelegatingConfigValue<T>() {
var value: T? = null
override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) =
value ?: forgeValue.get().apply { value = this }
}
fun String.translate() = I18n.get(this).asText()
fun String.translateTooltip(lineLength: Int = MAX_LINE_LEN) =
I18n.get("$this.tooltip").splitToSequence(" ").fold(mutableListOf("")) { tooltips, word ->
if (tooltips.last().length + word.length < lineLength) {
tooltips[tooltips.lastIndex] += "$word "
} else {
tooltips.add("$word ")
}
tooltips
}.map { it.trim().asText() }.toTypedArray()
fun boolean(
default: Boolean,
langKey: (List<String>)->String = { it.joinToString(".") },
valueOverride: (Boolean)->Boolean = { it }
) = object : DelegatingConfigValueFactory<Boolean> {
override fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String) =
builder.define(name, default)
override fun createClothNode(prop: CachingConfigProperty<Boolean>, path: List<String>) = ConfigEntryBuilder.create()
.startBooleanToggle(langKey(path).translate(), prop.forgeValue.get())
.setTooltip(langKey(path).let { if (I18n.exists("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
.setSaveConsumer { prop.forgeValue.set(valueOverride(it)); prop.value = null }
.build()
}
fun integer(
default: Int = 0, min: Int = 0, max: Int,
langKey: (List<String>)->String = { it.joinToString(".") },
valueOverride: (Int)->Int = { it }
) = object : DelegatingConfigValueFactory<Int> {
override fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String) =
builder.defineInRange(name, default, min, max)
override fun createClothNode(prop: CachingConfigProperty<Int>, path: List<String>) = ConfigEntryBuilder.create()
.startIntField(langKey(path).translate(), prop.forgeValue.get())
.setTooltip(langKey(path).let { if (I18n.exists("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
.setMin(min).setMax(max)
.setSaveConsumer { prop.forgeValue.set(valueOverride(it)); prop.value = null }
.build()
}
fun double(
default: Double = 0.0, min: Double = 0.0, max: Double = 1.0,
langKey: (List<String>)->String = { it.joinToString(".") },
valueOverride: (Double)->Double = { it }
) = object : DelegatingConfigValueFactory<Double> {
override fun createForgeNode(builder: ForgeConfigSpec.Builder, name: String) =
builder.defineInRange(name, default, min, max)
override fun createClothNode(prop: CachingConfigProperty<Double>, path: List<String>) = ConfigEntryBuilder.create()
.startDoubleField(langKey(path).translate(), prop.forgeValue.get())
.setTooltip(langKey(path).let { if (I18n.exists("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() })
.setMin(min).setMax(max)
.setSaveConsumer { prop.forgeValue.set(valueOverride(it)); prop.value = null }
.build()
}
val recurring = { path: List<String> -> "${path.first()}.${path.last()}" }
fun fakeCategory(name: String) = { names: List<String> ->
(listOf(names.first(), name) + names.drop(1)).joinToString(".")
}

View File

@@ -1,89 +0,0 @@
@file:JvmName("DelegatingConfigKt")
package mods.betterfoliage.config
import mods.betterfoliage.util.reflectDelegates
import mods.betterfoliage.util.reflectNestedObjects
import net.minecraftforge.common.ForgeConfigSpec
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
open class DelegatingConfig(val modId: String, val langPrefix: String) {
fun build() = ForgeConfigSpec.Builder().apply { ConfigBuildContext(langPrefix, emptyList(), this).addCategory(this@DelegatingConfig) }.build()
}
class ConfigBuildContext(val langPrefix: String, val path: List<String>, val builder: ForgeConfigSpec.Builder) {
fun addCategory(configObj: Any) {
configObj.reflectNestedObjects.forEach { (name, category) ->
builder.push(name)
descend(name).addCategory(category)
builder.pop()
}
configObj.reflectDelegates(ConfigDelegate::class.java).forEach { (name, delegate) ->
descend(name).apply { delegate.addToBuilder(this) }
}
}
fun descend(pathName: String) = ConfigBuildContext(langPrefix, path + pathName, builder)
}
open class ConfigCategory(val comment: String? = null) {
}
abstract class ConfigDelegate<T> : ReadOnlyProperty<Any, T> {
lateinit var configValue: ForgeConfigSpec.ConfigValue<T>
var cachedValue: T? = null
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (cachedValue == null) cachedValue = configValue.get()
return cachedValue!!
}
abstract fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder): ForgeConfigSpec.ConfigValue<T>
fun addToBuilder(ctx: ConfigBuildContext) {
val langKey = ctx.langPrefix + "." + (langPrefixOverride ?: ctx.path.joinToString("."))
ctx.builder.translation(langKey)
configValue = getConfigValue(ctx.path.last(), ctx.builder)
}
var langPrefixOverride: String? = null
fun lang(prefix: String) = apply { langPrefixOverride = prefix }
}
class DelegatingBooleanValue(val defaultValue: Boolean) : ConfigDelegate<Boolean>() {
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.define(name, defaultValue)
}
class DelegatingIntValue(
val minValue: Int = 0,
val maxValue: Int = 1,
val defaultValue: Int = 0
) : ConfigDelegate<Int>() {
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
}
class DelegatingLongValue(
val minValue: Long = 0,
val maxValue: Long = 1,
val defaultValue: Long = 0
) : ConfigDelegate<Long>() {
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
}
class DelegatingDoubleValue(
val minValue: Double = 0.0,
val maxValue: Double = 1.0,
val defaultValue: Double = 0.0
) : ConfigDelegate<Double>() {
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
}
// ============================
// Delegate factory methods
// ============================
fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = DelegatingDoubleValue(min, max, default)
fun int(min: Int = 0, max: Int, default: Int) = DelegatingIntValue(min, max, default)
fun long(min: Long = 0, max: Long, default: Long) = DelegatingLongValue(min, max, default)
fun boolean(default: Boolean) = DelegatingBooleanValue(default)

View File

@@ -0,0 +1,158 @@
package mods.betterfoliage.config
import mods.betterfoliage.BetterFoliageMod
import java.util.Random
fun featureEnable(default: Boolean = true) = boolean(default, langKey = recurring)
val Config get() = BetterFoliageMod.config
abstract class PopulationConfigGroup : DelegatingConfigGroup() {
abstract val enabled: Boolean
abstract val population: Int
fun enabled(random: Random) = random.nextInt(64) < population && enabled
}
class MainConfig : DelegatingConfigGroup() {
val enabled by boolean(true, langKey = { "betterfoliage.global.enabled" })
val nVidia by boolean(false)
val leaves by subNode(::LeavesConfig)
val shortGrass by subNode(::ShortGrassConfig)
val connectedGrass by subNode(::ConnectedGrassConfig)
val roundLogs by subNode(::RoundLogConfig)
val cactus by subNode(::CactusConfig)
val lilypad by subNode(::LilypadConfig)
val reed by subNode(::ReedConfig)
val algae by subNode(::AlgaeConfig)
val coral by subNode(::CoralConfig)
val netherrack by subNode(::NetherrackConfig)
val fallingLeaves by subNode(::FallingLeavesConfig)
val risingSoul by subNode(::RisingSoulConfig)
}
class LeavesConfig : DelegatingConfigGroup() {
val enabled by featureEnable()
val snowEnabled by boolean(true)
val hOffset by double(max=0.4, default=0.2, langKey = recurring)
val vOffset by double(max=0.4, default=0.1, langKey = recurring)
val size by double(min=0.75, max=2.5, default=1.4, langKey = recurring)
val dense by boolean(false)
val hideInternal by boolean(true)
val saturationThreshold by double(default=0.1, langKey = recurring)
}
class ShortGrassConfig : PopulationConfigGroup() {
override val enabled by featureEnable()
val grassEnabled by boolean(true)
val myceliumEnabled by boolean(true)
val snowEnabled by boolean(true)
val hOffset by double(max=0.4, default=0.2, langKey = recurring)
val heightMin by double(min=0.1, max=2.5, default=0.6, langKey = recurring)
val heightMax by double(min=0.1, max=2.5, default=0.8, langKey = recurring)
val size by double(min=0.5, max=1.5, default=1.0, langKey = recurring)
override val population by integer(max=64, default=64, langKey = recurring)
val useGenerated by boolean(false)
val shaderWind by boolean(true, langKey = recurring)
val saturationThreshold by double(default=0.1, langKey = recurring)
}
class ConnectedGrassConfig : DelegatingConfigGroup() {
val enabled by boolean(true)
val snowEnabled by boolean(false)
}
class RoundLogConfig : DelegatingConfigGroup() {
val enabled by featureEnable()
val plantsOnly by boolean(true)
val radiusSmall by double(max=0.5, default=0.25)
val radiusLarge by double(max=0.5, default=0.44)
val dimming by double(default = 0.7)
val connectSolids by boolean(false)
val lenientConnect by boolean(true)
val connectPerpendicular by boolean(true)
val connectGrass by boolean(true)
val defaultY by boolean(false)
val zProtection by double(min = 0.9, default = 0.99)
}
class CactusConfig : DelegatingConfigGroup() {
val enabled by featureEnable()
val size by double(min=0.5, max=1.5, default=0.8, langKey = recurring)
val sizeVariation by double(max=0.5, default=0.1)
val hOffset by double(max=0.5, default=0.1, langKey = recurring)
}
class LilypadConfig : PopulationConfigGroup() {
override val enabled by featureEnable()
val hOffset by double(max=0.25, default=0.1, langKey = recurring)
override val population by integer(max=64, default=16, min=0, langKey = recurring)
val shaderWind by boolean(true, langKey = recurring)
}
class ReedConfig : PopulationConfigGroup() {
override val enabled by featureEnable()
val hOffset by double(max=0.4, default=0.2, langKey = recurring)
val heightMin by double(min=1.5, max=3.5, default=1.7, langKey = recurring)
val heightMax by double(min=1.5, max=3.5, default=2.2, langKey = recurring)
override val population by integer(max=64, default=32, langKey = recurring)
val minBiomeTemp by double(default=0.4)
val minBiomeRainfall by double(default=0.4)
val shaderWind by boolean(true, langKey = recurring)
}
class AlgaeConfig : PopulationConfigGroup() {
override val enabled by featureEnable()
val hOffset by double(max=0.25, default=0.1, langKey = recurring)
val size by double(min=0.5, max=1.5, default=1.0, langKey = recurring)
val heightMin by double(min=0.1, max=1.5, default=0.5, langKey = recurring)
val heightMax by double(min=0.1, max=1.5, default=1.0, langKey = recurring)
override val population by integer(max=64, default=48, langKey = recurring)
val shaderWind by boolean(true, langKey = recurring)
}
class CoralConfig : PopulationConfigGroup() {
override val enabled by featureEnable()
val shallowWater by boolean(false)
val hOffset by double(max=0.4, default=0.2, langKey = recurring)
val vOffset by double(max=0.4, default=0.1, langKey = recurring)
val size by double(min=0.5, max=1.5, default=0.7, langKey = recurring)
val crustSize by double(min=0.5, max=1.5, default=1.4)
val chance by integer(max=64, default=32)
override val population by integer(max=64, default=48, langKey = recurring)
}
class NetherrackConfig : DelegatingConfigGroup() {
val enabled by featureEnable()
val hOffset by double(max=0.4, default=0.2, langKey = recurring)
val heightMin by double(min=0.1, max=1.5, default=0.6, langKey = recurring)
val heightMax by double(min=0.1, max=1.5, default=0.8, langKey = recurring)
val size by double(min=0.5, max=1.5, default=1.0, langKey = recurring)
}
class FallingLeavesConfig : DelegatingConfigGroup() {
val enabled by featureEnable()
val speed by double(min=0.01, max=0.15, default=0.05)
val windStrength by double(min=0.1, max=2.0, default=0.5)
val stormStrength by double(min=0.1, max=2.0, default=0.8)
val size by double(min=0.25, max=1.5, default=0.75, langKey = recurring)
val chance by double(min=0.001, max=1.0, default=0.02)
val perturb by double(min=0.01, max=1.0, default=0.25)
val lifetime by double(min=1.0, max=15.0, default=5.0)
val opacityHack by boolean(true)
}
class RisingSoulConfig : DelegatingConfigGroup() {
val enabled by featureEnable()
val chance by double(min=0.001, max=1.0, default=0.02)
val perturb by double(min=0.01, max=0.25, default=0.05)
val headSize by double(min=0.25, max=1.5, default=1.0)
val trailSize by double(min=0.25, max=1.5, default=0.75)
val opacity by double(min=0.05, max=1.0, default=0.5)
val sizeDecay by double(min=0.5, max=1.0, default=0.97)
val opacityDecay by double(min=0.5, max=1.0, default=0.97)
val lifetime by double(min=1.0, max=15.0, default=4.0)
val trailLength by integer(min=2, max=128, default=48)
val trailDensity by integer(min=1, max=16, default=3)
}

View File

@@ -0,0 +1,13 @@
package mods.betterfoliage.config
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.material.Material
import net.minecraft.world.biome.Biome
val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN)
val SNOW_MATERIALS = listOf(Material.TOP_SNOW, Material.SNOW)
val BlockState.isSnow: Boolean get() = material in SNOW_MATERIALS
val ACCEPTED_ROUND_LOG_MATERIALS = listOf(Material.WOOD, Material.GRASS)

View File

@@ -0,0 +1,162 @@
package mods.betterfoliage.config.match
enum class MatchMethod {
EXACT_MATCH, EXTENDS, CONTAINS;
fun description(isSuccess: Boolean) = when (this) {
EXACT_MATCH -> if (isSuccess) "matches" else "does not match"
EXTENDS -> if (isSuccess) "extends" else "does not extend"
CONTAINS -> if (isSuccess) "contains" else "does not contain"
}
}
/**
* Basic Either monad implementation
*/
sealed class Either<out L, out R> {
class Left<L>(val left: L) : Either<L, Nothing>()
class Right<R>(val right: R) : Either<Nothing, R>()
fun leftOrNull() = if (this is Left) left else null
fun rightOrNull() = if (this is Right) right else null
fun <R2> map(func: (R) -> R2): Either<L, R2> = when (this) {
is Left<L> -> this
is Right<R> -> Right(func(right))
}
fun <L2> mapLeft(func: (L) -> L2): Either<L2, R> = when (this) {
is Left<L> -> Left(func(left))
is Right<R> -> this
}
fun ifRight(action: (R) -> Unit) {
if (this is Right) action(right)
}
companion object {
fun <L> ofLeft(left: L) = Left(left)
fun <R> ofRight(right: R) = Right(right)
}
}
// this cannot be inside the class for variance reasons
fun <L, R, R2> Either<L, R>.flatMap(func: (R) -> Either<L, R2>) = when (this) {
is Either.Left<L> -> this
is Either.Right<R> -> func(right)
}
fun <L, R, L2> Either<L, R>.flatMapLeft(func: (L) -> Either<L2, R>) = when (this) {
is Either.Left<L> -> func(left)
is Either.Right<R> -> this
}
fun <T> Either<T, T>.flatten() = when (this) {
is Either.Left -> left
is Either.Right -> right
}
interface MAnything<out T> {
val value: T
val immutable: Boolean
}
class MListAll(val list: List<MAnything<Boolean>>) : MAnything<Boolean> {
override val value get() = list.all { it.value }
override val immutable get() = list.all { it.immutable }
}
class MListAny(val list: List<MValue<Boolean>>) : MAnything<Boolean> {
override val value get() = list.any { it.value }
override val immutable get() = list.all { it.immutable }
}
class MNegated(val inner: MAnything<Boolean>) : MAnything<Boolean> {
override val value get() = !inner.value
override val immutable get() = inner.immutable
}
/**
* Value with metadata related to rule matching applied.
*
* @param value the wrapped value
* @param description human-readable description of what the value represents
* @param configSource identifies where the value is described in the config
* @param immutable true if the value never changes
* (another [MValue] constructed in the same way will have the same value)
*
*/
class MValue<out T>(
override val value: T,
val description: String,
val configSource: ConfigSource,
override val immutable: Boolean,
) : MAnything<T> {
companion object {
fun <T> right(value: T, description: String, configSource: ConfigSource, immutable: Boolean = true) =
Either.ofRight(MValue(value, description, configSource, immutable))
fun left(description: String, configSource: ConfigSource, immutable: Boolean = true) =
Either.ofLeft(MValue(false, description, configSource, immutable))
}
}
typealias MEither<T> = Either<MValue<Boolean>, MValue<T>>
val Node.Value.asEither get() = MValue.right(value, value, configSource, true)
fun Node.Value.left(description: String) = MValue.left(description, configSource)
fun Node.invalidTypeFor(type: String) = MValue.left("invalid type for $type: [${this::class.java.name}]", configSource)
fun Node.error(description: String) = MValue.left(description, configSource)
fun <T, R> MEither<T>.mapValue(func: (T) -> R) = map {
MValue(func(it.value), it.description, it.configSource, it.immutable)
}
fun <T> MEither<T>.mapDescription(func: (MValue<T>) -> String) = map {
MValue(it.value, func(it), it.configSource, it.immutable)
}
fun <T, R> MEither<T>.map(
func: (T) -> R,
description: (MValue<T>, R) -> String
) = map { t -> func(t.value).let { r -> MValue(r, description(t, r), t.configSource, t.immutable) } }
fun <T, R> MEither<T>.mapNotNull(
func: (T) -> R?,
dLeft: (MValue<T>) -> String = { it.description },
dRight: (MValue<T>, R) -> String = { m, _ -> m.description }
) = flatMap { t ->
func(t.value)?.let { r ->
MValue.right(r, dRight(t, r), t.configSource, t.immutable)
} ?: MValue.left(dLeft(t), t.configSource, t.immutable)
}
fun <T> MEither<T>.toRight(value: T) =
flatMapLeft { MValue.right(value, it.description, it.configSource, it.immutable) }
data class MComparison<T1, T2>(
private val opTrue: String,
private val opFalse: String,
val testFunc: (T1, T2) -> Boolean
) {
fun compare(value1: MEither<T1>, value2: MEither<T2>) = when {
value1 is Either.Left -> value1
value2 is Either.Left -> value2
else -> {
val isSuccess = testFunc((value1 as Either.Right).right.value, (value2 as Either.Right).right.value)
MValue.right(
isSuccess,
"${value1.right.description} ${if (isSuccess) opTrue else opFalse} ${value2.right.description}",
value2.right.configSource,
value1.right.immutable && value2.right.immutable
)
}
}.flatten()
companion object {
fun <T1, T2> of(matchMethod: MatchMethod, testFunc: (T1, T2) -> Boolean) =
MComparison(matchMethod.description(true), matchMethod.description(false), testFunc)
val equals = of(MatchMethod.EXACT_MATCH) { t1: Any, t2: Any -> t1 == t2 }
}
}

View File

@@ -0,0 +1,42 @@
package mods.betterfoliage.config.match
data class ConfigSource(
val configFile: String,
val line: Int,
val column: Int
) {
override fun toString() = "$configFile @ R$line,C$column"
}
sealed class Node {
enum class MatchSource { BLOCK_CLASS, BLOCK_NAME, MODEL_LOCATION }
abstract val configSource: ConfigSource
class MatchValueList(
val matchSource: MatchSource,
val matchMethod: MatchMethod,
override val configSource: ConfigSource,
val values: List<Value>
) : Node()
class MatchParam(
val name: String,
val values: List<Value>,
override val configSource: ConfigSource,
) : Node()
class Negate(val node: Node) : Node() {
override val configSource get() = node.configSource
}
class SetParam(val name: String, val value: Value, override val configSource: ConfigSource) : Node()
class MatchAll(override val configSource: ConfigSource, val list: List<Node>) : Node()
abstract class Value(override val configSource: ConfigSource, val value: String) : Node() {
class Literal(configSource: ConfigSource, value: String) : Value(configSource, value)
class ClassOf(configSource: ConfigSource, value: String) : Value(configSource, value)
class Texture(configSource: ConfigSource, value: String) : Value(configSource, value)
class Tint(configSource: ConfigSource, value: String) : Value(configSource, value)
}
}

View File

@@ -0,0 +1,172 @@
package mods.betterfoliage.config.match
import mods.betterfoliage.config.match.MatchMethod.CONTAINS
import mods.betterfoliage.config.match.MatchMethod.EXACT_MATCH
import mods.betterfoliage.config.match.MatchMethod.EXTENDS
import mods.betterfoliage.resource.discovery.RuleProcessingContext
import mods.betterfoliage.util.findFirst
import mods.betterfoliage.util.quoted
import mods.betterfoliage.util.tryDefault
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.ResourceLocation
import net.minecraftforge.registries.ForgeRegistries
typealias PartialLocation = Pair<String?, String>
object MatchRules {
fun visitRoot(ctx: RuleProcessingContext, node: Node.MatchAll): MListAll {
val results = mutableListOf<MAnything<Boolean>>()
for (rule in node.list) {
val result = mNode(ctx, rule)
results.add(result)
if (!result.value) break
}
return MListAll(results)
}
fun mNode(ctx: RuleProcessingContext, node: Node): MAnything<Boolean> = when(node) {
is Node.MatchValueList -> mMatchList(ctx, node)
is Node.MatchParam -> mParam(ctx, node)
is Node.SetParam -> mParamSet(ctx, node)
is Node.Negate -> mNegate(ctx, node)
else -> node.error("match type not implemented: ${node::class.java.name.quoted}").left
}
fun mNegate(ctx: RuleProcessingContext, node: Node.Negate) = MNegated(mNode(ctx, node.node))
fun mMatchList(ctx: RuleProcessingContext, node: Node.MatchValueList) = node.values.map { value ->
when (node.matchSource) {
Node.MatchSource.BLOCK_CLASS -> mBlockClass(ctx, node, value)
Node.MatchSource.BLOCK_NAME -> mBlockName(ctx, node, value)
Node.MatchSource.MODEL_LOCATION -> mModel(ctx, node, value)
}
}.let { MListAny(it) }
fun mBlockClass(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MValue<Boolean> {
val blockClass = ctx.discovery.blockState.block::class.java.let {
MValue.right(it, "block class ${it.name.quoted}", node.configSource)
}
val target = when(value) {
is Node.Value.Literal -> value.asEither.mapNotNull(
func = { tryDefault(null) { Class.forName(it) }},
dLeft = { "missing class ${it.value}" },
dRight = { m, _ -> " class ${m.value}" }
)
is Node.Value.ClassOf -> value.asEither.mapValue(::ResourceLocation).mapNotNull(
func = { loc -> ForgeRegistries.BLOCKS.getValue(loc)?.let { it::class.java } },
dLeft = { "missing block ${it.value.toString().quoted}" },
dRight = { m, r -> "class ${r.name.quoted} of block ${m.value}" }
)
else -> value.invalidTypeFor("block class")
}
return when(node.matchMethod) {
EXACT_MATCH -> MComparison.equals.compare(blockClass, target)
EXTENDS -> classExtends.compare(blockClass, target)
CONTAINS -> node.error("invalid match type for block class: contains").left
}
}
fun mBlockName(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MValue<Boolean> {
val blockName = MValue.right(ctx.discovery.blockState.block, "", node.configSource).mapNotNull(
func = { it.registryName }, dLeft = { "missing block name" }, dRight = { _, r -> "block name ${r.toString().quoted}" }
)
val target = when(value) {
is Node.Value.Literal -> value.asEither.map(::splitLocation, ::quoteString)
else -> value.invalidTypeFor("block name")
}
return when(node.matchMethod) {
EXACT_MATCH -> blockNameExact.compare(blockName, target)
CONTAINS -> blockNameContains.compare(blockName, target)
EXTENDS -> node.error("invalid match type for block name: extends").left
}
}
fun mModel(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MValue<Boolean> {
val model = (ctx.discovery.getUnbaked() as? BlockModel)?.let {
MValue.right(it, "model ${it.name.quoted}", node.configSource)
} ?: node.error("unsupported model type: ${ctx.discovery.getUnbaked()::class.java.name.quoted}")
val target = when(value) {
is Node.Value.Literal -> value.asEither.map(::splitLocation, ::quoteString)
else -> value.invalidTypeFor("model")
}
val models = when(node.matchMethod) {
EXTENDS -> model.mapValue { ctx.discovery.loadHierarchy(it).ancestors() }
else -> model.mapValue { listOf(it) }
}
return when(node.matchMethod) {
EXACT_MATCH, EXTENDS -> anyModel(node.matchMethod, ::locationMatches)
CONTAINS -> anyModel(CONTAINS, ::locationContains)
}.compare(models, target)
}
fun mParam(ctx: RuleProcessingContext, node: Node.MatchParam) = node.values.map { value ->
val paramValue = ctx.params[node.name] ?.let {
MValue.right(it, "parameter ${node.name.quoted}", node.configSource, immutable = false)
} ?: node.error("missing parameter ${node.name.quoted}")
val target = when(value) {
is Node.Value.Literal -> value.asEither.mapDescription { it.description.quoted }
else -> value.invalidTypeFor("parameter")
}
MComparison.equals.compare(paramValue, target)
}.let { MListAny(it) }
fun mParamSet(ctx: RuleProcessingContext, node: Node.SetParam): MValue<Boolean> {
val target = when(node.value) {
is Node.Value.Literal -> node.value.asEither
is Node.Value.Texture -> when(val model = ctx.discovery.getUnbaked()) {
is BlockModel -> node.value.asEither.map(
func = { model.getMaterial(it).texture().toString() },
description = { m, r -> "texture \"${m.value}\" = \"$r\" of model ${model.name}"}
)
else -> node.error("unsupported model type: ${model::class.java.name.quoted}")
}
is Node.Value.Tint -> when(val model = ctx.discovery.getUnbaked()) {
is BlockModel -> node.value.asEither.mapNotNull(
func = { model.tintOf(it)?.toString() },
dRight = { m, r -> "tint index $r for sprite ${m.value}" },
dLeft = { m -> "tint index -1 for unused sprite ${m.value}"}
).toRight("-1")
else -> node.error("unsupported model type: ${model::class.java.name.quoted}")
}
else -> node.value.invalidTypeFor("prameter")
}
target.ifRight { ctx.params[node.name] = it.value }
return target.mapDescription { m -> "parameter ${node.name} set to ${m.value}" }.mapValue { true }.flatten()
}
private val classExtends = MComparison.of<Class<*>, Class<*>>(EXTENDS) { c1, c2 -> c2.isAssignableFrom(c1) }
private val blockNameExact = MComparison.of<ResourceLocation, PartialLocation>(EXACT_MATCH) { block, partial ->
locationMatches(block, partial)
}
private val blockNameContains = MComparison.of<ResourceLocation, Pair<String?, String>>(CONTAINS) { block, partial ->
locationContains(block, partial)
}
private fun anyModel(matchMethod: MatchMethod, func: (ResourceLocation, PartialLocation)->Boolean) =
MComparison.of<List<BlockModel>, PartialLocation>(matchMethod) { models, partial ->
models.any { func(ResourceLocation(it.name), partial) }
}
fun locationMatches(loc: ResourceLocation, partial: PartialLocation) =
(partial.first == null || loc.namespace == partial.first) && loc.path == partial.second
fun locationContains(loc: ResourceLocation, partial: PartialLocation) =
(partial.first == null || loc.namespace.contains(partial.first!!)) && loc.path.contains(partial.second)
fun splitLocation(str: String): PartialLocation =
if (str.contains(":")) ResourceLocation(str).let { it.namespace to it.path } else null to str
fun <T, R> quoteString(mValue: MValue<T>, newValue: R) = mValue.description.quoted
fun BlockModel.ancestors(): List<BlockModel> = if (parent == null) listOf(this) else parent!!.ancestors() + this
fun BlockModel.tintOf(spriteName: String) =
elements.findFirst { element ->
element.faces.entries.findFirst { (_, face) ->
if (face.texture == "#$spriteName") face.tintIndex else null
}
}
}

View File

@@ -4,10 +4,10 @@ import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.util.ThreadLocalDelegate
import mods.betterfoliage.util.allAvailable
import mods.betterfoliage.util.reflectField
import mods.octarinecore.BlockPos
import mods.octarinecore.BlockState
import mods.octarinecore.CustomColors
import mods.octarinecore.RenderEnv
import mods.betterfoliage.BlockPos
import mods.betterfoliage.BlockState
import mods.betterfoliage.CustomColors
import mods.betterfoliage.RenderEnv
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.BakedQuad
@@ -34,7 +34,7 @@ object OptifineCustomColors {
val fakeQuad = BakedQuad(IntArray(0), 1, UP, null, true)
fun getBlockColor(ctx: BlockCtx, resolver: ColorResolver): Int {
val ofColor = if (isColorAvailable && Minecraft.getInstance().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
val ofColor = if (isColorAvailable && Minecraft.getInstance().options.reflectField<Boolean>("ofCustomColors") == true) {
renderEnv.reset(ctx.state, ctx.pos)
CustomColors.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int
} else null

View File

@@ -1,48 +1,78 @@
package mods.betterfoliage.integration
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BlockAliases
import mods.betterfoliage.BufferBuilder_sVertexBuilder
import mods.betterfoliage.SVertexBuilder
import mods.betterfoliage.Shaders
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.RenderCtxForge
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.allAvailable
import mods.betterfoliage.util.get
import mods.octarinecore.*
import mods.betterfoliage.util.mapArray
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.Direction
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.EAST
import net.minecraft.util.Direction.NORTH
import net.minecraft.util.Direction.SOUTH
import net.minecraft.util.Direction.WEST
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.IBlockDisplayReader
import net.minecraftforge.client.model.pipeline.LightUtil
import org.apache.logging.log4j.Level.INFO
/**
* Integration for ShadersMod.
*/
object ShadersModIntegration : HasLogger() {
@JvmStatic val isAvailable = allAvailable(SVertexBuilder, SVertexBuilder.pushState, SVertexBuilder.popState, BlockAliases.getAliasBlockId)
@JvmStatic val isEffectsAvailable = allAvailable(SVertexBuilder.pushState, SVertexBuilder.popState, BlockAliases.getAliasBlockId)
@JvmStatic val isDiffuseAvailable = allAvailable(Shaders.shaderPackLoaded, Shaders.blockLightLevel05, Shaders.blockLightLevel06, Shaders.blockLightLevel08)
val defaultLeaves = Blocks.OAK_LEAVES.defaultState
val defaultGrass = Blocks.GRASS.defaultState
@JvmStatic val defaultLeaves = Blocks.OAK_LEAVES.defaultBlockState()
@JvmStatic val defaultGrass = Blocks.GRASS.defaultBlockState()
@JvmStatic var diffuseShades = Direction.values().mapArray { LightUtil.diffuseLight(it) }
/**
* Called from transformed ShadersMod code.
* @see mods.betterfoliage.loader.BetterFoliageTransformer
*/
@JvmStatic fun getBlockStateOverride(state: BlockState, world: ILightReader, pos: BlockPos): BlockState {
// if (LeafRegistry[state, world, pos] != null) return defaultLeaves
@JvmStatic fun getBlockStateOverride(state: BlockState, world: IBlockDisplayReader, pos: BlockPos): BlockState {
if (state in BetterFoliage.blockTypes.leaf) return defaultLeaves
// if (BlockConfig.crops.matchesClass(state.block)) return defaultGrass
return state
}
init {
logger.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
logger.log(INFO, "ShadersMod diffuse shading integration is ${if (isDiffuseAvailable) "enabled" else "disabled" }")
logger.log(INFO, "ShadersMod vertex shader integration is ${if (isEffectsAvailable) "enabled" else "disabled" }")
// Recalculate the diffuse shading values used when resources are reloaded
if (isDiffuseAvailable) BetterFoliage.modelManager.onInvalidate {
if (Shaders.shaderPackLoaded.getStatic()) {
diffuseShades = Direction.values().mapArray { face ->
when(face) {
DOWN -> Shaders.blockLightLevel05.getStatic()
WEST, EAST -> Shaders.blockLightLevel06.getStatic()
NORTH, SOUTH -> Shaders.blockLightLevel08.getStatic()
else -> LightUtil.diffuseLight(face)
}
}
} else {
diffuseShades = Direction.values().mapArray { LightUtil.diffuseLight(it) }
}
}
}
/** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(buffer: BufferBuilder, state: BlockState, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) {
if (isAvailable && enabled) {
if (isEffectsAvailable && enabled) {
val aliasBlockId = BlockAliases.getAliasBlockId.invokeStatic(state)
val sVertexBuilder = buffer[BufferBuilder_sVertexBuilder]
SVertexBuilder.pushState.invoke(sVertexBuilder, aliasBlockId)

View File

@@ -1,12 +1,19 @@
package mods.betterfoliage.model
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.WrappedLayerPredicate
import mods.betterfoliage.render.pipeline.layerPredicate
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.directionsAndNull
import mods.betterfoliage.util.mapArray
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.SimpleBakedModel
@@ -27,15 +34,23 @@ data class HalfBakedQuad(
open class HalfBakedSimpleModelWrapper(baseModel: SimpleBakedModel): IBakedModel by baseModel, SpecialRenderModel {
val baseQuads = baseModel.unbakeQuads()
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.renderQuads(baseQuads)
override fun prepare(ctx: BlockCtx, random: Random) = Unit
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
// if the passed data is a BlockState, render on the same layer(s) as that block
val testState = (data as? BlockState) ?: ctx.state
// this could get called for more layers than the underlying model is on
// ignore extra decoration layers
val shouldRender = when(val predicate = testState.block.layerPredicate) {
is WrappedLayerPredicate -> predicate.original.test(layer)
else -> RenderTypeLookup.canRenderInLayer(testState, layer)
}
if (shouldRender) ctx.renderQuads(baseQuads)
}
}
open class HalfBakedSpecialWrapper(val baseModel: SpecialRenderModel): IBakedModel by baseModel, SpecialRenderModel {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
baseModel.render(ctx, noDecorations)
}
open class HalfBakedSpecialWrapper(val baseModel: SpecialRenderModel): SpecialRenderModel by baseModel {
}
abstract class HalfBakedWrapperKey : ModelBakingKey, HasLogger() {
@@ -68,8 +83,8 @@ fun List<Quad>.bake(applyDiffuseLighting: Boolean) = map { quad ->
)
// don't fill lightmap UV coords
element.usage == VertexFormatElement.Usage.UV && element.type == VertexFormatElement.Type.FLOAT -> builder.put(idx,
quad.sprite.minU + (quad.sprite.maxU - quad.sprite.minU) * (vertex.uv.u + 0.5).toFloat(),
quad.sprite.minV + (quad.sprite.maxV - quad.sprite.minV) * (vertex.uv.v + 0.5).toFloat(),
quad.sprite.u0 + (quad.sprite.u1 - quad.sprite.u0) * (vertex.uv.u + 0.5).toFloat(),
quad.sprite.v0 + (quad.sprite.v1 - quad.sprite.v0) * (vertex.uv.v + 0.5).toFloat(),
0.0f, 1.0f
)
element.usage == VertexFormatElement.Usage.COLOR -> builder.put(idx,
@@ -96,18 +111,18 @@ fun Array<List<Quad>>.bake(applyDiffuseLighting: Boolean) = mapArray { it.bake(a
fun BakedQuad.unbake(): HalfBakedQuad {
val size = DefaultVertexFormats.BLOCK.integerSize
val verts = Array(4) { vIdx ->
val x = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 0])
val y = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 1])
val z = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 2])
val color = vertexData[vIdx * size + 3]
val u = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 4])
val v = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 5])
val x = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 0]) - 0.5f
val y = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 1]) - 0.5f
val z = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 2]) - 0.5f
val color = vertices[vIdx * size + 3]
val u = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 4])
val v = java.lang.Float.intBitsToFloat(vertices[vIdx * size + 5])
Vertex(Double3(x, y, z), UV(u.toDouble(), v.toDouble()), Color(color))
}
val unbaked = Quad(
verts[0], verts[1], verts[2], verts[3],
colorIndex = if (hasTintIndex()) tintIndex else -1,
face = face
colorIndex = if (isTinted) tintIndex else -1,
face = direction
)
return HalfBakedQuad(unbaked, this)
}

View File

@@ -70,6 +70,8 @@ data class Color(val alpha: Int, val red: Int, val green: Int, val blue: Int) {
)
val asInt get() = (alpha shl 24) or (red shl 16) or (green shl 8) or blue
val asHSB get() = HSB.fromColor(this)
operator fun times(f: Float) = Color(
alpha,
(f * red.toFloat()).toInt().coerceIn(0 until 256),
@@ -89,8 +91,17 @@ data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
val hsbVals = java.awt.Color.RGBtoHSB(color and 255, (color shr 8) and 255, (color shr 16) and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
fun fromColorBGRA(color: Int): HSB {
val hsbVals = java.awt.Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
fun fromColor(color: Color): HSB {
val hsbVals = java.awt.Color.RGBtoHSB(color.red, color.green, color.blue, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
}
val asColor: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
val asInt: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
val asColor: Color get() = Color(asInt)
}
/**

View File

@@ -1,34 +1,57 @@
package mods.betterfoliage.model
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.util.HasLogger
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.Material
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraft.client.renderer.model.WeightedBakedModel
import net.minecraft.util.WeightedRandom
import org.apache.logging.log4j.Level.WARN
import java.util.Random
import java.util.function.Function
/**
* Model that makes use of advanced rendering features.
*/
interface SpecialRenderModel : IBakedModel {
fun render(ctx: RenderCtxBase, noDecorations: Boolean = false)
/**
* Create custom renderdata object. Called once per block. Result is passed to renderLayer().
*/
fun prepare(ctx: BlockCtx, random: Random): Any
fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType)
/**
* Get the actual model that will be rendered. Useful for container models (like [WeightedBakedModel]).
*/
fun resolve(random: Random) = this
}
interface SpecialRenderData {
fun canRenderInLayer(layer: RenderType) = false
}
class WeightedModelWrapper(
val models: List<WeightedModel>, baseModel: SpecialRenderModel
): IBakedModel by baseModel, SpecialRenderModel {
) : IBakedModel by baseModel, SpecialRenderModel {
class WeightedModel(val model: SpecialRenderModel, weight: Int) : WeightedRandom.Item(weight)
val totalWeight = models.sumBy { it.itemWeight }
fun getModel(random: Random) = WeightedRandom.getRandomItem(models, random.nextInt(totalWeight))
val totalWeight = models.sumBy { it.weight }
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
getModel(ctx.random).model.render(ctx, noDecorations)
fun getModel(random: Random) = WeightedRandom.getWeightedItem(models, random.nextInt(totalWeight))
override fun resolve(random: Random) = getModel(random).model.resolve(random)
override fun prepare(ctx: BlockCtx, random: Random) = getModel(random).model.let { actual ->
WeightedRenderData(actual, actual.prepare(ctx, random))
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) = when (data) {
is WeightedRenderData -> data.model.renderLayer(ctx, data.modelRenderData, layer)
else -> getModel(ctx.random).model.renderLayer(ctx, data, layer)
}
}
data class WeightedRenderData(
val model: SpecialRenderModel,
val modelRenderData: Any
) : SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = (modelRenderData as? SpecialRenderData)?.canRenderInLayer(layer) ?: false
}

View File

@@ -59,7 +59,7 @@ class SpriteSetDelegate(
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
if (event.map.textureLocation != Atlas.BLOCKS.resourceId) return
if (event.map.location() != atlas.resourceId) return
spriteSet = null
idList = (0 until 16)
.map(idFunc)

View File

@@ -18,7 +18,6 @@ import kotlin.math.sin
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx.toDouble() / 64.0).let { Double3(cos(it), 0.0, sin(it)) }
data class TuftShapeKey(
val size: Double,
val height: Double,
@@ -50,13 +49,13 @@ fun tuftQuadSingle(size: Double, height: Double, flipU: Boolean) =
)
.mirrorUV(flipU, false)
fun tuftModelSet(shapes: Array<TuftShapeKey>, tintIndex: Int, spriteGetter: (Int) -> TextureAtlasSprite) =
fun tuftModelSet(shapes: Array<TuftShapeKey>, color: Color, tint: Int, spriteGetter: (Int) -> TextureAtlasSprite) =
shapes.mapIndexed { idx, shape ->
listOf(
tuftQuadSingle(shape.size, shape.height, shape.flipU1),
tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP))
).map { it.move(shape.offset) }
.map { it.colorIndex(tintIndex) }
.map { it.color(color).colorIndex(tint) }
.map { it.sprite(spriteGetter(idx)) }
}
@@ -85,20 +84,20 @@ fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Li
}
}
fun crossModelSingle(base: List<Quad>, sprite: TextureAtlasSprite, tintIndex: Int,scrambleUV: Boolean) =
fun crossModelSingle(base: List<Quad>, sprite: TextureAtlasSprite, color: Color, tint: Int, scrambleUV: Boolean) =
base.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it }
.map { it.colorIndex(tintIndex) }
.map { it.color(color).colorIndex(tint) }
.mapIndexed { idx, quad -> quad.sprite(sprite) }
.withOpposites()
.bake(false)
fun crossModelsTextured(
leafBase: Iterable<List<Quad>>,
tintIndex: Int,
color: Color, tint: Int,
scrambleUV: Boolean,
spriteGetter: (Int) -> ResourceLocation
) = leafBase.mapIndexed { idx, leaf ->
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], tintIndex, scrambleUV)
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], color, tint, scrambleUV)
}.toTypedArray()
fun Iterable<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) }

View File

@@ -1,8 +1,10 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
@@ -16,33 +18,25 @@ import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.lighting.RoundLeafLighting
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.get
import mods.betterfoliage.util.horizontalDirections
import mods.betterfoliage.util.idx
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardCactusDiscovery : AbstractModelDiscovery() {
val CACTUS_BLOCKS = listOf(Blocks.CACTUS)
override fun processModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
if (model is BlockModel && ctx.blockState.block in CACTUS_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardCactusKey)
ctx.sprites.add(StandardCactusModel.cactusCrossSprite)
}
super.processModel(ctx)
object StandardCactusDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
ctx.addReplacement(StandardCactusKey)
ctx.sprites.add(StandardCactusModel.cactusCrossSprite)
}
}
@@ -50,22 +44,30 @@ object StandardCactusKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardCactusModel(wrapped)
}
class CactusRenderData(val armSide: Int, val armIdx: Int, val crossIdx: Int)
class StandardCactusModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
override fun prepare(ctx: BlockCtx, random: Random): Any = when {
!Config.enabled || !Config.cactus.enabled -> Unit
else -> CactusRenderData(
armSide = random.nextInt() and 3,
armIdx = random.idx(cactusArmModels),
crossIdx = random.idx(cactusCrossModels)
)
}
val armLighting = horizontalDirections.map { LightingPreferredFace(it) }.toTypedArray()
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.checkSides = false
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.cactus.enabled) return
val armSide = ctx.random.nextInt() and 3
ctx.vertexLighter = armLighting[armSide]
ctx.renderQuads(cactusArmModels[armSide][ctx.random])
ctx.vertexLighter = RoundLeafLighting
ctx.renderQuads(cactusCrossModels[ctx.random])
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is CactusRenderData) {
ctx.vertexLighter = armLighting[data.armSide]
ctx.renderQuads(cactusArmModels[data.armSide][data.armIdx])
ctx.vertexLighter = RoundLeafLighting
ctx.renderQuads(cactusCrossModels[data.crossIdx])
}
}
companion object {
@@ -73,19 +75,19 @@ class StandardCactusModel(
val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx")
}
val cactusArmModels by LazyInvalidatable(BakeWrapperManager) {
val cactusArmModels by BetterFoliage.modelManager.lazy {
val shapes = Config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) }
val models = tuftModelSet(shapes, -1) { cactusArmSprites[randomI()] }
val models = tuftModelSet(shapes, Color.white, -1) { cactusArmSprites[randomI()] }
horizontalDirections.map { side ->
models.transform { move(0.0625 to DOWN).rotate(Rotation.fromUp[side.ordinal]) }.buildTufts()
}.toTypedArray()
}
val cactusCrossModels by LazyInvalidatable(BakeWrapperManager) {
val cactusCrossModels by BetterFoliage.modelManager.lazy {
val models = Config.cactus.let { config ->
crossModelsRaw(64, config.size, 0.0, 0.0)
.transform { rotateZ(randomD(-config.sizeVariation, config.sizeVariation)) }
}
crossModelsTextured(models, -1, true) { cactusCrossSprite }
crossModelsTextured(models, Color.white, -1, true) { cactusCrossSprite }
}
}
}

View File

@@ -1,54 +1,47 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.SALTWATER_BIOMES
import mods.betterfoliage.config.isSnow
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.resource.generated.CenteredSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.getBlockModel
import mods.betterfoliage.util.idxOrNull
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.randomI
import net.minecraft.block.Blocks
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome
import java.util.Random
object StandardDirtDiscovery : AbstractModelDiscovery() {
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL)
fun canRenderInLayer(layer: RenderType) = when {
!Config.enabled -> layer == RenderType.getSolid()
!Config.connectedGrass.enabled && !Config.algae.enabled && !Config.reed.enabled -> layer == RenderType.getSolid()
else -> layer == RenderType.getCutoutMipped()
}
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in DIRT_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardDirtKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
super.processModel(ctx)
object StandardDirtDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardDirtKey)
ctx.blockState.block.extendLayers()
}
}
@@ -56,45 +49,80 @@ object StandardDirtKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardDirtModel(wrapped)
}
class DirtRenderData(
val connectedGrassModel: SpecialRenderModel?,
val algaeIdx: Int?,
val reedIdx: Int?
) : SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = when {
connectedGrassModel != null && layer == Layers.connectedDirt -> true
(algaeIdx != null || reedIdx != null) && layer == Layers.tufts -> true
else -> false
}
}
class StandardDirtModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val vanillaTuftLighting = LightingPreferredFace(UP)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
val stateUp = ctx.offset(UP).state
val isConnectedGrass = Config.connectedGrass.enabled && stateUp in BetterFoliage.blockTypes.grass
if (isConnectedGrass) {
(ctx.blockModelShapes.getModel(stateUp) as? SpecialRenderModel)?.let { grassModel ->
ctx.renderMasquerade(UP.offset) {
grassModel.render(ctx, true)
}
return
}
return super.render(ctx, false)
}
super.render(ctx, false)
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
val stateUp = ctx.state(UP)
val state2Up = ctx.state(Int3(0, 2, 0))
val isConnectedGrass = Config.connectedGrass.enabled &&
stateUp in BetterFoliage.blockTypes.grass &&
(Config.connectedGrass.snowEnabled || !state2Up.isSnow)
val isWater = stateUp.material == Material.WATER
val isDeepWater = isWater && ctx.offset(Int3(2 to UP)).state.material == Material.WATER
val isShallowWater = isWater && ctx.offset(Int3(2 to UP)).state.isAir
val isSaltWater = isWater && ctx.biome?.category in SALTWATER_BIOMES
val isDeepWater = isWater && state2Up.material == Material.WATER
val isShallowWater = isWater && state2Up.isAir
val isSaltWater = isWater && ctx.biome?.biomeCategory in SALTWATER_BIOMES
if (Config.algae.enabled(ctx.random) && isDeepWater) {
(ctx as? RenderCtxVanilla)?.vertexLighter = vanillaTuftLighting
ctx.renderQuads(algaeModels[ctx.random])
} else if (Config.reed.enabled(ctx.random) && isShallowWater && !isSaltWater) {
(ctx as? RenderCtxVanilla)?.vertexLighter = vanillaTuftLighting
ctx.renderQuads(reedModels[ctx.random])
// get the actual grass model to use for connected grass rendering
// return null if the grass specifically does not want to connect
val connectedGrassModel = if (!isConnectedGrass) null else getBlockModel(stateUp).let { model ->
(model as? SpecialRenderModel)?.resolve(random)?.let { grassModel ->
if ((grassModel as? StandardGrassModel)?.key?.noConnect == true) null else grassModel
}
}
return DirtRenderData(
connectedGrassModel = connectedGrassModel,
algaeIdx = random.idxOrNull(algaeModels) { Config.algae.enabled(random) && isDeepWater && isSaltWater },
reedIdx = random.idxOrNull(reedModels) { Config.reed.enabled(random) && isShallowWater && !isSaltWater }
)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
if (data is DirtRenderData) {
if (data.connectedGrassModel != null) {
ctx.renderMasquerade(UP.offset) {
data.connectedGrassModel.renderLayer(ctx, ctx.state(UP), layer)
}
} else {
// render non-connected grass
super.renderLayer(ctx, data, layer)
}
if (layer == Layers.tufts) {
data.algaeIdx?.let {
ctx.vertexLighter = vanillaTuftLighting
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
ctx.renderQuads(algaeModels[it])
}
}
data.reedIdx?.let {
ctx.vertexLighter = vanillaTuftLighting
ShadersModIntegration.grass(ctx, Config.reed.shaderWind) {
ctx.renderQuads(reedModels[it])
}
}
}
} else super.renderLayer(ctx, data, layer)
}
companion object {
val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN)
val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_algae_$idx")
}
@@ -103,13 +131,13 @@ class StandardDirtModel(
idFunc = { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_reed_$idx") },
idRegister = { id -> CenteredSprite(id, aspectHeight = 2).register(BetterFoliage.generatedPack) }
)
val algaeModels by LazyInvalidatable(BakeWrapperManager) {
val algaeModels by BetterFoliage.modelManager.lazy {
val shapes = Config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, -1) { algaeSprites[randomI()] }.buildTufts()
tuftModelSet(shapes, Color.white, -1) { algaeSprites[randomI()] }.buildTufts()
}
val reedModels by LazyInvalidatable(BakeWrapperManager) {
val reedModels by BetterFoliage.modelManager.lazy {
val shapes = Config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, -1) { reedSprites[randomI()] }.buildTufts()
tuftModelSet(shapes, Color.white, -1) { reedSprites[randomI()] }.buildTufts()
}
}
}

View File

@@ -1,13 +1,15 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.isSnow
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
@@ -15,54 +17,68 @@ import mods.betterfoliage.model.fullCubeTextured
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.colorOverride
import mods.betterfoliage.util.get
import mods.betterfoliage.util.isSnow
import mods.betterfoliage.util.logColorOverride
import mods.betterfoliage.util.averageHSB
import mods.betterfoliage.util.idxOrNull
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.lazyMap
import mods.betterfoliage.util.brighten
import mods.betterfoliage.util.logTextureColor
import mods.betterfoliage.util.randomI
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.INFO
import java.util.Random
object StandardGrassDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.grassModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
ctx.addReplacement(StandardGrassKey(textureMatch[0], null))
object StandardGrassDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
val texture = params.location("texture") ?: return
val tint = params.int("tint") ?: -1
val color = Atlas.BLOCKS.file(texture).averageHSB.let {
detailLogger.logTextureColor(INFO, "grass texture \"$texture\"", it)
it.brighten().asColor
}
val noConnect = params["no-connect"] == "true"
ctx.addReplacement(StandardGrassKey(texture, tint, color, noConnect))
BetterFoliage.blockTypes.grass.add(ctx.blockState)
ctx.blockState.block.extendLayers()
}
}
data class StandardGrassKey(
val grassLocation: ResourceLocation,
val overrideColor: Color?
val sprite: ResourceLocation,
val tintIndex: Int,
val avgColor: Color,
val noConnect: Boolean
) : HalfBakedWrapperKey() {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
val grassSpriteColor = Atlas.BLOCKS[grassLocation].averageColor.let { hsb ->
logColorOverride(BetterFoliageMod.detailLogger(this), Config.shortGrass.saturationThreshold, hsb)
hsb.colorOverride(Config.shortGrass.saturationThreshold)
}
return StandardGrassModel(wrapped, this.copy(overrideColor = grassSpriteColor))
return StandardGrassModel(wrapped, this)
}
}
class GrassRenderData(
val isSnowed: Boolean,
val connectedGrassIdx: Int?,
val tuftIdx: Int?
): SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = when {
connectedGrassIdx != null && layer == Layers.connectedGrass -> true
tuftIdx != null && layer == Layers.tufts -> true
else -> false
}
}
class StandardGrassModel(
wrapped: SpecialRenderModel,
key: StandardGrassKey
val key: StandardGrassKey
) : HalfBakedSpecialWrapper(wrapped) {
val tuftNormal by grassTuftMeshesNormal.delegate(key)
@@ -70,48 +86,62 @@ class StandardGrassModel(
val fullBlock by grassFullBlockMeshes.delegate(key)
val tuftLighting = LightingPreferredFace(UP)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
val stateBelow = ctx.state(DOWN)
val stateAbove = ctx.state(UP)
val isAir = ctx.isAir(UP)
val isSnowed = stateAbove.isSnow
val connected = Config.connectedGrass.enabled &&
val connected = !key.noConnect && Config.connectedGrass.enabled &&
(!isSnowed || Config.connectedGrass.snowEnabled) &&
BetterFoliage.blockTypes.run { stateBelow in grass || stateBelow in dirt }
if (connected) {
ctx.renderQuads(if (isSnowed) snowFullBlockMeshes[ctx.random] else fullBlock[ctx.random])
} else {
super.render(ctx, noDecorations)
}
if (Config.shortGrass.enabled(ctx.random) && (isAir || isSnowed)) {
ctx.vertexLighter = tuftLighting
ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
ctx.renderQuads(if (isSnowed) tuftSnowed[ctx.random] else tuftNormal[ctx.random])
return GrassRenderData(
isSnowed = isSnowed,
connectedGrassIdx = random.idxOrNull(if (isSnowed) snowFullBlockMeshes else fullBlock) { connected },
tuftIdx = random.idxOrNull(if (isSnowed) tuftSnowed else tuftNormal) {
Config.shortGrass.enabled(random) &&
Config.shortGrass.grassEnabled &&
(isAir || isSnowed)
}
}
)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
if (data is GrassRenderData) {
if (data.connectedGrassIdx != null) {
if (layer == Layers.connectedGrass)
ctx.renderQuads((if (data.isSnowed) snowFullBlockMeshes else fullBlock)[data.connectedGrassIdx])
} else {
super.renderLayer(ctx, data, layer)
}
if (data.tuftIdx != null && layer == Layers.tufts) {
ctx.vertexLighter = tuftLighting
ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
ctx.renderQuads((if (data.isSnowed) tuftSnowed else tuftNormal)[data.tuftIdx])
}
}
} else super.renderLayer(ctx, data, layer)
}
companion object {
val grassTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx")
}
val grassTuftShapes by LazyInvalidatable(BakeWrapperManager) {
val grassTuftShapes by BetterFoliage.modelManager.lazy {
Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
}
val grassTuftMeshesNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes, key.tintIndex) { idx -> grassTuftSprites[randomI()] }.buildTufts()
val grassTuftMeshesNormal = BetterFoliage.modelManager.lazyMap { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes, key.avgColor, key.tintIndex) { idx -> grassTuftSprites[randomI()] }.buildTufts()
}
val grassTuftMeshesSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes, -1) { idx -> grassTuftSprites[randomI()] }.buildTufts()
val grassTuftMeshesSnowed = BetterFoliage.modelManager.lazyMap { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes, Color.white, -1) { idx -> grassTuftSprites[randomI()] }.buildTufts()
}
val grassFullBlockMeshes = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
Array(64) { fullCubeTextured(key.grassLocation, key.tintIndex) }
val grassFullBlockMeshes = BetterFoliage.modelManager.lazyMap { key: StandardGrassKey ->
Array(64) { fullCubeTextured(key.sprite, key.tintIndex) }
}
val snowFullBlockMeshes by LazyInvalidatable(BakeWrapperManager) {
val snowFullBlockMeshes by BetterFoliage.modelManager.lazy {
Array(64) { fullCubeTextured(ResourceLocation("block/snow"), -1) }
}
}

View File

@@ -1,9 +1,10 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.isSnow
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
@@ -16,51 +17,49 @@ import mods.betterfoliage.render.particle.LeafBlockModel
import mods.betterfoliage.render.particle.LeafParticleKey
import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.resource.generated.GeneratedLeafSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.colorOverride
import mods.betterfoliage.util.isSnow
import mods.betterfoliage.util.logColorOverride
import mods.betterfoliage.util.averageHSB
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.lazyMap
import mods.betterfoliage.util.brighten
import mods.betterfoliage.util.logTextureColor
import mods.betterfoliage.util.saturate
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.INFO
object StandardLeafDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default"
val generated = GeneratedLeafSprite(textureMatch[0], leafType)
object StandardLeafDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
val texture = params.location("texture") ?: return
val tint = params.int("tint") ?: -1
val color = Atlas.BLOCKS.file(texture).averageHSB.let {
detailLogger.logTextureColor(INFO, "leaf texture \"$texture\"", it)
it.brighten().asColor
}
val leafType = params["particle"] ?: "default"
val generated = GeneratedLeafSprite(texture, leafType)
.register(BetterFoliage.generatedPack)
.apply { ctx.sprites.add(this) }
detailLogger.log(INFO, " particle $leafType")
ctx.addReplacement(StandardLeafKey(generated, leafType, null))
ctx.addReplacement(StandardLeafKey(generated, leafType, tint, color))
BetterFoliage.blockTypes.leaf.add(ctx.blockState)
}
}
data class StandardLeafKey(
val roundLeafTexture: ResourceLocation,
override val leafType: String,
override val overrideColor: Color?
override val tintIndex: Int,
override val avgColor: Color
) : HalfBakedWrapperKey(), LeafParticleKey {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
val leafSpriteColor = Atlas.BLOCKS[roundLeafTexture].averageColor.let { hsb ->
logColorOverride(BetterFoliageMod.detailLogger(this), Config.leaves.saturationThreshold, hsb)
hsb.colorOverride(Config.leaves.saturationThreshold)
}
return StandardLeafModel(wrapped, this.copy(overrideColor = leafSpriteColor))
return StandardLeafModel(wrapped, this)
}
}
@@ -71,28 +70,32 @@ class StandardLeafModel(
val leafNormal by leafModelsNormal.delegate(key)
val leafSnowed by leafModelsSnowed.delegate(key)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.leaves.enabled || noDecorations) return
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
ShadersModIntegration.leaves(ctx, true) {
super.renderLayer(ctx, data, layer)
if (!Config.enabled || !Config.leaves.enabled) return
ctx.vertexLighter = RoundLeafLightingPreferUp
val leafIdx = ctx.random.nextInt(64)
ctx.renderQuads(leafNormal[leafIdx])
if (ctx.state(UP).isSnow) ctx.renderQuads(leafSnowed[leafIdx])
ctx.vertexLighter = RoundLeafLightingPreferUp
val leafIdx = ctx.random.nextInt(64)
ctx.renderQuads(leafNormal[leafIdx])
if (Config.leaves.snowEnabled && ctx.state(UP).isSnow) ctx.renderQuads(leafSnowed[leafIdx])
}
}
companion object {
val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx")
}
val leafModelsBase = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
val leafModelsBase by BetterFoliage.modelManager.lazy {
Config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) }
}
val leafModelsNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], key.tintIndex, true) { key.roundLeafTexture }
val leafModelsNormal = BetterFoliage.modelManager.lazyMap { key: StandardLeafKey ->
// generated leaf textures naturally carry the color of their source textures
// no need to color the quad a second time
crossModelsTextured(leafModelsBase, Color.white, key.tintIndex, true) { key.roundLeafTexture }
}
val leafModelsSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], -1, false) { leafSpritesSnowed[it].name }
val leafModelsSnowed = BetterFoliage.modelManager.lazyMap { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase, Color.white, -1, false) { leafSpritesSnowed[it].name }
}
}
}

View File

@@ -1,8 +1,11 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
@@ -12,29 +15,21 @@ import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.ModelBakery
import mods.betterfoliage.util.idx
import mods.betterfoliage.util.idxOrNull
import mods.betterfoliage.util.lazy
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardLilypadDiscovery : AbstractModelDiscovery() {
val LILYPAD_BLOCKS = listOf(Blocks.LILY_PAD)
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in LILYPAD_BLOCKS) {
ctx.addReplacement(StandardLilypadKey)
}
super.processModel(ctx)
object StandardLilypadDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
ctx.addReplacement(StandardLilypadKey)
}
}
@@ -42,18 +37,32 @@ object StandardLilypadKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardLilypadModel(wrapped)
}
class LilypadRenderData(
val rootIdx: Int,
val flowerIdx: Int?
)
class StandardLilypadModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.checkSides = false
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.lilypad.enabled) return
ShadersModIntegration.grass(ctx, Config.lilypad.shaderWind) {
ctx.renderQuads(lilypadRootModels[ctx.random])
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
return LilypadRenderData(
rootIdx = random.idx(lilypadRootModels),
flowerIdx = random.idxOrNull(lilypadFlowerModels) { Config.lilypad.enabled(random) }
)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
ctx.checkSides = false
super.renderLayer(ctx, data, layer)
if (data is LilypadRenderData) {
data.flowerIdx?.let { ctx.renderQuads(lilypadFlowerModels[it]) }
ShadersModIntegration.grass(ctx, Config.lilypad.shaderWind) {
ctx.renderQuads(lilypadRootModels[data.rootIdx])
}
}
if (Config.lilypad.enabled(ctx.random)) ctx.renderQuads(lilypadFlowerModels[ctx.random])
}
companion object {
@@ -63,15 +72,15 @@ class StandardLilypadModel(
val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_flower_$idx")
}
val lilypadRootModels by LazyInvalidatable(BakeWrapperManager) {
val lilypadRootModels by BetterFoliage.modelManager.lazy {
val shapes = tuftShapeSet(1.0, 1.0, 1.0, Config.lilypad.hOffset)
tuftModelSet(shapes, -1) { lilypadRootSprites[it] }
tuftModelSet(shapes, Color.white, -1) { lilypadRootSprites[it] }
.transform { move(2.0 to DOWN) }
.buildTufts()
}
val lilypadFlowerModels by LazyInvalidatable(BakeWrapperManager) {
val lilypadFlowerModels by BetterFoliage.modelManager.lazy {
val shapes = tuftShapeSet(0.5, 0.5, 0.5, Config.lilypad.hOffset)
tuftModelSet(shapes, -1) { lilypadFlowerSprites[it] }
tuftModelSet(shapes, Color.white, -1) { lilypadFlowerSprites[it] }
.transform { move(1.0 to DOWN) }
.buildTufts()
}

View File

@@ -1,63 +1,87 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.averageHSB
import mods.betterfoliage.util.idxOrNull
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.lazyMap
import mods.betterfoliage.util.brighten
import mods.betterfoliage.util.randomI
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.Direction
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardMyceliumDiscovery : AbstractModelDiscovery() {
val MYCELIUM_BLOCKS = listOf(Blocks.MYCELIUM)
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in MYCELIUM_BLOCKS) {
ctx.addReplacement(StandardMyceliumKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.getCutout())
}
super.processModel(ctx)
object StandardMyceliumDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
val texture = params.location("texture") ?: return
val tint = params.int("tint") ?: -1
val color = Atlas.BLOCKS.file(texture).averageHSB.brighten(multiplier = 1.5f).asColor
ctx.addReplacement(StandardMyceliumKey(texture, tint, color))
ctx.blockState.block.extendLayers()
}
}
object StandardMyceliumKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardMyceliumModel(wrapped)
data class StandardMyceliumKey(
val sprite: ResourceLocation,
val tintIndex: Int,
val avgColor: Color,
) : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
return StandardMyceliumModel(wrapped, this)
}
}
class MyceliumRenderData(
val tuftIndex: Int?
) : SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = tuftIndex != null && layer == Layers.tufts
}
class StandardMyceliumModel(
wrapped: SpecialRenderModel
wrapped: SpecialRenderModel,
key: StandardMyceliumKey
) : HalfBakedSpecialWrapper(wrapped) {
val tuftModels by myceliumTuftModels.delegate(key)
val tuftLighting = LightingPreferredFace(Direction.UP)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.render(ctx, noDecorations)
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
return MyceliumRenderData(
random.idxOrNull(tuftModels) {
Config.shortGrass.enabled(random) &&
Config.shortGrass.myceliumEnabled &&
ctx.state(Direction.UP).isAir(ctx.world, ctx.pos)
}
)
}
if (Config.shortGrass.enabled &&
Config.shortGrass.myceliumEnabled &&
Config.shortGrass.enabled(ctx.random) &&
ctx.state(Direction.UP).isAir(ctx.world, ctx.pos)
) {
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is MyceliumRenderData && data.tuftIndex != null && layer == Layers.tufts) {
ctx.vertexLighter = tuftLighting
ctx.renderQuads(myceliumTuftModels[ctx.random])
ctx.renderQuads(tuftModels[data.tuftIndex])
}
}
@@ -65,9 +89,11 @@ class StandardMyceliumModel(
val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx")
}
val myceliumTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, -1) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
val myceliumTuftShapes by BetterFoliage.modelManager.lazy {
Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
}
val myceliumTuftModels = BetterFoliage.modelManager.lazyMap { key: StandardMyceliumKey ->
tuftModelSet(myceliumTuftShapes, key.avgColor, key.tintIndex) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
}
}
}

View File

@@ -1,10 +1,13 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
@@ -12,39 +15,26 @@ import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.get
import mods.betterfoliage.util.idxOrNull
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.randomI
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardNetherrackDiscovery : AbstractModelDiscovery() {
val NETHERRACK_BLOCKS = listOf(Blocks.NETHERRACK)
fun canRenderInLayer(layer: RenderType) = when {
!Config.enabled -> layer == RenderType.getSolid()
!Config.netherrack.enabled -> layer == RenderType.getSolid()
else -> layer == RenderType.getCutoutMipped()
}
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in NETHERRACK_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardNetherrackKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
super.processModel(ctx)
object StandardNetherrackDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
ctx.addReplacement(StandardNetherrackKey)
ctx.blockState.block.extendLayers()
}
}
@@ -52,19 +42,33 @@ object StandardNetherrackKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardNetherrackModel(wrapped)
}
class NetherrackRenderData(
val tuftIndex: Int?
): SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = tuftIndex != null && layer == Layers.tufts
}
class StandardNetherrackModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val tuftLighting = LightingPreferredFace(DOWN)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.netherrack.enabled) return
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
return NetherrackRenderData(
random.idxOrNull(netherrackTuftModels) {
Config.netherrack.enabled &&
ctx.isAir(DOWN)
}
)
}
if (ctx.isAir(DOWN)) {
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is NetherrackRenderData && data.tuftIndex != null && layer == Layers.tufts) {
ctx.vertexLighter = tuftLighting
ctx.renderQuads(netherrackTuftModels[ctx.random])
ctx.renderQuads(netherrackTuftModels[data.tuftIndex])
}
}
@@ -72,9 +76,9 @@ class StandardNetherrackModel(
val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx")
}
val netherrackTuftModels by LazyInvalidatable(BakeWrapperManager) {
val netherrackTuftModels by BetterFoliage.modelManager.lazy {
val shapes = Config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, -1) { netherrackTuftSprites[randomI()] }
tuftModelSet(shapes, Color.white, -1) { netherrackTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) }
.buildTufts()
}

View File

@@ -1,26 +1,23 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.ACCEPTED_ROUND_LOG_MATERIALS
import mods.betterfoliage.config.Config
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.render.column.ColumnBlockKey
import mods.betterfoliage.render.column.ColumnMeshSet
import mods.betterfoliage.render.column.ColumnModelBase
import mods.betterfoliage.render.column.ColumnRenderLayer
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.lazyMap
import mods.betterfoliage.util.tryDefault
import net.minecraft.block.BlockState
import net.minecraft.block.LogBlock
import net.minecraft.block.RotatedPillarBlock
import net.minecraft.util.Direction.Axis
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.INFO
@@ -37,19 +34,20 @@ object RoundLogOverlayLayer : ColumnRenderLayer() {
override val defaultToY: Boolean get() = Config.roundLogs.defaultY
}
object StandardRoundLogDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.logModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
object StandardRoundLogDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
val barkSprite = params.location("texture-side") ?: return
val endSprite = params.location("texture-end") ?: return
val axis = getAxis(ctx.blockState)
detailLogger.log(INFO, " axis $axis")
ctx.addReplacement(StandardRoundLogKey(axis, textureMatch[0], textureMatch[1]))
detailLogger.log(INFO, " axis $axis, material ${ctx.blockState.material}")
if (!Config.roundLogs.plantsOnly || ctx.blockState.material in ACCEPTED_ROUND_LOG_MATERIALS)
ctx.addReplacement(StandardRoundLogKey(axis, barkSprite, endSprite))
}
fun getAxis(state: BlockState): Axis? {
val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?:
state.values.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
val axis = tryDefault(null) { state.getValue(RotatedPillarBlock.AXIS).toString() } ?:
state.values.entries.find { it.key.name.toLowerCase() == "axis" }?.value?.toString()
return when (axis) {
"x" -> Axis.X
"y" -> Axis.Y
@@ -79,7 +77,7 @@ class StandardRoundLogModel(
override fun getMeshSet(axis: Axis, quadrant: Int) = modelSet
companion object {
val modelSets = LazyMapInvalidatable(BakeWrapperManager) { key: StandardRoundLogKey ->
val modelSets = BetterFoliage.modelManager.lazyMap { key: StandardRoundLogKey ->
val barkSprite = Atlas.BLOCKS[key.barkSprite]
val endSprite = Atlas.BLOCKS[key.endSprite]
Config.roundLogs.let { config ->

View File

@@ -1,11 +1,15 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.SALTWATER_BIOMES
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.Quad
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.bake
@@ -13,41 +17,34 @@ import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.block.vanilla.StandardDirtModel.Companion.SALTWATER_BIOMES
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.get
import mods.betterfoliage.util.idx
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.mapArray
import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.minecraft.block.Blocks
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.Direction
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardSandDiscovery : AbstractModelDiscovery() {
val SAND_BLOCKS = listOf(Blocks.SAND, Blocks.RED_SAND)
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in SAND_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardSandKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.getCutoutMipped())
}
super.processModel(ctx)
object StandardSandDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
ctx.addReplacement(StandardSandKey)
ctx.blockState.block.extendLayers()
}
}
@@ -55,23 +52,46 @@ object StandardSandKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardSandModel(wrapped)
}
class SandRenderData(
val crustIdx: Array<Int?>,
val tuftIdx: Array<Int?>
): SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = when {
(crustIdx.any { it != null } || tuftIdx.any { it != null }) && layer == Layers.coral -> true
else -> false
}
}
class StandardSandModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val coralLighting = Direction.values().mapArray { LightingPreferredFace(it) }
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.render(ctx, noDecorations)
if (noDecorations || !Config.enabled || !Config.coral.enabled(ctx.random)) return
if (ctx.biome?.category !in SALTWATER_BIOMES) return
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
if (!Config.coral.enabled(random)) return Unit
if (ctx.biome?.biomeCategory !in SALTWATER_BIOMES) return Unit
allDirections.filter { ctx.random.nextInt(64) < Config.coral.chance }.forEach { face ->
val crustIdx = Array<Int?>(6) { null }
val tuftIdx = Array<Int?>(6) { null }
allDirections.filter { random.nextInt(64) < Config.coral.chance }.forEach { face ->
val isWater = ctx.state(face).material == Material.WATER
val isDeepWater = isWater && ctx.offset(face).state(UP).material == Material.WATER
if (isDeepWater) {
crustIdx[face.ordinal] = random.idx(coralCrustModels)
tuftIdx[face.ordinal] = random.idx(coralTuftModels)
}
}
return SandRenderData(crustIdx, tuftIdx)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is SandRenderData && layer == Layers.coral) {
for (face in 0 until 6) {
ctx.vertexLighter = coralLighting[face]
ctx.renderQuads(coralCrustModels[face][ctx.random])
ctx.renderQuads(coralTuftModels[face][ctx.random])
data.crustIdx[face]?.let { ctx.renderQuads(coralCrustModels[face][it]) }
data.tuftIdx[face]?.let { ctx.renderQuads(coralTuftModels[face][it]) }
}
}
}
@@ -83,15 +103,15 @@ class StandardSandModel(
val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx")
}
val coralTuftModels by LazyInvalidatable(BakeWrapperManager) {
val coralTuftModels by BetterFoliage.modelManager.lazy {
val shapes = Config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) }
allDirections.mapArray { face ->
tuftModelSet(shapes, -1) { coralTuftSprites[randomI()] }
tuftModelSet(shapes, Color.white, -1) { coralTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[face]) }
.buildTufts()
}
}
val coralCrustModels by LazyInvalidatable(BakeWrapperManager) {
val coralCrustModels by BetterFoliage.modelManager.lazy {
allDirections.map { face ->
Array(64) { idx ->
listOf(

View File

@@ -13,6 +13,7 @@ import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantTy
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import mods.betterfoliage.render.lighting.ColumnLighting
import mods.betterfoliage.render.pipeline.RenderCtxBase
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction.Axis
abstract class ColumnModelBase(
@@ -24,21 +25,21 @@ abstract class ColumnModelBase(
abstract val connectPerpendicular: Boolean
abstract fun getMeshSet(axis: Axis, quadrant: Int): ColumnMeshSet
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!enabled) return super.render(ctx, noDecorations)
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
if (!enabled) return super.renderLayer(ctx, data, layer)
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
val roundLog = overlayLayer[ctx]
when(roundLog) {
ColumnLayerData.SkipRender -> return
NormalRender -> return super.render(ctx, noDecorations)
NormalRender -> return super.renderLayer(ctx, data, layer)
ColumnLayerData.ResolveError, null -> {
return super.render(ctx, noDecorations)
return super.renderLayer(ctx, data, layer)
}
}
// if log axis is not defined and "Default to vertical" config option is not set, render normally
if ((roundLog as ColumnLayerData.SpecialRender).column.axis == null && !overlayLayer.defaultToY) {
return super.render(ctx, noDecorations)
return super.renderLayer(ctx, data, layer)
}
ctx.vertexLighter = ColumnLighting

View File

@@ -23,7 +23,7 @@ import net.minecraft.block.BlockState
import net.minecraft.util.Direction
import net.minecraft.util.Direction.Axis
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.IBlockDisplayReader
/** Index of SOUTH-EAST quadrant. */
const val SE = 0
@@ -73,7 +73,7 @@ sealed class ColumnLayerData {
object ResolveError : ColumnLayerData()
}
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData>() {
abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean
@@ -83,13 +83,13 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
override fun onBlockUpdate(world: ILightReader, pos: BlockPos) {
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
override fun onBlockUpdate(world: IBlockDisplayReader, pos: BlockPos) {
allNeighborOffsets.forEach { offset -> remove(world, pos + offset) }
}
override fun calculate(ctx: BlockCtx): ColumnLayerData {
// TODO detect round logs
if (allDirections.all { dir -> ctx.offset(dir).let { it.isNormalCube } }) return ColumnLayerData.SkipRender
if (allDirections.all { dir -> ctx.offset(dir).let { it.isFullBlock } }) return ColumnLayerData.SkipRender
val columnTextures = getColumnKey(ctx.state) ?: return ColumnLayerData.ResolveError
// if log axis is not defined and "Default to vertical" config option is not set, render normally
@@ -185,7 +185,7 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
val offsetRot = offset.rotate(rotation)
val key = getColumnKey(state(offsetRot))
return if (key == null) {
if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
if (offset(offsetRot).isFullBlock) SOLID else NONSOLID
} else {
(key.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let {
if (it == axis) PARALLEL else PERPENDICULAR

View File

@@ -5,7 +5,7 @@ import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.IBlockDisplayReader
data class LightingData(
@JvmField var packedLight: Int = 0,
@@ -23,13 +23,17 @@ data class LightingData(
}
}
// Vanilla has a very suspicious-looking offset here, which Indigo gets rid of and calls it a fix
// Naturally, we're going to believe Indigo, it's a hardcoded option for now
const val OCCLUSION_OFFSET_FIX = true
/**
* Replacement for [BlockModelRenderer.AmbientOcclusionFace]
* This gets called on a LOT, so object instantiation is avoided.
* Not thread-safe, always use a [ThreadLocal] instance
*/
class VanillaAoCalculator {
lateinit var world: ILightReader
lateinit var world: IBlockDisplayReader
/** [blockPos] is used to get block-related information (i.e. tint, opacity, etc.)
* [lightPos] is used to get light-related information
@@ -37,7 +41,7 @@ class VanillaAoCalculator {
lateinit var blockPos: BlockPos
lateinit var lightPos: BlockPos
private val probe = LightProbe(BlockModelRenderer.CACHE_COMBINED_LIGHT.get())
private val probe = LightProbe(BlockModelRenderer.CACHE.get())
val isValid = BooleanArray(6)
val aoData = Array(24) { LightingData() }
@@ -70,13 +74,13 @@ class VanillaAoCalculator {
// Bit 0 of the bitset in vanilla calculations
// true if the block model is planar with the block boundary
val isFullBlock = forceFull ?: world.getBlockState(blockPos).isCollisionShapeOpaque(world, blockPos)
val isFullBlock = forceFull ?: world.getBlockState(blockPos).isCollisionShapeFullBlock(world, blockPos)
val lightOrigin = if (isFullBlock) lightPos.offset(lightFace) else lightPos
val lightOrigin = if (isFullBlock) lightPos.relative(lightFace) else lightPos
// AO calculation for the face center
probe.position { setPos(lightOrigin) }.writeTo(centerAo)
if (!isFullBlock && !probe.position { move(lightFace) }.state.isOpaqueCube(world, probe.pos)) {
probe.position { set(lightOrigin) }.writeTo(centerAo)
if (!isFullBlock && !probe.position { move(lightFace) }.state.isSolidRender(world, probe.pos)) {
// if the neighboring block in the lightface direction is
// transparent (non-opaque), use its packed light instead of our own
// (if our block is a full block, we are already using this value)
@@ -86,10 +90,11 @@ class VanillaAoCalculator {
// AO calculation for the 4 sides
sideHelper.sides.forEachIndexed { sideIdx, sideDir ->
// record light data in the block 1 step to the side
probe.position { setPos(lightOrigin).move(sideDir) }.writeTo(sideAo[sideIdx])
probe.position { set(lightOrigin).move(sideDir) }.writeTo(sideAo[sideIdx])
// side is considered occluded if the block 1 step to that side and
// 1 step forward (in the lightface direction) is not fully transparent
isOccluded[sideIdx] = probe.position { move(lightFace) }.isNonTransparent
if (!OCCLUSION_OFFSET_FIX) probe.position { move(lightFace) }
isOccluded[sideIdx] = probe.isNonTransparent
}
// AO Calculation for the 4 corners
@@ -103,7 +108,7 @@ class VanillaAoCalculator {
else {
// lookup actual packed light from the cornering block in the world
probe.position {
setPos(lightOrigin)
set(lightOrigin)
.move(sideHelper.sides[sideIndices.first])
.move(sideHelper.sides[sideIndices.second])
}.writeTo(cornerAo[cornerIdx])
@@ -129,9 +134,9 @@ class VanillaAoCalculator {
lateinit var state: BlockState
val pos = BlockPos.Mutable()
val packedLight: Int get() = cache.getPackedLight(state, world, pos)
val colorMultiplier: Float get() = cache.getBrightness(state, world, pos)
val isNonTransparent: Boolean get() = state.getOpacity(world, pos) > 0
val packedLight: Int get() = cache.getLightColor(state, world, pos)
val colorMultiplier: Float get() = cache.getShadeBrightness(state, world, pos)
val isNonTransparent: Boolean get() = state.getLightBlock(world, pos) > 0
fun writeTo(data: LightingData) {
data.packedLight = packedLight

View File

@@ -1,15 +1,15 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.EPSILON_ONE
import mods.betterfoliage.util.EPSILON_ZERO
import mods.betterfoliage.util.get
import mods.betterfoliage.util.minBy
import net.minecraft.client.renderer.color.BlockColors
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import net.minecraft.util.Direction.Axis
import net.minecraftforge.client.model.pipeline.LightUtil
import kotlin.math.abs
class VanillaQuadLighting {
@@ -34,7 +34,7 @@ class VanillaQuadLighting {
}
fun applyDiffuseLighting(face: Direction) {
val factor = LightUtil.diffuseLight(face)
val factor = ShadersModIntegration.diffuseShades[face]
tint[0] *= factor; tint[1] *= factor; tint[2] *= factor
}
}
@@ -69,7 +69,7 @@ object VanillaFullBlockLighting : VanillaVertexLighter() {
lighting.calc.fillLightData(face, true)
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
lighting.updateBlockTint(quad.baked.tintIndex)
if (quad.baked.shouldApplyDiffuseLighting()) lighting.applyDiffuseLighting(face)
if (quad.baked.isShade) lighting.applyDiffuseLighting(face)
}
}

View File

@@ -1,127 +0,0 @@
package mods.betterfoliage.render.old
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import net.minecraft.client.Minecraft
import net.minecraft.client.particle.IParticleRenderType
import net.minecraft.client.particle.SpriteTexturedParticle
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.world.World
import kotlin.math.cos
import kotlin.math.sin
abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : SpriteTexturedParticle(world, x, y, z) {
companion object {
@JvmStatic val sin = Array(64) { idx -> sin(PI2 / 64.0 * idx) }
@JvmStatic val cos = Array(64) { idx -> cos(PI2 / 64.0 * idx) }
}
val billboardRot = Pair(Double3.zero, Double3.zero)
val currentPos = Double3.zero
val prevPos = Double3.zero
val velocity = Double3.zero
override fun tick() {
super.tick()
currentPos.setTo(posX, posY, posZ)
prevPos.setTo(prevPosX, prevPosY, prevPosZ)
velocity.setTo(motionX, motionY, motionZ)
update()
posX = currentPos.x; posY = currentPos.y; posZ = currentPos.z;
motionX = velocity.x; motionY = velocity.y; motionZ = velocity.z;
}
/** Render the particle. */
abstract fun render(worldRenderer: BufferBuilder, partialTickTime: Float)
/** Update particle on world tick. */
abstract fun update()
/** True if the particle is renderable. */
abstract val isValid: Boolean
/** Add the particle to the effect renderer if it is valid. */
fun addIfValid() { if (isValid) Minecraft.getInstance().particles.addEffect(this) }
// override fun renderParticle(buffer: BufferBuilder, entity: ActiveRenderInfo, partialTicks: Float, rotX: Float, rotZ: Float, rotYZ: Float, rotXY: Float, rotXZ: Float) {
// billboardRot.first.setTo(rotX + rotXY, rotZ, rotYZ + rotXZ)
// billboardRot.second.setTo(rotX - rotXY, -rotZ, rotYZ - rotXZ)
// render(buffer, partialTicks)
// }
/**
* Render a particle quad.
*
* @param[tessellator] the [Tessellator] instance to use
* @param[partialTickTime] partial tick time
* @param[currentPos] render position
* @param[prevPos] previous tick position for interpolation
* @param[size] particle size
* @param[rotation] viewpoint-dependent particle rotation (64 steps)
* @param[icon] particle texture
* @param[isMirrored] mirror particle texture along V-axis
* @param[alpha] aplha blending
*/
// fun renderParticleQuad(worldRenderer: BufferBuilder,
// partialTickTime: Float,
// currentPos: Double3 = this.currentPos,
// prevPos: Double3 = this.prevPos,
// size: Double = particleScale.toDouble(),
// rotation: Int = 0,
// icon: TextureAtlasSprite = sprite,
// isMirrored: Boolean = false,
// alpha: Float = this.particleAlpha) {
//
// val minU = (if (isMirrored) icon.minU else icon.maxU).toDouble()
// val maxU = (if (isMirrored) icon.maxU else icon.minU).toDouble()
// val minV = icon.minV.toDouble()
// val maxV = icon.maxV.toDouble()
//
// val center = currentPos.copy().sub(prevPos).mul(partialTickTime.toDouble()).add(prevPos).sub(interpPosX, interpPosY, interpPosZ)
// val v1 = if (rotation == 0) billboardRot.first * size else
// Double3.weight(billboardRot.first, cos[rotation and 63] * size, billboardRot.second, sin[rotation and 63] * size)
// val v2 = if (rotation == 0) billboardRot.second * size else
// Double3.weight(billboardRot.first, -sin[rotation and 63] * size, billboardRot.second, cos[rotation and 63] * size)
//
// val renderBrightness = this.getBrightnessForRender(partialTickTime)
// val brLow = renderBrightness shr 16 and 65535
// val brHigh = renderBrightness and 65535
//
// worldRenderer
// .pos(center.x - v1.x, center.y - v1.y, center.z - v1.z)
// .tex(maxU, maxV)
// .color(particleRed, particleGreen, particleBlue, alpha)
// .lightmap(brLow, brHigh)
// .endVertex()
//
// worldRenderer
// .pos(center.x - v2.x, center.y - v2.y, center.z - v2.z)
// .tex(maxU, minV)
// .color(particleRed, particleGreen, particleBlue, alpha)
// .lightmap(brLow, brHigh)
// .endVertex()
//
// worldRenderer
// .pos(center.x + v1.x, center.y + v1.y, center.z + v1.z)
// .tex(minU, minV)
// .color(particleRed, particleGreen, particleBlue, alpha)
// .lightmap(brLow, brHigh)
// .endVertex()
//
// worldRenderer
// .pos(center.x + v2.x, center.y + v2.y, center.z + v2.z)
// .tex(minU, maxV)
// .color(particleRed, particleGreen, particleBlue, alpha)
// .lightmap(brLow, brHigh)
// .endVertex()
// }
// override fun getFXLayer() = 1
override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
fun setColor(color: Int) {
particleBlue = (color and 255) / 256.0f
particleGreen = ((color shr 8) and 255) / 256.0f
particleRed = ((color shr 16) and 255) / 256.0f
}
}

View File

@@ -1,16 +1,18 @@
package mods.betterfoliage.render.particle
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HSB
import mods.betterfoliage.util.Double3
import net.minecraft.client.Minecraft
import net.minecraft.client.particle.SpriteTexturedParticle
import net.minecraft.client.renderer.ActiveRenderInfo
import net.minecraft.client.renderer.Vector3f
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import net.minecraft.util.math.vector.Vector3f
abstract class AbstractParticle(world: World, x: Double, y: Double, z: Double) : SpriteTexturedParticle(world, x, y, z) {
abstract class AbstractParticle(world: ClientWorld, x: Double, y: Double, z: Double) : SpriteTexturedParticle(world, x, y, z) {
companion object {
// @JvmStatic val sin = Array(64) { idx -> Math.sin(PI2 / 64.0 * idx) }
@@ -24,12 +26,12 @@ abstract class AbstractParticle(world: World, x: Double, y: Double, z: Double) :
override fun tick() {
super.tick()
currentPos.setTo(posX, posY, posZ)
prevPos.setTo(prevPosX, prevPosY, prevPosZ)
velocity.setTo(motionX, motionY, motionZ)
currentPos.setTo(x, y, z)
prevPos.setTo(xo, yo, zo)
velocity.setTo(xd, yd, zd)
update()
posX = currentPos.x; posY = currentPos.y; posZ = currentPos.z;
motionX = velocity.x; motionY = velocity.y; motionZ = velocity.z;
x = currentPos.x; y = currentPos.y; z = currentPos.z;
xd = velocity.x; yd = velocity.y; zd = velocity.z;
}
/** Update particle on world tick. */
@@ -39,10 +41,10 @@ abstract class AbstractParticle(world: World, x: Double, y: Double, z: Double) :
abstract val isValid: Boolean
/** Add the particle to the effect renderer if it is valid. */
fun addIfValid() { if (isValid) Minecraft.getInstance().particles.addEffect(this) }
fun addIfValid() { if (isValid) Minecraft.getInstance().particleEngine.add(this) }
override fun renderParticle(vertexBuilder: IVertexBuilder, camera: ActiveRenderInfo, tickDelta: Float) {
super.renderParticle(vertexBuilder, camera, tickDelta)
override fun render(vertexBuilder: IVertexBuilder, camera: ActiveRenderInfo, tickDelta: Float) {
super.render(vertexBuilder, camera, tickDelta)
}
/**
@@ -63,39 +65,45 @@ abstract class AbstractParticle(world: World, x: Double, y: Double, z: Double) :
tickDelta: Float,
currentPos: Double3 = this.currentPos,
prevPos: Double3 = this.prevPos,
size: Double = particleScale.toDouble(),
currentAngle: Float = this.particleAngle,
prevAngle: Float = this.prevParticleAngle,
size: Double = quadSize.toDouble(),
currentAngle: Float = this.roll,
prevAngle: Float = this.oRoll,
sprite: TextureAtlasSprite = this.sprite,
alpha: Float = this.particleAlpha) {
alpha: Float = this.alpha) {
val center = Double3.lerp(tickDelta.toDouble(), prevPos, currentPos)
val angle = MathHelper.lerp(tickDelta, prevAngle, currentAngle)
val rotation = camera.rotation.copy().apply { multiply(Vector3f.ZP.rotation(angle)) }
val lightmapCoord = getBrightnessForRender(tickDelta)
val rotation = camera.rotation().copy().apply { mul(Vector3f.ZP.rotation(angle)) }
val lightmapCoord = getLightColor(tickDelta)
val coords = arrayOf(
Double3(-1.0, -1.0, 0.0),
Double3(-1.0, 1.0, 0.0),
Double3(1.0, 1.0, 0.0),
Double3(1.0, -1.0, 0.0)
).map { it.rotate(rotation).mul(size).add(center).sub(camera.projectedView.x, camera.projectedView.y, camera.projectedView.z) }
).map { it.rotate(rotation).mul(size).add(center).sub(camera.position.x, camera.position.y, camera.position.z) }
fun renderVertex(vertex: Double3, u: Float, v: Float) = vertexConsumer
.pos(vertex.x, vertex.y, vertex.z).tex(u, v)
.color(particleRed, particleGreen, particleBlue, alpha).lightmap(lightmapCoord)
.vertex(vertex.x, vertex.y, vertex.z).uv(u, v)
.color(rCol, gCol, bCol, alpha).uv2(lightmapCoord)
.endVertex()
renderVertex(coords[0], sprite.maxU, sprite.maxV)
renderVertex(coords[1], sprite.maxU, sprite.minV)
renderVertex(coords[2], sprite.minU, sprite.minV)
renderVertex(coords[3], sprite.minU, sprite.maxV)
renderVertex(coords[0], sprite.u1, sprite.v1)
renderVertex(coords[1], sprite.u1, sprite.v0)
renderVertex(coords[2], sprite.u0, sprite.v0)
renderVertex(coords[3], sprite.u0, sprite.v1)
}
fun setColor(color: Int) {
particleBlue = (color and 255) / 256.0f
particleGreen = ((color shr 8) and 255) / 256.0f
particleRed = ((color shr 16) and 255) / 256.0f
fun setColor(color: Color) {
rCol = color.red / 256.0f
gCol = color.green / 256.0f
bCol = color.blue / 256.0f
}
/**
* Set particle color to the "stronger" of the given colors, determined by higher color saturation
*/
fun setColor(color1: Color, color2: Color) =
setColor(if (color1.asHSB.saturation > color2.asHSB.saturation) color1 else color2)
}

View File

@@ -1,6 +1,7 @@
package mods.betterfoliage.render.particle
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.Color
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.minmax
@@ -8,22 +9,22 @@ import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomF
import mods.betterfoliage.util.randomI
import net.minecraft.client.Minecraft
import net.minecraft.client.particle.IParticleRenderType
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.LogicalSide
import java.util.Random
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
class FallingLeafParticle(
world: World, pos: BlockPos, leaf: LeafParticleKey, blockColor: Int, random: Random
world: ClientWorld, pos: BlockPos, leaf: LeafParticleKey, blockColor: Int, random: Random
) : AbstractParticle(
world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5
) {
@@ -37,14 +38,14 @@ class FallingLeafParticle(
var wasCollided = false
init {
particleAngle = random.randomF(max = PI2)
prevParticleAngle = particleAngle - rotationSpeed
roll = random.randomF(max = PI2)
oRoll = roll - rotationSpeed
maxAge = MathHelper.floor(randomD(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0)
motionY = -Config.fallingLeaves.speed
lifetime = MathHelper.floor(randomD(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0)
yd = -Config.fallingLeaves.speed
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
setColor(leaf.overrideColor?.asInt ?: blockColor)
quadSize = Config.fallingLeaves.size.toFloat() * 0.1f
if (leaf.tintIndex == -1) setColor(leaf.avgColor) else setColor(leaf.avgColor, Color(blockColor))
sprite = LeafParticleRegistry[leaf.leafType][randomI(max = 1024)]
}
@@ -52,21 +53,21 @@ class FallingLeafParticle(
override fun update() {
if (rand.nextFloat() > 0.95f) rotationSpeed *= -1.0f
if (age > maxAge - 20) particleAlpha = 0.05f * (maxAge - age)
if (random.nextFloat() > 0.95f) rotationSpeed *= -1.0f
if (age > lifetime - 20) alpha = 0.05f * (lifetime - age)
if (onGround || wasCollided) {
velocity.setTo(0.0, 0.0, 0.0)
if (!wasCollided) {
age = age.coerceAtLeast(maxAge - 20)
age = age.coerceAtLeast(lifetime - 20)
wasCollided = true
}
} else {
val cosRotation = cos(particleAngle).toDouble(); val sinRotation = sin(particleAngle).toDouble()
val cosRotation = cos(roll).toDouble(); val sinRotation = sin(roll).toDouble()
velocity.setTo(cosRotation, 0.0, sinRotation).mul(Config.fallingLeaves.perturb)
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed)
prevParticleAngle = particleAngle
particleAngle += rotationSpeed
oRoll = roll
roll += rotationSpeed
}
}
@@ -84,7 +85,7 @@ object LeafWindTracker {
}
fun changeWind(world: World) {
nextChange = world.worldInfo.gameTime + 120 + random.nextInt(80)
nextChange = world.gameTime + 120 + random.nextInt(80)
val direction = PI2 * random.nextDouble()
val speed = abs(random.nextGaussian()) * Config.fallingLeaves.windStrength +
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * Config.fallingLeaves.stormStrength)
@@ -92,10 +93,10 @@ object LeafWindTracker {
}
@SubscribeEvent
fun handleWorldTick(event: TickEvent.ClientTickEvent) {
if (event.phase == TickEvent.Phase.START) Minecraft.getInstance().world?.let { world ->
fun handleWorldTick(event: TickEvent.WorldTickEvent) {
if (event.phase == TickEvent.Phase.START && event.side == LogicalSide.CLIENT) event.world.let { world ->
// change target wind speed
if (world.worldInfo.dayTime >= nextChange) changeWind(world)
if (world.dayTime >= nextChange) changeWind(world)
// change current wind speed
val changeRate = if (world.isRaining) 0.015 else 0.005
@@ -107,6 +108,6 @@ object LeafWindTracker {
}
}
@SubscribeEvent
fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isRemote) changeWind(event.world.world) }
// @SubscribeEvent
// fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isClientSide) changeWind(event.world) }
}

View File

@@ -10,8 +10,10 @@ import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.get
import mods.betterfoliage.util.getLines
import mods.betterfoliage.util.resourceManager
import mods.betterfoliage.util.stripEnd
import mods.betterfoliage.util.stripStart
import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
@@ -23,25 +25,28 @@ interface LeafBlockModel {
interface LeafParticleKey {
val leafType: String
val overrideColor: Color?
val tintIndex: Int
val avgColor: Color
}
object LeafParticleRegistry : HasLogger(), VeryEarlyReloadListener {
val typeMappings = TextureMatcher()
val allTypes get() = (typeMappings.mappings.map { it.type } + "default").distinct()
val allTypes = mutableSetOf<String>()
val particles = hashMapOf<String, SpriteSet>()
operator fun get(type: String) = particles[type] ?: particles["default"]!!
override fun onReloadStarted() {
typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.MOD_ID, "leaf_texture_mappings.cfg"))
detailLogger.log(INFO, "Loaded leaf particle mappings, types = [${allTypes.joinToString(", ")}]")
override fun onReloadStarted(resourceManager: IResourceManager) {
allTypes.clear()
resourceManager.listResources("textures/particle") { it.startsWith("falling_leaf_") }
.filter { it.namespace == BetterFoliageMod.MOD_ID }
.map { it.stripStart("textures/particle/falling_leaf_").stripEnd(".png") }
.map { it.path.substringBefore("_", "") }
.forEach { leafType -> if (!leafType.isEmpty()) allTypes.add(leafType) }
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
if (event.map.textureLocation == Atlas.PARTICLES.resourceId) {
if (event.map.location() == Atlas.PARTICLES.resourceId) {
allTypes.forEach { leafType ->
val locations = (0 until 16).map { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "particle/falling_leaf_${leafType}_$idx")
@@ -55,8 +60,8 @@ object LeafParticleRegistry : HasLogger(), VeryEarlyReloadListener {
@SubscribeEvent
fun handlePostStitch(event: TextureStitchEvent.Post) {
if (event.map.textureLocation == Atlas.PARTICLES.resourceId) {
(typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
if (event.map.location() == Atlas.PARTICLES.resourceId) {
allTypes.forEach { leafType ->
val sprites = (0 until 16).map { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "particle/falling_leaf_${leafType}_$idx")
}

View File

@@ -13,6 +13,7 @@ import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.minecraft.client.particle.IParticleRenderType
import net.minecraft.client.renderer.ActiveRenderInfo
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
@@ -23,7 +24,7 @@ import kotlin.math.cos
import kotlin.math.sin
class RisingSoulParticle(
world: World, pos: BlockPos
world: ClientWorld, pos: BlockPos
) : AbstractParticle(
world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5
) {
@@ -32,10 +33,10 @@ class RisingSoulParticle(
val initialPhase = randomD(max = PI2)
init {
motionY = 0.1
particleGravity = 0.0f
yd = 0.1
gravity = 0.0f
sprite = headIcons[randomI(max = 1024)]
maxAge = MathHelper.floor((0.6 + 0.4 * randomD()) * Config.risingSoul.lifetime * 20.0)
lifetime = MathHelper.floor((0.6 + 0.4 * randomD()) * Config.risingSoul.lifetime * 20.0)
}
override val isValid: Boolean get() = true
@@ -49,12 +50,12 @@ class RisingSoulParticle(
particleTrail.addFirst(currentPos.copy())
while (particleTrail.size > Config.risingSoul.trailLength) particleTrail.removeLast()
if (!Config.enabled) setExpired()
if (!Config.enabled) remove()
}
override fun renderParticle(vertexBuilder: IVertexBuilder, camera: ActiveRenderInfo, tickDelta: Float) {
override fun render(vertexBuilder: IVertexBuilder, camera: ActiveRenderInfo, tickDelta: Float) {
var alpha = Config.risingSoul.opacity.toFloat()
if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f
if (age > lifetime - 40) alpha *= (lifetime - age) / 40.0f
renderParticleQuad(
vertexBuilder, camera, tickDelta,

View File

@@ -0,0 +1,42 @@
package mods.betterfoliage.render.pipeline
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.RenderTypeLookup
import net.minecraft.block.Block
import net.minecraft.client.renderer.RenderType
import java.util.function.Predicate
object Layers {
val tufts = RenderType.cutout()
val connectedGrass = RenderType.solid()
val connectedDirt = RenderType.cutoutMipped()
val coral = RenderType.cutoutMipped()
}
val defaultLayerBehaviour = Predicate<RenderType> { layer -> layer == RenderType.solid() }
class WrappedLayerPredicate(val original: Predicate<RenderType>, val func: (RenderType, Predicate<RenderType>) -> Boolean) : Predicate<RenderType> {
override fun test(layer: RenderType) = func(layer, original)
}
/**
* Extension method to access the canRenderInLayer() predicate in [RenderTypeLookup]
*/
var Block.layerPredicate : Predicate<RenderType>?
get() = RenderTypeLookup.blockRenderChecks.getStatic()[delegate]
set(value) {
RenderTypeLookup.blockRenderChecks.getStatic()[delegate] = value!!
}
/**
* Add a wrapper to the block's canRenderInLayer() predicate to enable dynamic multi-layer rendering.
* If the render data for the block implements [SpecialRenderData], the layers it enables will be
* rendered _in addition to_ the block's normal layers.
*/
fun Block.extendLayers() {
val original = layerPredicate ?: defaultLayerBehaviour
if (original !is WrappedLayerPredicate) layerPredicate = WrappedLayerPredicate(original) { layer, original ->
original.test(layer) ||
(RenderCtxBase.specialRenderData.get() as? SpecialRenderData)?.canRenderInLayer(layer) ?: false
}
}

View File

@@ -3,18 +3,20 @@ package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.render.lighting.VanillaFullBlockLighting
import mods.betterfoliage.render.lighting.VanillaQuadLighting
import mods.betterfoliage.render.lighting.VanillaVertexLighter
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.plus
import net.minecraft.block.Block
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.IBlockDisplayReader
import net.minecraftforge.client.model.data.IModelData
import java.util.Random
@@ -25,25 +27,29 @@ import java.util.Random
* push-based partial rendering pipeline for [SpecialRenderModel] instances.
*/
abstract class RenderCtxBase(
world: ILightReader,
pos: BlockPos,
blockCtx: BlockCtx,
val matrixStack: MatrixStack,
var checkSides: Boolean,
val random: Random,
val modelData: IModelData
) : BlockCtx by BasicBlockCtx(world, pos) {
abstract fun renderQuad(quad: HalfBakedQuad)
val modelData: IModelData,
) : BlockCtx by blockCtx {
var hasRendered = false
val blockModelShapes = Minecraft.getInstance().blockRendererDispatcher.blockModelShapes
var modelRenderData: Any? = null
inline fun <reified T> withRenderData(renderFunc: (T)->Boolean) = (modelRenderData as? T?).let {
if (it == null) false else renderFunc(it)
}
val blockModelShapes = Minecraft.getInstance().blockRenderer.blockModelShaper
var vertexLighter: VanillaVertexLighter = VanillaFullBlockLighting
protected val lightingData = RenderCtxBase.lightingData.get().apply {
calc.reset(this@RenderCtxBase)
blockColors = Minecraft.getInstance().blockColors
}
inline fun Direction?.shouldRender() = this == null || !checkSides || Block.shouldSideBeRendered(state, world, pos, this)
abstract fun renderQuad(quad: HalfBakedQuad)
inline fun Direction?.shouldRender() = this == null || !checkSides || Block.shouldRenderFace(state, world, pos, this)
fun renderQuads(quads: Iterable<HalfBakedQuad>) {
quads.forEach { quad ->
@@ -61,6 +67,17 @@ abstract class RenderCtxBase(
}
companion object {
@JvmStatic
fun reset(chunkRenderCache: IBlockDisplayReader, blockRendererDispatcher: BlockRendererDispatcher, pos: BlockPos, random: Random) {
// prepare render data
val blockCtx = BasicBlockCtx(chunkRenderCache, pos)
val model = blockRendererDispatcher.getBlockModel(blockCtx.state)
random.setSeed(blockCtx.seed)
val data = if (model is SpecialRenderModel) model.prepare(blockCtx, random) else Unit
specialRenderData.set(data)
}
val lightingData = ThreadLocal.withInitial { VanillaQuadLighting() }
val specialRenderData = ThreadLocal<Any?>()
}
}

View File

@@ -1,27 +1,31 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.render.lighting.ForgeVertexLighter
import mods.betterfoliage.render.lighting.ForgeVertexLighterAccess
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.util.getWithDefault
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.LightTexture
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.IBlockDisplayReader
import net.minecraftforge.client.ForgeHooksClient
import net.minecraftforge.client.MinecraftForgeClient
import net.minecraftforge.client.model.data.IModelData
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
import java.util.Random
class RenderCtxForge(
world: ILightReader,
pos: BlockPos,
blockCtx: BlockCtx,
val lighter: VertexLighterFlat,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
modelData: IModelData
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData), ForgeVertexLighter {
modelData: IModelData,
) : RenderCtxBase(blockCtx, matrixStack, checkSides, random, modelData), ForgeVertexLighter {
override fun renderQuad(quad: HalfBakedQuad) {
// set Forge lighter AO calculator to us
@@ -33,12 +37,20 @@ class RenderCtxForge(
var vIdx = 0
override fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float) {
lightingData.packedLight[vIdx].let { packedLight ->
lightmap[0] = LightTexture.getLightBlock(packedLight) / 0xF.toFloat()
lightmap[1] = LightTexture.getLightSky(packedLight) / 0xF.toFloat()
lightmap[0] = LightTexture.block(packedLight) / 0xF.toFloat()
lightmap[1] = LightTexture.sky(packedLight) / 0xF.toFloat()
}
}
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
override fun updateVertexColor(
normal: FloatArray,
color: FloatArray,
x: Float,
y: Float,
z: Float,
tint: Float,
multiplier: Int
) {
color[0] = lightingData.tint[0] * lightingData.colorMultiplier[vIdx]
color[1] = lightingData.tint[1] * lightingData.colorMultiplier[vIdx]
color[2] = lightingData.tint[2] * lightingData.colorMultiplier[vIdx]
@@ -49,23 +61,27 @@ class RenderCtxForge(
@JvmStatic
fun render(
lighter: VertexLighterFlat,
world: ILightReader,
world: IBlockDisplayReader,
model: SpecialRenderModel,
state: BlockState,
pos: BlockPos,
matrixStack: MatrixStack,
checkSides: Boolean,
rand: Random, seed: Long,
random: Random, seed: Long,
modelData: IModelData
): Boolean {
lighter.setWorld(world)
lighter.setState(state)
lighter.setBlockPos(pos)
rand.setSeed(seed)
lighter.updateBlockInfo()
return RenderCtxForge(world, pos, lighter, matrixStack, checkSides, rand, modelData).let {
val blockCtx = BasicBlockCtx(world, pos)
val ctx = RenderCtxForge(blockCtx, lighter, matrixStack, checkSides, random, modelData).apply {
lighter.setWorld(world)
lighter.setState(state)
lighter.setBlockPos(pos)
lighter.updateBlockInfo()
}
// render layer
return ctx.let {
(lighter as ForgeVertexLighterAccess).vertexLighter = it
model.render(it, false)
model.renderLayer(it, specialRenderData.get()!!, MinecraftForgeClient.getRenderLayer())
lighter.resetBlockInfo()
it.hasRendered
}

View File

@@ -2,33 +2,36 @@ package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.util.getWithDefault
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.IBlockDisplayReader
import net.minecraftforge.client.MinecraftForgeClient
import net.minecraftforge.client.model.data.IModelData
import java.util.Random
class RenderCtxVanilla(
val renderer: BlockModelRenderer,
world: ILightReader,
pos: BlockPos,
blockCtx: BlockCtx,
val buffer: IVertexBuilder,
val combinedOverlay: Int,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
val seed: Long,
val randomSeed: Long,
modelData: IModelData,
val useAO: Boolean
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData) {
): RenderCtxBase(blockCtx, matrixStack, checkSides, random, modelData) {
override fun renderQuad(quad: HalfBakedQuad) {
vertexLighter.updateLightmapAndColor(quad, lightingData)
buffer.addQuad(
matrixStack.last, quad.baked,
buffer.putBulkData(
matrixStack.last(), quad.baked,
lightingData.colorMultiplier,
lightingData.tint[0], lightingData.tint[1], lightingData.tint[2],
lightingData.packedLight, combinedOverlay, true
@@ -39,7 +42,7 @@ class RenderCtxVanilla(
@JvmStatic
fun render(
renderer: BlockModelRenderer,
world: ILightReader,
world: IBlockDisplayReader,
model: SpecialRenderModel,
state: BlockState,
pos: BlockPos,
@@ -47,20 +50,16 @@ class RenderCtxVanilla(
buffer: IVertexBuilder,
checkSides: Boolean,
random: Random,
rand: Long,
seed: Long,
combinedOverlay: Int,
modelData: IModelData,
smooth: Boolean
): Boolean {
random.setSeed(rand)
val ctx = RenderCtxVanilla(renderer, world, pos, buffer, combinedOverlay, matrixStack, checkSides, random, rand, modelData, smooth)
lightingData.apply {
}
model.render(ctx, false)
val blockCtx = BasicBlockCtx(world, pos)
// init context if missing (this is the first render layer)
val ctx = RenderCtxVanilla(renderer, blockCtx, buffer, combinedOverlay, matrixStack, checkSides, random, seed, modelData, smooth)
model.renderLayer(ctx, specialRenderData.get()!!, MinecraftForgeClient.getRenderLayer())
return ctx.hasRendered
}
}
}

View File

@@ -19,9 +19,9 @@ interface VeryEarlyReloadListener : IFutureReloadListener {
backgroundExecutor: Executor,
gameExecutor: Executor
): CompletableFuture<Void> {
onReloadStarted()
return stage.markCompleteAwaitingOthers(null)
onReloadStarted(resourceManager)
return stage.wait(null)
}
fun onReloadStarted() {}
fun onReloadStarted(resourceManager: IResourceManager) {}
}

View File

@@ -1,15 +1,17 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.Invalidator
import mods.betterfoliage.util.resourceManager
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.IModelTransform
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.Material
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.RenderMaterial
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.ModelBakeEvent
@@ -30,7 +32,7 @@ data class ModelDefinitionsLoadedEvent(
interface ModelBakingKey {
fun bake(ctx: ModelBakingContext): IBakedModel? =
ctx.getUnbaked().bakeModel(ctx.bakery, ctx.spriteGetter, ctx.transform, ctx.location)
ctx.getUnbaked().bake(ctx.bakery, ctx.spriteGetter, ctx.transform, ctx.location)
}
interface ModelDiscovery {
@@ -49,26 +51,29 @@ data class ModelDiscoveryContext(
val replacements: MutableMap<ResourceLocation, ModelBakingKey>,
val logger: Logger
) {
fun getUnbaked(location: ResourceLocation = modelLocation) = bakery.getUnbakedModel(location)
fun getUnbaked(location: ResourceLocation = modelLocation) = bakery.getModel(location)
fun addReplacement(key: ModelBakingKey, addToStateKeys: Boolean = true) {
replacements[modelLocation] = key
if (addToStateKeys) BetterFoliage.blockTypes.stateKeys[blockState] = key
logger.log(INFO, "Adding model replacement $modelLocation -> $key")
}
fun <T: IUnbakedModel> loadHierarchy(model: T) = model.apply {
getMaterials(this@ModelDiscoveryContext::getUnbaked, mutableSetOf())
}
}
data class ModelBakingContext(
val bakery: ModelBakery,
val spriteGetter: Function<Material, TextureAtlasSprite>,
val spriteGetter: Function<RenderMaterial, TextureAtlasSprite>,
val location: ResourceLocation,
val transform: IModelTransform,
val logger: Logger
) {
fun getUnbaked() = bakery.getUnbakedModel(location)
fun getUnbaked() = bakery.getModel(location)
fun getBaked() = bakery.getBakedModel(location, transform, spriteGetter)
}
object BakeWrapperManager : Invalidator, HasLogger() {
class BakeWrapperManager : Invalidator, HasLogger() {
val discoverers = mutableListOf<ModelDiscovery>()
override val callbacks = mutableListOf<WeakReference<()->Unit>>()
@@ -79,6 +84,7 @@ object BakeWrapperManager : Invalidator, HasLogger() {
fun handleModelLoad(event: ModelDefinitionsLoadedEvent) {
val startTime = System.currentTimeMillis()
invalidate()
BetterFoliage.blockConfig.readConfig(resourceManager)
BetterFoliage.blockTypes = BlockTypeCache()
StartupMessageManager.addModMessage("BetterFoliage: discovering models")
@@ -94,7 +100,7 @@ object BakeWrapperManager : Invalidator, HasLogger() {
@SubscribeEvent
fun handleStitch(event: TextureStitchEvent.Pre) {
if (event.map.textureLocation == Atlas.BLOCKS.resourceId) {
if (event.map.location() == Atlas.BLOCKS.resourceId) {
logger.log(INFO, "Adding ${sprites.size} sprites to block atlas")
sprites.forEach { event.addSprite(it) }
sprites.clear()
@@ -109,7 +115,7 @@ object BakeWrapperManager : Invalidator, HasLogger() {
fun onBake(
unbaked: IUnbakedModel,
bakery: ModelBakery,
spriteGetter: Function<Material, TextureAtlasSprite>,
spriteGetter: Function<RenderMaterial, TextureAtlasSprite>,
transform: IModelTransform,
location: ResourceLocation
): IBakedModel? {
@@ -124,6 +130,6 @@ object BakeWrapperManager : Invalidator, HasLogger() {
logger.log(WARN, "Error while baking $replacement", e)
}
}
return unbaked.bakeModel(bakery, spriteGetter, transform, location)
return unbaked.bake(bakery, spriteGetter, transform, location)
}
}

View File

@@ -1,6 +1,5 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.getJavaClass
import mods.betterfoliage.util.getLines
@@ -45,8 +44,8 @@ class ConfigurableBlockMatcher(val location: ResourceLocation) : HasLogger(), IB
fun readDefaults() {
blackList.clear()
whiteList.clear()
resourceManager.getAllResources(location).forEach { resource ->
detailLogger.log(INFO, "Reading block class configuration $location from pack ${resource.packName}")
resourceManager.getResources(location).forEach { resource ->
detailLogger.log(INFO, "Reading block class configuration $location from pack ${resource.sourceName}")
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
if (line.startsWith("-")) getJavaClass(line.substring(1))?.let { blackList.add(it) }
else getJavaClass(line)?.let { whiteList.add(it) }
@@ -63,8 +62,8 @@ data class ModelTextureList(val modelLocation: ResourceLocation, val textureName
class ModelTextureListConfiguration(val location: ResourceLocation) : HasLogger() {
val modelList = mutableListOf<ModelTextureList>()
fun readDefaults() {
resourceManager.getAllResources(location).forEach { resource ->
detailLogger.log(INFO, "Reading model/texture configuration $location from pack ${resource.packName}")
resourceManager.getResources(location).forEach { resource ->
detailLogger.log(INFO, "Reading model/texture configuration $location from pack ${resource.sourceName}")
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
val elements = line.split(",")
modelList.add(ModelTextureList(ResourceLocation(elements.first()), elements.drop(1)))

View File

@@ -18,9 +18,9 @@ abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery {
replacements: MutableMap<ResourceLocation, ModelBakingKey>
) {
ForgeRegistries.BLOCKS
.flatMap { block -> block.stateContainer.validStates }
.flatMap { block -> block.stateDefinition.possibleStates }
.forEach { state ->
val location = BlockModelShapes.getModelLocation(state)
val location = BlockModelShapes.stateToModelLocation(state)
val ctx = ModelDiscoveryContext(bakery, state, location, sprites, replacements, detailLogger)
try {
processModel(ctx)
@@ -31,6 +31,10 @@ abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery {
}
open fun processModel(ctx: ModelDiscoveryContext) {
processContainerModel(ctx)
}
fun processContainerModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
// built-in support for container models
@@ -38,7 +42,7 @@ abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery {
// per-location replacements need to be scoped to the variant list, as replacement models
// may need information from the BlockState which is not available at baking time
val scopedReplacements = mutableMapOf<ResourceLocation, ModelBakingKey>()
model.variantList.forEach { variant ->
model.variants.forEach { variant ->
processModel(ctx.copy(modelLocation = variant.modelLocation, replacements = scopedReplacements))
}
if (scopedReplacements.isNotEmpty()) {
@@ -66,20 +70,25 @@ abstract class ConfigurableModelDiscovery : AbstractModelDiscovery() {
detailLogger.log(Level.INFO, " model ${ctx.modelLocation}")
detailLogger.log(Level.INFO, " class ${ctx.blockState.block.javaClass.name} matches ${matchClass.name}")
modelTextures
.filter { matcher -> ctx.bakery.modelDerivesFrom(model, ctx.modelLocation, matcher.modelLocation) }
.forEach { match ->
val ancestry = ctx.bakery.getAncestry(ctx.modelLocation)
val matches = modelTextures.filter { matcher ->
matcher.modelLocation in ancestry
}
matches.forEach { match ->
detailLogger.log(Level.INFO, " model $model matches ${match.modelLocation}")
val materials = match.textureNames.map { it to model.resolveTextureName(it) }
val texMapString = Joiner.on(", ").join(materials.map { "${it.first}=${it.second.textureLocation}" })
detailLogger.log(Level.INFO, " sprites [$texMapString]")
val materials = match.textureNames.map { it to model.getMaterial(it) }
val texMapString = Joiner.on(", ").join(materials.map { "${it.first}=${it.second.texture()}" })
detailLogger.log(Level.INFO, " sprites [$texMapString]")
if (materials.all { it.second.textureLocation != MissingTextureSprite.getLocation() }) {
// found a valid model (all required textures exist)
processModel(ctx, materials.map { it.second.textureLocation })
}
if (materials.all { it.second.texture() != MissingTextureSprite.getLocation() }) {
// found a valid model (all required textures exist)
processModel(ctx, materials.map { it.second.texture() })
}
}
if (matches.isEmpty()) {
detailLogger.log(Level.INFO, " no matches for model ${ctx.modelLocation}, inheritance chain ${ancestry.joinToString(" -> ")}")
}
}
return super.processModel(ctx)
}
@@ -88,6 +97,12 @@ abstract class ConfigurableModelDiscovery : AbstractModelDiscovery() {
fun ModelBakery.modelDerivesFrom(model: BlockModel, location: ResourceLocation, target: ResourceLocation): Boolean =
if (location == target) true
else model.parentLocation
?.let { getUnbakedModel(it) as? BlockModel }
?.let { getModel(it) as? BlockModel }
?.let { parent -> modelDerivesFrom(parent, model.parentLocation!!, target) }
?: false
fun ModelBakery.getAncestry(location: ResourceLocation): List<ResourceLocation> {
val model = getModel(location) as? BlockModel ?: return listOf(location)
val parentAncestry = model.parentLocation?.let { getAncestry(it) } ?: emptyList()
return listOf(location) + parentAncestry
}

View File

@@ -0,0 +1,97 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.match.MAnything
import mods.betterfoliage.config.match.MListAll
import mods.betterfoliage.config.match.MListAny
import mods.betterfoliage.config.match.MNegated
import mods.betterfoliage.config.match.MValue
import mods.betterfoliage.config.match.MatchRules
import mods.betterfoliage.util.HasLogger
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level
abstract class ParametrizedModelDiscovery : HasLogger() {
abstract fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>)
fun Map<String, String>.location(key: String): ResourceLocation? {
val result = get(key)?.let { ResourceLocation(it) }
if (result == null) detailLogger.log(Level.WARN, "Cannot find texture parameter \"$key\"")
return result
}
fun Map<String, String>.int(key: String): Int? {
val result = get(key)?.toInt()
if (result == null) detailLogger.log(Level.WARN, "Cannot find integer parameter \"$key\"")
return result
}
}
class RuleProcessingContext(
val discovery: ModelDiscoveryContext
) {
val params = mutableMapOf("type" to "none")
}
class RuleBasedDiscovery : AbstractModelDiscovery() {
val discoverers = mutableMapOf<String, ParametrizedModelDiscovery>()
override fun processModel(ctx: ModelDiscoveryContext) = when(ctx.getUnbaked()) {
is VariantList -> processContainerModel(ctx)
is BlockModel -> processBlockModel(ctx)
else -> Unit
}
fun processBlockModel(ctx: ModelDiscoveryContext) {
val ruleCtx = RuleProcessingContext(ctx)
val rulesToCheck = BetterFoliage.blockConfig.rules.toMutableList()
val ruleResults = mutableListOf<MListAll>()
var previousSize = 0
// stop processing if nothing changes anymore
while (rulesToCheck.size != previousSize) {
previousSize = rulesToCheck.size
val iterator = rulesToCheck.listIterator()
while (iterator.hasNext()) iterator.next().let { rule ->
// process single rule
MatchRules.visitRoot(ruleCtx, rule).let { result ->
ruleResults.add(result)
// remove rule from active list if:
// - rule succeeded (all directives returned success)
// - rule is immutable (result will always be the same)
if (result.value || result.immutable) iterator.remove()
}
}
}
// log result of rule processing
if (ruleResults.any { it.value }) {
detailLogger.log(Level.INFO, "================================")
detailLogger.log(Level.INFO, "block state: ${ctx.blockState}")
detailLogger.log(Level.INFO, "block class: ${ctx.blockState.block::class.java.name}")
detailLogger.log(Level.INFO, "model : ${ctx.modelLocation}")
detailLogger.log(Level.INFO, "--------------------------------")
ruleResults.forEach { logResult(it) }
}
discoverers[ruleCtx.params["type"]]?.processModel(ctx, ruleCtx.params)
}
fun logResult(match: MAnything<Boolean>) {
when(match) {
is MListAll -> if (match.list.any { it.value }) {
var seenInvariantSuccess = false
match.list.forEach { item ->
if (item.immutable && item.value) seenInvariantSuccess = true
if (seenInvariantSuccess) logResult(item)
}
}
is MListAny -> if (match.value) match.list.first { it.value }.let { logResult(it) }
else match.list.forEach { logResult(it) }
is MNegated -> logResult(match.inner)
is MValue<Boolean> -> detailLogger.log(Level.INFO, "[${match.configSource}] ${match.description}")
}
}
}

View File

@@ -20,7 +20,7 @@ class WeightedUnbakedKey(
if (unbaked !is VariantList) return super.bake(ctx)
// bake all variants, replace as needed
val bakedModels = unbaked.variantList.mapNotNull {
val bakedModels = unbaked.variants.mapNotNull {
val variantCtx = ctx.copy(location = it.modelLocation, transform = it)
val replacement = replacements[it.modelLocation]
val baked = replacement?.let { replacement ->
@@ -40,10 +40,10 @@ class WeightedUnbakedKey(
// let it through unchanged
if (bakedModels.isEmpty()) return super.bake(ctx)
if (bakedModels.size < unbaked.variantList.size) {
if (bakedModels.size < unbaked.variants.size) {
detailLogger.log(
WARN,
"Dropped ${unbaked.variantList.size - bakedModels.size} variants from model ${ctx.location}"
"Dropped ${unbaked.variants.size - bakedModels.size} variants from model ${ctx.location}"
)
}
val weightedSpecials = bakedModels.map { (variant, model) ->

View File

@@ -1,10 +1,8 @@
package mods.betterfoliage.resource.generated
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.ClientResourcePackInfo
import net.minecraft.resources.*
import net.minecraft.resources.ResourcePackType.CLIENT_RESOURCES
import net.minecraft.resources.data.IMetadataSectionSerializer
@@ -12,6 +10,7 @@ import net.minecraft.util.ResourceLocation
import net.minecraft.util.text.StringTextComponent
import org.apache.logging.log4j.Level.INFO
import java.util.*
import java.util.function.Consumer
import java.util.function.Predicate
import java.util.function.Supplier
@@ -25,10 +24,10 @@ class GeneratedTexturePack(
val nameSpace: String, val packName: String
) : HasLogger(), IResourcePack {
override fun getName() = packName
override fun getResourceNamespaces(type: ResourcePackType) = setOf(nameSpace)
override fun <T : Any?> getMetadata(deserializer: IMetadataSectionSerializer<T>) = null
override fun getRootResourceStream(id: String) = null
override fun getAllResourceLocations(type: ResourcePackType, namespace:String, path: String, maxDepth: Int, filter: Predicate<String>) = emptyList<ResourceLocation>()
override fun getNamespaces(type: ResourcePackType) = setOf(nameSpace)
override fun <T : Any?> getMetadataSection(deserializer: IMetadataSectionSerializer<T>) = null
override fun getRootResource(id: String) = null
override fun getResources(type: ResourcePackType, namespace:String, path: String, maxDepth: Int, filter: Predicate<String>) = emptyList<ResourceLocation>()
override fun close() {}
@@ -49,21 +48,22 @@ class GeneratedTexturePack(
return id
}
override fun getResourceStream(type: ResourcePackType, id: ResourceLocation) =
override fun getResource(type: ResourcePackType, id: ResourceLocation) =
if (type != CLIENT_RESOURCES) null else resources[id]?.inputStream()
override fun resourceExists(type: ResourcePackType, id: ResourceLocation) =
override fun hasResource(type: ResourcePackType, id: ResourceLocation) =
type == CLIENT_RESOURCES && resources.containsKey(id)
val finder = object : IPackFinder {
val packInfo = ClientResourcePackInfo(
val packInfo = ResourcePackInfo(
packName, true, Supplier { this@GeneratedTexturePack },
StringTextComponent(packName),
StringTextComponent("Generated block textures resource pack"),
PackCompatibility.COMPATIBLE, ResourcePackInfo.Priority.TOP, true, null, true
PackCompatibility.COMPATIBLE, ResourcePackInfo.Priority.TOP, true, IPackNameDecorator.DEFAULT, true
)
override fun <T : ResourcePackInfo> addPackInfosToMap(nameToPackMap: MutableMap<String, T>, packInfoFactory: ResourcePackInfo.IFactory<T>) {
(nameToPackMap as MutableMap<String, ResourcePackInfo>).put(packName, packInfo)
override fun loadPacks(p0: Consumer<ResourcePackInfo>, p1: ResourcePackInfo.IFactory) {
p0.accept(packInfo)
}
}
}

View File

@@ -1,9 +0,0 @@
package mods.betterfoliage.util
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.material.Material
val BlockState.isSnow: Boolean get() = material.let { it == Material.SNOW }
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT)

View File

@@ -62,3 +62,6 @@ open class LazyMapInvalidatable<K, V>(val invalidator: Invalidator, val valueFac
}
}
}
fun <V> Invalidator.lazy(valueFactory: ()->V) = LazyInvalidatable(this, valueFactory)
fun <K, V> Invalidator.lazyMap(valueFactory: (K)->V) = LazyMapInvalidatable(this, valueFactory)

View File

@@ -65,6 +65,9 @@ inline fun <T> MutableList<T>.exchange(idx1: Int, idx2: Int) {
/** Return a random element from the array using the provided random generator */
inline operator fun <T> Array<T>.get(random: Random) = get(random.nextInt(Int.MAX_VALUE) % size)
inline fun Random.idx(array: Array<*>) = nextInt(Int.MAX_VALUE) % array.size
inline fun Random.idxOrNull(array: Array<*>, predicate: ()->Boolean) = if (predicate()) idx(array) else null
fun <T> Iterable<T>.toImmutableList() = ImmutableList.builder<T>().let { builder ->
forEach { builder.add(it) }
builder.build()

View File

@@ -1,12 +1,12 @@
package mods.betterfoliage.util
import net.minecraft.client.renderer.Quaternion
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import net.minecraft.util.Direction.Axis.*
import net.minecraft.util.Direction.AxisDirection.NEGATIVE
import net.minecraft.util.Direction.AxisDirection.POSITIVE
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.vector.Quaternion
val EPSILON_ZERO = 0.05
val EPSILON_ONE = 0.95
@@ -55,15 +55,15 @@ val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
// Vectors
// ================================
operator fun Direction.times(scale: Double) =
Double3(directionVec.x.toDouble() * scale, directionVec.y.toDouble() * scale, directionVec.z.toDouble() * scale)
val Direction.vec: Double3 get() = Double3(directionVec.x.toDouble(), directionVec.y.toDouble(), directionVec.z.toDouble())
Double3(normal.x.toDouble() * scale, normal.y.toDouble() * scale, normal.z.toDouble() * scale)
val Direction.vec: Double3 get() = Double3(normal.x.toDouble(), normal.y.toDouble(), normal.z.toDouble())
operator fun BlockPos.plus(other: Int3) = BlockPos(x + other.x, y + other.y, z + other.z)
/** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */
data class Double3(var x: Double, var y: Double, var z: Double) {
constructor(x: Float, y: Float, z: Float) : this(x.toDouble(), y.toDouble(), z.toDouble())
constructor(dir: Direction) : this(dir.directionVec.x.toDouble(), dir.directionVec.y.toDouble(), dir.directionVec.z.toDouble())
constructor(dir: Direction) : this(dir.normal.x.toDouble(), dir.normal.y.toDouble(), dir.normal.z.toDouble())
companion object {
val zero: Double3 get() = Double3(0.0, 0.0, 0.0)
fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) =
@@ -88,9 +88,9 @@ data class Double3(var x: Double, var y: Double, var z: Double) {
/** Rotate vector by the given [Quaternion] */
fun rotate(quat: Quaternion) =
quat.copy()
.apply { multiply(Quaternion(x, y, z, 0.0F)) }
.apply { multiply(quat.copy().apply(Quaternion::conjugate)) }
.let { Double3(it.x, it.y, it.z) }
.apply { mul(Quaternion(x.toFloat(), y.toFloat(), z.toFloat(), 0.0F)) }
.apply { mul(quat.copy().apply(Quaternion::conj)) }
.let { Double3(it.i().toDouble(), it.j().toDouble(), it.k().toDouble()) }
// mutable operations
fun setTo(other: Double3): Double3 { x = other.x; y = other.y; z = other.z; return this }
@@ -120,11 +120,11 @@ data class Double3(var x: Double, var y: Double, var z: Double) {
/** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */
data class Int3(var x: Int, var y: Int, var z: Int) {
constructor(dir: Direction) : this(dir.directionVec.x, dir.directionVec.y, dir.directionVec.z)
constructor(dir: Direction) : this(dir.normal.x, dir.normal.y, dir.normal.z)
constructor(offset: Pair<Int, Direction>) : this(
offset.first * offset.second.directionVec.x,
offset.first * offset.second.directionVec.y,
offset.first * offset.second.directionVec.z
offset.first * offset.second.normal.x,
offset.first * offset.second.normal.y,
offset.first * offset.second.normal.z
)
companion object {
val zero = Int3(0, 0, 0)
@@ -133,9 +133,9 @@ data class Int3(var x: Int, var y: Int, var z: Int) {
// immutable operations
operator fun plus(other: Int3) = Int3(x + other.x, y + other.y, z + other.z)
operator fun plus(other: Pair<Int, Direction>) = Int3(
x + other.first * other.second.directionVec.x,
y + other.first * other.second.directionVec.y,
z + other.first * other.second.directionVec.z
x + other.first * other.second.normal.x,
y + other.first * other.second.normal.y,
z + other.first * other.second.normal.z
)
operator fun unaryMinus() = Int3(-x, -y, -z)
operator fun minus(other: Int3) = Int3(x - other.x, y - other.y, z - other.z)

View File

@@ -7,16 +7,19 @@ import net.minecraft.util.text.TextFormatting
import net.minecraft.util.text.TextFormatting.AQUA
import net.minecraft.util.text.TextFormatting.GRAY
fun stripTooltipDefaultText(tooltip: MutableList<String>) {
var defaultRows = false
val iter = tooltip.iterator()
while (iter.hasNext()) {
if (iter.next().startsWith(AQUA.toString())) defaultRows = true
if (defaultRows) iter.remove()
}
}
//fun stripTooltipDefaultText(tooltip: MutableList<String>) {
// var defaultRows = false
// val iter = tooltip.iterator()
// while (iter.hasNext()) {
// if (iter.next().startsWith(AQUA.toString())) defaultRows = true
// if (defaultRows) iter.remove()
// }
//}
//
//fun textComponent(msg: String, color: TextFormatting = GRAY): StringTextComponent {
// val style = Style().apply { this.color = color }
// return StringTextComponent(msg).apply { this.style = style }
//}
val styleGray = Style.EMPTY.applyFormats(GRAY)
fun textComponent(msg: String, color: TextFormatting = GRAY): StringTextComponent {
val style = Style().apply { this.color = color }
return StringTextComponent(msg).apply { this.style = style }
}
fun String.asText() = StringTextComponent(this).setStyle(styleGray)

View File

@@ -2,6 +2,9 @@
package mods.betterfoliage.util
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.model.HSB
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
@@ -20,6 +23,8 @@ inline fun String.stripEnd(str: String, ignoreCase: Boolean = true) = if (endsWi
inline fun ResourceLocation.stripStart(str: String) = ResourceLocation(namespace, path.stripStart(str))
inline fun ResourceLocation.stripEnd(str: String) = ResourceLocation(namespace, path.stripEnd(str))
val String.quoted: String get() = "\"$this\""
/**
* Property-level delegate backed by a [ThreadLocal].
*
@@ -31,6 +36,11 @@ class ThreadLocalDelegate<T>(init: () -> T) {
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { tlVal.set(value) }
}
fun <T> ThreadLocal<T?>.getWithDefault(factory: ()->T): T {
get()?.let { return it }
return factory().apply { set(this) }
}
/** Call the supplied lambda and return its result, or the given default value if an exception is thrown. */
fun <T> tryDefault(default: T, work: ()->T) = try { work() } catch (e: Throwable) { default }
@@ -56,6 +66,12 @@ abstract class HasLogger {
val detailLogger = BetterFoliageMod.detailLogger(this)
}
fun Logger.logTextureColor(level: Level, description: String, avgColor: HSB) {
val rgb = avgColor.asColor
log(level, "$description average color RGB[${rgb.red},${rgb.green},${rgb.blue}], HSB[${avgColor.hue},${avgColor.saturation},${avgColor.brightness}]")
}
fun getBlockModel(state: BlockState) = Minecraft.getInstance().blockRenderer.blockModelShaper.getBlockModel(state)
/**
* Check if the Chunk containing the given [BlockPos] is loaded.
* Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances.

View File

@@ -1,8 +1,6 @@
package mods.betterfoliage.util
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.Material
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.resources.IReloadableResourceManager
import net.minecraft.resources.IResource
import net.minecraft.resources.IResourceManager
@@ -19,11 +17,6 @@ operator fun ResourceLocation.plus(str: String) = ResourceLocation(namespace, pa
fun ResourceLocation.prependLocation(basePath: String) =
ResourceLocation(namespace, basePath.stripEnd("/").let { "$it/$path" })
val ResourceLocation.asBlockMaterial: Material get() = Material(
AtlasTexture.LOCATION_BLOCKS_TEXTURE,
this
)
/** Index operator to get a resource. */
operator fun IResourceManager.get(domain: String, path: String): IResource? = get(ResourceLocation(domain, path))
/** Index operator to get a resource. */

View File

@@ -3,7 +3,6 @@ package mods.betterfoliage.util
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HSB
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.Material
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.resources.IResource
@@ -17,11 +16,13 @@ import java.io.IOException
import javax.imageio.ImageIO
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sin
enum class Atlas(val resourceId: ResourceLocation) {
BLOCKS(AtlasTexture.LOCATION_BLOCKS_TEXTURE),
PARTICLES(AtlasTexture.LOCATION_PARTICLES_TEXTURE);
BLOCKS(AtlasTexture.LOCATION_BLOCKS),
PARTICLES(AtlasTexture.LOCATION_PARTICLES);
/** Get the fully-qualified resource name for sprites belonging to this atlas */
fun file(resource: ResourceLocation) = ResourceLocation(resource.namespace, "textures/${resource.path}.png")
@@ -33,7 +34,7 @@ enum class Atlas(val resourceId: ResourceLocation) {
operator fun get(location: ResourceLocation) = atlas.getSprite(location)
}
val Material.atlas: Atlas get() = Atlas.values().find { it.resourceId == atlasLocation } ?: Atlas.BLOCKS
//val Spr.atlas: Atlas get() = Atlas.values().find { it.resourceId == atlasLocation } ?: Atlas.BLOCKS
inline operator fun AtlasTexture.get(res: ResourceLocation): TextureAtlasSprite? = this.getSprite(res)
inline operator fun AtlasTexture.get(name: String): TextureAtlasSprite? = get(ResourceLocation(name))
@@ -83,6 +84,36 @@ val TextureAtlasSprite.averageColor: HSB get() {
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat())
}
val ResourceLocation.averageHSB: HSB get() = resourceManager.loadSprite(this).let { image ->
var numOpaque = 0
var sumHueX = 0.0
var sumHueY = 0.0
var sumSaturation = 0.0f
var sumBrightness = 0.0f
for (x in 0 until image.width)
for (y in 0 until image.width) {
val pixel = image[x, y]
val alpha = (pixel shr 24) and 255
val hsb = HSB.fromColorBGRA(pixel)
if (alpha == 255) {
numOpaque++
sumHueX += cos((hsb.hue.toDouble() - 0.5) * PI2)
sumHueY += sin((hsb.hue.toDouble() - 0.5) * PI2)
sumSaturation += hsb.saturation
sumBrightness += hsb.brightness
}
}
// circular average - transform sum vector to polar angle
val avgHue = (atan2(sumHueY, sumHueX) / PI2 + 0.5).toFloat()
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat())
}
fun HSB.brighten(multiplier: Float = 1.5f, floor: Float = 0.1f, ceiling: Float = 0.9f) =
copy(brightness = (brightness * multiplier).coerceAtMost(max(ceiling, brightness)).coerceAtLeast(min(floor, brightness)))
fun HSB.saturate(multiplier: Float = 1.5f, floor: Float = 0.1f, ceiling: Float = 0.9f) =
copy(saturation = (saturation * multiplier).coerceAtMost(max(ceiling, saturation)).coerceAtLeast(min(floor, saturation)))
/** Weighted blend of 2 packed RGB colors */
fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
val r = (((rgb1 shr 16) and 255) * weight1 + ((rgb2 shr 16) and 255) * weight2) / (weight1 + weight2)
@@ -103,4 +134,4 @@ fun logColorOverride(logger: Logger, threshold: Double, hsb: HSB) {
}
fun HSB.colorOverride(threshold: Double) =
if (saturation < threshold) null else copy(brightness = (brightness * 2.0f).coerceAtMost(0.9f)).asColor.let { Color(it) }
if (saturation < threshold) null else copy(brightness = (brightness * 2.0f).coerceAtMost(0.9f)).asInt.let { Color(it) }

View File

@@ -1,14 +1,5 @@
#public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace
#public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace <init>
#public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178206_b #vertexColorMultiplier
#public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178207_c #vertexBrightness
#public net.minecraft.block.BlockState$Cache
public net.minecraft.client.renderer.chunk.ChunkRenderCache field_212408_i #world
#public net.minecraft.client.renderer.texture.AtlasTexture$SheetData field_217808_d # sprites
public net.minecraft.client.renderer.BlockModelRenderer$Cache
public net.minecraft.client.renderer.BlockModelRenderer field_210267_b
public net.minecraft.client.renderer.BlockModelRenderer field_187499_a
public net.minecraft.client.renderer.BlockModelRenderer field_187499_a

View File

@@ -1,7 +1,7 @@
modLoader="kotlinfml"
loaderVersion="[1.4,)"
modLoader="kotlinforforge"
loaderVersion="[1,)"
issueTrackerURL="https://github.com/octarine-noise/BetterFoliage/issues"
license="MIT"
[[mods]]
modId="betterfoliage"
version="${version}"

View File

@@ -0,0 +1,11 @@
match isParam("type", "leaf")
model.extends("biomesoplenty:block/leaves_overlay")
setParam("texture", model.texture("leaves"))
setParam("tint", model.tint("leaves"))
end
match isParam("type", "grass")
model.extends("biomesoplenty:block/origin_grass_block")
setParam("texture", model.texture("top"))
setParam("tint", model.tint("top"))
end

View File

@@ -0,0 +1,48 @@
// A lot of BYG leaf models are very sloppily made, extending "block/cube" or even "block/block"
// These rules are meh, but there's no better way to do it, there's no method to the madness here
// snowy leaves
match isParam("type", "leaf") block.name.contains("byg:leaves") model.contains("snowy")
setParam("texture", model.texture("up")) setParam("tint", model.tint("up")) end
// list of leaves where texture is "up"
match model.matches(
"byg:block/aspen_leaves",
"byg:block/baobab_leaves",
"byg:block/blue_enchanted_leaves"
) setParam("texture", model.texture("up")) setParam("tint", model.tint("up"))
end
// list of leaves where texture is "top"
match model.matches(
"byg:block/flowering_orchard_leaves",
"byg:block/joshua_leaves",
"byg:block/mahogany_leaves",
"byg:block/maple_leaves",
"byg:block/orchard_leaves",
"byg:block/rainbow_eucalyptus_leaves",
"byg:block/willow_leaves"
) setParam("texture", model.texture("top")) setParam("tint", model.tint("top"))
end
// ripe leaves (tint comes from overlay)
match model.matches(
"byg:block/ripe_joshua_leaves",
"byg:block/ripe_orchard_leaves"
) setParam("texture", model.texture("top")) setParam("tint", model.tint("overlay"))
end
//
// other blocks
//
match block.name.matches("byg:meadow_dirt") setParam("type", "dirt") end
match block.name.matches("byg:overgrown_crimson_blackstone") setParam("type", "mycelium") end
match model.matches("byg:block/meadow_grass_block", "byg:block/overgrown_stone", "byg:block/overgrown_dacite", "byg:block/overgrown_netherrack")
setParam("type", "grass")
setParam("texture", model.texture("top")) setParam("tint", model.tint("top"))
end
match block.name.matches("byg:overgrown_stone", "byg:overgrown_dacite", "byg:overgrown_netherrack", "byg:podzol_dacite")
setParam("no-connect", "true")
end

View File

@@ -0,0 +1 @@
match block.class.extends(classOf("desolation:charred_branches")) setParam("type", "leaf") end

View File

@@ -0,0 +1 @@
match block.class.extends(classOf("environmental:blue_wisteria_leaves")) setParam("type", "leaf") end

View File

@@ -0,0 +1,9 @@
match isParam("type", "leaf")
block.name.contains("spruce", "fir")
setParam("particle", "spruce")
end
match isParam("type", "leaf")
block.name.contains("jungle", "palm")
setParam("particle", "jungle")
end

View File

@@ -0,0 +1,58 @@
// Leaves
match block.class.extends(classOf("minecraft:oak_leaves")) setParam("type", "leaf") end
match isParam("type", "leaf")
model.extends("minecraft:block/leaves", "minecraft:block/cube_all")
setParam("texture", model.texture("all"))
setParam("tint", model.tint("all"))
end
// Podzol
match block.name.matches("minecraft:podzol") setParam("type", "grass") end
// Grass
match block.class.extends(classOf("minecraft:grass_block")) setParam("type", "grass") end
match isParam("type", "grass")
model.extends("minecraft:block/grass_block", "minecraft:block/cube_bottom_top")
setParam("texture", model.texture("top"))
setParam("tint", model.tint("top"))
end
// Mycelium & Nylium
match block.name.matches("minecraft:mycelium", "minecraft:crimson_nylium", "minecraft:warped_nylium") setParam("type", "mycelium") end
match isParam("type", "mycelium")
model.extends("minecraft:block/cube_bottom_top")
setParam("texture", model.texture("top"))
setParam("tint", model.tint("top"))
end
// Dirt
match block.name.matches("minecraft:dirt") setParam("type", "dirt") end
// Wood Log
match block.class.extends(classOf("minecraft:oak_log")) setParam("type", "round-log") end
match isParam("type", "round-log")
model.extends("minecraft:block/cube_column", "minecraft:block/cube_column_horizontal")
setParam("texture-side", model.texture("side"))
setParam("texture-end", model.texture("end"))
end
match isParam("type", "round-log")
model.extends("minecraft:block/cube_all")
setParam("texture-side", model.texture("all"))
setParam("texture-end", model.texture("all"))
end
// Sand & Dirt
match block.name.matches("minecraft:sand", "minecraft:red_sand") setParam("type", "sand") end
// Cactus, Lilypad, Netherrack
match block.name.matches("minecraft:cactus") setParam("type", "cactus") end
match block.name.matches("minecraft:lilypad") setParam("type", "lilypad") end
match block.name.matches("minecraft:netherrack") setParam("type", "netherrack") end
// Crops
match block.class.extends(classOf("minecraft:wheat")) setParam("type", "crop") end

View File

@@ -1,15 +0,0 @@
// Vanilla
net.minecraft.block.TallGrassBlock
net.minecraft.block.CropsBlock
-net.minecraft.block.ReedBlock
-net.minecraft.block.DoublePlantBlock
-net.minecraft.block.CarrotBlock
-net.minecraft.block.PotatoBlock
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPFlower
biomesoplenty.common.block.BlockBOPTurnip
biomesoplenty.common.block.BlockBOPPlant
// Tinkers' Construct
tconstruct.blocks.slime.SlimeTallGrass

View File

@@ -1,2 +0,0 @@
// Vanilla
net.minecraft.block.DirtBlock

View File

@@ -1,2 +0,0 @@
// Vanilla
net.minecraft.block.GrassBlock

View File

@@ -1,3 +0,0 @@
// Vanilla
block/grass_block,top
block/cube_bottom_top,top

View File

@@ -0,0 +1,177 @@
{
"key.betterfoliage.gui": "Open Settings",
"betterfoliage.title": "Better Foliage configuration",
"betterfoliage.global.enabled": "Enable Mod",
"betterfoliage.global.enabled.tooltip": "If set to false, BetterFoliage will not render anything",
"betterfoliage.nVidia": "nVidia GPU",
"betterfoliage.nVidia.tooltip": "Specify whether you have an nVidia GPU",
"betterfoliage.enabled": "Enable",
"betterfoliage.enabled.tooltip": "Is this feature enabled?",
"betterfoliage.hOffset": "Horizontal offset",
"betterfoliage.hOffset.tooltip": "The distance this element is shifted horizontally, in blocks",
"betterfoliage.vOffset": "Vertical offset",
"betterfoliage.vOffset.tooltip": "The distance this element is shifted vertically, in blocks",
"betterfoliage.size": "Size",
"betterfoliage.size.tooltip": "Size of this element",
"betterfoliage.heightMin": "Minimum height",
"betterfoliage.heightMin.tooltip": "Minimum height of element",
"betterfoliage.heightMax": "Maximum height",
"betterfoliage.heightMax.tooltip": "Maximum height of element",
"betterfoliage.population": "Population",
"betterfoliage.population.tooltip": "Chance (N in 64) that an eligible block will have this feature",
"betterfoliage.shaderWind": "Shader wind effects",
"betterfoliage.shaderWind.tooltip": "Apply wind effects from ShaderMod shaders to this element?",
"betterfoliage.distance": "Distance limit",
"betterfoliage.distance.tooltip": "Maximum distance from player at which to render this feature",
"betterfoliage.saturationThreshold": "Saturation threshold",
"betterfoliage.saturationThreshold.tooltip": "Color saturation cutoff between \"colorless\" blocks (using biome color) and \"colorful\" blocks (using their own specific color)",
"betterfoliage.rendererror": "§a[BetterFoliage]§f Error rendering block %s at position %s",
"betterfoliage.shaders": "Shader configuration",
"betterfoliage.shaders.tooltip": "Configure integration with shaders",
"betterfoliage.shaders.leavesId": "Leaves ID",
"betterfoliage.shaders.leavesId.tooltip": "Block ID reported to shader programs for all kinds of leaves. If your shader uses a §6block.properties§e file, you'll probably need to change this to match the shader's mappings.",
"betterfoliage.shaders.grassId": "Grass ID",
"betterfoliage.shaders.grassId.tooltip": "Block ID reported to shader programs for all grasses and crops. If your shader uses a §6block.properties§e file, you'll probably need to change this to match the shader's mappings.",
"betterfoliage.leaves": "Extra Leaves",
"betterfoliage.leaves.tooltip": "Extra round leaves on leaf blocks",
"betterfoliage.leaves.dense": "Dense mode",
"betterfoliage.leaves.dense.tooltip": "Dense mode has more round leaves",
"betterfoliage.leaves.snowEnabled": "Enable snow",
"betterfoliage.leaves.snowEnabled.tooltip": "Enable snow on extra leaves?",
"betterfoliage.leaves.hideInternal": "Hide internal leaves",
"betterfoliage.leaves.hideInternal.tooltip": "Skip rendering extra leaves if leaf block is completely surrounded by other leaves or solid blocks",
"betterfoliage.shortGrass": "Short Grass & Mycelium",
"betterfoliage.shortGrass.tooltip": "Tufts of grass/mycelium on top of appropriate blocks",
"betterfoliage.shortGrass.useGenerated": "Use generated texture for grass",
"betterfoliage.shortGrass.useGenerated.tooltip": "Generated texture is made by slicing the tallgrass texture from the active resource pack in half",
"betterfoliage.shortGrass.myceliumEnabled": "Enable Mycelium",
"betterfoliage.shortGrass.myceliumEnabled.tooltip": "Is this feature enabled for mycelium blocks?",
"betterfoliage.shortGrass.grassEnabled": "Enable Grass",
"betterfoliage.shortGrass.grassEnabled.tooltip": "Is this feature enabled for grass blocks?",
"betterfoliage.shortGrass.snowEnabled": "Enable under snow",
"betterfoliage.shortGrass.snowEnabled.tooltip": "Enable on snowed grass blocks?",
"betterfoliage.connectedGrass": "Connected grass textures",
"betterfoliage.connectedGrass.tooltip": "Connected grass textures",
"betterfoliage.connectedGrass.enabled": "Enable",
"betterfoliage.connectedGrass.enabled.tooltip": "If there is a grass block on top of a dirt block: draw grass top texture on all grass block sides,",
"betterfoliage.connectedGrass.snowEnabled": "Enable under snow",
"betterfoliage.connectedGrass.snowEnabled.tooltip": "Enable on snowed grass blocks?",
"betterfoliage.hangingGrass": "Hanging Grass",
"betterfoliage.hangingGrass.tooltip": "Grass tufts hanging down from the top edges of grass blocks",
"betterfoliage.hangingGrass.separation": "Separation",
"betterfoliage.hangingGrass.separation.tooltip": "How much the hanging grass stands out from the block",
"betterfoliage.cactus": "Better Cactus",
"betterfoliage.cactus.tooltip": "Enhance cactus with extra bits and smooth shading",
"betterfoliage.cactus.sizeVariation": "Size variation",
"betterfoliage.cactus.sizeVariation.tooltip": "Amount of random variation on cactus size",
"betterfoliage.lilypad": "Better Lilypad",
"betterfoliage.lilypad.tooltip": "Enhance lilypad with roots and occasional flowers",
"betterfoliage.lilypad.flowerChance": "Flower chance",
"betterfoliage.lilypad.flowerChance.tooltip": "Chance (N in 64) of a lilypad having a flower on it",
"betterfoliage.reed": "Reeds",
"betterfoliage.reed.tooltip": "Reeds on dirt blocks in shallow water",
"betterfoliage.reed.biomes": "Biome List",
"betterfoliage.reed.biomes.tooltip": "Configure which biomes reeds are allowed to appear in",
"betterfoliage.reed.biomes.tooltip.element": "Should reeds appear in the %s biome?",
"betterfoliage.algae": "Algae",
"betterfoliage.algae.tooltip": "Algae on dirt blocks in deep water",
"betterfoliage.algae.biomes": "Biome List",
"betterfoliage.algae.biomes.tooltip": "Configure which biomes algae is allowed to appear in",
"betterfoliage.algae.biomes.tooltip.element": "Should algae appear in the %s biome?",
"betterfoliage.coral": "Coral",
"betterfoliage.coral.tooltip": "Coral on sand blocks in deep water",
"betterfoliage.coral.size": "Coral size",
"betterfoliage.coral.size.tooltip": "Size of coral bits sticking out",
"betterfoliage.coral.crustSize": "Crust size",
"betterfoliage.coral.crustSize.tooltip": "Size of the flat coral part",
"betterfoliage.coral.chance": "Coral chance",
"betterfoliage.coral.chance.tooltip": "Chance (N in 64) of a specific face of the block to show coral",
"betterfoliage.coral.biomes": "Biome List",
"betterfoliage.coral.biomes.tooltip": "Configure which biomes coral is allowed to appear in",
"betterfoliage.coral.biomes.tooltip.element": "Should coral appear in the %s biome?",
"betterfoliage.coral.shallowWater": "Shallow water coral",
"betterfoliage.coral.shallowWater.tooltip": "Should coral appear in 1 block deep water?",
"betterfoliage.netherrack": "Netherrack Vines",
"betterfoliage.netherrack.tooltip": "Hanging Vines under netherrack",
"betterfoliage.fallingLeaves": "Falling leaves",
"betterfoliage.fallingLeaves.tooltip": "Falling leaf particle FX emitted from the bottom of leaf blocks",
"betterfoliage.fallingLeaves.speed": "Particle speed",
"betterfoliage.fallingLeaves.speed.tooltip": "Overall particle speed",
"betterfoliage.fallingLeaves.windStrength": "Wind strength",
"betterfoliage.fallingLeaves.windStrength.tooltip": "Magnitude of wind effects in good weather (spread of normal distribution centered on 0)",
"betterfoliage.fallingLeaves.stormStrength": "Storm strength",
"betterfoliage.fallingLeaves.stormStrength.tooltip": "Additional magnitude of wind effects in rainy weather (spread of normal distribution centered on 0)",
"betterfoliage.fallingLeaves.size": "Particle size",
"betterfoliage.fallingLeaves.chance": "Particle chance",
"betterfoliage.fallingLeaves.chance.tooltip": "Chance of each random render tick hitting a leaf block to spawn a particle",
"betterfoliage.fallingLeaves.perturb": "Perturbation",
"betterfoliage.fallingLeaves.perturb.tooltip": "Magnitude of perturbation effect. Adds a corkscrew-like motion to the particle synchronized to its rotation",
"betterfoliage.fallingLeaves.lifetime": "Maximum lifetime",
"betterfoliage.fallingLeaves.lifetime.tooltip": "Maximum lifetime of particle in seconds. Minimum lifetime is 60%% of this value",
"betterfoliage.fallingLeaves.opacityHack": "Opaque particles",
"betterfoliage.fallingLeaves.opacityHack.tooltip": "Stop transparent blocks obscuring particles even when particle is in front. WARNING: may cause glitches.",
"betterfoliage.risingSoul": "Rising souls",
"betterfoliage.risingSoul.tooltip": "Rising soul particle FX emitted from the top of soulsand blocks",
"betterfoliage.risingSoul.chance": "Particle chance",
"betterfoliage.risingSoul.chance.tooltip": "Chance of each random render tick hitting a soulsand block to spawn a particle",
"betterfoliage.risingSoul.speed": "Particle speed",
"betterfoliage.risingSoul.speed.tooltip": "Vertical speed of soul particles",
"betterfoliage.risingSoul.perturb": "Perturbation",
"betterfoliage.risingSoul.perturb.tooltip": "Magnitude of perturbation effect. Adds a corkscrew-like motion to the particle",
"betterfoliage.risingSoul.headSize": "Soul size",
"betterfoliage.risingSoul.headSize.tooltip": "Size of the soul particle",
"betterfoliage.risingSoul.trailSize": "Trail size",
"betterfoliage.risingSoul.trailSize.tooltip": "Initial size of the particle trail",
"betterfoliage.risingSoul.opacity": "Opacity",
"betterfoliage.risingSoul.opacity.tooltip": "Opacity of the particle effect",
"betterfoliage.risingSoul.sizeDecay": "Size decay",
"betterfoliage.risingSoul.sizeDecay.tooltip": "Trail particle size relative to its size in the previous tick",
"betterfoliage.risingSoul.opacityDecay": "Opacity decay",
"betterfoliage.risingSoul.opacityDecay.tooltip": "Trail particle opacity relative to its opacity in the previous tick",
"betterfoliage.risingSoul.lifetime": "Maximum lifetime",
"betterfoliage.risingSoul.lifetime.tooltip": "Maximum lifetime of particle effect in seconds. Minimum lifetime is 60%% of this value",
"betterfoliage.risingSoul.trailLength": "Trail length",
"betterfoliage.risingSoul.trailLength.tooltip": "Number of previous positions the particle remembers in ticks",
"betterfoliage.risingSoul.trailDensity": "Trail density",
"betterfoliage.risingSoul.trailDensity.tooltip": "Render every Nth previous position in the particle trail",
"betterfoliage.roundLogs": "Round Logs",
"betterfoliage.roundLogs.tooltip": "Round Logs",
"betterfoliage.roundLogs.connectSolids": "Connect to solid",
"betterfoliage.roundLogs.connectSolids.tooltip": "Connect round blocks to solid full blocks?",
"betterfoliage.roundLogs.connectPerpendicular": "Connect to perpendicular logs",
"betterfoliage.roundLogs.connectPerpendicular.tooltip": "Connect round logs to perpendicular logs along its axis?",
"betterfoliage.roundLogs.lenientConnect": "Lenient rounding",
"betterfoliage.roundLogs.lenientConnect.tooltip": "Connect parallel round logs in an L-shape too, not just 2x2",
"betterfoliage.roundLogs.connectGrass": "Connect Grass",
"betterfoliage.roundLogs.connectGrass.tooltip": "Render grass block under trees instead of dirt if there is grass nearby",
"betterfoliage.roundLogs.radiusSmall": "Chamfer radius",
"betterfoliage.roundLogs.radiusSmall.tooltip": "How much to chop off from the log corner",
"betterfoliage.roundLogs.radiusLarge": "Connected chamfer radius",
"betterfoliage.roundLogs.radiusLarge.tooltip": "How much to chop off from the outer corner of connected logs",
"betterfoliage.roundLogs.dimming": "Dimming",
"betterfoliage.roundLogs.dimming.tooltip": "Amount to darken obscured log faces",
"betterfoliage.roundLogs.zProtection": "Z-Protection",
"betterfoliage.roundLogs.zProtection.tooltip": "Amount to scale parallel log connection bits to stop Z-fighting (flickering). Try to set it as high as possible without having glitches.",
"betterfoliage.roundLogs.defaultY": "Default to vertical",
"betterfoliage.roundLogs.defaultY.tooltip": "If true, log blocks where the orientation cannot be determined will be rendered as vertical. Otherwise, they will be rendered as cube blocks.",
"betterfoliage.roundLogs.plantsOnly": "Plants only",
"betterfoliage.roundLogs.plantsOnly.tooltip": "If true, only blocks with plant materials (wood, grass) will be rounded. If false, all column blocks will be rounded, including stone columns."
}

View File

@@ -1,250 +0,0 @@
key.betterfoliage.gui=Open Settings
betterfoliage.global.enabled=Enable Mod
betterfoliage.global.enabled.tooltip=If set to false, BetterFoliage will not render anything
betterfoliage.global.nVidia=nVidia GPU
betterfoliage.global.nVidia.tooltip=Specify whether you have an nVidia GPU
betterfoliage.enabled=Enable
betterfoliage.enabled.tooltip=Is this feature enabled?
betterfoliage.hOffset=Horizontal offset
betterfoliage.hOffset.tooltip=The distance this element is shifted horizontally, in blocks
betterfoliage.vOffset=Vertical offset
betterfoliage.vOffset.tooltip=The distance this element is shifted vertically, in blocks
betterfoliage.size=Size
betterfoliage.size.tooltip=Size of this element
betterfoliage.heightMin=Minimum height
betterfoliage.heightMin.tooltip=Minimum height of element
betterfoliage.heightMax=Maximum height
betterfoliage.heightMax.tooltip=Maximum height of element
betterfoliage.population=Population
betterfoliage.population.tooltip=Chance (N in 64) that an eligible block will have this feature
betterfoliage.shaderWind=Shader wind effects
betterfoliage.shaderWind.tooltip=Apply wind effects from ShaderMod shaders to this element?
betterfoliage.distance=Distance limit
betterfoliage.distance.tooltip=Maximum distance from player at which to render this feature
betterfoliage.rendererror=§a[BetterFoliage]§f Error rendering block %s at position %s
betterfoliage.blocks=Block Types
betterfoliage.blocks.tooltip=Configure lists of block classes that will have specific features applied to them
betterfoliage.blocks.dirtWhitelist=Dirt Whitelist
betterfoliage.blocks.dirtBlacklist=Dirt Blacklist
betterfoliage.blocks.dirtWhitelist.arrayEntry=%d entries
betterfoliage.blocks.dirtBlacklist.arrayEntry=%d entries
betterfoliage.blocks.dirtWhitelist.tooltip=Blocks recognized as Dirt. Has an impact on Reeds, Algae, Connected Grass
betterfoliage.blocks.dirtBlacklist.tooltip=Blocks never accepted as Dirt. Has an impact on Reeds, Algae, Connected Grass
betterfoliage.blocks.grassClassesWhitelist=Grass Whitelist
betterfoliage.blocks.grassClassesBlacklist=Grass Blacklist
betterfoliage.blocks.grassClassesWhitelist.arrayEntry=%d entries
betterfoliage.blocks.grassClassesBlacklist.arrayEntry=%d entries
betterfoliage.blocks.grassModels=Grass Models
betterfoliage.blocks.grassModels.arrayEntry=%d entries
betterfoliage.blocks.grassWhitelist.tooltip=Blocks recognized as Grass. Has an impact on Short Grass, Connected Grass
betterfoliage.blocks.grassBlacklist.tooltip=Blocks never accepted as Grass. Has an impact on Short Grass, Connected Grass
betterfoliage.blocks.grassModels.tooltip=Models and textures recognized for grass blocks
betterfoliage.blocks.leavesClassesWhitelist=Leaves Whitelist
betterfoliage.blocks.leavesClassesBlacklist=Leaves Blacklist
betterfoliage.blocks.leavesClassesWhitelist.arrayEntry=%d entries
betterfoliage.blocks.leavesClassesBlacklist.arrayEntry=%d entries
betterfoliage.blocks.leavesModels=Leaves Models
betterfoliage.blocks.leavesModels.arrayEntry=%d entries
betterfoliage.blocks.leavesClassesWhitelist.tooltip=Blocks recognized as Leaves. Has an impact on Extra Leaves, Falling Leaves. Leaves will render with leaves block ID in shader programs
betterfoliage.blocks.leavesClassesBlacklist.tooltip=Blocks never accepted as Leaves. Has an impact on Extra Leaves, Falling Leaves. Leaves will render with leaves block ID in shader programs
betterfoliage.blocks.leavesModels.tooltip=Models and textures recognized for leaves blocks
betterfoliage.blocks.cropsWhitelist=Crop Whitelist
betterfoliage.blocks.cropsBlacklist=Crop Blacklist
betterfoliage.blocks.cropsWhitelist.arrayEntry=%d entries
betterfoliage.blocks.cropsBlacklist.arrayEntry=%d entries
betterfoliage.blocks.cropsWhitelist.tooltip=Blocks recognized as crops. Crops will render with tallgrass block ID in shader programs
betterfoliage.blocks.cropsBlacklist.tooltip=Blocks never accepted as crops. Crops will render with tallgrass block ID in shader programs
betterfoliage.blocks.logClassesWhitelist=Wood Log Whitelist
betterfoliage.blocks.logClassesBlacklist=Wood Log Blacklist
betterfoliage.blocks.logClassesWhitelist.arrayEntry=%d entries
betterfoliage.blocks.logClassesBlacklist.arrayEntry=%d entries
betterfoliage.blocks.logModels=Wood Log Models
betterfoliage.blocks.logModels.arrayEntry=%d entries
betterfoliage.blocks.logClassesWhitelist.tooltip=Blocks recognized as wooden logs. Has an impact on Rounded Logs
betterfoliage.blocks.logClassesBlacklist.tooltip=Blocks never accepted as wooden logs. Has an impact on Rounded Logs
betterfoliage.blocks.logModels.tooltip=Models and textures recognized for wood log blocks
betterfoliage.blocks.sandWhitelist=Sand Whitelist
betterfoliage.blocks.sandBlacklist=Sand Blacklist
betterfoliage.blocks.sandWhitelist.arrayEntry=%d entries
betterfoliage.blocks.sandBlacklist.arrayEntry=%d entries
betterfoliage.blocks.sandWhitelist.tooltip=Blocks recognized as Sand. Has an impact on Coral
betterfoliage.blocks.sandBlacklist.tooltip=Blocks never accepted Sand. Has an impact on Coral
betterfoliage.blocks.lilypadWhitelist=Lilypad Whitelist
betterfoliage.blocks.lilypadBlacklist=Lilypad Blacklist
betterfoliage.blocks.lilypadWhitelist.arrayEntry=%d entries
betterfoliage.blocks.lilypadBlacklist.arrayEntry=%d entries
betterfoliage.blocks.lilypadWhitelist.tooltip=Blocks recognized as Lilypad. Has an impact on Better Lilypad
betterfoliage.blocks.lilypadBlacklist.tooltip=Blocks never accepted Lilypad. Has an impact on Better Lilypad
betterfoliage.blocks.cactusWhitelist=Cactus Whitelist
betterfoliage.blocks.cactusBlacklist=Cactus Blacklist
betterfoliage.blocks.cactusWhitelist.arrayEntry=%d entries
betterfoliage.blocks.cactusBlacklist.arrayEntry=%d entries
betterfoliage.blocks.cactusWhitelist.tooltip=Blocks recognized as Cactus. Has an impact on Better Cactus
betterfoliage.blocks.cactusBlacklist.tooltip=Blocks never accepted Cactus. Has an impact on Better Cactus
betterfoliage.blocks.myceliumWhitelist=Mycelium Whitelist
betterfoliage.blocks.myceliumBlacklist=Mycelium Blacklist
betterfoliage.blocks.myceliumWhitelist.arrayEntry=%d entries
betterfoliage.blocks.myceliumBlacklist.arrayEntry=%d entries
betterfoliage.blocks.myceliumWhitelist.tooltip=Blocks recognized as Mycelium. Has an impact on Better Grass
betterfoliage.blocks.myceliumBlacklist.tooltip=Blocks never accepted Mycelium. Has an impact on Better Grass
betterfoliage.blocks.netherrackWhitelist=Netherrack Whitelist
betterfoliage.blocks.netherrackBlacklist=Netherrack Blacklist
betterfoliage.blocks.netherrackWhitelist.arrayEntry=%d entries
betterfoliage.blocks.netherrackBlacklist.arrayEntry=%d entries
betterfoliage.blocks.netherrackWhitelist.tooltip=Blocks recognized as Netherrack. Has an impact on Netherrack Vines
betterfoliage.blocks.netherrackBlacklist.tooltip=Blocks never accepted Netherrack. Has an impact on Netherrack Vines
betterfoliage.shaders=Shader configuration
betterfoliage.shaders.tooltip=Configure integration with shaders
betterfoliage.shaders.leavesId=Leaves ID
betterfoliage.shaders.leavesId.tooltip=Block ID reported to shader programs for all kinds of leaves. If your shader uses a §6block.properties§e file, you'll probably need to change this to match the shader's mappings.
betterfoliage.shaders.grassId=Grass ID
betterfoliage.shaders.grassId.tooltip=Block ID reported to shader programs for all grasses and crops. If your shader uses a §6block.properties§e file, you'll probably need to change this to match the shader's mappings.
betterfoliage.leaves=Extra Leaves
betterfoliage.leaves.tooltip=Extra round leaves on leaf blocks
betterfoliage.leaves.dense=Dense mode
betterfoliage.leaves.dense.tooltip=Dense mode has more round leaves
betterfoliage.leaves.snowEnabled=Enable snow
betterfoliage.leaves.snowEnabled.tooltip=Enable snow on extra leaves?
betterfoliage.leaves.hideInternal=Hide internal leaves
betterfoliage.leaves.hideInternal.tooltip=Skip rendering extra leaves if leaf block is completely surrounded by other leaves or solid blocks
betterfoliage.shortGrass=Short Grass & Mycelium
betterfoliage.shortGrass.tooltip=Tufts of grass/mycelium on top of appropriate blocks
betterfoliage.shortGrass.useGenerated=Use generated texture for grass
betterfoliage.shortGrass.useGenerated.tooltip=Generated texture is made by slicing the tallgrass texture from the active resource pack in half
betterfoliage.shortGrass.myceliumEnabled=Enable Mycelium
betterfoliage.shortGrass.myceliumEnabled.tooltip=Is this feature enabled for mycelium blocks?
betterfoliage.shortGrass.grassEnabled=Enable Grass
betterfoliage.shortGrass.grassEnabled.tooltip=Is this feature enabled for grass blocks?
betterfoliage.shortGrass.snowEnabled=Enable under snow
betterfoliage.shortGrass.snowEnabled.tooltip=Enable on snowed grass blocks?
betterfoliage.shortGrass.saturationThreshold=Saturation threshold
betterfoliage.shortGrass.saturationThreshold.tooltip=Color saturation cutoff between "colorless" blocks (using biome color) and "colorful" blocks (using their own specific color)
betterfoliage.hangingGrass=Hanging Grass
betterfoliage.hangingGrass.tooltip=Grass tufts hanging down from the top edges of grass blocks
betterfoliage.hangingGrass.separation=Separation
betterfoliage.hangingGrass.separation.tooltip=How much the hanging grass stands out from the block
betterfoliage.cactus=Better Cactus
betterfoliage.cactus.tooltip=Enhance cactus with extra bits and smooth shading
betterfoliage.cactus.sizeVariation=Size variation
betterfoliage.cactus.sizeVariation.tooltip=Amount of random variation on cactus size
betterfoliage.lilypad=Better Lilypad
betterfoliage.lilypad.tooltip=Enhance lilypad with roots and occasional flowers
betterfoliage.lilypad.flowerChance=Flower chance
betterfoliage.lilypad.flowerChance.tooltip=Chance (N in 64) of a lilypad having a flower on it
betterfoliage.reed=Reeds
betterfoliage.reed.tooltip=Reeds on dirt blocks in shallow water
betterfoliage.reed.biomes=Biome List
betterfoliage.reed.biomes.tooltip=Configure which biomes reeds are allowed to appear in
betterfoliage.reed.biomes.tooltip.element=Should reeds appear in the %s biome?
betterfoliage.algae=Algae
betterfoliage.algae.tooltip=Algae on dirt blocks in deep water
betterfoliage.algae.biomes=Biome List
betterfoliage.algae.biomes.tooltip=Configure which biomes algae is allowed to appear in
betterfoliage.algae.biomes.tooltip.element=Should algae appear in the %s biome?
betterfoliage.coral=Coral
betterfoliage.coral.tooltip=Coral on sand blocks in deep water
betterfoliage.coral.size=Coral size
betterfoliage.coral.size.tooltip=Size of coral bits sticking out
betterfoliage.coral.crustSize=Crust size
betterfoliage.coral.crustSize.tooltip=Size of the flat coral part
betterfoliage.coral.chance=Coral chance
betterfoliage.coral.chance.tooltip=Chance (N in 64) of a specific face of the block to show coral
betterfoliage.coral.biomes=Biome List
betterfoliage.coral.biomes.tooltip=Configure which biomes coral is allowed to appear in
betterfoliage.coral.biomes.tooltip.element=Should coral appear in the %s biome?
betterfoliage.coral.shallowWater=Shallow water coral
betterfoliage.coral.shallowWater.tooltip=Should coral appear in 1 block deep water?
betterfoliage.netherrack=Netherrack Vines
betterfoliage.netherrack.tooltip=Hanging Vines under netherrack
betterfoliage.fallingLeaves=Falling leaves
betterfoliage.fallingLeaves.tooltip=Falling leaf particle FX emitted from the bottom of leaf blocks
betterfoliage.fallingLeaves.speed=Particle speed
betterfoliage.fallingLeaves.speed.tooltip=Overall particle speed
betterfoliage.fallingLeaves.windStrength=Wind strength
betterfoliage.fallingLeaves.windStrength.tooltip=Magnitude of wind effects in good weather (spread of normal distribution centered on 0)
betterfoliage.fallingLeaves.stormStrength=Storm strength
betterfoliage.fallingLeaves.stormStrength.tooltip=Additional magnitude of wind effects in rainy weather (spread of normal distribution centered on 0)
betterfoliage.fallingLeaves.size=Particle size
betterfoliage.fallingLeaves.chance=Particle chance
betterfoliage.fallingLeaves.chance.tooltip=Chance of each random render tick hitting a leaf block to spawn a particle
betterfoliage.fallingLeaves.perturb=Perturbation
betterfoliage.fallingLeaves.perturb.tooltip=Magnitude of perturbation effect. Adds a corkscrew-like motion to the particle synchronized to its rotation
betterfoliage.fallingLeaves.lifetime=Maximum lifetime
betterfoliage.fallingLeaves.lifetime.tooltip=Maximum lifetime of particle in seconds. Minimum lifetime is 60%% of this value
betterfoliage.fallingLeaves.opacityHack=Opaque particles
betterfoliage.fallingLeaves.opacityHack.tooltip=Stop transparent blocks obscuring particles even when particle is in front. WARNING: may cause glitches.
betterfoliage.risingSoul=Rising souls
betterfoliage.risingSoul.tooltip=Rising soul particle FX emitted from the top of soulsand blocks
betterfoliage.risingSoul.chance=Particle chance
betterfoliage.risingSoul.chance.tooltip=Chance of each random render tick hitting a soulsand block to spawn a particle
betterfoliage.risingSoul.speed=Particle speed
betterfoliage.risingSoul.speed.tooltip=Vertical speed of soul particles
betterfoliage.risingSoul.perturb=Perturbation
betterfoliage.risingSoul.perturb.tooltip=Magnitude of perturbation effect. Adds a corkscrew-like motion to the particle
betterfoliage.risingSoul.headSize=Soul size
betterfoliage.risingSoul.headSize.tooltip=Size of the soul particle
betterfoliage.risingSoul.trailSize=Trail size
betterfoliage.risingSoul.trailSize.tooltip=Initial size of the particle trail
betterfoliage.risingSoul.opacity=Opacity
betterfoliage.risingSoul.opacity.tooltip=Opacity of the particle effect
betterfoliage.risingSoul.sizeDecay=Size decay
betterfoliage.risingSoul.sizeDecay.tooltip=Trail particle size relative to its size in the previous tick
betterfoliage.risingSoul.opacityDecay=Opacity decay
betterfoliage.risingSoul.opacityDecay.tooltip=Trail particle opacity relative to its opacity in the previous tick
betterfoliage.risingSoul.lifetime=Maximum lifetime
betterfoliage.risingSoul.lifetime.tooltip=Maximum lifetime of particle effect in seconds. Minimum lifetime is 60%% of this value
betterfoliage.risingSoul.trailLength=Trail length
betterfoliage.risingSoul.trailLength.tooltip=Number of previous positions the particle remembers in ticks
betterfoliage.risingSoul.trailDensity=Trail density
betterfoliage.risingSoul.trailDensity.tooltip=Render every Nth previous position in the particle trail
betterfoliage.connectedGrass=Connected grass textures
betterfoliage.connectedGrass.enabled=Enable
betterfoliage.connectedGrass.enabled.tooltip=If there is a grass block on top of a dirt block: draw grass top texture on all grass block sides,
betterfoliage.roundLogs=Round Logs
betterfoliage.roundLogs.tooltip=Connect round blocks to solid full blocks?
betterfoliage.roundLogs.connectSolids=Connect to solid
betterfoliage.roundLogs.connectSolids.tooltip=Connect round blocks to solid full blocks?
betterfoliage.roundLogs.connectPerpendicular=Connect to perpendicular logs
betterfoliage.roundLogs.connectPerpendicular.tooltip=Connect round logs to perpendicular logs along its axis?
betterfoliage.roundLogs.lenientConnect=Lenient rounding
betterfoliage.roundLogs.lenientConnect.tooltip=Connect parallel round logs in an L-shape too, not just 2x2
betterfoliage.roundLogs.connectGrass=Connect Grass
betterfoliage.roundLogs.connectGrass.tooltip=Render grass block under trees instead of dirt if there is grass nearby
betterfoliage.roundLogs.radiusSmall=Chamfer radius
betterfoliage.roundLogs.radiusSmall.tooltip=How much to chop off from the log corner
betterfoliage.roundLogs.radiusLarge=Connected chamfer radius
betterfoliage.roundLogs.radiusLarge.tooltip=How much to chop off from the outer corner of connected logs
betterfoliage.roundLogs.dimming=Dimming
betterfoliage.roundLogs.dimming.tooltip=Amount to darken obscured log faces
betterfoliage.roundLogs.zProtection=Z-Protection
betterfoliage.roundLogs.zProtection.tooltip=Amount to scale parallel log connection bits to stop Z-fighting (flickering). Try to set it as high as possible without having glitches.
betterfoliage.roundLogs.defaultY=Default to vertical
betterfoliage.roundLogs.defaultY.tooltip=If true, log blocks where the orientation cannot be determined will be rendered as vertical. Otherwise, they will be rendered as cube blocks.

View File

@@ -0,0 +1,155 @@
{
"key.betterfoliage.gui": "설정",
"betterfoliage.global.enabled": "모드 활성화",
"betterfoliage.global.enabled.tooltip": "비활성화 할 경우, 환경강화모드 렌더링이 보이지 않습니다.",
"betterfoliage.enabled": "활성화",
"betterfoliage.enabled.tooltip": "이 기능이 활성화 되어 있습니까?",
"betterfoliage.hOffset": "수평(가로) 상쇄시키다",
"betterfoliage.hOffset.tooltip": "이 성분이 블럭 수평으로 이동된다.",
"betterfoliage.vOffset": "수직(세로) 상쇄시키다",
"betterfoliage.vOffset.tooltip": "이 성분이 블럭 수직으로 이동된다.",
"betterfoliage.size": "사이즈(크기)",
"betterfoliage.size.tooltip": "사이즈(크기)의 최소",
"betterfoliage.heightMin": "최소한 높이",
"betterfoliage.heightMin.tooltip": "높이 최소한의 최소",
"betterfoliage.heightMax": "최대한 높이",
"betterfoliage.heightMax.tooltip": "높이 최대한의 최소",
"betterfoliage.population": "주민",
"betterfoliage.population.tooltip": "자격을 갖춘 블럭의 기능 확률(N 분의 64)",
"betterfoliage.shaderWind": "쉐이더 바람 효과",
"betterfoliage.shaderWind.tooltip": "바람효과를 쉐이더에 적용시키겠습니까?",
"betterfoliage.distance": "거리 제한",
"betterfoliage.distance.tooltip": "이 기능을 렌더링하는 플레이어의 최대거리",
"betterfoliage.saturationThreshold": "채도 임계값",
"betterfoliage.saturationThreshold.tooltip": "(특정 색상을 사용하여)\"무채색\"블록과 (바이옴 색을 사용하여)\"화려한\"블록 사이의 채도 차단",
"betterfoliage.leaves": "잎 추가",
"betterfoliage.leaves.tooltip": "둥글게 나뭇잎을 추가시켜줍니다.",
"betterfoliage.leaves.dense": "조밀한 모드",
"betterfoliage.leaves.dense.tooltip": "조밀한 모드는 둥근 나뭇잎을 더 추가시켜줍니다.",
"betterfoliage.shortGrass": "이쁜 잔디 & 균사체",
"betterfoliage.shortGrass.tooltip": "블록 상단에 잔디 / 균사체",
"betterfoliage.shortGrass.useGenerated": "잔디텍스쳐를 활성화 합니다.",
"betterfoliage.shortGrass.useGenerated.tooltip": "소스팩의 일부분에 활성화 되어있는 큰잔디 텍스쳐를 생성시킵니다.",
"betterfoliage.shortGrass.myceliumEnabled": "균사체 활성화",
"betterfoliage.shortGrass.myceliumEnabled.tooltip": "균사체 블록에 있는 기능을 활성화 시키겠습니까?",
"betterfoliage.shortGrass.grassEnabled": "잔디 활성화",
"betterfoliage.shortGrass.grassEnabled.tooltip": "잔디 블록에 있는 기능을 활성화 시키겠습니까?",
"betterfoliage.shortGrass.snowEnabled": "눈 활성화",
"betterfoliage.shortGrass.snowEnabled.tooltip": "잔디 블록위에 있는 눈을 활성화 시키겠습니까?",
"betterfoliage.hangingGrass": "매달려있는 잔디",
"betterfoliage.hangingGrass.tooltip": "잔디 블록 상단 가장자리에서 아래로 매달려 있는 잔디 다발",
"betterfoliage.hangingGrass.separation": "분리",
"betterfoliage.hangingGrass.separation.tooltip": "잔디블럭에 매달려있는 잔디의 양 ",
"betterfoliage.cactus": "선인장",
"betterfoliage.cactus.tooltip": "선인장의 비트 수와 부드러운 그림자를 추가",
"betterfoliage.cactus.sizeVariation": "사이즈 변화",
"betterfoliage.cactus.sizeVariation.tooltip": "선인장의 사이즈 크기를 무작위 변화 시킵니다.",
"betterfoliage.lilypad": "연꽃잎",
"betterfoliage.lilypad.tooltip": "연꽃의 뿌리와 약간의 꽃 추가",
"betterfoliage.lilypad.flowerChance": "꽃 확률",
"betterfoliage.lilypad.flowerChance.tooltip": "연꽃 위에 있는 꽃 확률(N 분의 64)",
"betterfoliage.reed": "갈대",
"betterfoliage.reed.tooltip": "물 속에서 흙블럭 위에 있는 갈대",
"betterfoliage.reed.biomes": "바이옴 리스트",
"betterfoliage.reed.biomes.tooltip": "갈대를 바이옴에 따라 표시",
"betterfoliage.reed.biomes.tooltip.element": "갈대를 %s 바이옴에 따라 나타내시겠습니까? ",
"betterfoliage.algae": "해조류",
"betterfoliage.algae.tooltip": "깊은 물 속 흙 블럭 위에 있는 해조류",
"betterfoliage.algae.biomes": "바이옴 리스트",
"betterfoliage.algae.biomes.tooltip": "해조류를 바이옴에 따라 표시",
"betterfoliage.algae.biomes.tooltip.element": "해조류를 %s 바이옴에 따라 나타내시겠습니까? ",
"betterfoliage.coral": "산호",
"betterfoliage.coral.tooltip": "깊은 물 속 모래 블럭 위에있는 산호",
"betterfoliage.coral.size": "산호 사이즈(크기)",
"betterfoliage.coral.size.tooltip": "산호 이미지만큼 적용",
"betterfoliage.coral.crustSize": "껍질 크기",
"betterfoliage.coral.crustSize.tooltip": "산호 부분의 크기 ",
"betterfoliage.coral.chance": "산호 확률",
"betterfoliage.coral.chance.tooltip": "산호를 특정블록에 표시하는 확률(N 분의 64)",
"betterfoliage.coral.biomes": "바이옴 리스트",
"betterfoliage.coral.biomes.tooltip": "산호를 바이옴에 따라 표시",
"betterfoliage.coral.biomes.tooltip.element": "산호를 %s 바이옴에 따라 나타내시겠습니까?",
"betterfoliage.coral.shallowWater": "얕은 물 산호",
"betterfoliage.coral.shallowWater.tooltip": "산호를 깊은 물에 표시하시겠습니까?",
"betterfoliage.netherrack": "네더랙 덩굴",
"betterfoliage.netherrack.tooltip": "네더랙에 매달려있는 덩굴",
"betterfoliage.fallingLeaves": "떨어지는 나뭇잎",
"betterfoliage.fallingLeaves.tooltip": "잎 블록에서 바닥으로 떨어지는 잎 파티클",
"betterfoliage.fallingLeaves.speed": "파티클 속도",
"betterfoliage.fallingLeaves.speed.tooltip": "전체 파티클 속도",
"betterfoliage.fallingLeaves.windStrength": "바람 세기",
"betterfoliage.fallingLeaves.windStrength.tooltip": "날씨 바람 추가효과 (0을 중심으로 정규분포의 확산)",
"betterfoliage.fallingLeaves.stormStrength": "폭풍 세기",
"betterfoliage.fallingLeaves.stormStrength.tooltip": "비 날씨에 바람 추가효과 (0을 중심으로 정규분포의 확산)",
"betterfoliage.fallingLeaves.size": "파티클 크기",
"betterfoliage.fallingLeaves.chance": "파티클 확률",
"betterfoliage.fallingLeaves.chance.tooltip": "잎 파티클 랜덤 확률 설정",
"betterfoliage.fallingLeaves.perturb": "움직임",
"betterfoliage.fallingLeaves.perturb.tooltip": "움직임 효과의 크기. 코르크 같은 움직임을 추가합니다.",
"betterfoliage.fallingLeaves.lifetime": "파티클 지속시간",
"betterfoliage.fallingLeaves.lifetime.tooltip": "최대 파티클 지속시간을 설정합니다. 최소 지속시간은 60%입니다.",
"betterfoliage.fallingLeaves.opacityHack": "불투명 파티클",
"betterfoliage.fallingLeaves.opacityHack.tooltip": "파티클이 앞에 있어도 파티클을 가리는 투명블럭을 없앱니다. 경고: 오류주의",
"betterfoliage.risingSoul": "소울 상승",
"betterfoliage.risingSoul.tooltip": "소울 블럭에서 올라오는 파티클",
"betterfoliage.risingSoul.chance": "파티클 수정",
"betterfoliage.risingSoul.chance.tooltip": "소울 파티클 랜덤 확률 설정",
"betterfoliage.risingSoul.speed": "파티클 속도",
"betterfoliage.risingSoul.speed.tooltip": "파티클 속도",
"betterfoliage.risingSoul.perturb": "움직임",
"betterfoliage.risingSoul.perturb.tooltip": "움직임 효과의 크기. 코르크 같은 움직임을 추가합니다.",
"betterfoliage.risingSoul.headSize": "소울 크기",
"betterfoliage.risingSoul.headSize.tooltip": "소울 파티클의 크기",
"betterfoliage.risingSoul.trailSize": "자취 사이즈",
"betterfoliage.risingSoul.trailSize.tooltip": "자취의 크기",
"betterfoliage.risingSoul.opacity": "불투명",
"betterfoliage.risingSoul.opacity.tooltip": "파티클의 불투명",
"betterfoliage.risingSoul.sizeDecay": "크기 감소",
"betterfoliage.risingSoul.sizeDecay.tooltip": "상대적인 자취 파티클 크기",
"betterfoliage.risingSoul.opacityDecay": "불투명 감소",
"betterfoliage.risingSoul.opacityDecay.tooltip": "상대적인 입자의 파티클 불투명도",
"betterfoliage.risingSoul.lifetime": "최대 지속시간",
"betterfoliage.risingSoul.lifetime.tooltip": "최대 파티클 지속시간을 설정합니다. 최소 지속시간은 60%입니다.",
"betterfoliage.risingSoul.trailLength": "자취의 세기",
"betterfoliage.risingSoul.trailLength.tooltip": "이전 파티클 틱으로 자취를 생성합니다.",
"betterfoliage.risingSoul.trailDensity": "자취의 밀도",
"betterfoliage.risingSoul.trailDensity.tooltip": "파티클 자취를 모두 렌더링 합니다.",
"betterfoliage.connectedGrass": "잔디 연결 텍스쳐",
"betterfoliage.connectedGrass.enabled": "활성화",
"betterfoliage.connectedGrass.enabled.tooltip": "잔디블록이 흙블록 위에 있을 경우 잔디블록 윗텍스쳐를 옆텍스쳐에 전부 씌웁니다.",
"betterfoliage.roundLogs": "둥근 나무",
"betterfoliage.roundLogs.tooltip": "둥근부분을 블럭과 연결.",
"betterfoliage.roundLogs.connectSolids": "블럭과 연결",
"betterfoliage.roundLogs.connectSolids.tooltip": "둥근부분을 블럭과 연결.",
"betterfoliage.roundLogs.connectPerpendicular": "나무 세로부분과 연결",
"betterfoliage.roundLogs.connectPerpendicular.tooltip": "나무 세로부분을 나무부분끼리 연결",
"betterfoliage.roundLogs.lenientConnect": "부드럽게 둥글게 연결",
"betterfoliage.roundLogs.lenientConnect.tooltip": "2x2사이즈처럼 나무 평형된 부분끼리 서로 연결합니다.",
"betterfoliage.roundLogs.connectGrass": "잔디 연결 텍스쳐",
"betterfoliage.roundLogs.connectGrass.tooltip": "잔디가 근처에 있을 경우 나무 아래 잔디 블록을 렌더링",
"betterfoliage.roundLogs.radiusSmall": "모서리 깎는 반지름",
"betterfoliage.roundLogs.radiusSmall.tooltip": "나무 모서리 깎는 정도",
"betterfoliage.roundLogs.radiusLarge": "모서리 깎는 부분 연결",
"betterfoliage.roundLogs.radiusLarge.tooltip": "나무 모서리 부분을 연결",
"betterfoliage.roundLogs.dimming": "조광",
"betterfoliage.roundLogs.dimming.tooltip": "나무 표면부분을 어둡게하는 양",
"betterfoliage.roundLogs.zProtection": "Z-Protection",
"betterfoliage.roundLogs.zProtection.tooltip": "Amount to scale parallel log connection bits to stop Z-fighting (flickering). Try to set it as high as possible without having glitches.",
"betterfoliage.roundLogs.defaultY": "수직선에대한 기본값",
"betterfoliage.roundLogs.defaultY.tooltip": "true 일경우, 나무 블럭이 수직선에대한 렌더링을 할수가 없습니다. 그렇지 아니하면, 네모난 블럭으로 렌더링 될것임니다."
}

View File

@@ -1,216 +0,0 @@
key.betterfoliage.gui=설정
betterfoliage.global.enabled=모드 활성화
betterfoliage.global.enabled.tooltip=비활성화 할 경우, 환경강화모드 렌더링이 보이지 않습니다.
betterfoliage.enabled=활성화
betterfoliage.enabled.tooltip=이 기능이 활성화 되어 있습니까?
betterfoliage.hOffset=수평(가로) 상쇄시키다
betterfoliage.hOffset.tooltip=이 성분이 블럭 수평으로 이동된다.
betterfoliage.vOffset=수직(세로) 상쇄시키다
betterfoliage.vOffset.tooltip=이 성분이 블럭 수직으로 이동된다.
betterfoliage.size=사이즈(크기)
betterfoliage.size.tooltip=사이즈(크기)의 최소
betterfoliage.heightMin=최소한 높이
betterfoliage.heightMin.tooltip=높이 최소한의 최소
betterfoliage.heightMax=최대한 높이
betterfoliage.heightMax.tooltip=높이 최대한의 최소
betterfoliage.population=주민
betterfoliage.population.tooltip=자격을 갖춘 블럭의 기능 확률(N 분의 64)
betterfoliage.shaderWind=쉐이더 바람 효과
betterfoliage.shaderWind.tooltip=바람효과를 쉐이더에 적용시키겠습니까?
betterfoliage.distance=거리 제한
betterfoliage.distance.tooltip=이 기능을 렌더링하는 플레이어의 최대거리
betterfoliage.blocks=블록 타입
betterfoliage.blocks.tooltip=세팅 된 것에 따라 블록이 바뀔 것입니다.
betterfoliage.blocks.dirtWhitelist=흙 허용목록
betterfoliage.blocks.dirtBlacklist=흙 차단목록
betterfoliage.blocks.dirtWhitelist.arrayEntry=%d entries
betterfoliage.blocks.dirtBlacklist.arrayEntry=%d entries
betterfoliage.blocks.grassWhitelist=잔디 허용목록
betterfoliage.blocks.grassBlacklist=잔디 차단목록
betterfoliage.blocks.grassWhitelist.arrayEntry=%d entries
betterfoliage.blocks.grassBlacklist.arrayEntry=%d entries
betterfoliage.blocks.leavesWhitelist=잎 허용목록
betterfoliage.blocks.leavesBlacklist=잎 차단목록
betterfoliage.blocks.leavesWhitelist.arrayEntry=%d entries
betterfoliage.blocks.leavesBlacklist.arrayEntry=%d entries
betterfoliage.blocks.cropsWhitelist=농작물 허용목록
betterfoliage.blocks.cropsBlacklist=농작물 차단목록
betterfoliage.blocks.cropsWhitelist.arrayEntry=%d entries
betterfoliage.blocks.cropsBlacklist.arrayEntry=%d entries
betterfoliage.blocks.logsWhitelist=나무 허용목록
betterfoliage.blocks.logsBlacklist=나무 차단목록
betterfoliage.blocks.logsWhitelist.arrayEntry=%d entries
betterfoliage.blocks.logsBlacklist.arrayEntry=%d entries
betterfoliage.blocks.sandWhitelist=모래 허용목록
betterfoliage.blocks.sandBlacklist=모래 차단목록
betterfoliage.blocks.sandWhitelist.arrayEntry=%d entries
betterfoliage.blocks.sandBlacklist.arrayEntry=%d entries
betterfoliage.blocks.lilypadWhitelist=연꽃 허용목록
betterfoliage.blocks.lilypadBlacklist=연꽃 차단목록
betterfoliage.blocks.lilypadWhitelist.arrayEntry=%d entries
betterfoliage.blocks.lilypadBlacklist.arrayEntry=%d entries
betterfoliage.blocks.cactusWhitelist=선인장 허용목록
betterfoliage.blocks.cactusBlacklist=선인장 차단목록
betterfoliage.blocks.cactusWhitelist.arrayEntry=%d entries
betterfoliage.blocks.cactusBlacklist.arrayEntry=%d entries
betterfoliage.blocks.dirtWhitelist.tooltip=흙으로 인식됩니다. 갈대, 조류, 잔디 텍스쳐 연결에 영향을 줍니다.
betterfoliage.blocks.dirtBlacklist.tooltip=흙으로 인식 되지 않습니다. 갈대, 조류, 잔디 텍스쳐 연결에 영향을 주지 않습니다.
betterfoliage.blocks.grassWhitelist.tooltip=잔디로 인식됩니다. 짧은 잔디, 잔디 텍스쳐 연결에 영향을 줍니다.
betterfoliage.blocks.grassBlacklist.tooltip=잔디로 인식 되지 않습니다. 짧은 잔디, 잔디 텍스쳐 연결에 영향을 주지 않습니다.
betterfoliage.blocks.leavesWhitelist.tooltip=잎으로 인식됩니다. 추가 잎, 떨어지는 잎에 영향을 줍니다.
betterfoliage.blocks.leavesBlacklist.tooltip=잎으로 인식 되지 않습니다. 추가 잎, 떨어지는 잎에 영향을 주지 않습니다.
betterfoliage.blocks.cropsWhitelist.tooltip=농작물로 인식됩니다. 농작물은 쉐이더 적용하면 큰잔디로 렌더링 됩니다.
betterfoliage.blocks.cropsBlacklist.tooltip=농작물로 인식 되지 않습니다. 농작물은 쉐이더 적용하면 큰잔디로 렌더링 되지 않습니다.
betterfoliage.blocks.logsWhitelist.tooltip=나무로 인식됩니다. 둥근 나무에 영향을 줍니다.
betterfoliage.blocks.logsBlacklist.tooltip=나무로 인식 되지 않습니다. 둥근 나무에 영향을 주지 않습니다.
betterfoliage.blocks.sandWhitelist.tooltip=모래로 인식됩니다. 산호에 영향을 줍니다
betterfoliage.blocks.sandBlacklist.tooltip=모래로 인식되지 않습니다. 산호에 영향을 주지 않습니다.
betterfoliage.blocks.lilypadWhitelist.tooltip=연꽃으로 인식됩니다. 보다 나은 연꽃에 영향을 줍니다.
betterfoliage.blocks.lilypadBlacklist.tooltip=연꽃으로 인식되지 않습니다. 보다 나은 연꽃에 영향을 주지 않습니다.
betterfoliage.blocks.cactusWhitelist.tooltip=선인장으로 인식됩니다. 보다 나은 선인장에 영향을 줍니다.
betterfoliage.blocks.cactusBlacklist.tooltip=선인장으로 인식되지 않습니다. 보다 나은 선인장에 영향을 주지 않습니다.
betterfoliage.leaves=잎 추가
betterfoliage.leaves.tooltip=둥글게 나뭇잎을 추가시켜줍니다.
betterfoliage.leaves.dense=조밀한 모드
betterfoliage.leaves.dense.tooltip=조밀한 모드는 둥근 나뭇잎을 더 추가시켜줍니다.
betterfoliage.shortGrass=이쁜 잔디 & 균사체
betterfoliage.shortGrass.tooltip=블록 상단에 잔디 / 균사체
betterfoliage.shortGrass.useGenerated=잔디텍스쳐를 활성화 합니다.
betterfoliage.shortGrass.useGenerated.tooltip=소스팩의 일부분에 활성화 되어있는 큰잔디 텍스쳐를 생성시킵니다.
betterfoliage.shortGrass.myceliumEnabled=균사체 활성화
betterfoliage.shortGrass.myceliumEnabled.tooltip=균사체 블록에 있는 기능을 활성화 시키겠습니까?
betterfoliage.shortGrass.grassEnabled=잔디 활성화
betterfoliage.shortGrass.grassEnabled.tooltip=잔디 블록에 있는 기능을 활성화 시키겠습니까?
betterfoliage.shortGrass.snowEnabled=눈 활성화
betterfoliage.shortGrass.snowEnabled.tooltip=잔디 블록위에 있는 눈을 활성화 시키겠습니까?
betterfoliage.shortGrass.saturationThreshold=채도 임계값
betterfoliage.shortGrass.saturationThreshold.tooltip=(특정 색상을 사용하여)"무채색"블록과 (바이옴 색을 사용하여)"화려한"블록 사이의 채도 차단
betterfoliage.hangingGrass=매달려있는 잔디
betterfoliage.hangingGrass.tooltip=잔디 블록 상단 가장자리에서 아래로 매달려 있는 잔디 다발
betterfoliage.hangingGrass.separation=분리
betterfoliage.hangingGrass.separation.tooltip=잔디블럭에 매달려있는 잔디의 양
betterfoliage.cactus=선인장
betterfoliage.cactus.tooltip=선인장의 비트 수와 부드러운 그림자를 추가
betterfoliage.cactus.sizeVariation=사이즈 변화
betterfoliage.cactus.sizeVariation.tooltip=선인장의 사이즈 크기를 무작위 변화 시킵니다.
betterfoliage.lilypad=연꽃잎
betterfoliage.lilypad.tooltip=연꽃의 뿌리와 약간의 꽃 추가
betterfoliage.lilypad.flowerChance=꽃 확률
betterfoliage.lilypad.flowerChance.tooltip=연꽃 위에 있는 꽃 확률(N 분의 64)
betterfoliage.reed=갈대
betterfoliage.reed.tooltip=물 속에서 흙블럭 위에 있는 갈대
betterfoliage.reed.biomes=바이옴 리스트
betterfoliage.reed.biomes.tooltip=갈대를 바이옴에 따라 표시
betterfoliage.reed.biomes.tooltip.element=갈대를 %s 바이옴에 따라 나타내시겠습니까?
betterfoliage.algae=해조류
betterfoliage.algae.tooltip=깊은 물 속 흙 블럭 위에 있는 해조류
betterfoliage.algae.biomes=바이옴 리스트
betterfoliage.algae.biomes.tooltip=해조류를 바이옴에 따라 표시
betterfoliage.algae.biomes.tooltip.element=해조류를 %s 바이옴에 따라 나타내시겠습니까?
betterfoliage.coral=산호
betterfoliage.coral.tooltip=깊은 물 속 모래 블럭 위에있는 산호
betterfoliage.coral.size=산호 사이즈(크기)
betterfoliage.coral.size.tooltip=산호 이미지만큼 적용
betterfoliage.coral.crustSize=껍질 크기
betterfoliage.coral.crustSize.tooltip=산호 부분의 크기
betterfoliage.coral.chance=산호 확률
betterfoliage.coral.chance.tooltip=산호를 특정블록에 표시하는 확률(N 분의 64)
betterfoliage.coral.biomes=바이옴 리스트
betterfoliage.coral.biomes.tooltip=산호를 바이옴에 따라 표시
betterfoliage.coral.biomes.tooltip.element=산호를 %s 바이옴에 따라 나타내시겠습니까?
betterfoliage.coral.shallowWater=얕은 물 산호
betterfoliage.coral.shallowWater.tooltip=산호를 깊은 물에 표시하시겠습니까?
betterfoliage.netherrack=네더랙 덩굴
betterfoliage.netherrack.tooltip=네더랙에 매달려있는 덩굴
betterfoliage.fallingLeaves=떨어지는 나뭇잎
betterfoliage.fallingLeaves.tooltip=잎 블록에서 바닥으로 떨어지는 잎 파티클
betterfoliage.fallingLeaves.speed=파티클 속도
betterfoliage.fallingLeaves.speed.tooltip=전체 파티클 속도
betterfoliage.fallingLeaves.windStrength=바람 세기
betterfoliage.fallingLeaves.windStrength.tooltip=날씨 바람 추가효과 (0을 중심으로 정규분포의 확산)
betterfoliage.fallingLeaves.stormStrength=폭풍 세기
betterfoliage.fallingLeaves.stormStrength.tooltip=비 날씨에 바람 추가효과 (0을 중심으로 정규분포의 확산)
betterfoliage.fallingLeaves.size=파티클 크기
betterfoliage.fallingLeaves.chance=파티클 확률
betterfoliage.fallingLeaves.chance.tooltip=잎 파티클 랜덤 확률 설정
betterfoliage.fallingLeaves.perturb=움직임
betterfoliage.fallingLeaves.perturb.tooltip=움직임 효과의 크기. 코르크 같은 움직임을 추가합니다.
betterfoliage.fallingLeaves.lifetime=파티클 지속시간
betterfoliage.fallingLeaves.lifetime.tooltip=최대 파티클 지속시간을 설정합니다. 최소 지속시간은 60%입니다.
betterfoliage.fallingLeaves.opacityHack=불투명 파티클
betterfoliage.fallingLeaves.opacityHack.tooltip=파티클이 앞에 있어도 파티클을 가리는 투명블럭을 없앱니다. 경고: 오류주의
betterfoliage.risingSoul=소울 상승
betterfoliage.risingSoul.tooltip=소울 블럭에서 올라오는 파티클
betterfoliage.risingSoul.chance=파티클 수정
betterfoliage.risingSoul.chance.tooltip=소울 파티클 랜덤 확률 설정
betterfoliage.risingSoul.speed=파티클 속도
betterfoliage.risingSoul.speed.tooltip=파티클 속도
betterfoliage.risingSoul.perturb=움직임
betterfoliage.risingSoul.perturb.tooltip=움직임 효과의 크기. 코르크 같은 움직임을 추가합니다.
betterfoliage.risingSoul.headSize=소울 크기
betterfoliage.risingSoul.headSize.tooltip=소울 파티클의 크기
betterfoliage.risingSoul.trailSize=자취 사이즈
betterfoliage.risingSoul.trailSize.tooltip=자취의 크기
betterfoliage.risingSoul.opacity=불투명
betterfoliage.risingSoul.opacity.tooltip=파티클의 불투명
betterfoliage.risingSoul.sizeDecay=크기 감소
betterfoliage.risingSoul.sizeDecay.tooltip=상대적인 자취 파티클 크기
betterfoliage.risingSoul.opacityDecay=불투명 감소
betterfoliage.risingSoul.opacityDecay.tooltip=상대적인 입자의 파티클 불투명도
betterfoliage.risingSoul.lifetime=최대 지속시간
betterfoliage.risingSoul.lifetime.tooltip=최대 파티클 지속시간을 설정합니다. 최소 지속시간은 60%입니다.
betterfoliage.risingSoul.trailLength=자취의 세기
betterfoliage.risingSoul.trailLength.tooltip=이전 파티클 틱으로 자취를 생성합니다.
betterfoliage.risingSoul.trailDensity=자취의 밀도
betterfoliage.risingSoul.trailDensity.tooltip=파티클 자취를 모두 렌더링 합니다.
betterfoliage.connectedGrass=잔디 연결 텍스쳐
betterfoliage.connectedGrass.enabled=활성화
betterfoliage.connectedGrass.enabled.tooltip=잔디블록이 흙블록 위에 있을 경우 잔디블록 윗텍스쳐를 옆텍스쳐에 전부 씌웁니다.
betterfoliage.roundLogs=둥근 나무
betterfoliage.roundLogs.tooltip=둥근부분을 블럭과 연결.
betterfoliage.roundLogs.connectSolids=블럭과 연결
betterfoliage.roundLogs.connectSolids.tooltip=둥근부분을 블럭과 연결.
betterfoliage.roundLogs.connectPerpendicular=나무 세로부분과 연결
betterfoliage.roundLogs.connectPerpendicular.tooltip=나무 세로부분을 나무부분끼리 연결
betterfoliage.roundLogs.lenientConnect=부드럽게 둥글게 연결
betterfoliage.roundLogs.lenientConnect.tooltip=2x2사이즈처럼 나무 평형된 부분끼리 서로 연결합니다.
betterfoliage.roundLogs.connectGrass=잔디 연결 텍스쳐
betterfoliage.roundLogs.connectGrass.tooltip=잔디가 근처에 있을 경우 나무 아래 잔디 블록을 렌더링
betterfoliage.roundLogs.radiusSmall=모서리 깎는 반지름
betterfoliage.roundLogs.radiusSmall.tooltip=나무 모서리 깎는 정도
betterfoliage.roundLogs.radiusLarge=모서리 깎는 부분 연결
betterfoliage.roundLogs.radiusLarge.tooltip=나무 모서리 부분을 연결
betterfoliage.roundLogs.dimming=조광
betterfoliage.roundLogs.dimming.tooltip=나무 표면부분을 어둡게하는 양
betterfoliage.roundLogs.zProtection=Z-Protection
betterfoliage.roundLogs.zProtection.tooltip=Amount to scale parallel log connection bits to stop Z-fighting (flickering). Try to set it as high as possible without having glitches.
betterfoliage.roundLogs.defaultY=수직선에대한 기본값
betterfoliage.roundLogs.defaultY.tooltip=true 일경우, 나무 블럭이 수직선에대한 렌더링을 할수가 없습니다. 그렇지 아니하면, 네모난 블럭으로 렌더링 될것임니다.
# Translate by IS_Jump for feedback mail acarus22@gmail.com

View File

@@ -0,0 +1,154 @@
{
"key.betterfoliage.gui": "Открыть настройки",
"betterfoliage.global.enabled": "Включить мод",
"betterfoliage.global.enabled.tooltip": "Если установлено на false, BetterFoliage не будет ничего рендерить",
"betterfoliage.enabled": "Включить",
"betterfoliage.enabled.tooltip": "Включена ли эта функция?",
"betterfoliage.hOffset": "Горизонтальное смещение",
"betterfoliage.hOffset.tooltip": "Дистанция горизонтального смещения этого элемента в блоках",
"betterfoliage.vOffset": "Вертикальное смещение",
"betterfoliage.vOffset.tooltip": "Дистанция вертикального смещения этого элемента в блоках",
"betterfoliage.size": "Размер",
"betterfoliage.size.tooltip": "Размер этого элемента",
"betterfoliage.heightMin": "Минимальная высота",
"betterfoliage.heightMin.tooltip": "Минимальная высота элемента",
"betterfoliage.heightMax": "Максимальная высота",
"betterfoliage.heightMax.tooltip": "Максимальная высота этого элемента",
"betterfoliage.population": "Популяция",
"betterfoliage.population.tooltip": "Шанс (N к 64), что блок будет иметь эту функцию",
"betterfoliage.shaderWind": "Шейдерные эффекты ветра",
"betterfoliage.shaderWind.tooltip": "Применить эффекты ветра с ShaderMod для этого элемента?",
"betterfoliage.distance": "Лимит дистанции",
"betterfoliage.distance.tooltip": "Максимальное расстояние от игрока для рендеринга этой функции",
"betterfoliage.saturationThreshold": "Порог насыщения",
"betterfoliage.saturationThreshold.tooltip": "Насыщенность цвета разделяется на: \"обесцвеченные\" блоки (используя цвет биома) и \"цветные\" блоки (используя их собственный цвет)",
"betterfoliage.leaves": "Улучшенная листва",
"betterfoliage.leaves.tooltip": "Дополнительное округление листьев на блоках листвы.",
"betterfoliage.leaves.dense": "Плотный режим",
"betterfoliage.leaves.dense.tooltip": "Плотный режим имеет более округлые листья.",
"betterfoliage.shortGrass": "Низкая трава и мицелий",
"betterfoliage.shortGrass.tooltip": "Пучки травы / мицелия на поверхности соответствующих блоков.",
"betterfoliage.shortGrass.useGenerated": "Использовать сгенерированные текстуры для травы.",
"betterfoliage.shortGrass.useGenerated.tooltip": "Сгенерированная текстура создается путём разрезания текстуры высокой травы с активного ресурс-пака пополам.",
"betterfoliage.shortGrass.myceliumEnabled": "Включить мицелий",
"betterfoliage.shortGrass.myceliumEnabled.tooltip": "Включить эту особенность для блоков мицелия?",
"betterfoliage.shortGrass.grassEnabled": "Включить траву",
"betterfoliage.shortGrass.grassEnabled.tooltip": "Включить эту особенность для блоков травы?",
"betterfoliage.shortGrass.snowEnabled": "Включить траву под снегом",
"betterfoliage.shortGrass.snowEnabled.tooltip": "Включить эту особенность для заснеженных блоков травы?",
"betterfoliage.hangingGrass": "Висячая трава",
"betterfoliage.hangingGrass.tooltip": "Пучки травы свисают вниз с верхних краев блока травы.",
"betterfoliage.hangingGrass.separation": "Разделение",
"betterfoliage.hangingGrass.separation.tooltip": "Как долго подвесная трава выделяется из блока?",
"betterfoliage.cactus": "Улучшенные кактусы",
"betterfoliage.cactus.tooltip": "Улучшить кактус с дополнительными частицами и плавными тенями.",
"betterfoliage.cactus.sizeVariation": "Вариации размера",
"betterfoliage.cactus.sizeVariation.tooltip": "Количество случайных изменений в размере кактусов.",
"betterfoliage.lilypad": "Улучшенные кувшинки",
"betterfoliage.lilypad.tooltip": "Добавить кувшинкам корни и цветы.",
"betterfoliage.lilypad.flowerChance": "Шанс появления цветов",
"betterfoliage.lilypad.flowerChance.tooltip": "Шанс (N к 64) появления цветка на кувшинке.",
"betterfoliage.reed": "Камыши",
"betterfoliage.reed.tooltip": "Мелководные камыши на блоках земли.",
"betterfoliage.reed.biomes": "Список биомов",
"betterfoliage.reed.biomes.tooltip": "Настройка биомов, в которых камышам разрешено появляться.",
"betterfoliage.reed.biomes.tooltip.element": "Должны ли камыши встречаться в %s биоме?",
"betterfoliage.algae": "Морские водоросли",
"betterfoliage.algae.tooltip": "Глубоководные водоросли на блоках земли.",
"betterfoliage.algae.biomes": "Список биомов",
"betterfoliage.algae.biomes.tooltip": "Настройка биомов, в которых водорослям разрешено появляться.",
"betterfoliage.algae.biomes.tooltip.element": "Должны ли водоросли встречаться в %s биоме?",
"betterfoliage.coral": "Кораллы",
"betterfoliage.coral.tooltip": "Кораллы на песчаных блоках в глубокой воде.",
"betterfoliage.coral.size": "Размер кораллов",
"betterfoliage.coral.size.tooltip": "Размер торчащих частичек кораллов.",
"betterfoliage.coral.crustSize": "Размер коры",
"betterfoliage.coral.crustSize.tooltip": "Размер плоской части кораллов.",
"betterfoliage.coral.chance": "Шанс кораллов",
"betterfoliage.coral.chance.tooltip": "Шанс (N in 64) появления кораллов на определенном блоке.",
"betterfoliage.coral.biomes": "Список биомов",
"betterfoliage.coral.biomes.tooltip": "Настройка биомов, в которых разрешено появляться кораллам.",
"betterfoliage.coral.biomes.tooltip.element": "Должны ли кораллы появляться в %s биоме?",
"betterfoliage.coral.shallowWater": "Мелководные кораллы",
"betterfoliage.coral.shallowWater.tooltip": "Должны ли появляться кораллы в воде, глубиной в 1 блок?",
"betterfoliage.netherrack": "Адская лоза",
"betterfoliage.netherrack.tooltip": "Висячая лоза под адским камнем",
"betterfoliage.fallingLeaves": "Падающие листья",
"betterfoliage.fallingLeaves.tooltip": "Падение FX частиц листвы исходящие из низа блоков листвы",
"betterfoliage.fallingLeaves.speed": "Скорость частиц",
"betterfoliage.fallingLeaves.speed.tooltip": "Общая скорость частиц",
"betterfoliage.fallingLeaves.windStrength": "Сила ветра",
"betterfoliage.fallingLeaves.windStrength.tooltip": "Величина воздействия ветра в хорошую погоду (распространение нормального распределения сосредоточено на 0)",
"betterfoliage.fallingLeaves.stormStrength": "Сила шторма",
"betterfoliage.fallingLeaves.stormStrength.tooltip": "Дополнительная величина воздействия ветра в ненастную погоду (распространение нормального распределения сосредоточено на 0)",
"betterfoliage.fallingLeaves.size": "Размер частиц",
"betterfoliage.fallingLeaves.chance": "Шанс частиц",
"betterfoliage.fallingLeaves.chance.tooltip": "Вероятность каждого случайного рендеринга в такт (1/20 секунды) опадения частицы блока листвы.",
"betterfoliage.fallingLeaves.perturb": "Возмущение",
"betterfoliage.fallingLeaves.perturb.tooltip": "Величина эффекта возмущений. Добавляет штопорообразное движение к частице синхронизированной с его вращением.",
"betterfoliage.fallingLeaves.lifetime": "Максимальное время жизни",
"betterfoliage.fallingLeaves.lifetime.tooltip": "Максимальное время жизни частиц. Минимальное время жизни - 60%% от этого значения.",
"betterfoliage.fallingLeaves.opacityHack": "Непрозрачные частицы",
"betterfoliage.fallingLeaves.opacityHack.tooltip": "Запретить прозрачным блокам затемнять частицы даже тогда, когда частицы впереди. ВНИМАНИЕ: может спровоцировать баги.",
"betterfoliage.risingSoul": "Адские духи",
"betterfoliage.risingSoul.tooltip": "Количество душ-частиц FX, испускаемых из верхней части блоков песка душ.",
"betterfoliage.risingSoul.chance": "Шанс частиц",
"betterfoliage.risingSoul.chance.tooltip": "Частота генерации частиц на песке душ.",
"betterfoliage.risingSoul.speed": "Скорость частиц",
"betterfoliage.risingSoul.speed.tooltip": "Вертикальная скорость движения частиц духов.",
"betterfoliage.risingSoul.perturb": "Возмущение",
"betterfoliage.risingSoul.perturb.tooltip": "Магнитуда эффекта возмущений. Добавляет штопороподобное движение частиц.",
"betterfoliage.risingSoul.headSize": "Размер духа",
"betterfoliage.risingSoul.headSize.tooltip": "Размер частицы духа",
"betterfoliage.risingSoul.trailSize": "Размер следов",
"betterfoliage.risingSoul.trailSize.tooltip": "Начальный размер следа частиц",
"betterfoliage.risingSoul.opacity": "Прозрачность",
"betterfoliage.risingSoul.opacity.tooltip": "Непрозрачность эффекта частиц",
"betterfoliage.risingSoul.sizeDecay": "Размер распада",
"betterfoliage.risingSoul.sizeDecay.tooltip": "Следующий размер частицы соответствует их же размеру в предыдущем такте (1/20 секунды).",
"betterfoliage.risingSoul.opacityDecay": "Непрозрачность распада",
"betterfoliage.risingSoul.opacityDecay.tooltip": "Следующий уровень прозрачности частицы соответствует их же уровню прозрачности в предыдущем такте (1/20 секунды).",
"betterfoliage.risingSoul.lifetime": "Максимальное время жизни",
"betterfoliage.risingSoul.lifetime.tooltip": "Максимальное время жизни эффекта частиц. Минимальное время жизни равно 60%% от этого числа.",
"betterfoliage.risingSoul.trailLength": "Длина следов",
"betterfoliage.risingSoul.trailLength.tooltip": "Количество предыдущих позиций, которые запомнила частица в тактах (1/20 секунды).",
"betterfoliage.risingSoul.trailDensity": "Плотность следов",
"betterfoliage.risingSoul.trailDensity.tooltip": "Рендер каждой предыдущий Nой позиции в следах частиц.",
"betterfoliage.connectedGrass": "Соединенные текстуры травы",
"betterfoliage.connectedGrass.enabled": "Включить",
"betterfoliage.connectedGrass.enabled.tooltip": "Если блок травы находится над блоком земли: прорисовать верхнюю текстуру травы на всех сторонах блока травы.",
"betterfoliage.roundLogs": "Цилиндрические брёвна",
"betterfoliage.roundLogs.tooltip": "Соединить круглые блоки в сплошные, полные блоки?",
"betterfoliage.roundLogs.connectSolids": "Соединение в крупные брёвна",
"betterfoliage.roundLogs.connectSolids.tooltip": "Соединить круглые блоки в сплошные, полные блоки?",
"betterfoliage.roundLogs.connectPerpendicular": "Соединение в перпендикулярные брёвна",
"betterfoliage.roundLogs.connectPerpendicular.tooltip": "Соединить круглые брёвна к перпендикулярным брёвнам относительно их оси?",
"betterfoliage.roundLogs.lenientConnect": "Мягкое округление",
"betterfoliage.roundLogs.lenientConnect.tooltip": "Соединение в параллельные круглые брёвна L-формы, не только 2х2.",
"betterfoliage.roundLogs.connectGrass": "Соединенная трава",
"betterfoliage.roundLogs.connectGrass.tooltip": "Заменяет землю под деревьями на траву, если она есть поблизости.",
"betterfoliage.roundLogs.radiusSmall": "Радиус фаски",
"betterfoliage.roundLogs.radiusSmall.tooltip": "Радиус обрезки углов от бревна.",
"betterfoliage.roundLogs.radiusLarge": "Радиус соединенной фаски",
"betterfoliage.roundLogs.radiusLarge.tooltip": "Радиус среза внешнего угла соединённых брёвен.",
"betterfoliage.roundLogs.dimming": "Затемнение",
"betterfoliage.roundLogs.dimming.tooltip": "Затемнить неясные длинные грани.",
"betterfoliage.roundLogs.zProtection": "Z-Защита",
"betterfoliage.roundLogs.zProtection.tooltip": "Для масштабирования параллельных битов соединения бревен, чтобы остановить Z-бой (мерцание). Попробуйте установить его как можно выше, для устранения мерцания."
}

View File

@@ -1,213 +0,0 @@
key.betterfoliage.gui=Открыть настройки
betterfoliage.global.enabled=Включить мод
betterfoliage.global.enabled.tooltip=Если установлено на false, BetterFoliage не будет ничего рендерить
betterfoliage.enabled=Включить
betterfoliage.enabled.tooltip=Включена ли эта функция?
betterfoliage.hOffset=Горизонтальное смещение
betterfoliage.hOffset.tooltip=Дистанция горизонтального смещения этого элемента в блоках
betterfoliage.vOffset=Вертикальное смещение
betterfoliage.vOffset.tooltip=Дистанция вертикального смещения этого элемента в блоках
betterfoliage.size=Размер
betterfoliage.size.tooltip=Размер этого элемента
betterfoliage.heightMin=Минимальная высота
betterfoliage.heightMin.tooltip=Минимальная высота элемента
betterfoliage.heightMax=Максимальная высота
betterfoliage.heightMax.tooltip=Максимальная высота этого элемента
betterfoliage.population=Популяция
betterfoliage.population.tooltip=Шанс (N к 64), что блок будет иметь эту функцию
betterfoliage.shaderWind=Шейдерные эффекты ветра
betterfoliage.shaderWind.tooltip=Применить эффекты ветра с ShaderMod для этого элемента?
betterfoliage.distance=Лимит дистанции
betterfoliage.distance.tooltip=Максимальное расстояние от игрока для рендеринга этой функции
betterfoliage.blocks=Типы блоков
betterfoliage.blocks.tooltip=Настройки списка классов блоков, которые будут иметь примененные к ним функции
betterfoliage.blocks.dirtWhitelist=Белый список земли
betterfoliage.blocks.dirtBlacklist=Черный список земли
betterfoliage.blocks.dirtWhitelist.arrayEntry=%d записей
betterfoliage.blocks.dirtBlacklist.arrayEntry=%d записей
betterfoliage.blocks.grassWhitelist=Белый список травы
betterfoliage.blocks.grassBlacklist=Черный список травы
betterfoliage.blocks.grassWhitelist.arrayEntry=%d записей
betterfoliage.blocks.grassBlacklist.arrayEntry=%d записей
betterfoliage.blocks.leavesWhitelist=Белый список листвы
betterfoliage.blocks.leavesBlacklist=Черный список листвы
betterfoliage.blocks.leavesWhitelist.arrayEntry=%d записей
betterfoliage.blocks.leavesBlacklist.arrayEntry=%d записей
betterfoliage.blocks.cropsWhitelist=Белый список урожая
betterfoliage.blocks.cropsBlacklist=Черный список урожая
betterfoliage.blocks.cropsWhitelist.arrayEntry=%d записей
betterfoliage.blocks.cropsBlacklist.arrayEntry=%d записей
betterfoliage.blocks.logsWhitelist=Белый список древесины
betterfoliage.blocks.logsBlacklist=Черный список древесины
betterfoliage.blocks.logsWhitelist.arrayEntry=%d записей
betterfoliage.blocks.logsBlacklist.arrayEntry=%d записей
betterfoliage.blocks.sandWhitelist=Белый список песка
betterfoliage.blocks.sandBlacklist=Черный список песка
betterfoliage.blocks.sandWhitelist.arrayEntry=%d записей
betterfoliage.blocks.sandBlacklist.arrayEntry=%d записей
betterfoliage.blocks.lilypadWhitelist=Белый список кувшинок
betterfoliage.blocks.lilypadBlacklist=Черный список кувшинок
betterfoliage.blocks.lilypadWhitelist.arrayEntry=%d записей
betterfoliage.blocks.lilypadBlacklist.arrayEntry=%d записей
betterfoliage.blocks.cactusWhitelist=Белый список кактусов
betterfoliage.blocks.cactusBlacklist=Черный список кактусов
betterfoliage.blocks.cactusWhitelist.arrayEntry=%d записей
betterfoliage.blocks.cactusBlacklist.arrayEntry=%d записей
betterfoliage.blocks.dirtWhitelist.tooltip=Блоки, которые будут восприниматься в качестве земли. Влияет на камыши, водоросли, соединенную траву.
betterfoliage.blocks.dirtBlacklist.tooltip=Блоки, которые не будут восприниматься в качестве земли. Влияет на камыши, водоросли, соединенную траву.
betterfoliage.blocks.grassWhitelist.tooltip=Блоки, которые будут восприниматься в качестве травы. Влияет на короткую и соединенную траву.
betterfoliage.blocks.grassBlacklist.tooltip=Блоки, которые не будут восприниматься в качестве травы. Влияет на короткую и соединенную траву.
betterfoliage.blocks.leavesWhitelist.tooltip=Блоки, которые будут восприниматься в качестве листвы. Влияет на дополнительную листву, падающую листву. Листва будут рендериться с ID листвы в шейдер-программах.
betterfoliage.blocks.leavesBlacklist.tooltip=Блоки, которые никогда не будут восприниматься как листва. Влияет на дополнительную листву, падающую листву. Листва будут рендериться с ID листвы в шейдер-программах.
betterfoliage.blocks.cropsWhitelist.tooltip=Блоки, которые будут восприниматься как культуры. Культуры будут рендериться с ID высокой травы в шейдер-программах.
betterfoliage.blocks.cropsBlacklist.tooltip= Блоки, которые никогда не будут восприниматься как культуры. Культуры будут рендериться с ID высокой травы в шейдер-программах.
betterfoliage.blocks.logsWhitelist.tooltip=Блоки, которые будут восприниматься в качестве деревянных брёвен. Влияет на цилиндрические брёвна.
betterfoliage.blocks.logsBlacklist.tooltip=Блоки, которые никогда не будут восприниматься в качестве деревянных брёвен. Влияет на цилиндрические брёвна.
betterfoliage.blocks.sandWhitelist.tooltip=Блоки, которые будут восприниматься в качестве песка. Влияет на кораллы.
betterfoliage.blocks.sandBlacklist.tooltip=Блоки, которые никогда не будут восприниматься в качестве песка. Влияет на кораллы.
betterfoliage.blocks.lilypadWhitelist.tooltip=Блоки, которые будут восприниматься в качестве кувшинок. Влияет на улучшенные кувшинки.
betterfoliage.blocks.lilypadBlacklist.tooltip=Блоки, которые никогда не будут восприниматься в качестве кувшинок. Влияет на улучшенные кувшинки.
betterfoliage.blocks.cactusWhitelist.tooltip=Блоки, которые будут восприниматься в качестве кактусов. Влияет на улучшенные кактусы.
betterfoliage.blocks.cactusBlacklist.tooltip=Блоки, которые никогда не будут восприниматься в качестве кувшинок. Влияет на улучшенные кактусы.
betterfoliage.leaves=Улучшенная листва
betterfoliage.leaves.tooltip=Дополнительное округление листьев на блоках листвы.
betterfoliage.leaves.dense=Плотный режим
betterfoliage.leaves.dense.tooltip=Плотный режим имеет более округлые листья.
betterfoliage.shortGrass=Низкая трава и мицелий
betterfoliage.shortGrass.tooltip=Пучки травы / мицелия на поверхности соответствующих блоков.
betterfoliage.shortGrass.useGenerated=Использовать сгенерированные текстуры для травы.
betterfoliage.shortGrass.useGenerated.tooltip=Сгенерированная текстура создается путём разрезания текстуры высокой травы с активного ресурс-пака пополам.
betterfoliage.shortGrass.myceliumEnabled=Включить мицелий
betterfoliage.shortGrass.myceliumEnabled.tooltip=Включить эту особенность для блоков мицелия?
betterfoliage.shortGrass.grassEnabled=Включить траву
betterfoliage.shortGrass.grassEnabled.tooltip=Включить эту особенность для блоков травы?
betterfoliage.shortGrass.snowEnabled=Включить траву под снегом
betterfoliage.shortGrass.snowEnabled.tooltip=Включить эту особенность для заснеженных блоков травы?
betterfoliage.shortGrass.saturationThreshold=Порог насыщения
betterfoliage.shortGrass.saturationThreshold.tooltip=Насыщенность цвета разделяется на: "обесцвеченные" блоки (используя цвет биома) и "цветные" блоки (используя их собственный цвет)
betterfoliage.hangingGrass=Висячая трава
betterfoliage.hangingGrass.tooltip=Пучки травы свисают вниз с верхних краев блока травы.
betterfoliage.hangingGrass.separation=Разделение
betterfoliage.hangingGrass.separation.tooltip=Как долго подвесная трава выделяется из блока?
betterfoliage.cactus=Улучшенные кактусы
betterfoliage.cactus.tooltip=Улучшить кактус с дополнительными частицами и плавными тенями.
betterfoliage.cactus.sizeVariation=Вариации размера
betterfoliage.cactus.sizeVariation.tooltip=Количество случайных изменений в размере кактусов.
betterfoliage.lilypad=Улучшенные кувшинки
betterfoliage.lilypad.tooltip=Добавить кувшинкам корни и цветы.
betterfoliage.lilypad.flowerChance=Шанс появления цветов
betterfoliage.lilypad.flowerChance.tooltip=Шанс (N к 64) появления цветка на кувшинке.
betterfoliage.reed=Камыши
betterfoliage.reed.tooltip=Мелководные камыши на блоках земли.
betterfoliage.reed.biomes=Список биомов
betterfoliage.reed.biomes.tooltip=Настройка биомов, в которых камышам разрешено появляться.
betterfoliage.reed.biomes.tooltip.element=Должны ли камыши встречаться в %s биоме?
betterfoliage.algae=Морские водоросли
betterfoliage.algae.tooltip=Глубоководные водоросли на блоках земли.
betterfoliage.algae.biomes=Список биомов
betterfoliage.algae.biomes.tooltip=Настройка биомов, в которых водорослям разрешено появляться.
betterfoliage.algae.biomes.tooltip.element=Должны ли водоросли встречаться в %s биоме?
betterfoliage.coral=Кораллы
betterfoliage.coral.tooltip=Кораллы на песчаных блоках в глубокой воде.
betterfoliage.coral.size=Размер кораллов
betterfoliage.coral.size.tooltip=Размер торчащих частичек кораллов.
betterfoliage.coral.crustSize=Размер коры
betterfoliage.coral.crustSize.tooltip=Размер плоской части кораллов.
betterfoliage.coral.chance=Шанс кораллов
betterfoliage.coral.chance.tooltip=Шанс (N in 64) появления кораллов на определенном блоке.
betterfoliage.coral.biomes=Список биомов
betterfoliage.coral.biomes.tooltip=Настройка биомов, в которых разрешено появляться кораллам.
betterfoliage.coral.biomes.tooltip.element=Должны ли кораллы появляться в %s биоме?
betterfoliage.coral.shallowWater=Мелководные кораллы
betterfoliage.coral.shallowWater.tooltip=Должны ли появляться кораллы в воде, глубиной в 1 блок?
betterfoliage.netherrack=Адская лоза
betterfoliage.netherrack.tooltip=Висячая лоза под адским камнем
betterfoliage.fallingLeaves=Падающие листья
betterfoliage.fallingLeaves.tooltip=Падение FX частиц листвы исходящие из низа блоков листвы
betterfoliage.fallingLeaves.speed=Скорость частиц
betterfoliage.fallingLeaves.speed.tooltip=Общая скорость частиц
betterfoliage.fallingLeaves.windStrength=Сила ветра
betterfoliage.fallingLeaves.windStrength.tooltip=Величина воздействия ветра в хорошую погоду (распространение нормального распределения сосредоточено на 0)
betterfoliage.fallingLeaves.stormStrength=Сила шторма
betterfoliage.fallingLeaves.stormStrength.tooltip=Дополнительная величина воздействия ветра в ненастную погоду (распространение нормального распределения сосредоточено на 0)
betterfoliage.fallingLeaves.size=Размер частиц
betterfoliage.fallingLeaves.chance=Шанс частиц
betterfoliage.fallingLeaves.chance.tooltip=Вероятность каждого случайного рендеринга в такт (1/20 секунды) опадения частицы блока листвы.
betterfoliage.fallingLeaves.perturb=Возмущение
betterfoliage.fallingLeaves.perturb.tooltip=Величина эффекта возмущений. Добавляет штопорообразное движение к частице синхронизированной с его вращением.
betterfoliage.fallingLeaves.lifetime=Максимальное время жизни
betterfoliage.fallingLeaves.lifetime.tooltip=Максимальное время жизни частиц. Минимальное время жизни - 60%% от этого значения.
betterfoliage.fallingLeaves.opacityHack=Непрозрачные частицы
betterfoliage.fallingLeaves.opacityHack.tooltip=Запретить прозрачным блокам затемнять частицы даже тогда, когда частицы впереди. ВНИМАНИЕ: может спровоцировать баги.
betterfoliage.risingSoul=Адские духи
betterfoliage.risingSoul.tooltip=Количество душ-частиц FX, испускаемых из верхней части блоков песка душ.
betterfoliage.risingSoul.chance=Шанс частиц
betterfoliage.risingSoul.chance.tooltip=Частота генерации частиц на песке душ.
betterfoliage.risingSoul.speed=Скорость частиц
betterfoliage.risingSoul.speed.tooltip=Вертикальная скорость движения частиц духов.
betterfoliage.risingSoul.perturb=Возмущение
betterfoliage.risingSoul.perturb.tooltip=Магнитуда эффекта возмущений. Добавляет штопороподобное движение частиц.
betterfoliage.risingSoul.headSize=Размер духа
betterfoliage.risingSoul.headSize.tooltip=Размер частицы духа
betterfoliage.risingSoul.trailSize=Размер следов
betterfoliage.risingSoul.trailSize.tooltip=Начальный размер следа частиц
betterfoliage.risingSoul.opacity=Прозрачность
betterfoliage.risingSoul.opacity.tooltip=Непрозрачность эффекта частиц
betterfoliage.risingSoul.sizeDecay=Размер распада
betterfoliage.risingSoul.sizeDecay.tooltip=Следующий размер частицы соответствует их же размеру в предыдущем такте (1/20 секунды).
betterfoliage.risingSoul.opacityDecay=Непрозрачность распада
betterfoliage.risingSoul.opacityDecay.tooltip=Следующий уровень прозрачности частицы соответствует их же уровню прозрачности в предыдущем такте (1/20 секунды).
betterfoliage.risingSoul.lifetime=Максимальное время жизни
betterfoliage.risingSoul.lifetime.tooltip=Максимальное время жизни эффекта частиц. Минимальное время жизни равно 60%% от этого числа.
betterfoliage.risingSoul.trailLength=Длина следов
betterfoliage.risingSoul.trailLength.tooltip=Количество предыдущих позиций, которые запомнила частица в тактах (1/20 секунды).
betterfoliage.risingSoul.trailDensity=Плотность следов
betterfoliage.risingSoul.trailDensity.tooltip=Рендер каждой предыдущий Nой позиции в следах частиц.
betterfoliage.connectedGrass=Соединенные текстуры травы
betterfoliage.connectedGrass.enabled=Включить
betterfoliage.connectedGrass.enabled.tooltip=Если блок травы находится над блоком земли: прорисовать верхнюю текстуру травы на всех сторонах блока травы.
betterfoliage.roundLogs=Цилиндрические брёвна
betterfoliage.roundLogs.tooltip=Соединить круглые блоки в сплошные, полные блоки?
betterfoliage.roundLogs.connectSolids=Соединение в крупные брёвна
betterfoliage.roundLogs.connectSolids.tooltip=Соединить круглые блоки в сплошные, полные блоки?
betterfoliage.roundLogs.connectPerpendicular=Соединение в перпендикулярные брёвна
betterfoliage.roundLogs.connectPerpendicular.tooltip=Соединить круглые брёвна к перпендикулярным брёвнам относительно их оси?
betterfoliage.roundLogs.lenientConnect=Мягкое округление
betterfoliage.roundLogs.lenientConnect.tooltip=Соединение в параллельные круглые брёвна L-формы, не только 2х2.
betterfoliage.roundLogs.connectGrass=Соединенная трава
betterfoliage.roundLogs.connectGrass.tooltip=Заменяет землю под деревьями на траву, если она есть поблизости.
betterfoliage.roundLogs.radiusSmall=Радиус фаски
betterfoliage.roundLogs.radiusSmall.tooltip=Радиус обрезки углов от бревна.
betterfoliage.roundLogs.radiusLarge=Радиус соединенной фаски
betterfoliage.roundLogs.radiusLarge.tooltip=Радиус среза внешнего угла соединённых брёвен.
betterfoliage.roundLogs.dimming=Затемнение
betterfoliage.roundLogs.dimming.tooltip=Затемнить неясные длинные грани.
betterfoliage.roundLogs.zProtection=Z-Защита
betterfoliage.roundLogs.zProtection.tooltip=Для масштабирования параллельных битов соединения бревен, чтобы остановить Z-бой (мерцание). Попробуйте установить его как можно выше, для устранения мерцания.

View File

@@ -0,0 +1,177 @@
{
"key.betterfoliage.gui": "打开BF设置",
"betterfoliage.global.enabled": "模组使用",
"betterfoliage.global.enabled.tooltip": "如果关闭,BetterFoliage不会呈现任何东西",
"betterfoliage.nVidia": "nVidia GPU",
"betterfoliage.nVidia.tooltip": "明确你是否有一个nVidia GPU",
"betterfoliage.enabled": "启用",
"betterfoliage.enabled.tooltip": "要启用这个功能吗?",
"betterfoliage.hOffset": "水平偏移",
"betterfoliage.hOffset.tooltip": "在方块中这个元素是横向距离移动的",
"betterfoliage.vOffset": "垂直偏移",
"betterfoliage.vOffset.tooltip": "在方块中这个元素是横向距离移动的",
"betterfoliage.size": "尺寸",
"betterfoliage.size.tooltip": "该元素的尺寸",
"betterfoliage.heightMin": "最小高度",
"betterfoliage.heightMin.tooltip": "最小元素的高度",
"betterfoliage.heightMax": "最大高度",
"betterfoliage.heightMax.tooltip": "最大元素的高度",
"betterfoliage.population": "物体密度",
"betterfoliage.population.tooltip": "生成的概率为64分之1",
"betterfoliage.shaderWind": "光影水反中的 风的影响",
"betterfoliage.shaderWind.tooltip": "能适用于光影水反中的\"风的影响\" 这一元素?",
"betterfoliage.distance": "距离限制",
"betterfoliage.distance.tooltip": "离游戏者的最大距离,在这个范围内呈现这个特征",
"betterfoliage.saturationThreshold": "饱和度阈值",
"betterfoliage.saturationThreshold.tooltip": "色彩饱和度将介于\"无色\"方块(使用生物群系的颜色)和\"有色\"方块(使用材质特定颜色)之间",
"betterfoliage.rendererror": "§a[BF更好的叶子]§f 错误:渲染方块 %s 此位置 %s",
"betterfoliage.shaders": "着色器配置",
"betterfoliage.shaders.tooltip": "Configure integration with shaders",
"betterfoliage.shaders.leavesId": "叶子 ID",
"betterfoliage.shaders.leavesId.tooltip": "方块 ID 将作为所有叶片都使用的相同ID提供给光影水反 如果你的光影水反文件使用了一个 §6block.properties§e 文件, 你可能需要修改这个以匹配光影水反的映射",
"betterfoliage.shaders.grassId": "草 ID",
"betterfoliage.shaders.grassId.tooltip": "方块 ID 将作为所有的草和作物都使用的相同ID提供给光影水反 如果你的光影水反文件使用了一个 §6block.properties§e 文件, 你可能需要修改这个以匹配光影水反的映射",
"betterfoliage.leaves": "额外的叶片",
"betterfoliage.leaves.tooltip": "叶方块上额外的密叶",
"betterfoliage.leaves.dense": "浓密模式",
"betterfoliage.leaves.dense.tooltip": "浓密的叶子会有更多额外的叶片",
"betterfoliage.leaves.snowEnabled": "启用雪覆盖叶片",
"betterfoliage.leaves.snowEnabled.tooltip": "是否启用被雪覆盖的额外叶片?",
"betterfoliage.leaves.hideInternal": "隐藏内部的叶片",
"betterfoliage.leaves.hideInternal.tooltip": "如果该叶方块完全被其他叶方块或者固体方块包围,将不渲染额外的叶片",
"betterfoliage.shortGrass": "短草和菌丝",
"betterfoliage.shortGrass.tooltip": "一簇小草或者一丛菌丝长在合适的方块的顶部",
"betterfoliage.shortGrass.useGenerated": "为草使用mod创建的材质",
"betterfoliage.shortGrass.useGenerated.tooltip": "mod创建的材质指的是将已选用的资源包中高草的材质切一半来用",
"betterfoliage.shortGrass.myceliumEnabled": "使用在菌丝上",
"betterfoliage.shortGrass.myceliumEnabled.tooltip": "将这个特性利用到菌丝中吗?",
"betterfoliage.shortGrass.grassEnabled": "使用在草方块上",
"betterfoliage.shortGrass.grassEnabled.tooltip": "将这个特性利用到草方块中吗?",
"betterfoliage.shortGrass.snowEnabled": "使用在雪地里",
"betterfoliage.shortGrass.snowEnabled.tooltip": "使用到被雪覆盖的草方块中去?",
"betterfoliage.shortGrass.saturationThreshold": "饱和度阈值",
"betterfoliage.shortGrass.saturationThreshold.tooltip": "色彩饱和度将介于\"无色\"方块(使用生物群系的颜色)和\"有色\"方块(使用材质特定颜色)之间",
"betterfoliage.connectedGrass": "草的纹理的连接",
"betterfoliage.connectedGrass.tooltip": "使得草方块侧面拥有像顶面的材质",
"betterfoliage.connectedGrass.enabled": "启用",
"betterfoliage.connectedGrass.enabled.tooltip": "如果泥土上有草方块:所有的草方块的侧面换成草方块顶部的材质,",
"betterfoliage.connectedGrass.snowEnabled": "使用在被雪覆盖的草方块上",
"betterfoliage.connectedGrass.snowEnabled.tooltip": "是否应用于被雪覆盖的草方块上?",
"betterfoliage.hangingGrass": "悬挂的草",
"betterfoliage.hangingGrass.tooltip": "草会在边缘的草方块上垂下来",
"betterfoliage.hangingGrass.separation": "垂下来的距离",
"betterfoliage.hangingGrass.separation.tooltip": "有多长的挂草会从草方块上垂下来",
"betterfoliage.cactus": "更好的仙人掌",
"betterfoliage.cactus.tooltip": "用额外的仙人掌刺和圆润的形状来提高仙人掌的真实性",
"betterfoliage.cactus.sizeVariation": "尺寸变化",
"betterfoliage.cactus.sizeVariation.tooltip": "仙人掌的尺寸随机变化的范围",
"betterfoliage.lilypad": "更好的莲叶与荷花",
"betterfoliage.lilypad.tooltip": "用莲叶的根和随机的莲花以提高莲叶的真实性",
"betterfoliage.lilypad.flowerChance": "花的生成概率",
"betterfoliage.lilypad.flowerChance.tooltip": "(64个莲叶中 N 个)莲叶具有花了",
"betterfoliage.reed": "芦苇",
"betterfoliage.reed.tooltip": "芦苇会生成在浅水中的泥土块上",
"betterfoliage.reed.biomes": "生物群系的列表",
"betterfoliage.reed.biomes.tooltip": "配置允许出现芦苇的生物群系",
"betterfoliage.reed.biomes.tooltip.element": "芦苇应该出现在%s生物群落?",
"betterfoliage.algae": "藻类",
"betterfoliage.algae.tooltip": "藻类会生成在深水中的泥土块上",
"betterfoliage.algae.biomes": "生物群系的列表",
"betterfoliage.algae.biomes.tooltip": "配置允许出现藻类的生物群系",
"betterfoliage.algae.biomes.tooltip.element": "藻类应该出现在%s生物群落?",
"betterfoliage.coral": "珊瑚",
"betterfoliage.coral.tooltip": "珊瑚会生成在深水中的沙块上",
"betterfoliage.coral.size": "珊瑚的大小",
"betterfoliage.coral.size.tooltip": "珊瑚伸出的部分的大小",
"betterfoliage.coral.crustSize": "外壳尺寸",
"betterfoliage.coral.crustSize.tooltip": "珊瑚的平的部分的尺寸",
"betterfoliage.coral.chance": "珊瑚的生成概率",
"betterfoliage.coral.chance.tooltip": "64个特殊表面的方块中有N个有机会出现珊瑚",
"betterfoliage.coral.biomes": "生物群落的列表",
"betterfoliage.coral.biomes.tooltip": "配置允许出现珊瑚的生物群系",
"betterfoliage.coral.biomes.tooltip.element": "珊瑚应该出现在%s生物群落?",
"betterfoliage.coral.shallowWater": "浅海珊瑚",
"betterfoliage.coral.shallowWater.tooltip": "珊瑚应该生成在深水1格?",
"betterfoliage.netherrack": "下界岩下面的藤蔓",
"betterfoliage.netherrack.tooltip": "下界岩下面将挂上藤蔓",
"betterfoliage.fallingLeaves": "落叶",
"betterfoliage.fallingLeaves.tooltip": "落叶粒子效果 FX 会从叶方块底部下落",
"betterfoliage.fallingLeaves.speed": "粒子速度",
"betterfoliage.fallingLeaves.speed.tooltip": "总体的粒子速度",
"betterfoliage.fallingLeaves.windStrength": "柔风的强度",
"betterfoliage.fallingLeaves.windStrength.tooltip": "在晴天中体现的风的效果(默认以0为中心散播)",
"betterfoliage.fallingLeaves.stormStrength": "风暴的强度",
"betterfoliage.fallingLeaves.stormStrength.tooltip": "附加在阴天里体现的风的效果(默认以0为中心散播)",
"betterfoliage.fallingLeaves.size": "粒子大小",
"betterfoliage.fallingLeaves.chance": "粒子生成机率",
"betterfoliage.fallingLeaves.chance.tooltip": "随机渲染刻激发叶方块生成粒子的概率",
"betterfoliage.fallingLeaves.perturb": "扰动",
"betterfoliage.fallingLeaves.perturb.tooltip": "扰动的大小.增加了一个与之旋转同步的螺旋状动量",
"betterfoliage.fallingLeaves.lifetime": "最长存在时间",
"betterfoliage.fallingLeaves.lifetime.tooltip": "粒子的最长存在时间,仅在几秒内,最短时间为这个值60%%",
"betterfoliage.fallingLeaves.opacityHack": "粒子不透明",
"betterfoliage.fallingLeaves.opacityHack.tooltip": "阻止透明方块隐藏粒子,即使粒子已经在前面了.警告:可能会导致错误",
"betterfoliage.risingSoul": "灵魂沙上飘起的魂灵",
"betterfoliage.risingSoul.tooltip": "灵魂沙的顶部飘出灵魂粒子 FX",
"betterfoliage.risingSoul.chance": "粒子的生成机率",
"betterfoliage.risingSoul.chance.tooltip": "随机渲染刻激发灵魂沙生成一个粒子的概率",
"betterfoliage.risingSoul.speed": "粒子速度",
"betterfoliage.risingSoul.speed.tooltip": "灵颗粒的垂直速度",
"betterfoliage.risingSoul.perturb": "扰动",
"betterfoliage.risingSoul.perturb.tooltip": "动的大小.增加了一个与之旋转同步的螺旋状动量",
"betterfoliage.risingSoul.headSize": "魂灵的大小",
"betterfoliage.risingSoul.headSize.tooltip": "灵魂粒子的大小",
"betterfoliage.risingSoul.trailSize": "魂灵轨迹的大小",
"betterfoliage.risingSoul.trailSize.tooltip": "灵魂粒子的轨迹的初始大小",
"betterfoliage.risingSoul.opacity": "不透明度",
"betterfoliage.risingSoul.opacity.tooltip": "粒子效果的不透明度",
"betterfoliage.risingSoul.sizeDecay": "大小的衰减",
"betterfoliage.risingSoul.sizeDecay.tooltip": "灵魂轨迹粒子大小与前一刻他的大小相关",
"betterfoliage.risingSoul.opacityDecay": "不透明度的衰减",
"betterfoliage.risingSoul.opacityDecay.tooltip": "灵魂轨迹粒子不透明度与前一刻他的不透明度相关",
"betterfoliage.risingSoul.lifetime": "最长存在时间",
"betterfoliage.risingSoul.lifetime.tooltip": "粒子的最长存在时间,仅在几秒内,最短时间为这个值60%%",
"betterfoliage.risingSoul.trailLength": "魂灵轨迹长度",
"betterfoliage.risingSoul.trailLength.tooltip": "在游戏刻内粒子记录的轨迹位置的数量",
"betterfoliage.risingSoul.trailDensity": "魂灵轨迹密集程度",
"betterfoliage.risingSoul.trailDensity.tooltip": "整条轨迹中渲染每第N个轨迹位置",
"betterfoliage.roundLogs": "圆木",
"betterfoliage.roundLogs.tooltip": "使木方块拥有八角形的横截面",
"betterfoliage.roundLogs.plantsOnly": "仅仅应用于植物性材料",
"betterfoliage.roundLogs.plantsOnly.tooltip": "是否仅仅应用于木质材料和干草堆,而不是所有圆柱方块?",
"betterfoliage.roundLogs.connectSolids": "连接到固体方块上",
"betterfoliage.roundLogs.connectSolids.tooltip": "要使圆木连接到完整的固体方块上吗?",
"betterfoliage.roundLogs.connectPerpendicular": "连接到垂直的圆木上",
"betterfoliage.roundLogs.connectPerpendicular.tooltip": "要使圆木根据它的角度连接到垂直圆木上吗?",
"betterfoliage.roundLogs.lenientConnect": "巨大的圆木",
"betterfoliage.roundLogs.lenientConnect.tooltip": "在L形中也连接成巨大的圆木, 不只是2x2形",
"betterfoliage.roundLogs.connectGrass": "连接到草方块上",
"betterfoliage.roundLogs.connectGrass.tooltip": "如果树木下的泥土附近有草方块,那么它也会被渲染成草方块",
"betterfoliage.roundLogs.radiusSmall": "圆木斜面的半径",
"betterfoliage.roundLogs.radiusSmall.tooltip": "从圆木的外角砍掉多大的部分",
"betterfoliage.roundLogs.radiusLarge": "连接圆木的斜面半径",
"betterfoliage.roundLogs.radiusLarge.tooltip": "从连接着的巨大圆木的外角砍掉多大的部分",
"betterfoliage.roundLogs.dimming": "调光变暗",
"betterfoliage.roundLogs.dimming.tooltip": "将圆木阴暗面变黑的程度",
"betterfoliage.roundLogs.zProtection": "Z-Protection [Z保护]",
"betterfoliage.roundLogs.zProtection.tooltip": "用多少倍放大平行圆木连接处的面 去停止 Z-fighting (闪烁)[斑驳,两个多边形共面所出现的效果].在没有发生故障的情况下,试着将这个值设置得尽可能的高[这是Z-fighting出现的原因:多个多面体的面重叠在一起,会一直闪烁]",
"betterfoliage.roundLogs.defaultY": "默认垂直",
"betterfoliage.roundLogs.defaultY.tooltip": "如果开启, 方向不确定的圆木将会渲染成垂直的.否则, 它们仅仅渲染为cube型方块."
}

View File

@@ -1,2 +0,0 @@
// Vanilla
net.minecraft.block.LeavesBlock

View File

@@ -1,4 +0,0 @@
minecraft:block/leaves,all
minecraft:block/cube_all,all
biomesoplenty:block/leaves_overlay,leaves

View File

@@ -1,8 +0,0 @@
// Vanilla
net.minecraft.block.BlockLilyPad
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPLilypad
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Vanilla.BlockCustomLilyPad

View File

@@ -1,2 +0,0 @@
// Vanilla
net.minecraft.block.LogBlock

View File

@@ -1,4 +0,0 @@
// Vanilla
block/column_side,side,end
block/cube_column,side,end
block/cube_all,all,all

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