diff --git a/build.gradle.kts b/build.gradle.kts index 9747d68..1c84d68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,23 +1,48 @@ +import net.fabricmc.loom.task.RemapJarTask +import org.ajoberstar.grgit.Grgit + plugins { - kotlin("jvm").version("1.3.61") - id("net.minecraftforge.gradle").version("3.0.157") - id("org.spongepowered.mixin").version("0.7-SNAPSHOT") + kotlin("jvm").version("1.3.60") + id("fabric-loom").version("0.2.6-SNAPSHOT") + id("org.ajoberstar.grgit").version("3.1.1") } -apply(plugin = "org.spongepowered.mixin") +apply(plugin = "org.ajoberstar.grgit") repositories { - maven("http://files.minecraftforge.net/maven") - maven("https://repo.spongepowered.org/maven") + maven("http://maven.fabricmc.net/") maven("https://minecraft.curseforge.com/api/maven") + maven("http://maven.sargunv.s3-website-us-west-2.amazonaws.com/") + maven("http://maven.modmuss50.me/") + maven("https://grondag-repo.appspot.com").credentials { username = "guest"; password = "" } + maven("https://jitpack.io") } +val gitHash = (project.ext.get("grgit") as Grgit).head().abbreviatedId + +val semVer = "${project.version}+$gitHash" + +val jarName = "BetterFoliage-$semVer-Fabric-${properties["mcVersion"]}" +print("VERSION: $jarName") dependencies { - "minecraft"("net.minecraftforge:forge:${properties["mcVersion"]}-${properties["forgeVersion"]}") + "minecraft"("com.mojang:minecraft:${properties["mcVersion"]}") + "mappings"("net.fabricmc:yarn:${properties["yarnMappings"]}:v2") - "implementation"("kottle:Kottle:${properties["kottleVersion"]}") + "modImplementation"("net.fabricmc:fabric-loader:${properties["loaderVersion"]}") + "modImplementation"("net.fabricmc.fabric-api:fabric-api:${properties["fabricVersion"]}") + "modImplementation"("net.fabricmc:fabric-language-kotlin:${properties["fabricKotlinVersion"]}") - "implementation"("org.spongepowered:mixin:0.8-SNAPSHOT") - annotationProcessor("org.spongepowered:mixin:0.8-SNAPSHOT") + listOf("modImplementation", "include").map { configuration -> + configuration("me.shedaniel.cloth:config-2:${project.properties["clothConfigVersion"]}") + configuration("io.github.prospector:modmenu:${project.properties["modMenuVersion"]}") + configuration("me.zeroeightsix:fiber:0.8.0-2") + } + + // Canvas Renderer +// "modImplementation"("grondag:canvas:0.7.+") + + // Optifabric + "modImplementation"("com.github.modmuss50:OptiFabric:df03dc2c22") + "implementation"("org.zeroturnaround:zt-zip:1.13") } sourceSets { @@ -29,37 +54,24 @@ kotlin.sourceSets { get("main").kotlin.srcDir("src/forge/kotlin") } - -minecraft { - mappings(properties["mappingsChannel"] as String, properties["mappingsVersion"] as String) - accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg")) - - runs.create("client") { - workingDirectory(file("run")) - properties["forge.logging.markers"] = "CORE" - properties["forge.logging.console.level"] = "debug" - mods.create("betterfoliage") { - source(sourceSets["main"]) - } - } -} - java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } kotlin { + target.platformType target.compilations.configureEach { + kotlinOptions.jvmTarget = "1.8" kotlinOptions.freeCompilerArgs += listOf("-Xno-param-assertions", "-Xno-call-assertions") } } -tasks.getByName("jar") { - archiveName = "BetterFoliage-${project.version}-Forge-${properties["mcVersion"]}.jar" - manifest { - from(file("src/main/resources/META-INF/MANIFEST.MF")) - attributes["Implementation-Version"] = project.version - } +tasks.getByName("processResources") { + filesMatching("fabric.mod.json") { expand(mutableMapOf("version" to semVer)) } +} + +tasks.getByName("remapJar") { + archiveName = "$jarName.jar" exclude("net") } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index b9ccad1..bef8ee7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,13 +2,21 @@ org.gradle.jvmargs=-Xmx2G org.gradle.daemon=false group = com.github.octarine-noise +name = betterfoliage jarName = BetterFoliage-Forge -version = 2.3.1 +version = 2.5.0 mcVersion = 1.14.4 -forgeVersion = 28.1.109 -mappingsChannel = snapshot -mappingsVersion = 20190719-1.14.3 +yarnMappings=1.14.4+build.15 +loaderVersion=0.7.3+build.176 +fabricVersion=0.4.2+build.246-1.14 +loomVersion=0.2.6-SNAPSHOT -kottleVersion = 1.4.0 +kotlinVersion=1.3.60 +fabricKotlinVersion=1.3.60+build.1 + +clothConfigVersion=1.8 +autoConfigVersion=1.2.0+mc1.14.4 + +modMenuVersion=1.7.6+build.115 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 0d4a951..cc4fdc2 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradlew b/gradlew index cccdd3d..2fe81a7 100644 --- a/gradlew +++ b/gradlew @@ -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" @@ -109,8 +125,8 @@ 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"` @@ -138,19 +154,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 +175,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" "$@" diff --git a/gradlew.bat b/gradlew.bat index f955316..9618d8d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -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 @@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @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 diff --git a/settings.gradle.kts b/settings.gradle.kts index 080fa38..2861757 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,13 +1,8 @@ pluginManagement { repositories { - maven("http://files.minecraftforge.net/maven") - maven("https://repo.spongepowered.org/maven") + jcenter() + maven("https://maven.fabricmc.net/") gradlePluginPortal() } - resolutionStrategy { - eachPlugin { - if (requested.id.let { it.namespace == "net.minecraftforge" && it.name == "gradle"} ) useModule("net.minecraftforge.gradle:ForgeGradle:${requested.version}") - if (requested.id.let { it.namespace == "org.spongepowered" && it.name == "mixin"} ) useModule("org.spongepowered:mixingradle:${requested.version}") - } - } } +rootProject.name = "betterfoliage" \ No newline at end of file diff --git a/src/main/java/mods/betterfoliage/MixinConnector.java b/src/main/java/mods/betterfoliage/MixinConnector.java deleted file mode 100644 index ee57666..0000000 --- a/src/main/java/mods/betterfoliage/MixinConnector.java +++ /dev/null @@ -1,19 +0,0 @@ -package mods.betterfoliage; - -import org.spongepowered.asm.mixin.Mixins; -import org.spongepowered.asm.mixin.connect.IMixinConnector; - -public class MixinConnector implements IMixinConnector { - @Override - public void connect() { - Mixins.addConfiguration("betterfoliage.common.mixins.json"); - - try { - Class.forName("optifine.OptiFineTransformationService"); - Mixins.addConfiguration("betterfoliage.optifine.mixins.json"); - } catch (ClassNotFoundException e) { - Mixins.addConfiguration("betterfoliage.vanilla.mixins.json"); - } - - } -} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinBlock.java b/src/main/java/mods/betterfoliage/mixin/MixinBlock.java index 58e8b0f..6bbc749 100644 --- a/src/main/java/mods/betterfoliage/mixin/MixinBlock.java +++ b/src/main/java/mods/betterfoliage/mixin/MixinBlock.java @@ -1,29 +1,44 @@ package mods.betterfoliage.mixin; -import mods.betterfoliage.client.Hooks; +import mods.betterfoliage.Hooks; import net.minecraft.block.Block; import net.minecraft.block.BlockState; -import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.shapes.VoxelShape; -import net.minecraft.world.IBlockReader; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.BlockView; +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 overriding the {@link VoxelShape} used for the neighbor block in {@link Block}.shouldSideBeRendered(). - * - * This way log blocks can be made to not block rendering, without altering any {@link Block} or - * {@link BlockState} properties with potential gameplay ramifications. - */ @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 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 shouldSideBeRendered = "shouldDrawSide(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Z"; + private static final String getVoxelShape = "Lnet/minecraft/block/BlockState;getCullShape(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Lnet/minecraft/util/shape/VoxelShape;"; + private static final String randomDisplayTick = "randomDisplayTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V"; + /** + * Override the {@link VoxelShape} used for the neighbor block in {@link Block}.shouldSideBeRendered(). + * + * This way log blocks can be made to not block rendering, without altering any {@link Block} or + * {@link BlockState} properties with potential gameplay ramifications. + */ @Redirect(method = shouldSideBeRendered, at = @At(value = "INVOKE", target = getVoxelShape, ordinal = 1)) - private static VoxelShape getVoxelShapeOverride(BlockState state, IBlockReader reader, BlockPos pos, Direction dir) { + private static VoxelShape getVoxelShapeOverride(BlockState state, BlockView reader, BlockPos pos, Direction dir) { return Hooks.getVoxelShapeOverride(state, reader, pos, dir); } + + /** + * Inject a callback to call for every random display tick. Used for adding custom particle effects to blocks. + */ + @Inject(method = randomDisplayTick, at = @At("HEAD")) + void onRandomDisplayTick(BlockState state, World world, BlockPos pos, Random rnd, CallbackInfo ci) { +// Hooks.onRandomDisplayTick(state.getBlock(), state, world, pos, rnd); + } } diff --git a/src/main/java/mods/betterfoliage/mixin/MixinBlockModels.java b/src/main/java/mods/betterfoliage/mixin/MixinBlockModels.java new file mode 100644 index 0000000..5f601cc --- /dev/null +++ b/src/main/java/mods/betterfoliage/mixin/MixinBlockModels.java @@ -0,0 +1,17 @@ +package mods.betterfoliage.mixin; + +import mods.betterfoliage.BlockModelsReloadCallback; +import net.minecraft.client.render.block.BlockModels; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(BlockModels.class) +public class MixinBlockModels { + + @Inject(method = "reload()V", at = @At("RETURN")) + void onReload(CallbackInfo ci) { + BlockModelsReloadCallback.EVENT.invoker().reloadBlockModels((BlockModels) (Object) this); + } +} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinBlockState.java b/src/main/java/mods/betterfoliage/mixin/MixinBlockState.java index 55b3e4b..8490581 100644 --- a/src/main/java/mods/betterfoliage/mixin/MixinBlockState.java +++ b/src/main/java/mods/betterfoliage/mixin/MixinBlockState.java @@ -1,10 +1,10 @@ package mods.betterfoliage.mixin; -import mods.betterfoliage.client.Hooks; +import mods.betterfoliage.Hooks; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.IBlockReader; +import net.minecraft.world.BlockView; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; @@ -17,11 +17,11 @@ import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(BlockState.class) @SuppressWarnings({"UnnecessaryQualifiedMemberReference", "deprecation"}) public class MixinBlockState { - private static final String callFrom = "Lnet/minecraft/block/BlockState;func_215703_d(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F"; - private static final String callTo = "Lnet/minecraft/block/Block;func_220080_a(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F"; + private static final String callFrom = "Lnet/minecraft/block/BlockState;getAmbientOcclusionLightLevel(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)F"; + private static final String callTo = "Lnet/minecraft/block/Block;getAmbientOcclusionLightLevel(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;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.func_220080_a(state, reader, pos), state); + float getAmbientOcclusionValue(Block block, BlockState state, BlockView reader, BlockPos pos) { + return Hooks.getAmbientOcclusionLightValueOverride(block.getAmbientOcclusionLightLevel(state, reader, pos), state); } } diff --git a/src/main/java/mods/betterfoliage/mixin/MixinChunkRender.java b/src/main/java/mods/betterfoliage/mixin/MixinChunkRender.java deleted file mode 100644 index 5747fe7..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinChunkRender.java +++ /dev/null @@ -1,28 +0,0 @@ -package mods.betterfoliage.mixin; - -import mods.betterfoliage.client.Hooks; -import net.minecraft.block.BlockState; -import net.minecraft.client.renderer.BlockRendererDispatcher; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.chunk.ChunkRender; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.IEnviromentBlockReader; -import net.minecraftforge.client.MinecraftForgeClient; -import net.minecraftforge.client.model.data.IModelData; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -import java.util.Random; - -@Mixin(ChunkRender.class) -public class MixinChunkRender { - - private static final String rebuildChunk = "rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V"; - private static final String renderBlock = "Lnet/minecraft/client/renderer/BlockRendererDispatcher;renderBlock(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/IEnviromentBlockReader;Lnet/minecraft/client/renderer/BufferBuilder;Ljava/util/Random;Lnet/minecraftforge/client/model/data/IModelData;)Z"; - - @Redirect(method = rebuildChunk, at = @At(value = "INVOKE", target = renderBlock)) - public boolean renderBlock(BlockRendererDispatcher dispatcher, BlockState state, BlockPos pos, IEnviromentBlockReader reader, BufferBuilder buffer, Random random, IModelData modelData) { - return Hooks.renderWorldBlock(dispatcher, state, pos, reader, buffer, random, modelData, MinecraftForgeClient.getRenderLayer()); - } -} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinChunkRenderVanilla.java b/src/main/java/mods/betterfoliage/mixin/MixinChunkRenderVanilla.java deleted file mode 100644 index 03a918e..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinChunkRenderVanilla.java +++ /dev/null @@ -1,21 +0,0 @@ -package mods.betterfoliage.mixin; - -import mods.betterfoliage.client.Hooks; -import net.minecraft.block.BlockState; -import net.minecraft.client.renderer.chunk.ChunkRender; -import net.minecraft.util.BlockRenderLayer; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -@Mixin(ChunkRender.class) -public class MixinChunkRenderVanilla { - - private static final String rebuildChunk = "rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V"; - private static final String canRenderInLayer = "Lnet/minecraft/block/BlockState;canRenderInLayer(Lnet/minecraft/util/BlockRenderLayer;)Z"; - - @Redirect(method = rebuildChunk, at = @At(value = "INVOKE", target = canRenderInLayer)) - boolean canRenderInLayer(BlockState state, BlockRenderLayer layer) { - return Hooks.canRenderInLayerOverride(state, layer); - } -} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinClientChunkManager.java b/src/main/java/mods/betterfoliage/mixin/MixinClientChunkManager.java new file mode 100644 index 0000000..07a0edc --- /dev/null +++ b/src/main/java/mods/betterfoliage/mixin/MixinClientChunkManager.java @@ -0,0 +1,23 @@ +package mods.betterfoliage.mixin; + +import mods.betterfoliage.ClientChunkLoadCallback; +import net.minecraft.client.world.ClientChunkManager; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.world.World; +import net.minecraft.world.chunk.WorldChunk; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ClientChunkManager.class) +public class MixinClientChunkManager { + + private static final String onLoadChunkFromPacket = "loadChunkFromPacket(Lnet/minecraft/world/World;IILnet/minecraft/util/PacketByteBuf;Lnet/minecraft/nbt/CompoundTag;IZ)Lnet/minecraft/world/chunk/WorldChunk;"; + + @Inject(method = onLoadChunkFromPacket, at = @At(value = "RETURN", ordinal = 2)) + void onLoadChunkFromPacket(World world, int chunkX, int chunkZ, PacketByteBuf data, CompoundTag nbt, int updatedSectionsBits, boolean clearOld, CallbackInfoReturnable ci) { + ClientChunkLoadCallback.EVENT.invoker().loadChunk(ci.getReturnValue()); + } +} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinClientChunkManagerChunkMap.java b/src/main/java/mods/betterfoliage/mixin/MixinClientChunkManagerChunkMap.java new file mode 100644 index 0000000..9ea4a04 --- /dev/null +++ b/src/main/java/mods/betterfoliage/mixin/MixinClientChunkManagerChunkMap.java @@ -0,0 +1,19 @@ +package mods.betterfoliage.mixin; + +import mods.betterfoliage.ClientChunkLoadCallback; +import net.minecraft.world.chunk.WorldChunk; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(targets = {"net.minecraft.client.world.ClientChunkManager$ClientChunkMap"}) +public class MixinClientChunkManagerChunkMap { + + private static final String onSetAndCompare = "method_20183(ILnet/minecraft/world/chunk/WorldChunk;Lnet/minecraft/world/chunk/WorldChunk;)Lnet/minecraft/world/chunk/WorldChunk;"; + + @Inject(method = onSetAndCompare, at = @At("HEAD")) + void onSetAndCompare(int i, WorldChunk oldChunk, WorldChunk newChunk, CallbackInfoReturnable ci) { + ClientChunkLoadCallback.EVENT.invoker().unloadChunk(oldChunk); + } +} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinClientWorld.java b/src/main/java/mods/betterfoliage/mixin/MixinClientWorld.java index a76519d..d24597e 100644 --- a/src/main/java/mods/betterfoliage/mixin/MixinClientWorld.java +++ b/src/main/java/mods/betterfoliage/mixin/MixinClientWorld.java @@ -1,44 +1,50 @@ package mods.betterfoliage.mixin; -import mods.betterfoliage.client.Hooks; -import net.minecraft.block.Block; +import mods.betterfoliage.ClientWorldLoadCallback; +import mods.betterfoliage.Hooks; import net.minecraft.block.BlockState; -import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.world.ClientWorld; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.IBlockReader; -import net.minecraft.world.World; +import net.minecraft.util.profiler.Profiler; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.level.LevelInfo; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Inject; +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$MutableBlockPos;)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 ctor = "(Lnet/minecraft/client/network/ClientPlayNetworkHandler;Lnet/minecraft/world/level/LevelInfo;Lnet/minecraft/world/dimension/DimensionType;ILnet/minecraft/util/profiler/Profiler;Lnet/minecraft/client/render/WorldRenderer;)V"; + private static final String scheduleBlockRender = "scheduleBlockRender(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)V"; + private static final String rendererNotify = "Lnet/minecraft/client/render/WorldRenderer;method_21596(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;)V"; + private static final String worldDisplayTick = "randomBlockDisplayTick(IIIILjava/util/Random;ZLnet/minecraft/util/math/BlockPos$Mutable;)V"; + private static final String blockDisplayTick = "Lnet/minecraft/block/Block;randomDisplayTick(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"; + /** + * Inject callback to get notified of client-side blockstate changes. + * Used to invalidate caches in the {@link mods.betterfoliage.chunk.ChunkOverlayManager} + */ + @Inject(method = scheduleBlockRender, at = @At(value = "HEAD")) + void onClientBlockChanged(BlockPos pos, BlockState oldState, BlockState newState, CallbackInfo ci) { + Hooks.onClientBlockChanged((ClientWorld) (Object) this, pos, oldState, newState); + } + + @Inject(method = ctor, at = @At("RETURN")) + void onClientWorldCreated(ClientPlayNetworkHandler netHandler, LevelInfo levelInfo, DimensionType dimensionType, int i, Profiler profiler, WorldRenderer worldRenderer, CallbackInfo ci) { + ClientWorldLoadCallback.EVENT.invoker().loadWorld((ClientWorld) (Object) this); + } /** * 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 callback to get notified of client-side blockstate changes. - * Used to invalidate caches in the {@link mods.betterfoliage.client.chunk.ChunkOverlayManager} - */ - @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); + @Inject(method = worldDisplayTick, at = @At(value = "INVOKE", target = blockDisplayTick)) + void onRandomDisplayTick(int xCenter, int yCenter, int zCenter, int radius, Random random, boolean spawnBarrierParticles, BlockPos.Mutable mutable, CallbackInfo ci) { + Hooks.onRandomDisplayTick((ClientWorld) (Object) this, mutable); } } diff --git a/src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java b/src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java deleted file mode 100644 index 0388eb6..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java +++ /dev/null @@ -1,27 +0,0 @@ -package mods.betterfoliage.mixin; - -import mods.betterfoliage.BetterFoliage; -import mods.octarinecore.client.resource.AsnycSpriteProviderManager; -import net.minecraft.client.renderer.model.ModelBakery; -import net.minecraft.client.renderer.texture.AtlasTexture; -import net.minecraft.profiler.IProfiler; -import net.minecraft.resources.IResourceManager; -import net.minecraft.util.ResourceLocation; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -@Mixin(ModelBakery.class) -abstract public class MixinModelBakery { - - private static final String processLoading = "processLoading(Lnet/minecraft/profiler/IProfiler;)V"; - private static final String stitch = "Lnet/minecraft/client/renderer/texture/AtlasTexture;stitch(Lnet/minecraft/resources/IResourceManager;Ljava/lang/Iterable;Lnet/minecraft/profiler/IProfiler;)Lnet/minecraft/client/renderer/texture/AtlasTexture$SheetData;"; - - @Redirect(method = processLoading, at = @At(value = "INVOKE", target = stitch)) - AtlasTexture.SheetData onStitchModelTextures(AtlasTexture atlas, IResourceManager manager, Iterable idList, IProfiler profiler) { - AsnycSpriteProviderManager.StitchWrapper wrapper = BetterFoliage.INSTANCE.getBlockSprites().prepare(this, atlas, manager, idList, profiler); - AtlasTexture.SheetData sheet = atlas.stitch(manager, wrapper.getIdList(), profiler); - wrapper.complete(sheet); - return sheet; - } -} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinModelLoader.java b/src/main/java/mods/betterfoliage/mixin/MixinModelLoader.java new file mode 100644 index 0000000..b75f960 --- /dev/null +++ b/src/main/java/mods/betterfoliage/mixin/MixinModelLoader.java @@ -0,0 +1,27 @@ +package mods.betterfoliage.mixin; + +import mods.betterfoliage.ModelLoadingCallback; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.resource.ResourceManager; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import static net.minecraft.client.render.model.ModelLoader.MISSING; + +@Mixin(ModelLoader.class) +public class MixinModelLoader { + + @Shadow @Final private ResourceManager resourceManager; + + @Inject(at = @At("HEAD"), method = "addModel") + private void addModelHook(ModelIdentifier id, CallbackInfo info) { + if (id == MISSING) { + ModelLoadingCallback.EVENT.invoker().beginLoadModels((ModelLoader) (Object)this, resourceManager); + } + } +} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinOptifineBlockUtils.java b/src/main/java/mods/betterfoliage/mixin/MixinOptifineBlockUtils.java deleted file mode 100644 index 6809178..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinOptifineBlockUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package mods.betterfoliage.mixin; - -import mods.betterfoliage.client.Hooks; -import net.minecraft.block.BlockState; -import net.minecraft.util.Direction; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.shapes.VoxelShape; -import net.minecraft.world.IBlockReader; -import net.optifine.util.BlockUtils; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -@Mixin(BlockUtils.class) -public class MixinOptifineBlockUtils { - private static final String shouldSideBeRenderedCached = "shouldSideBeRenderedCached(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;Lnet/optifine/render/RenderEnv;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;)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;"; - - @SuppressWarnings("UnresolvedMixinReference") - @Redirect(method = shouldSideBeRenderedCached, at = @At(value = "INVOKE", target = getVoxelShape, ordinal = 1)) - private static VoxelShape getVoxelShapeOverride(BlockState state, IBlockReader reader, BlockPos pos, Direction dir) { - return Hooks.getVoxelShapeOverride(state, reader, pos, dir); - } -} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinOptifineChunkRender.java b/src/main/java/mods/betterfoliage/mixin/MixinOptifineChunkRender.java deleted file mode 100644 index 9a83c3f..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinOptifineChunkRender.java +++ /dev/null @@ -1,31 +0,0 @@ -package mods.betterfoliage.mixin; - -import mods.betterfoliage.client.Hooks; -import net.minecraft.block.BlockState; -import net.minecraft.client.renderer.chunk.ChunkRender; -import net.minecraft.util.BlockRenderLayer; -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; -import org.spongepowered.asm.mixin.injection.Slice; - -@Mixin(ChunkRender.class) -public class MixinOptifineChunkRender { - - private static final String rebuildChunk = "rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V"; - private static final String invokeReflector = "Lnet/optifine/reflect/Reflector;callBoolean(Ljava/lang/Object;Lnet/optifine/reflect/ReflectorMethod;[Ljava/lang/Object;)Z"; - private static final String forgeBlockCanRender = "Lnet/minecraft/client/renderer/chunk/ChunkRender;FORGE_BLOCK_CAN_RENDER_IN_LAYER:Z"; - - @Redirect( - method = rebuildChunk, - at = @At(value = "INVOKE", target = invokeReflector), - slice = @Slice( - from = @At(value = "FIELD", target = forgeBlockCanRender) - ) - ) - @SuppressWarnings("UnresolvedMixinReference") - boolean canRenderInLayer(Object state, @Coerce Object reflector, Object[] layer) { - return Hooks.canRenderInLayerOverride((BlockState) state, (BlockRenderLayer) layer[0]); - } -} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinParticleManager.java b/src/main/java/mods/betterfoliage/mixin/MixinParticleManager.java deleted file mode 100644 index 7a536d5..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinParticleManager.java +++ /dev/null @@ -1,29 +0,0 @@ -package mods.betterfoliage.mixin; - -import mods.betterfoliage.BetterFoliage; -import mods.octarinecore.client.resource.AsnycSpriteProviderManager; -import net.minecraft.client.particle.ParticleManager; -import net.minecraft.client.renderer.texture.AtlasTexture; -import net.minecraft.profiler.IProfiler; -import net.minecraft.resources.IResourceManager; -import net.minecraft.util.ResourceLocation; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -@Mixin(ParticleManager.class) -public class MixinParticleManager { - - private static final String reload = "reload(Lnet/minecraft/resources/IFutureReloadListener$IStage;Lnet/minecraft/resources/IResourceManager;Lnet/minecraft/profiler/IProfiler;Lnet/minecraft/profiler/IProfiler;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"; - private static final String stitch = "Lnet/minecraft/client/renderer/texture/AtlasTexture;stitch(Lnet/minecraft/resources/IResourceManager;Ljava/lang/Iterable;Lnet/minecraft/profiler/IProfiler;)Lnet/minecraft/client/renderer/texture/AtlasTexture$SheetData;"; - - // ewww :S - @SuppressWarnings("UnresolvedMixinReference") - @Redirect(method = "*", at = @At(value = "INVOKE", target = stitch)) - AtlasTexture.SheetData onStitchModelTextures(AtlasTexture atlas, IResourceManager manager, Iterable idList, IProfiler profiler) { - AsnycSpriteProviderManager.StitchWrapper wrapper = BetterFoliage.INSTANCE.getParticleSprites().prepare(this, atlas, manager, idList, profiler); - AtlasTexture.SheetData sheet = atlas.stitch(manager, wrapper.getIdList(), profiler); - wrapper.complete(sheet); - return sheet; - } -} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinShadersBlockModelRenderer.java b/src/main/java/mods/betterfoliage/mixin/MixinShadersBlockModelRenderer.java deleted file mode 100644 index 7ff78ac..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinShadersBlockModelRenderer.java +++ /dev/null @@ -1,24 +0,0 @@ -package mods.betterfoliage.mixin; - -import mods.betterfoliage.client.integration.ShadersModIntegration; -import net.minecraft.block.BlockState; -import net.minecraft.client.renderer.BlockModelRenderer; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.IEnviromentBlockReader; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyArg; - -@Mixin(BlockModelRenderer.class) -public class MixinShadersBlockModelRenderer { - - private static final String renderModel = "renderModel(Lnet/minecraft/world/IEnviromentBlockReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/client/renderer/BufferBuilder;ZLjava/util/Random;JLnet/minecraftforge/client/model/data/IModelData;)Z"; - private static final String pushEntity = "Lnet/optifine/shaders/SVertexBuilder;pushEntity(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/IEnviromentBlockReader;Lnet/minecraft/client/renderer/BufferBuilder;)V"; - - @SuppressWarnings("UnresolvedMixinReference") - @ModifyArg(method = renderModel, at = @At(value = "INVOKE", target = pushEntity), remap = false) - BlockState overrideBlockState(BlockState state, BlockPos pos, IEnviromentBlockReader world, BufferBuilder buffer) { - return ShadersModIntegration.getBlockStateOverride(state, world, pos); - } -} diff --git a/src/main/java/net/optifine/util/BlockUtils.java b/src/main/java/net/optifine/util/BlockUtils.java deleted file mode 100644 index 0f9706c..0000000 --- a/src/main/java/net/optifine/util/BlockUtils.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.optifine.util; - -public class BlockUtils { - // whyyyy? -} diff --git a/src/main/kotlin/mods/betterfoliage/BetterFoliage.kt b/src/main/kotlin/mods/betterfoliage/BetterFoliage.kt index 1873b9c..728be4f 100644 --- a/src/main/kotlin/mods/betterfoliage/BetterFoliage.kt +++ b/src/main/kotlin/mods/betterfoliage/BetterFoliage.kt @@ -1,20 +1,25 @@ package mods.betterfoliage -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.render.RisingSoulTextures -import mods.octarinecore.client.gui.textComponent -import mods.octarinecore.client.resource.AsnycSpriteProviderManager -import mods.octarinecore.client.resource.AsyncSpriteProvider -import mods.octarinecore.client.resource.Atlas -import mods.octarinecore.client.resource.GeneratedBlockTexturePack -import net.minecraft.block.BlockState -import net.minecraft.client.Minecraft -import net.minecraft.client.particle.ParticleManager -import net.minecraft.client.renderer.model.ModelBakery -import net.minecraft.util.math.BlockPos -import net.minecraft.util.text.TextFormatting -import net.minecraft.util.text.TranslationTextComponent -import net.minecraftforge.registries.ForgeRegistries +import io.github.prospector.modmenu.api.ModMenuApi +import me.shedaniel.clothconfig2.api.ConfigBuilder +import me.zeroeightsix.fiber.JanksonSettings +import mods.betterfoliage.chunk.ChunkOverlayManager +import mods.betterfoliage.config.BlockConfig +import mods.betterfoliage.config.MainConfig +import mods.betterfoliage.render.block.vanilla.* +import mods.betterfoliage.render.particle.LeafParticleRegistry +import mods.betterfoliage.render.particle.RisingSoulParticle +import mods.betterfoliage.resource.discovery.BakedModelReplacer +import mods.betterfoliage.resource.generated.GeneratedBlockTexturePack +import net.fabricmc.api.ClientModInitializer +import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener +import net.fabricmc.fabric.api.resource.ResourceManagerHelper +import net.fabricmc.loader.api.FabricLoader +import net.minecraft.client.MinecraftClient +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.resource.language.I18n +import net.minecraft.resource.ResourceType +import net.minecraft.util.Identifier import org.apache.logging.log4j.Level import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.simple.SimpleLogger @@ -22,9 +27,13 @@ import org.apache.logging.log4j.util.PropertiesUtil import java.io.File import java.io.PrintStream import java.util.* +import java.util.function.Function -object BetterFoliage { - var log = LogManager.getLogger("BetterFoliage") +object BetterFoliage : ClientModInitializer, ModMenuApi { + const val MOD_ID = "betterfoliage" + override fun getModId() = MOD_ID + + var logger = LogManager.getLogger() var logDetail = SimpleLogger( "BetterFoliage", Level.DEBUG, @@ -32,45 +41,68 @@ object BetterFoliage { "yyyy-MM-dd HH:mm:ss", null, PropertiesUtil(Properties()), - PrintStream(File("logs/betterfoliage.log").apply { + PrintStream(File(FabricLoader.getInstance().gameDirectory, "logs/betterfoliage.log").apply { parentFile.mkdirs() if (!exists()) createNewFile() }) ) - val blockSprites = AsnycSpriteProviderManager("bf-blocks-extra") - val particleSprites = AsnycSpriteProviderManager("bf-particles-extra") - val asyncPack = GeneratedBlockTexturePack("bf_gen", "Better Foliage generated assets", logDetail) + val configFile get() = File(FabricLoader.getInstance().configDirectory, "BetterFoliage.json") - fun getSpriteManager(atlas: Atlas) = when(atlas) { - Atlas.BLOCKS -> blockSprites - Atlas.PARTICLES -> particleSprites - } as AsnycSpriteProviderManager - - init { - blockSprites.providers.add(asyncPack) + val config = MainConfig().apply { + if (configFile.exists()) JanksonSettings().deserialize(fiberNode, configFile.inputStream()) + else JanksonSettings().serialize(fiberNode, configFile.outputStream(), false) } - fun log(level: Level, msg: String) { - log.log(level, "[BetterFoliage] $msg") - logDetail.log(level, msg) + val blockConfig = BlockConfig() + override fun getConfigScreenFactory() = Function { screen: Screen -> + val builder = ConfigBuilder.create() + .setParentScreen(screen) + .setTitle(I18n.translate("betterfoliage.title")) + config.createClothNode(listOf("betterfoliage")).value.forEach { rootOption -> + builder.getOrCreateCategory("main").addEntry(rootOption) + } + builder.savingRunnable = Runnable { + JanksonSettings().serialize(config.fiberNode, configFile.outputStream(), false) + modelReplacer.invalidate() + } + builder.build() } - fun logDetail(msg: String) { - logDetail.log(Level.DEBUG, msg) + val generatedPack = GeneratedBlockTexturePack(Identifier(MOD_ID, "generated"), "betterfoliage-generated", "Better Foliage", "Generated leaf textures", logDetail) + val modelReplacer = BakedModelReplacer() + + override fun onInitializeClient() { + // Register generated resource pack + ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(generatedPack) + MinecraftClient.getInstance().resourcePackContainerManager.addCreator(generatedPack.finder) + + // Add standard block support + modelReplacer.discoverers.add(StandardLeafDiscovery) + modelReplacer.discoverers.add(StandardGrassDiscovery) + modelReplacer.discoverers.add(StandardLogDiscovery) + modelReplacer.discoverers.add(StandardCactusDiscovery) + modelReplacer.discoverers.add(LilyPadDiscovery) + modelReplacer.discoverers.add(DirtDiscovery) + modelReplacer.discoverers.add(SandDiscovery) + modelReplacer.discoverers.add(MyceliumDiscovery) + modelReplacer.discoverers.add(NetherrackDiscovery) + + // Init overlay layers + ChunkOverlayManager.layers.add(RoundLogOverlayLayer) + + // Init singletons + LeafParticleRegistry + NormalLeavesModel.Companion + GrassBlockModel.Companion + RoundLogModel.Companion + CactusModel.Companion + LilypadModel.Companion + DirtModel.Companion + SandModel.Companion + MyceliumModel.Companion + NetherrackModel.Companion + RisingSoulParticle.Companion } - fun logRenderError(state: BlockState, location: BlockPos) { - if (state in Client.suppressRenderErrors) return - Client.suppressRenderErrors.add(state) - - val blockName = ForgeRegistries.BLOCKS.getKey(state.block).toString() - val blockLoc = "${location.x},${location.y},${location.z}" - Minecraft.getInstance().ingameGUI.chatGUI.printChatMessage(TranslationTextComponent( - "betterfoliage.rendererror", - textComponent(blockName, TextFormatting.GOLD), - textComponent(blockLoc, TextFormatting.GOLD) - )) - logDetail("Error rendering block $state at $blockLoc") - } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt b/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt deleted file mode 100644 index 156c707..0000000 --- a/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt +++ /dev/null @@ -1,35 +0,0 @@ -package mods.betterfoliage - -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.config.Config -import mods.octarinecore.client.resource.AsnycSpriteProviderManager -import mods.octarinecore.client.resource.GeneratedBlockTexturePack -import net.alexwells.kottle.FMLKotlinModLoadingContext -import net.minecraft.client.Minecraft -import net.minecraft.client.particle.ParticleManager -import net.minecraft.client.renderer.model.ModelBakery -import net.minecraftforge.fml.ModLoadingContext -import net.minecraftforge.fml.common.Mod -import net.minecraftforge.fml.config.ModConfig -import org.apache.logging.log4j.Level.DEBUG -import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.simple.SimpleLogger -import org.apache.logging.log4j.util.PropertiesUtil -import java.io.File -import java.io.PrintStream -import java.util.* - -@Mod(BetterFoliageMod.MOD_ID) -object BetterFoliageMod { - const val MOD_ID = "betterfoliage" - - val bus = FMLKotlinModLoadingContext.get().modEventBus - - init { - ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.build()) - Minecraft.getInstance().resourcePackList.addPackFinder(BetterFoliage.asyncPack.finder) - bus.register(BlockConfig) - Client.init() - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/CommonRefs.kt b/src/main/kotlin/mods/betterfoliage/CommonRefs.kt new file mode 100644 index 0000000..110033f --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/CommonRefs.kt @@ -0,0 +1,29 @@ +package mods.betterfoliage + + +// Optifine +//val OptifineClassTransformer = ClassRefOld("optifine.OptiFineClassTransformer") +//val BlockPosM = ClassRefOld("net.optifine.BlockPosM") +//object ChunkCacheOF : ClassRefOld("net.optifine.override.ChunkCacheOF") { +// val chunkCache = FieldRefOld(this, "chunkCache", ChunkRendererRegion) +//} + +//object RenderEnv : ClassRefOld("net.optifine.render.RenderEnv") { +// val reset = MethodRefOld(this, "reset", void, BlockState, BlockPos) +//} + +// Optifine custom colors +//val IColorizer = ClassRefOld("net.optifine.CustomColors\$IColorizer") +//object CustomColors : ClassRefOld("net.optifine.CustomColors") { +// val getColorMultiplier = MethodRefOld(this, "getColorMultiplier", int, BakedQuad, BlockState, ExtendedBlockView, BlockPos, RenderEnv) +//} + +// Optifine shaders +//object SVertexBuilder : ClassRefOld("net.optifine.shaders.SVertexBuilder") { +// val pushState = MethodRefOld(this, "pushEntity", void, BlockState, BlockPos, ExtendedBlockView, BufferBuilder) +// val pushNum = MethodRefOld(this, "pushEntity", void, long) +// val pop = MethodRefOld(this, "popEntity", void) +//} + + + diff --git a/src/main/kotlin/mods/betterfoliage/Events.kt b/src/main/kotlin/mods/betterfoliage/Events.kt new file mode 100644 index 0000000..d13a212 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/Events.kt @@ -0,0 +1,69 @@ +package mods.betterfoliage + +import net.fabricmc.fabric.api.event.Event +import net.fabricmc.fabric.api.event.EventFactory +import net.minecraft.client.render.block.BlockModels +import net.minecraft.client.render.model.ModelLoader +import net.minecraft.client.world.ClientWorld +import net.minecraft.resource.ResourceManager +import net.minecraft.world.chunk.WorldChunk + +interface ClientChunkLoadCallback { + fun loadChunk(chunk: WorldChunk) + fun unloadChunk(chunk: WorldChunk) + + companion object { + @JvmField val EVENT: Event = EventFactory.createArrayBacked(ClientChunkLoadCallback::class.java) { listeners -> + object : ClientChunkLoadCallback { + override fun loadChunk(chunk: WorldChunk) { listeners.forEach { it.loadChunk(chunk) } } + override fun unloadChunk(chunk: WorldChunk) { listeners.forEach { it.unloadChunk(chunk) } } + } + } + } +} + +interface ClientWorldLoadCallback { + fun loadWorld(world: ClientWorld) + + companion object { + @JvmField val EVENT : Event = EventFactory.createArrayBacked(ClientWorldLoadCallback::class.java) { listeners -> + object : ClientWorldLoadCallback { + override fun loadWorld(world: ClientWorld) { listeners.forEach { it.loadWorld(world) } } + } + } + } +} + +interface BlockModelsReloadCallback { + fun reloadBlockModels(blockModels: BlockModels) + + companion object { + @JvmField val EVENT: Event = EventFactory.createArrayBacked(BlockModelsReloadCallback::class.java) { listeners -> + object : BlockModelsReloadCallback { + override fun reloadBlockModels(blockModels: BlockModels) { + listeners.forEach { it.reloadBlockModels(blockModels) } + } + } + } + } +} + +/** + * Event fired when the [ModelLoader] first starts loading models. + * + * This happens during the constructor, so BEWARE! + * Try to avoid any interaction until the block texture atlas starts stitching. + */ +interface ModelLoadingCallback { + fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) + + companion object { + @JvmField val EVENT: Event = EventFactory.createArrayBacked(ModelLoadingCallback::class.java) { listeners -> + object : ModelLoadingCallback { + override fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) { + listeners.forEach { it.beginLoadModels(loader, manager) } + } + } + } + } +} diff --git a/src/main/kotlin/mods/betterfoliage/Hooks.kt b/src/main/kotlin/mods/betterfoliage/Hooks.kt new file mode 100644 index 0000000..857fc1c --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/Hooks.kt @@ -0,0 +1,67 @@ +@file:JvmName("Hooks") +package mods.betterfoliage + +import mods.betterfoliage.chunk.ChunkOverlayManager +import mods.betterfoliage.render.particle.FallingLeafParticle +import mods.betterfoliage.render.particle.RisingSoulParticle +import mods.betterfoliage.render.block.vanilla.LeafKey +import mods.betterfoliage.render.block.vanilla.RoundLogKey +import mods.betterfoliage.util.offset +import mods.betterfoliage.util.plus +import mods.betterfoliage.util.randomD +import net.minecraft.block.BlockRenderLayer +import net.minecraft.block.BlockRenderLayer.CUTOUT +import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED +import net.minecraft.block.BlockState +import net.minecraft.block.Blocks +import net.minecraft.client.MinecraftClient +import net.minecraft.client.world.ClientWorld +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.util.shape.VoxelShape +import net.minecraft.util.shape.VoxelShapes +import net.minecraft.world.BlockView + +fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float { + if (BetterFoliage.config.enabled && + BetterFoliage.config.roundLogs.enabled && + BetterFoliage.modelReplacer.getTyped(state) != null + ) return BetterFoliage.config.roundLogs.dimming.toFloat() + return original +} + +fun getUseNeighborBrightnessOverride(original: Boolean, state: BlockState): Boolean { + return original || (BetterFoliage.config.enabled && BetterFoliage.config.roundLogs.enabled && BetterFoliage.blockConfig.logBlocks.matchesClass(state.block)); +} + +fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: BlockState, newState: BlockState) { + ChunkOverlayManager.onBlockChange(worldClient, pos) +} + +fun onRandomDisplayTick(world: ClientWorld, pos: BlockPos) { + val state = world.getBlockState(pos) + + if (BetterFoliage.config.enabled && + BetterFoliage.config.risingSoul.enabled && + state.block == Blocks.SOUL_SAND && + world.isAir(pos + Direction.UP.offset) && + Math.random() < BetterFoliage.config.risingSoul.chance) { + RisingSoulParticle(world, pos).addIfValid() + } + + if (BetterFoliage.config.enabled && + BetterFoliage.config.fallingLeaves.enabled && + world.isAir(pos + Direction.DOWN.offset) && + randomD() < BetterFoliage.config.fallingLeaves.chance) { + BetterFoliage.modelReplacer.getTyped(state)?.let { key -> + FallingLeafParticle(world, pos, key).addIfValid() + } + } +} + +fun getVoxelShapeOverride(state: BlockState, reader: BlockView, pos: BlockPos, dir: Direction): VoxelShape { + if (BetterFoliage.modelReplacer[state] is RoundLogKey) { + return VoxelShapes.empty() + } + return state.getCullShape(reader, pos, dir) +} diff --git a/src/main/kotlin/mods/betterfoliage/chunk/BlockContext.kt b/src/main/kotlin/mods/betterfoliage/chunk/BlockContext.kt new file mode 100644 index 0000000..345657e --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/chunk/BlockContext.kt @@ -0,0 +1,55 @@ +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 net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.client.MinecraftClient +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.world.ExtendedBlockView +import net.minecraft.world.biome.Biome + +/** + * Represents the block being rendered. Has properties and methods to query the neighborhood of the block in + * block-relative coordinates. + */ +interface BlockCtx { + val world: ExtendedBlockView + val pos: BlockPos + + fun offset(dir: Direction) = offset(dir.offset) + fun offset(offset: Int3): BlockCtx + + val state: BlockState get() = world.getBlockState(pos) + fun state(dir: Direction) = world.getBlockState(pos + dir.offset) + fun state(offset: Int3) = world.getBlockState(pos + offset) + + val biome: Biome get() = world.getBiome(pos) + + val isNormalCube: Boolean get() = state.isSimpleFullBlock(world, pos) + + fun shouldSideBeRendered(side: Direction) = Block.shouldDrawSide(state, world, pos, side) + + fun isNeighborSolid(dir: Direction) = offset(dir).let { it.state.isSideSolidFullSquare(it.world, it.pos, dir.opposite) } + + fun model(dir: Direction) = state(dir).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! } + fun model(offset: Int3) = state(offset).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! } +} + +open class BasicBlockCtx( + override val world: ExtendedBlockView, + override val pos: BlockPos +) : BlockCtx { + override val state = world.getBlockState(pos) + override fun offset(offset: Int3) = BasicBlockCtx(world, pos + offset) + fun cache() = CachedBlockCtx(world, pos) +} + +open class CachedBlockCtx(world: ExtendedBlockView, pos: BlockPos) : BasicBlockCtx(world, pos) { + var neighbors = Array(6) { world.getBlockState(pos + allDirections[it].offset) } + override var biome: Biome = world.getBiome(pos) + override fun state(dir: Direction) = neighbors[dir.ordinal] +} diff --git a/src/main/kotlin/mods/octarinecore/client/render/OffsetBlockReader.kt b/src/main/kotlin/mods/betterfoliage/chunk/OffsetBlockView.kt similarity index 67% rename from src/main/kotlin/mods/octarinecore/client/render/OffsetBlockReader.kt rename to src/main/kotlin/mods/betterfoliage/chunk/OffsetBlockView.kt index f0df03a..3aa9a49 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/OffsetBlockReader.kt +++ b/src/main/kotlin/mods/betterfoliage/chunk/OffsetBlockView.kt @@ -1,10 +1,8 @@ -package mods.octarinecore.client.render +package mods.betterfoliage.chunk -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.plus import net.minecraft.util.math.BlockPos -import net.minecraft.world.IBlockReader -import net.minecraft.world.IEnviromentBlockReader +import net.minecraft.world.BlockView +import net.minecraft.world.ExtendedBlockView import net.minecraft.world.LightType /** @@ -14,24 +12,24 @@ import net.minecraft.world.LightType * @param[original] the [IBlockAccess] that is delegated to */ @Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType") -open class OffsetBlockReader(open val original: IBlockReader, val modded: BlockPos, val target: BlockPos) : IBlockReader { +open class OffsetBlockView(open val original: BlockView, val modded: BlockPos, val target: BlockPos) : BlockView { inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos)) - override fun getTileEntity(pos: BlockPos) = original.getTileEntity(actualPos(pos)) + override fun getBlockEntity(pos: BlockPos) = original.getBlockEntity(actualPos(pos)) override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos)) } @Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType") -class OffsetEnvBlockReader(val original: IEnviromentBlockReader, val modded: BlockPos, val target: BlockPos) : IEnviromentBlockReader by original { +class OffsetExtBlockView(val original: ExtendedBlockView, val modded: BlockPos, val target: BlockPos) : ExtendedBlockView by original { inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos)) - override fun getTileEntity(pos: BlockPos) = original.getTileEntity(actualPos(pos)) + override fun getBlockEntity(pos: BlockPos) = original.getBlockEntity(actualPos(pos)) override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos)) - override fun getLightFor(type: LightType, pos: BlockPos) = original.getLightFor(type, actualPos(pos)) - override fun getCombinedLight(pos: BlockPos, light: Int) = original.getCombinedLight(actualPos(pos), light) + override fun getLightLevel(type: LightType, pos: BlockPos) = original.getLightLevel(type, actualPos(pos)) + override fun getLightmapIndex(pos: BlockPos, light: Int) = original.getLightmapIndex(actualPos(pos), light) override fun getBiome(pos: BlockPos) = original.getBiome(actualPos(pos)) } diff --git a/src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt b/src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt similarity index 60% rename from src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt rename to src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt index 8000a5b..e213ecf 100644 --- a/src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt +++ b/src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt @@ -1,20 +1,17 @@ -package mods.betterfoliage.client.chunk +package mods.betterfoliage.chunk -import mods.octarinecore.ChunkCacheOF -import mods.octarinecore.client.render.BlockCtx -import mods.octarinecore.metaprog.get -import mods.octarinecore.metaprog.isInstance -import net.minecraft.client.renderer.chunk.ChunkRenderCache +import mods.betterfoliage.* +import mods.betterfoliage.util.YarnHelper +import mods.betterfoliage.util.get +import net.minecraft.client.render.chunk.ChunkRendererRegion import net.minecraft.client.world.ClientWorld import net.minecraft.util.math.BlockPos import net.minecraft.util.math.ChunkPos -import net.minecraft.world.IEnviromentBlockReader -import net.minecraft.world.IWorldReader +import net.minecraft.world.ExtendedBlockView +import net.minecraft.world.ViewableWorld +import net.minecraft.world.World +import net.minecraft.world.chunk.WorldChunk 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 @@ -24,10 +21,15 @@ import kotlin.collections.mutableListOf import kotlin.collections.mutableMapOf import kotlin.collections.set -val IEnviromentBlockReader.dimType: DimensionType get() = when { - this is IWorldReader -> dimension.type - this is ChunkRenderCache -> world.dimension.type - this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache].world.dimension.type +// net.minecraft.world.chunk.WorldChunk.world +val WorldChunk_world = YarnHelper.requiredField("net.minecraft.class_2818", "field_12858", "Lnet/minecraft/class_1937;") +// net.minecraft.client.render.chunk.ChunkRendererRegion.world +val ChunkRendererRegion_world = YarnHelper.requiredField("net.minecraft.class_853", "field_4490", "Lnet/minecraft/class_1937;") + +val ExtendedBlockView.dimType: DimensionType get() = when { + this is ViewableWorld -> dimension.type + this is ChunkRendererRegion -> this[ChunkRendererRegion_world]!!.dimension.type +// this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache]!!.dimType else -> throw IllegalArgumentException("DimensionType of world with class ${this::class.qualifiedName} cannot be determined!") } @@ -36,18 +38,17 @@ val IEnviromentBlockReader.dimType: DimensionType get() = when { */ interface ChunkOverlayLayer { fun calculate(ctx: BlockCtx): T - fun onBlockUpdate(world: IEnviromentBlockReader, pos: BlockPos) + fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos) } /** * Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data. */ -object ChunkOverlayManager { - - var tempCounter = 0 +object ChunkOverlayManager : ClientChunkLoadCallback, ClientWorldLoadCallback { init { - MinecraftForge.EVENT_BUS.register(this) + ClientWorldLoadCallback.EVENT.register(this) + ClientChunkLoadCallback.EVENT.register(this) } val chunkData = IdentityHashMap>() @@ -86,27 +87,25 @@ object ChunkOverlayManager { } } - @SubscribeEvent - fun handleLoadWorld(event: WorldEvent.Load) = (event.world as? ClientWorld)?.let { world -> - chunkData[world.dimType] = mutableMapOf() - } - - @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) + override fun loadChunk(chunk: WorldChunk) { + chunk[WorldChunk_world]!!.dimType.let { dim -> + val data = chunkData[dim] ?: mutableMapOf().apply { chunkData[dim] = this } + data.let { chunks -> + // check for existence first because Optifine fires a TON of these + if (chunk.pos !in chunks.keys) chunks[chunk.pos] = ChunkOverlayData(layers) + } } } - @SubscribeEvent - fun handleUnloadChunk(event: ChunkEvent.Unload) = (event.world as? ClientWorld)?.let { world -> - chunkData[world.dimType]?.remove(event.chunk.pos) + override fun unloadChunk(chunk: WorldChunk) { + chunk[WorldChunk_world]!!.dimType.let { dim -> + chunkData[dim]?.remove(chunk.pos) + } + } + + override fun loadWorld(world: ClientWorld) { + val dim = world.dimType +// chunkData.keys.forEach { if (it == dim) chunkData[dim] = mutableMapOf() else chunkData.remove(dim)} } } diff --git a/src/main/kotlin/mods/betterfoliage/client/Client.kt b/src/main/kotlin/mods/betterfoliage/client/Client.kt deleted file mode 100644 index 432a3a1..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/Client.kt +++ /dev/null @@ -1,78 +0,0 @@ -package mods.betterfoliage.client - -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.chunk.ChunkOverlayManager -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.integration.* -import mods.betterfoliage.client.render.* -import mods.betterfoliage.client.texture.AsyncGrassDiscovery -import mods.betterfoliage.client.texture.AsyncLeafDiscovery -import mods.betterfoliage.client.texture.LeafParticleRegistry -import mods.octarinecore.client.gui.textComponent -import mods.octarinecore.client.render.RenderDecorator -import mods.octarinecore.client.resource.IConfigChangeListener -import net.minecraft.block.BlockState -import net.minecraft.client.Minecraft -import net.minecraft.util.math.BlockPos -import net.minecraft.util.text.TextFormatting -import net.minecraft.util.text.TranslationTextComponent -import net.minecraftforge.registries.ForgeRegistries -import org.apache.logging.log4j.Level - -/** - * Object responsible for initializing (and holding a reference to) all the infrastructure of the mod - * except for the call hooks. - */ -object Client { - var renderers= emptyList() - var configListeners = emptyList() - - val suppressRenderErrors = mutableSetOf() - - fun init() { - // init renderers - renderers = listOf( - RenderGrass(), - RenderMycelium(), - RenderLeaves(), - RenderCactus(), - RenderLilypad(), - RenderReeds(), - RenderAlgae(), - RenderCoral(), - RenderLog(), - RenderNetherrack(), - RenderConnectedGrass(), - RenderConnectedGrassLog() - ) - - // init other singletons - val singletons = listOf( - BlockConfig, - ChunkOverlayManager, - LeafWindTracker, - RisingSoulTextures - ) - - // init mod integrations - val integrations = listOf( - ShadersModIntegration, - OptifineCustomColors, - ForestryIntegration, - IC2RubberIntegration, - TechRebornRubberIntegration - ) - - LeafParticleRegistry.init() - - // add basic block support instances as last - AsyncLeafDiscovery.init() - AsyncGrassDiscovery.init() - AsyncLogDiscovery.init() - AsyncCactusDiscovery.init() - - configListeners = listOf(renderers, singletons, integrations).flatten().filterIsInstance() - configListeners.forEach { it.onConfigChange() } - } -} - diff --git a/src/main/kotlin/mods/betterfoliage/client/Hooks.kt b/src/main/kotlin/mods/betterfoliage/client/Hooks.kt deleted file mode 100644 index 7c2e9a8..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/Hooks.kt +++ /dev/null @@ -1,112 +0,0 @@ -@file:JvmName("Hooks") -package mods.betterfoliage.client - -import mods.betterfoliage.client.chunk.ChunkOverlayManager -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.render.* -import mods.octarinecore.ThreadLocalDelegate -import mods.octarinecore.client.render.* -import mods.octarinecore.client.render.lighting.DefaultLightingCtx -import mods.octarinecore.common.plus -import net.minecraft.block.Block -import net.minecraft.block.BlockState -import net.minecraft.block.Blocks -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.client.world.ClientWorld -import net.minecraft.util.BlockRenderLayer -import net.minecraft.util.BlockRenderLayer.CUTOUT -import net.minecraft.util.BlockRenderLayer.CUTOUT_MIPPED -import net.minecraft.util.Direction -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.shapes.VoxelShape -import net.minecraft.util.math.shapes.VoxelShapes -import net.minecraft.world.IBlockReader -import net.minecraft.world.IEnviromentBlockReader -import net.minecraft.world.World -import net.minecraftforge.client.model.data.IModelData -import java.util.* - -fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float { - if (Config.enabled && Config.roundLogs.enabled && BlockConfig.logBlocks.matchesClass(state.block)) return Config.roundLogs.dimming.toFloat(); - return original -} - -fun getUseNeighborBrightnessOverride(original: Boolean, state: BlockState): Boolean { - return original || (Config.enabled && Config.roundLogs.enabled && BlockConfig.logBlocks.matchesClass(state.block)); -} - -fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: BlockState, newState: BlockState, flags: Int) { - ChunkOverlayManager.onBlockChange(worldClient, pos) -} - -fun onRandomDisplayTick(block: Block, state: BlockState, world: World, pos: BlockPos, random: Random) { - if (Config.enabled && - Config.risingSoul.enabled && - state.block == Blocks.SOUL_SAND && - world.isAirBlock(pos + up1) && - Math.random() < Config.risingSoul.chance) { - EntityRisingSoulFX(world, pos).addIfValid() - } - - if (Config.enabled && - Config.fallingLeaves.enabled && - BlockConfig.leafBlocks.matchesClass(state.block) && - world.isAirBlock(pos + down1) && - Math.random() < Config.fallingLeaves.chance) { - EntityFallingLeavesFX(world, pos).addIfValid() - } -} - -fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos, dir: Direction): VoxelShape { - if (LogRegistry[state, reader, pos] != null) return VoxelShapes.empty() - return state.func_215702_a(reader, pos, dir) -} - -val lightingCtx by ThreadLocalDelegate { DefaultLightingCtx(BasicBlockCtx(NonNullWorld, BlockPos.ZERO)) } -fun renderWorldBlock(dispatcher: BlockRendererDispatcher, - state: BlockState, - pos: BlockPos, - reader: IEnviromentBlockReader, - buffer: BufferBuilder, - random: Random, - modelData: IModelData, - layer: BlockRenderLayer -): Boolean { - // build context - val blockCtx = CachedBlockCtx(reader, pos) - val renderCtx = RenderCtx(dispatcher, buffer, layer, random) - lightingCtx.reset(blockCtx) - val combinedCtx = CombinedContext(blockCtx, renderCtx, lightingCtx) - - // loop render decorators - val doBaseRender = state.canRenderInLayer(layer) || (layer == targetCutoutLayer && state.canRenderInLayer(otherCutoutLayer)) - Client.renderers.forEach { renderer -> - if (renderer.isEligible(combinedCtx)) { - // render on the block's default layer - // also render on the cutout layer if the renderer requires it - - val doCutoutRender = renderer.renderOnCutout && layer == targetCutoutLayer - val stopRender = renderer.onlyOnCutout && !layer.isCutout - - if ((doBaseRender || doCutoutRender) && !stopRender) { - renderer.render(combinedCtx) - return combinedCtx.hasRendered - } - } - } - - // no render decorators have taken on this block, proceed to normal rendering - combinedCtx.render() - return combinedCtx.hasRendered -} - -fun canRenderInLayerOverride(state: BlockState, layer: BlockRenderLayer) = state.canRenderInLayer(layer) || layer == targetCutoutLayer - -fun canRenderInLayerOverrideOptifine(state: BlockState, optifineReflector: Any?, layerArray: Array) = - canRenderInLayerOverride(state, layerArray[0] as BlockRenderLayer) - -val targetCutoutLayer: BlockRenderLayer get() = if (Minecraft.getInstance().gameSettings.mipmapLevels > 0) CUTOUT_MIPPED else CUTOUT -val otherCutoutLayer: BlockRenderLayer get() = if (Minecraft.getInstance().gameSettings.mipmapLevels > 0) CUTOUT else CUTOUT_MIPPED diff --git a/src/main/kotlin/mods/betterfoliage/client/config/Config.kt b/src/main/kotlin/mods/betterfoliage/client/config/Config.kt deleted file mode 100644 index 8aad302..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/config/Config.kt +++ /dev/null @@ -1,172 +0,0 @@ -package mods.betterfoliage.client.config - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.integration.ShadersModIntegration -import mods.octarinecore.common.config.* -import net.minecraft.util.ResourceLocation -import net.minecraftforge.eventbus.api.SubscribeEvent -import net.minecraftforge.fml.config.ModConfig - -private fun featureEnable() = boolean(true).lang("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) - } - - object shortGrass : ConfigCategory(){ - 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).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") - 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 boolean(true) - 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=0.5, max=1.5, default=0.8).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 : ConfigCategory(){ - val enabled by featureEnable() - val hOffset by double(max=0.25, default=0.1).lang("hOffset") - val flowerChance by int(max=64, default=16, min=0) - } - - object reed : ConfigCategory(){ - 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") - 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 : ConfigCategory(){ - 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") - 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 : ConfigCategory(){ - 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) - 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.6).lang("heightMin") - val heightMax by double(min=0.1, max=1.5, default=0.8).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.05) - 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() - - 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 sand = blocks("sand_default.cfg") - val lilypad = blocks("lilypad_default.cfg") - val cactus = blocks("cactus_default.cfg") - val netherrack = blocks("netherrack_blocks_default.cfg") - - init { BetterFoliageMod.bus.register(this) } - private fun blocks(cfgName: String) = ConfigurableBlockMatcher(BetterFoliage.logDetail, ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) } - private fun models(cfgName: String) = ModelTextureListConfiguration(BetterFoliage.logDetail, 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() - } } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/ForestryIntegration.kt b/src/main/kotlin/mods/betterfoliage/client/integration/ForestryIntegration.kt deleted file mode 100644 index bf06004..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/integration/ForestryIntegration.kt +++ /dev/null @@ -1,134 +0,0 @@ -package mods.betterfoliage.client.integration - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.render.AsyncLogDiscovery -import mods.betterfoliage.client.render.column.ColumnTextureInfo -import mods.betterfoliage.client.render.column.SimpleColumnInfo -import mods.betterfoliage.client.resource.Identifier -import mods.betterfoliage.client.texture.LeafInfo -import mods.betterfoliage.client.texture.defaultRegisterLeaf -import mods.octarinecore.HasLogger -import mods.octarinecore.Map -import mods.octarinecore.ResourceLocation -import mods.octarinecore.String -import mods.octarinecore.client.resource.* -import mods.octarinecore.metaprog.* -import mods.octarinecore.metaprog.ClassRef.Companion.boolean -import net.minecraft.block.BlockState -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.model.ModelBakery -import net.minecraft.resources.IResourceManager -import net.minecraft.util.math.BlockPos -import net.minecraft.world.IBlockReader -import net.minecraftforge.fml.ModList -import org.apache.logging.log4j.Level -import java.util.concurrent.CompletableFuture -import kotlin.collections.component1 -import kotlin.collections.component2 - -val TextureLeaves = ClassRef("forestry.arboriculture.models.TextureLeaves") -val TextureLeaves_leafTextures = FieldRef(TextureLeaves, "leafTextures", Map) -val TextureLeaves_plain = FieldRef(TextureLeaves, "plain", ResourceLocation) -val TextureLeaves_fancy = FieldRef(TextureLeaves, "fancy", ResourceLocation) -val TextureLeaves_pollinatedPlain = FieldRef(TextureLeaves, "pollinatedPlain", ResourceLocation) -val TextureLeaves_pollinatedFancy = FieldRef(TextureLeaves, "pollinatedFancy", ResourceLocation) - - -val TileLeaves = ClassRef("forestry.arboriculture.tiles.TileLeaves") -val TileLeaves_getLeaveSprite = MethodRef(TileLeaves, "getLeaveSprite", ResourceLocation, boolean) -val PropertyWoodType = ClassRef("forestry.arboriculture.blocks.PropertyWoodType") -val IWoodType = ClassRef("forestry.api.arboriculture.IWoodType") -val IWoodType_barkTex = MethodRef(IWoodType, "getBarkTexture", String) -val IWoodType_heartTex = MethodRef(IWoodType, "getHeartTexture", String) - -val PropertyTreeType = ClassRef("forestry.arboriculture.blocks.PropertyTreeType") -val IAlleleTreeSpecies = ClassRef("forestry.api.arboriculture.IAlleleTreeSpecies") -val ILeafSpriteProvider = ClassRef("forestry.api.arboriculture.ILeafSpriteProvider") -val TreeDefinition = ClassRef("forestry.arboriculture.genetics.TreeDefinition") - -val IAlleleTreeSpecies_getLeafSpriteProvider = MethodRef(IAlleleTreeSpecies, "getLeafSpriteProvider", ILeafSpriteProvider) -val TreeDefinition_species = FieldRef(TreeDefinition, "species", IAlleleTreeSpecies) -val ILeafSpriteProvider_getSprite = MethodRef(ILeafSpriteProvider, "getSprite", ResourceLocation, boolean, boolean) - -object ForestryIntegration { - init { - if (ModList.get().isLoaded("forestry") && allAvailable(TileLeaves_getLeaveSprite, IAlleleTreeSpecies_getLeafSpriteProvider, ILeafSpriteProvider_getSprite)) { - } - } -} - -object ForestryLeafDiscovery : HasLogger, AsyncSpriteProvider, ModelRenderRegistry { - override val logger = BetterFoliage.logDetail - var idToValue = emptyMap() - - override fun get(state: BlockState, world: IBlockReader, pos: BlockPos): LeafInfo? { - // check variant property (used in decorative leaves) - state.values.entries.find { - PropertyTreeType.isInstance(it.key) && TreeDefinition.isInstance(it.value) - } ?.let { - val species = it.value[TreeDefinition_species] - val spriteProvider = species[IAlleleTreeSpecies_getLeafSpriteProvider]() - val textureLoc = spriteProvider[ILeafSpriteProvider_getSprite](false, Minecraft.isFancyGraphicsEnabled()) - return idToValue[textureLoc] - } - - // extract leaf texture information from TileEntity - val tile = world.getTileEntity(pos) ?: return null - if (!TileLeaves.isInstance(tile)) return null - val textureLoc = tile[TileLeaves_getLeaveSprite](Minecraft.isFancyGraphicsEnabled()) - return idToValue[textureLoc] - } - - override fun setup(manager: IResourceManager, bakeryF: CompletableFuture, atlasFuture: AtlasFuture): StitchPhases { - val futures = mutableMapOf>() - - return StitchPhases( - discovery = bakeryF.thenRunAsync { - val allLeaves = TextureLeaves_leafTextures.getStatic() - allLeaves.entries.forEach { (type, leaves) -> - log("base leaf type $type") - leaves!! - listOf( - leaves[TextureLeaves_plain], leaves[TextureLeaves_pollinatedPlain], - leaves[TextureLeaves_fancy], leaves[TextureLeaves_pollinatedFancy] - ).forEach { textureLocation -> - futures[textureLocation] = defaultRegisterLeaf(textureLocation, atlasFuture) - } - } - }, - cleanup = atlasFuture.runAfter { - idToValue = futures.mapValues { it.value.get() } - } - ) - } -} - -object ForestryLogDiscovery : ModelDiscovery() { - override val logger = BetterFoliage.logDetail - override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? { - // respect class list to avoid triggering on fences, stairs, etc. - if (!BlockConfig.logBlocks.matchesClass(ctx.state.block)) return null - - // find wood type property - val woodType = ctx.state.values.entries.find { - PropertyWoodType.isInstance(it.key) && IWoodType.isInstance(it.value) - } - if (woodType != null) { - logger.log(Level.DEBUG, "ForestryLogRegistry: block state ${ctx.state}") - logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}") - - // get texture names for wood type - val bark = woodType.value[IWoodType_barkTex]() - val heart = woodType.value[IWoodType_heartTex]() - logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]") - - val heartSprite = atlas.sprite(heart) - val barkSprite = atlas.sprite(bark) - return atlas.mapAfter { - SimpleColumnInfo(AsyncLogDiscovery.getAxis(ctx.state), heartSprite.get(), heartSprite.get(), listOf(barkSprite.get())) - } - } - return null - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt b/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt deleted file mode 100644 index 107abb9..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt +++ /dev/null @@ -1,141 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.texture.LeafParticleRegistry -import mods.betterfoliage.client.texture.LeafRegistry -import mods.octarinecore.PI2 -import mods.octarinecore.client.render.AbstractEntityFX -import mods.octarinecore.client.render.lighting.HSB -import mods.octarinecore.common.Double3 -import mods.octarinecore.minmax -import mods.octarinecore.random -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.BufferBuilder -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 org.lwjgl.opengl.GL11 -import java.util.* -import kotlin.math.abs -import kotlin.math.cos -import kotlin.math.sin - -const val rotationFactor = PI2.toFloat() / 64.0f - -class EntityFallingLeavesFX( - world: World, pos: BlockPos -) : AbstractEntityFX( - world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5 -) { - - companion object { - @JvmStatic val biomeBrightnessMultiplier = 0.5f - } - - var particleRot = rand.nextInt(64) - var rotPositive = true - val isMirrored = (rand.nextInt() and 1) == 1 - var wasCollided = false - - init { - maxAge = MathHelper.floor(random(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0) - motionY = -Config.fallingLeaves.speed - - particleScale = Config.fallingLeaves.size.toFloat() * 0.1f - - val state = world.getBlockState(pos) - val leafInfo = LeafRegistry[state, world, pos] - val blockColor = Minecraft.getInstance().blockColors.getColor(state, world, pos, 0) - if (leafInfo != null) { - sprite = leafInfo.particleTextures[rand.nextInt(1024)] - calculateParticleColor(leafInfo.averageColor, blockColor) - } else { - sprite = LeafParticleRegistry["default"][rand.nextInt(1024)] - setColor(blockColor) - } - } - - override val isValid: Boolean get() = (sprite != null) - - override fun update() { - if (rand.nextFloat() > 0.95f) rotPositive = !rotPositive - if (age > maxAge - 20) particleAlpha = 0.05f * (maxAge - age) - - if (onGround || wasCollided) { - velocity.setTo(0.0, 0.0, 0.0) - if (!wasCollided) { - age = Math.max(age, maxAge - 20) - wasCollided = true - } - } else { - velocity.setTo(cos[particleRot], 0.0, sin[particleRot]).mul(Config.fallingLeaves.perturb) - .add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed) - particleRot = (particleRot + (if (rotPositive) 1 else -1)) and 63 - particleAngle = rotationFactor * particleRot.toFloat() - } - } - - override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) { - if (Config.fallingLeaves.opacityHack) GL11.glDepthMask(true) - renderParticleQuad(worldRenderer, partialTickTime, rotation = particleRot, isMirrored = isMirrored) - } - - fun calculateParticleColor(textureAvgColor: Int, blockColor: Int) { - val texture = HSB.fromColor(textureAvgColor) - val block = HSB.fromColor(blockColor) - - val weightTex = texture.saturation / (texture.saturation + block.saturation) - val weightBlock = 1.0f - weightTex - - // avoid circular average for hue for performance reasons - // one of the color components should dominate anyway - val particle = HSB( - weightTex * texture.hue + weightBlock * block.hue, - weightTex * texture.saturation + weightBlock * block.saturation, - weightTex * texture.brightness + weightBlock * block.brightness * biomeBrightnessMultiplier - ) - setColor(particle.asColor) - } -} - -object LeafWindTracker { - var random = Random() - val target = Double3.zero - val current = Double3.zero - var nextChange: Long = 0 - - init { - MinecraftForge.EVENT_BUS.register(this) - } - - fun changeWind(world: World) { - nextChange = world.worldInfo.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) - target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed) - } - - @SubscribeEvent - fun handleWorldTick(event: TickEvent.ClientTickEvent) { - if (event.phase == TickEvent.Phase.START) Minecraft.getInstance().world?.let { world -> - // change target wind speed - if (world.worldInfo.dayTime >= nextChange) changeWind(world) - - // change current wind speed - val changeRate = if (world.isRaining) 0.015 else 0.005 - current.add( - (target.x - current.x).minmax(-changeRate, changeRate), - 0.0, - (target.z - current.z).minmax(-changeRate, changeRate) - ) - } - } - - @SubscribeEvent - fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isRemote) changeWind(event.world.world) } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt b/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt deleted file mode 100644 index c20bb42..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt +++ /dev/null @@ -1,73 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.client.render.AbstractEntityFX -import mods.octarinecore.client.resource.Atlas -import mods.octarinecore.client.resource.ResourceHandler -import mods.octarinecore.common.Double3 -import mods.octarinecore.forEachPairIndexed -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.util.ResourceLocation -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.MathHelper -import net.minecraft.world.World -import org.apache.logging.log4j.Level.DEBUG -import java.util.* - -class EntityRisingSoulFX(world: World, pos: BlockPos) : -AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5) { - - val particleTrail: Deque = LinkedList() - val initialPhase = rand.nextInt(64) - - init { - motionY = 0.1 - particleGravity = 0.0f - sprite = RisingSoulTextures.headIcons[rand.nextInt(256)] - maxAge = MathHelper.floor((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0) - } - - override val isValid: Boolean get() = true - - override fun update() { - val phase = (initialPhase + age) % 64 - velocity.setTo(cos[phase] * Config.risingSoul.perturb, 0.1, sin[phase] * Config.risingSoul.perturb) - - particleTrail.addFirst(currentPos.copy()) - while (particleTrail.size > Config.risingSoul.trailLength) particleTrail.removeLast() - - if (!Config.enabled) setExpired() - } - - override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) { - var alpha = Config.risingSoul.opacity.toFloat() - if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f - - renderParticleQuad(worldRenderer, partialTickTime, - size = Config.risingSoul.headSize * 0.25, - alpha = alpha - ) - - var scale = Config.risingSoul.trailSize * 0.25 - particleTrail.forEachPairIndexed { idx, current, previous -> - scale *= Config.risingSoul.sizeDecay - alpha *= Config.risingSoul.opacityDecay.toFloat() - if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime, - currentPos = current, - prevPos = previous, - size = scale, - alpha = alpha, - icon = RisingSoulTextures.trackIcon - ) - } - } -} - -object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus, targetAtlas = Atlas.PARTICLES) { - val headIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "rising_soul_$idx") } - val trackIcon by sprite(Identifier(BetterFoliageMod.MOD_ID, "soul_track")) -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt b/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt deleted file mode 100644 index 6dc851f..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt +++ /dev/null @@ -1,146 +0,0 @@ -@file:JvmName("ModelColumn") -package mods.betterfoliage.client.render - -import mods.betterfoliage.client.config.Config -import mods.octarinecore.client.render.* -import mods.octarinecore.client.render.lighting.* -import mods.octarinecore.common.Double3 -import mods.octarinecore.exchange -import net.minecraft.util.Direction.* - -/** Weight of the same-side AO values on the outer edges of the 45deg chamfered column faces. */ -const val chamferAffinity = 0.9f - -/** Amount to shrink column extension bits to stop Z-fighting. */ -val zProtectionScale: Double3 get() = Double3(Config.roundLogs.zProtection, 1.0, Config.roundLogs.zProtection) - -fun Model.columnSide(radius: Double, yBottom: Double, yTop: Double, transform: (Quad) -> Quad = { it }) { - val halfRadius = radius * 0.5 - listOf( - verticalRectangle(x1 = 0.0, z1 = 0.5, x2 = 0.5 - radius, z2 = 0.5, yBottom = yBottom, yTop = yTop) - .clampUV(minU = 0.0, maxU = 0.5 - radius) - .setAoShader(faceOrientedInterpolate(overrideFace = SOUTH)) - .setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 1 || vi == 2}), - - verticalRectangle(x1 = 0.5 - radius, z1 = 0.5, x2 = 0.5 - halfRadius, z2 = 0.5 - halfRadius, yBottom = yBottom, yTop = yTop) - .clampUV(minU = 0.5 - radius) - .setAoShader( - faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming.toFloat())) - ) - .setAoShader( - faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming.toFloat())), - predicate = { v, vi -> vi == 1 || vi == 2} - ) - ).forEach { transform(it.setFlatShader(FaceFlat(SOUTH))).add() } - - listOf( - verticalRectangle(x1 = 0.5 - halfRadius, z1 = 0.5 - halfRadius, x2 = 0.5, z2 = 0.5 - radius, yBottom = yBottom, yTop = yTop) - .clampUV(maxU = radius - 0.5) - .setAoShader( - faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming.toFloat()))) - .setAoShader( - faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming.toFloat())), - predicate = { v, vi -> vi == 0 || vi == 3} - ), - - verticalRectangle(x1 = 0.5, z1 = 0.5 - radius, x2 = 0.5, z2 = 0.0, yBottom = yBottom, yTop = yTop) - .clampUV(minU = radius - 0.5, maxU = 0.0) - .setAoShader(faceOrientedInterpolate(overrideFace = EAST)) - .setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 0 || vi == 3}) - ).forEach { transform(it.setFlatShader(FaceFlat(EAST))).add() } - - quads.exchange(1, 2) -} - -/** - * Create a model of the side of a square column quadrant. - * - * @param[transform] transformation to apply to the model - */ -fun Model.columnSideSquare(yBottom: Double, yTop: Double, transform: (Quad) -> Quad = { it }) { - listOf( - verticalRectangle(x1 = 0.0, z1 = 0.5, x2 = 0.5, z2 = 0.5, yBottom = yBottom, yTop = yTop) - .clampUV(minU = 0.0) - .setAoShader(faceOrientedInterpolate(overrideFace = SOUTH)) - .setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 1 || vi == 2}), - - verticalRectangle(x1 = 0.5, z1 = 0.5, x2 = 0.5, z2 = 0.0, yBottom = yBottom, yTop = yTop) - .clampUV(maxU = 0.0) - .setAoShader(faceOrientedInterpolate(overrideFace = EAST)) - .setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 0 || vi == 3}) - ).forEach { - transform(it.setFlatShader(faceOrientedAuto(corner = cornerFlat))).add() - } -} - -/** - * Create a model of the top lid of a chamfered column quadrant. - * - * @param[radius] the chamfer radius - * @param[transform] transformation to apply to the model - */ -fun Model.columnLid(radius: Double, transform: (Quad)->Quad = { it }) { - val v1 = Vertex(Double3(0.0, 0.5, 0.0), UV(0.0, 0.0)) - val v2 = Vertex(Double3(0.0, 0.5, 0.5), UV(0.0, 0.5)) - val v3 = Vertex(Double3(0.5 - radius, 0.5, 0.5), UV(0.5 - radius, 0.5)) - val v4 = Vertex(Double3(0.5 - radius * 0.5, 0.5, 0.5 - radius * 0.5), UV(0.5, 0.5)) - val v5 = Vertex(Double3(0.5, 0.5, 0.5 - radius), UV(0.5, 0.5 - radius)) - val v6 = Vertex(Double3(0.5, 0.5, 0.0), UV(0.5, 0.0)) - - val q1 = Quad(v1, v2, v3, v4).setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y))) - .transformVI { vertex, idx -> vertex.copy(aoShader = when(idx) { - 0 -> FaceCenter(UP) - 1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0) - else -> vertex.aoShader - })} - .cycleVertices(if (Config.nVidia) 0 else 1) - val q2 = Quad(v1, v4, v5, v6).setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y))) - .transformVI { vertex, idx -> vertex.copy(aoShader = when(idx) { - 0 -> FaceCenter(UP) - 3 -> EdgeInterpolateFallback(UP, EAST, 0.0) - else -> vertex.aoShader - })} - .cycleVertices(if (Config.nVidia) 0 else 1) - listOf(q1, q2).forEach { transform(it.setFlatShader(FaceFlat(UP))).add() } -} - -/** - * Create a model of the top lid of a square column quadrant. - * - * @param[transform] transformation to apply to the model - */ -fun Model.columnLidSquare(transform: (Quad)-> Quad = { it }) { - transform( - horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = 0.5) - .transformVI { vertex, idx -> vertex.copy(uv = UV(vertex.xyz.x, vertex.xyz.z), aoShader = when(idx) { - 0 -> FaceCenter(UP) - 1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0) - 2 -> CornerSingleFallback(UP, SOUTH, EAST, UP) - else -> EdgeInterpolateFallback(UP, EAST, 0.0) - }) } - .setFlatShader(FaceFlat(UP)) - ).add() -} - -/** - * Transform a chamfered side quadrant model of a column that extends from the top of the block. - * (clamp UV coordinates, apply some scaling to avoid Z-fighting). - * - * @param[size] amount that the model extends from the top - */ -fun topExtension(size: Double) = { q: Quad -> - q.clampUV(minV = 0.5 - size).transformVI { vertex, idx -> - if (idx < 2) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale) - } -} -/** - * Transform a chamfered side quadrant model of a column that extends from the bottom of the block. - * (clamp UV coordinates, apply some scaling to avoid Z-fighting). - * - * @param[size] amount that the model extends from the bottom - */ -fun bottomExtension(size: Double) = { q: Quad -> - q.clampUV(maxV = -0.5 + size).transformVI { vertex, idx -> - if (idx > 1) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale) - } -} diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt deleted file mode 100644 index d8c78f1..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt +++ /dev/null @@ -1,42 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.integration.ShadersModIntegration -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.render.RenderDecorator -import net.minecraft.block.material.Material -import net.minecraft.tags.BlockTags -import net.minecraft.util.ResourceLocation -import net.minecraft.world.biome.Biome -import org.apache.logging.log4j.Level.DEBUG - -class RenderAlgae : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - val noise = simplexNoise() - - val algaeIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_algae_$idx") } - val algaeModels = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax)(idx) } - - override fun isEligible(ctx: CombinedContext) = - Config.enabled && Config.algae.enabled && - ctx.state(up2).material == Material.WATER && - ctx.state(up1).material == Material.WATER && - BlockTags.DIRT_LIKE.contains(ctx.state.block) && - ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH || it == Biome.Category.RIVER } && - noise[ctx.pos] < Config.algae.population - - override fun render(ctx: CombinedContext) { - ctx.render() - if (!ctx.isCutout) return - val rand = ctx.semiRandomArray(3) - ShadersModIntegration.grass(ctx, Config.algae.shaderWind) { - ctx.render( - algaeModels[rand[2]], - icon = { _, qi, _ -> algaeIcons[rand[qi and 1]] } - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt deleted file mode 100644 index c9978e4..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt +++ /dev/null @@ -1,104 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.render.column.ColumnTextureInfo -import mods.betterfoliage.client.render.column.SimpleColumnInfo -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.client.render.* -import mods.octarinecore.client.render.lighting.* -import mods.octarinecore.client.resource.* -import mods.octarinecore.common.Rotation -import mods.octarinecore.common.config.ModelTextureList -import mods.octarinecore.common.config.SimpleBlockMatcher -import net.minecraft.block.BlockState -import net.minecraft.block.CactusBlock -import net.minecraft.util.Direction.* -import org.apache.logging.log4j.Level.DEBUG -import java.util.concurrent.CompletableFuture - -object AsyncCactusDiscovery : ConfigurableModelDiscovery() { - override val logger = BetterFoliage.logDetail - override val matchClasses = SimpleBlockMatcher(CactusBlock::class.java) - override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side")) - override fun processModel(state: BlockState, textures: List, atlas: AtlasFuture): CompletableFuture? { - val sprites = textures.map { atlas.sprite(Identifier(it)) } - return atlas.mapAfter { - SimpleColumnInfo( - Axis.Y, - sprites[0].get(), - sprites[1].get(), - sprites.drop(2).map { it.get() } - ) - } - } - - fun init() { - BetterFoliage.blockSprites.providers.add(this) - } -} - -class RenderCactus : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - val cactusStemRadius = 0.4375 - val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] } - - val iconCross by sprite(Identifier(BetterFoliageMod.MOD_ID, "blocks/better_cactus")) - val iconArm = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx") } - - val modelStem = model { - horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5) - .scaleUV(cactusStemRadius * 2.0) - .let { listOf(it.flipped.move(1.0 to DOWN), it) } - .forEach { it.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y), edge = null)).add() } - - verticalRectangle(x1 = -0.5, z1 = cactusStemRadius, x2 = 0.5, z2 = cactusStemRadius, yBottom = -0.5, yTop = 0.5) - .setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y), edge = null)) - .toCross(UP).addAll() - } - val modelCross = modelSet(64) { modelIdx -> - verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41) - .setAoShader(edgeOrientedAuto(corner = cornerAoMaxGreen)) - .scale(1.4) - .transformV { v -> - val perturb = xzDisk(modelIdx) * Config.cactus.sizeVariation - Vertex(v.xyz + (if (v.uv.u < 0.0) perturb else -perturb), v.uv, v.aoShader) - } - .toCross(UP).addAll() - } - val modelArm = modelSet(64) { modelIdx -> - verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0) - .scale(Config.cactus.size).move(0.5 to UP) - - .setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y), edge = null)) - .toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll() - } - - override fun isEligible(ctx: CombinedContext): Boolean = - Config.enabled && Config.cactus.enabled && - AsyncCactusDiscovery[ctx] != null - - override val onlyOnCutout get() = true - - override fun render(ctx: CombinedContext) { - val icons = AsyncCactusDiscovery[ctx]!! - - ctx.render( - modelStem.model, - icon = { ctx, qi, q -> when(qi) { - 0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q) - } } - ) - ctx.render( - modelCross[ctx.semiRandom(0)], - icon = { _, _, _ -> iconCross } - ) - - ctx.render( - modelArm[ctx.semiRandom(1)], - cactusArmRotation[ctx.semiRandom(2) % 4], - icon = { _, _, _ -> iconArm[ctx.semiRandom(3)] } - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt deleted file mode 100644 index 9d5515b..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt +++ /dev/null @@ -1,45 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.texture.GrassRegistry -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.render.RenderDecorator -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.horizontalDirections -import mods.octarinecore.common.offset -import net.minecraft.tags.BlockTags - -class RenderConnectedGrass : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - override fun isEligible(ctx: CombinedContext) = - Config.enabled && Config.connectedGrass.enabled && - BlockTags.DIRT_LIKE.contains(ctx.state.block) && - GrassRegistry[ctx, up1] != null && - (Config.connectedGrass.snowEnabled || !ctx.state(up2).isSnow) - - override fun render(ctx: CombinedContext) { - // if the block sides are not visible anyway, render normally - if (horizontalDirections.none { ctx.shouldSideBeRendered(it) }) { - ctx.render() - } else { - ctx.exchange(Int3.zero, up1).exchange(up1, up2).render() - } - } -} - -class RenderConnectedGrassLog : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - override fun isEligible(ctx: CombinedContext) = - Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass && - BlockTags.DIRT_LIKE.contains(ctx.state.block) && - LogRegistry[ctx, up1] != null - - override fun render(ctx: CombinedContext) { - val grassDir = horizontalDirections.find { GrassRegistry[ctx, it.offset] != null } - if (grassDir == null) { - ctx.render() - } else { - ctx.exchange(Int3.zero, grassDir.offset).render() - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt deleted file mode 100644 index c5e3276..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt +++ /dev/null @@ -1,63 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.config.Config -import mods.octarinecore.client.render.* -import mods.octarinecore.client.render.lighting.* -import mods.octarinecore.common.allDirections -import mods.octarinecore.random -import net.minecraft.block.material.Material -import net.minecraft.util.Direction.Axis -import net.minecraft.util.Direction.UP -import net.minecraft.util.ResourceLocation -import net.minecraft.world.biome.Biome -import org.apache.logging.log4j.Level.DEBUG - -class RenderCoral : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - val noise = simplexNoise() - - val coralIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_coral_$idx") } - val crustIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx") } - val coralModels = modelSet(64) { modelIdx -> - verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0) - .scale(Config.coral.size).move(0.5 to UP) - .toCross(UP) { it.move(xzDisk(modelIdx) * Config.coral.hOffset) }.addAll() - - val separation = random(0.01, Config.coral.vOffset) - horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0) - .scale(Config.coral.crustSize).move(0.5 + separation to UP).add() - - transformQ { - it.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y))) - .setFlatShader(faceOrientedAuto(overrideFace = UP, corner = cornerFlat)) - } - } - - override fun isEligible(ctx: CombinedContext) = - Config.enabled && Config.coral.enabled && - (ctx.state(up2).material == Material.WATER || Config.coral.shallowWater) && - ctx.state(up1).material == Material.WATER && - BlockConfig.sand.matchesClass(ctx.state.block) && - ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH } && - noise[ctx.pos] < Config.coral.population - - override fun render(ctx: CombinedContext) { - val baseRender = ctx.render() - if (!ctx.isCutout) return - - allDirections.forEachIndexed { idx, face -> - if (ctx.state(face).material == Material.WATER && ctx.semiRandom(idx) < Config.coral.chance) { - var variation = ctx.semiRandom(6) - ctx.render( - coralModels[variation++], - rotationFromUp[idx], - icon = { _, qi, _ -> if (qi == 4) crustIcons[variation] else coralIcons[variation + (qi and 1)] } - ) - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt deleted file mode 100644 index bf7f529..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt +++ /dev/null @@ -1,102 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.integration.OptifineCustomColors -import mods.betterfoliage.client.integration.ShadersModIntegration -import mods.betterfoliage.client.resource.Identifier -import mods.betterfoliage.client.texture.GeneratedGrass -import mods.betterfoliage.client.texture.GrassRegistry -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.render.Model -import mods.octarinecore.client.render.RenderDecorator -import mods.octarinecore.client.render.fullCube -import mods.octarinecore.client.render.lighting.cornerAo -import mods.octarinecore.client.render.lighting.cornerFlat -import mods.octarinecore.client.render.lighting.faceOrientedAuto -import mods.octarinecore.common.Double3 -import mods.octarinecore.common.allDirections -import mods.octarinecore.random -import net.minecraft.tags.BlockTags -import net.minecraft.util.Direction.* -import org.apache.logging.log4j.Level.DEBUG - -class RenderGrass : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - companion object { - @JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx -> - verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5, - yTop = 0.5 + random(heightMin, heightMax) - ) - .setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y))) - .setFlatShader(faceOrientedAuto(overrideFace = UP, corner = cornerFlat)) - .toCross(UP) { it.move(xzDisk(modelIdx) * Config.shortGrass.hOffset) }.addAll() - } - } - - val noise = simplexNoise() - - val normalIcons = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx") } - val snowedIcons = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_grass_snowed_$idx") } - val normalGenIcon by sprite { GeneratedGrass(sprite = "minecraft:blocks/tallgrass", isSnowed = false).register(BetterFoliage.asyncPack) } - val snowedGenIcon by sprite { GeneratedGrass(sprite = "minecraft:blocks/tallgrass", isSnowed = true).register(BetterFoliage.asyncPack) } - - val grassModels = modelSet(64) { idx -> grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) } - - override fun isEligible(ctx: CombinedContext) = - Config.enabled && - (Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) && - GrassRegistry[ctx] != null - - override val onlyOnCutout get() = true - - override fun render(ctx: CombinedContext) { - val isConnected = BlockTags.DIRT_LIKE.contains(ctx.state(DOWN).block) || GrassRegistry[ctx, down1] != null - val isSnowed = ctx.state(UP).isSnow - val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled) - - val grass = GrassRegistry[ctx]!! - val blockColor = OptifineCustomColors.getBlockColor(ctx) - - if (connectedGrass) { - // check occlusion - val isVisible = allDirections.map { ctx.shouldSideBeRendered(it) } - - // render full grass block - ctx.render( - fullCube, - quadFilter = { qi, _ -> isVisible[qi] }, - icon = { _, _, _ -> grass.grassTopTexture }, - postProcess = { ctx, _, _, _, _ -> - rotateUV(2) - if (isSnowed) { - if (!ctx.aoEnabled) setGrey(1.4f) - } else if (ctx.aoEnabled && grass.overrideColor == null) multiplyColor(blockColor) - } - ) - } else { - ctx.render() - } - - if (!Config.shortGrass.grassEnabled) return - if (isSnowed && !Config.shortGrass.snowEnabled) return - if (ctx.offset(UP).isNormalCube) return - if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return - - // render grass quads - val iconset = if (isSnowed) snowedIcons else normalIcons - val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon - val rand = ctx.semiRandomArray(2) - - ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) { - ctx.render( - grassModels[rand[0]], - translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero), - icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen else iconset[rand[qi and 1]] }, - postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) } - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt deleted file mode 100644 index 9c710d8..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt +++ /dev/null @@ -1,78 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.integration.OptifineCustomColors -import mods.betterfoliage.client.integration.ShadersModIntegration -import mods.betterfoliage.client.resource.Identifier -import mods.betterfoliage.client.texture.LeafRegistry -import mods.octarinecore.PI2 -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.render.RenderDecorator -import mods.octarinecore.client.render.lighting.FlatOffset -import mods.octarinecore.client.render.lighting.cornerAoMaxGreen -import mods.octarinecore.client.render.lighting.edgeOrientedAuto -import mods.octarinecore.common.Double3 -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.allDirections -import mods.octarinecore.common.vec -import mods.octarinecore.random -import net.minecraft.util.Direction.UP -import java.lang.Math.cos -import java.lang.Math.sin - -class RenderLeaves : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - val leavesModel = model { - verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41) - .setAoShader(edgeOrientedAuto(corner = cornerAoMaxGreen)) - .setFlatShader(FlatOffset(Int3.zero)) - .scale(Config.leaves.size) - .toCross(UP).addAll() - } - val snowedIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx") } - - val perturbs = vectorSet(64) { idx -> - val angle = PI2 * idx / 64.0 - Double3(cos(angle), 0.0, sin(angle)) * Config.leaves.hOffset + - UP.vec * random(-1.0, 1.0) * Config.leaves.vOffset - } - - override fun isEligible(ctx: CombinedContext) = - Config.enabled && - Config.leaves.enabled && - LeafRegistry[ctx] != null && - !(Config.leaves.hideInternal && allDirections.all { ctx.offset(it).isNormalCube } ) - - override val onlyOnCutout get() = true - - override fun render(ctx: CombinedContext) { - val isSnowed = ctx.state(UP).isSnow - val leafInfo = LeafRegistry[ctx]!! - val blockColor = OptifineCustomColors.getBlockColor(ctx) - - ctx.render(force = true) - - ShadersModIntegration.leaves(ctx) { - val rand = ctx.semiRandomArray(2) - (if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation -> - ctx.render( - leavesModel.model, - rotation, - translation = ctx.blockCenter + perturbs[rand[0]], - icon = { _, _, _ -> leafInfo.roundLeafTexture }, - postProcess = { _, _, _, _, _ -> - rotateUV(rand[1]) - multiplyColor(blockColor) - } - ) - } - if (isSnowed && Config.leaves.snowEnabled) ctx.render( - leavesModel.model, - translation = ctx.blockCenter + perturbs[rand[0]], - icon = { _, _, _ -> snowedIcon[rand[1]] }, - postProcess = whitewash - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt deleted file mode 100644 index c2e4376..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt +++ /dev/null @@ -1,59 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.integration.ShadersModIntegration -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.render.RenderDecorator -import mods.octarinecore.client.render.lighting.FlatOffsetNoColor -import mods.octarinecore.common.Int3 -import net.minecraft.util.Direction.DOWN -import net.minecraft.util.Direction.UP -import org.apache.logging.log4j.Level.DEBUG - -class RenderLilypad : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - val rootModel = model { - verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5) - .setFlatShader(FlatOffsetNoColor(Int3.zero)) - .toCross(UP).addAll() - } - val flowerModel = model { - verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0) - .scale(0.5).move(0.5 to DOWN) - .setFlatShader(FlatOffsetNoColor(Int3.zero)) - .toCross(UP).addAll() - } - val rootIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_roots_$idx") } - val flowerIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_flower_$idx") } - val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset } - - override fun isEligible(ctx: CombinedContext): Boolean = - Config.enabled && Config.lilypad.enabled && - BlockConfig.lilypad.matchesClass(ctx.state.block) - - override fun render(ctx: CombinedContext) { - ctx.render() - - val rand = ctx.semiRandomArray(5) - ShadersModIntegration.grass(ctx) { - ctx.render( - rootModel.model, - translation = ctx.blockCenter.add(perturbs[rand[2]]), - forceFlat = true, - icon = { ctx, qi, q -> rootIcon[rand[qi and 1]] } - ) - } - - if (rand[3] < Config.lilypad.flowerChance) ctx.render( - flowerModel.model, - translation = ctx.blockCenter.add(perturbs[rand[4]]), - forceFlat = true, - icon = { _, _, _ -> flowerIcon[rand[0]] } - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt deleted file mode 100644 index 7919d34..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt +++ /dev/null @@ -1,86 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.chunk.ChunkOverlayManager -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.render.column.AbstractRenderColumn -import mods.betterfoliage.client.render.column.ColumnRenderLayer -import mods.betterfoliage.client.render.column.ColumnTextureInfo -import mods.betterfoliage.client.render.column.SimpleColumnInfo -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.resource.* -import mods.octarinecore.common.config.ConfigurableBlockMatcher -import mods.octarinecore.common.config.ModelTextureList -import mods.octarinecore.tryDefault -import net.minecraft.block.BlockState -import net.minecraft.block.LogBlock -import net.minecraft.util.Direction.Axis -import org.apache.logging.log4j.Level -import java.util.concurrent.CompletableFuture - -class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - override val renderOnCutout: Boolean get() = false - - override fun isEligible(ctx: CombinedContext) = - Config.enabled && Config.roundLogs.enabled && - LogRegistry[ctx] != null - - override val overlayLayer = RoundLogOverlayLayer() - override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular - override val radiusSmall: Double get() = Config.roundLogs.radiusSmall - override val radiusLarge: Double get() = Config.roundLogs.radiusLarge - init { - ChunkOverlayManager.layers.add(overlayLayer) - } -} - -class RoundLogOverlayLayer : ColumnRenderLayer() { - override val registry: ModelRenderRegistry get() = LogRegistry - override val blockPredicate = { state: BlockState -> BlockConfig.logBlocks.matchesClass(state.block) } - - override val connectSolids: Boolean get() = Config.roundLogs.connectSolids - override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect - override val defaultToY: Boolean get() = Config.roundLogs.defaultY -} - -object LogRegistry : ModelRenderRegistryRoot() - -object AsyncLogDiscovery : ConfigurableModelDiscovery() { - override val logger = BetterFoliage.logDetail - override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks - override val modelTextures: List get() = BlockConfig.logModels.modelList - - override fun processModel(state: BlockState, textures: List, atlas: AtlasFuture): CompletableFuture { - val axis = getAxis(state) - logger.log(Level.DEBUG, "$logName: axis $axis") - val spriteList = textures.map { atlas.sprite(Identifier(it)) } - return atlas.mapAfter { - SimpleColumnInfo( - axis, - spriteList[0].get(), - spriteList[1].get(), - spriteList.drop(2).map { it.get() } - ) - } - } - - 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() - return when (axis) { - "x" -> Axis.X - "y" -> Axis.Y - "z" -> Axis.Z - else -> null - } - } - - fun init() { - LogRegistry.registries.add(this) - BetterFoliage.blockSprites.providers.add(this) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt deleted file mode 100644 index 9b10f98..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt +++ /dev/null @@ -1,42 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.render.RenderDecorator -import mods.octarinecore.client.render.noPost -import mods.octarinecore.common.Double3 -import net.minecraft.util.Direction.UP -import org.apache.logging.log4j.Level.DEBUG - -class RenderMycelium : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - val myceliumIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx") } - val myceliumModel = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) } - - override fun isEligible(ctx: CombinedContext): Boolean { - if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false - return BlockConfig.mycelium.matchesClass(ctx.state.block) - } - - override fun render(ctx: CombinedContext) { - ctx.render() - if (!ctx.isCutout) return - - val isSnowed = ctx.state(UP).isSnow - if (isSnowed && !Config.shortGrass.snowEnabled) return - if (ctx.offset(UP).isNormalCube) return - val rand = ctx.semiRandomArray(2) - - ctx.render( - myceliumModel[rand[0]], - translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero), - icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]] }, - postProcess = if (isSnowed) whitewash else noPost - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt deleted file mode 100644 index 6c8e314..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt +++ /dev/null @@ -1,44 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.client.render.* -import mods.octarinecore.client.render.lighting.* -import mods.octarinecore.random -import net.minecraft.util.Direction.Axis -import net.minecraft.util.Direction.* -import org.apache.logging.log4j.Level.DEBUG - -class RenderNetherrack : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - val netherrackIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx") } - val netherrackModel = modelSet(64) { modelIdx -> - verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yTop = -0.5, - yBottom = -0.5 - random(Config.netherrack.heightMin, Config.netherrack.heightMax)) - .setAoShader(faceOrientedAuto(overrideFace = DOWN, corner = cornerAo(Axis.Y))) - .setFlatShader(faceOrientedAuto(overrideFace = DOWN, corner = cornerFlat)) - .toCross(UP) { it.move(xzDisk(modelIdx) * Config.shortGrass.hOffset) }.addAll() - - } - - override fun isEligible(ctx: CombinedContext): Boolean { - if (!Config.enabled || !Config.netherrack.enabled) return false - return BlockConfig.netherrack.matchesClass(ctx.state.block) - } - - override fun render(ctx: CombinedContext) { - ctx.render() - if (!ctx.isCutout) return - if (ctx.offset(DOWN).isNormalCube) return - - val rand = ctx.semiRandomArray(2) - ctx.render( - netherrackModel[rand[0]], - icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]] } - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt deleted file mode 100644 index 9e1e1c5..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt +++ /dev/null @@ -1,67 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.integration.ShadersModIntegration -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.render.RenderDecorator -import mods.octarinecore.client.render.lighting.FlatOffsetNoColor -import mods.octarinecore.client.resource.CenteredSprite -import mods.octarinecore.random -import net.minecraft.block.material.Material -import net.minecraft.tags.BlockTags -import net.minecraft.util.Direction.UP -import org.apache.logging.log4j.Level.DEBUG - -class RenderReeds : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) { - - val noise = simplexNoise() - val reedIcons = spriteSetTransformed( - check = { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_reed_$idx")}, - register = { CenteredSprite(it).register(BetterFoliage.asyncPack) } - ) - val reedModels = modelSet(64) { modelIdx -> - val height = random(Config.reed.heightMin, Config.reed.heightMax) - val waterline = 0.875f - val vCutLine = 0.5 - waterline / height - listOf( - // below waterline - verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5, yTop = 0.5 + waterline) - .setFlatShader(FlatOffsetNoColor(up1)).clampUV(minV = vCutLine), - - // above waterline - verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5 + waterline, yTop = 0.5 + height) - .setFlatShader(FlatOffsetNoColor(up2)).clampUV(maxV = vCutLine) - ).forEach { - it.clampUV(minU = -0.25, maxU = 0.25) - .toCross(UP) { it.move(xzDisk(modelIdx) * Config.reed.hOffset) }.addAll() - } - } - - override fun isEligible(ctx: CombinedContext) = - Config.enabled && Config.reed.enabled && - ctx.state(up2).material == Material.AIR && - ctx.state(UP).material == Material.WATER && - BlockTags.DIRT_LIKE.contains(ctx.state.block) && - ctx.biome.let { it.downfall > Config.reed.minBiomeRainfall && it.defaultTemperature >= Config.reed.minBiomeTemp } && - noise[ctx.pos] < Config.reed.population - - override val onlyOnCutout get() = false - - override fun render(ctx: CombinedContext) { - ctx.render() - if (!ctx.isCutout) return - - val iconVar = ctx.semiRandom(1) - ShadersModIntegration.grass(ctx, Config.reed.shaderWind) { - ctx.render( - reedModels[ctx.semiRandom(0)], - forceFlat = true, - icon = { _, _, _ -> reedIcons[iconVar] } - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt b/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt deleted file mode 100644 index 78f5aff..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt +++ /dev/null @@ -1,65 +0,0 @@ -@file:JvmName("Utils") -package mods.betterfoliage.client.render - -import mods.octarinecore.PI2 -import mods.octarinecore.client.render.Model -import mods.octarinecore.client.render.lighting.PostProcessLambda -import mods.octarinecore.client.render.Quad -import mods.octarinecore.common.Double3 -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.Rotation -import mods.octarinecore.common.times -import net.minecraft.block.BlockState -import net.minecraft.block.material.Material -import net.minecraft.util.BlockRenderLayer -import net.minecraft.util.Direction -import net.minecraft.util.Direction.* -import kotlin.math.cos -import kotlin.math.sin - -val up1 = Int3(1 to UP) -val up2 = Int3(2 to UP) -val down1 = Int3(1 to DOWN) -val snowOffset = UP * 0.0625 - -val normalLeavesRot = arrayOf(Rotation.identity) -val denseLeavesRot = arrayOf(Rotation.identity, Rotation.rot90[EAST.ordinal], Rotation.rot90[SOUTH.ordinal]) - -val whitewash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.4f) } -val greywash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.0f) } - -val BlockState.isSnow: Boolean get() = material.let { it == Material.SNOW } - -fun Quad.toCross(rotAxis: Direction, trans: (Quad)->Quad) = - (0..3).map { rotIdx -> - trans(rotate(Rotation.rot90[rotAxis.ordinal] * rotIdx).mirrorUV(rotIdx > 1, false)) - } -fun Quad.toCross(rotAxis: Direction) = toCross(rotAxis) { it } - -fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(cos(it), 0.0, sin(it)) } - -val rotationFromUp = arrayOf( - Rotation.rot90[EAST.ordinal] * 2, - Rotation.identity, - Rotation.rot90[WEST.ordinal], - Rotation.rot90[EAST.ordinal], - Rotation.rot90[SOUTH.ordinal], - Rotation.rot90[NORTH.ordinal] -) - -fun Model.mix(first: Model, second: Model, predicate: (Int)->Boolean) { - first.quads.forEachIndexed { qi, quad -> - val otherQuad = second.quads[qi] - Quad( - if (predicate(0)) otherQuad.v1.copy() else quad.v1.copy(), - if (predicate(1)) otherQuad.v2.copy() else quad.v2.copy(), - if (predicate(2)) otherQuad.v3.copy() else quad.v3.copy(), - if (predicate(3)) otherQuad.v4.copy() else quad.v4.copy() - ).add() - } -} - -val BlockRenderLayer.isCutout: Boolean get() = (this == BlockRenderLayer.CUTOUT) || (this == BlockRenderLayer.CUTOUT_MIPPED) - -fun BlockState.canRenderInLayer(layer: BlockRenderLayer) = this.block.canRenderInLayer(this, layer) -fun BlockState.canRenderInCutout() = this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT) || this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT_MIPPED) \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/column/AbstractRenderer.kt b/src/main/kotlin/mods/betterfoliage/client/render/column/AbstractRenderer.kt deleted file mode 100644 index 29f2fc6..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/column/AbstractRenderer.kt +++ /dev/null @@ -1,210 +0,0 @@ -package mods.betterfoliage.client.render.column - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.client.Client -import mods.betterfoliage.client.chunk.ChunkOverlayManager -import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs -import mods.betterfoliage.client.render.* -import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.* -import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType -import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.* -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.render.Model -import mods.octarinecore.client.render.RenderDecorator -import mods.octarinecore.client.render.noPost -import mods.octarinecore.common.Rotation -import mods.octarinecore.common.face -import mods.octarinecore.common.rot -import net.minecraft.block.BlockRenderType.MODEL -import net.minecraft.util.Direction.* -import net.minecraftforge.eventbus.api.IEventBus - -@Suppress("NOTHING_TO_INLINE") -abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : RenderDecorator(modId, modBus) { - - /** The rotations necessary to bring the models in position for the 4 quadrants */ - val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it } - - // ============================ - // Configuration - // ============================ - abstract val overlayLayer: ColumnRenderLayer - abstract val connectPerpendicular: Boolean - abstract val radiusSmall: Double - abstract val radiusLarge: Double - - // ============================ - // Models - // ============================ - val sideSquare = model { columnSideSquare(-0.5, 0.5) } - val sideRoundSmall = model { columnSide(radiusSmall, -0.5, 0.5) } - val sideRoundLarge = model { columnSide(radiusLarge, -0.5, 0.5) } - - val extendTopSquare = model { columnSideSquare(0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) } - val extendTopRoundSmall = model { columnSide(radiusSmall, 0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) } - val extendTopRoundLarge = model { columnSide(radiusLarge, 0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) } - inline fun extendTop(type: QuadrantType) = when(type) { - SMALL_RADIUS -> extendTopRoundSmall.model - LARGE_RADIUS -> extendTopRoundLarge.model - SQUARE -> extendTopSquare.model - INVISIBLE -> extendTopSquare.model - else -> null - } - - val extendBottomSquare = model { columnSideSquare(-0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) } - val extendBottomRoundSmall = model { columnSide(radiusSmall, -0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) } - val extendBottomRoundLarge = model { columnSide(radiusLarge, -0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) } - inline fun extendBottom(type: QuadrantType) = when (type) { - SMALL_RADIUS -> extendBottomRoundSmall.model - LARGE_RADIUS -> extendBottomRoundLarge.model - SQUARE -> extendBottomSquare.model - INVISIBLE -> extendBottomSquare.model - else -> null - } - - val topSquare = model { columnLidSquare() } - val topRoundSmall = model { columnLid(radiusSmall) } - val topRoundLarge = model { columnLid(radiusLarge) } - inline fun flatTop(type: QuadrantType) = when(type) { - SMALL_RADIUS -> topRoundSmall.model - LARGE_RADIUS -> topRoundLarge.model - SQUARE -> topSquare.model - INVISIBLE -> topSquare.model - else -> null - } - - val bottomSquare = model { columnLidSquare() { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } } - val bottomRoundSmall = model { columnLid(radiusSmall) { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } } - val bottomRoundLarge = model { columnLid(radiusLarge) { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } } - inline fun flatBottom(type: QuadrantType) = when(type) { - SMALL_RADIUS -> bottomRoundSmall.model - LARGE_RADIUS -> bottomRoundLarge.model - SQUARE -> bottomSquare.model - INVISIBLE -> bottomSquare.model - else -> null - } - - val transitionTop = model { mix(sideRoundLarge.model, sideRoundSmall.model) { it > 1 } } - val transitionBottom = model { mix(sideRoundSmall.model, sideRoundLarge.model) { it > 1 } } - - inline fun continuous(q1: QuadrantType, q2: QuadrantType) = - q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE)) - - @Suppress("NON_EXHAUSTIVE_WHEN") - override fun render(ctx: CombinedContext) { - - val roundLog = ChunkOverlayManager.get(overlayLayer, ctx) - when(roundLog) { - ColumnLayerData.SkipRender -> return - ColumnLayerData.NormalRender -> return ctx.render() - ColumnLayerData.ResolveError, null -> { - BetterFoliage.logRenderError(ctx.state, ctx.pos) - return ctx.render() - } - } - - // 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 ctx.render() - } - - val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal] - renderAs(ctx, MODEL) { - quadrantRotations.forEachIndexed { idx, quadrantRotation -> - // set rotation for the current quadrant - val rotation = baseRotation + quadrantRotation - - // disallow sharp discontinuities in the chamfer radius, or tapering-in where inappropriate - if (roundLog.quadrants[idx] == LARGE_RADIUS && - roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] != LARGE_RADIUS && - roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] != LARGE_RADIUS) { - roundLog.quadrants[idx] = SMALL_RADIUS - } - - // render side of current quadrant - val sideModel = when (roundLog.quadrants[idx]) { - SMALL_RADIUS -> sideRoundSmall.model - LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) transitionTop.model - else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) transitionBottom.model - else sideRoundLarge.model - SQUARE -> sideSquare.model - else -> null - } - - if (sideModel != null) ctx.render( - sideModel, - rotation, - icon = roundLog.column.side, - postProcess = noPost - ) - - // render top and bottom end of current quadrant - var upModel: Model? = null - var downModel: Model? = null - var upIcon = roundLog.column.top - var downIcon = roundLog.column.bottom - var isLidUp = true - var isLidDown = true - - when (roundLog.upType) { - NONSOLID -> upModel = flatTop(roundLog.quadrants[idx]) - PERPENDICULAR -> { - if (!connectPerpendicular) { - upModel = flatTop(roundLog.quadrants[idx]) - } else { - upIcon = roundLog.column.side - upModel = extendTop(roundLog.quadrants[idx]) - isLidUp = false - } - } - PARALLEL -> { - if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsTop[idx])) { - if (roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE) { - upModel = topSquare.model - } - } - } - } - when (roundLog.downType) { - NONSOLID -> downModel = flatBottom(roundLog.quadrants[idx]) - PERPENDICULAR -> { - if (!connectPerpendicular) { - downModel = flatBottom(roundLog.quadrants[idx]) - } else { - downIcon = roundLog.column.side - downModel = extendBottom(roundLog.quadrants[idx]) - isLidDown = false - } - } - PARALLEL -> { - if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsBottom[idx]) && - (roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE)) { - downModel = bottomSquare.model - } - } - } - - if (upModel != null) ctx.render( - upModel, - rotation, - icon = upIcon, - postProcess = { _, _, _, _, _ -> - if (isLidUp) { - rotateUV(idx + if (roundLog.column.axis == Axis.X) 1 else 0) - } - } - ) - if (downModel != null) ctx.render( - downModel, - rotation, - icon = downIcon, - postProcess = { _, _, _, _, _ -> - if (isLidDown) { - rotateUV((if (roundLog.column.axis == Axis.X) 0 else 3) - idx) - } - } - ) - } - } - } -} diff --git a/src/main/kotlin/mods/betterfoliage/client/render/column/RenderData.kt b/src/main/kotlin/mods/betterfoliage/client/render/column/RenderData.kt deleted file mode 100644 index 4593c92..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/column/RenderData.kt +++ /dev/null @@ -1,32 +0,0 @@ -package mods.betterfoliage.client.render.column - -import mods.octarinecore.client.render.lighting.QuadIconResolver -import mods.octarinecore.common.rotate -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.util.Direction.* - -interface ColumnTextureInfo { - val axis: Axis? - val top: QuadIconResolver - val bottom: QuadIconResolver - val side: QuadIconResolver -} - -open class SimpleColumnInfo( - override val axis: Axis?, - val topTexture: TextureAtlasSprite, - val bottomTexture: TextureAtlasSprite, - val sideTextures: List -) : ColumnTextureInfo { - - // index offsets for EnumFacings, to make it less likely for neighboring faces to get the same bark texture - val dirToIdx = arrayOf(0, 1, 2, 4, 3, 5) - - override val top: QuadIconResolver = { _, _, _ -> topTexture } - override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture } - override val side: QuadIconResolver = { ctx, idx, _ -> - val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation) - val sideIdx = if (sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0 - sideTextures[sideIdx] - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/resource/Aliases.kt b/src/main/kotlin/mods/betterfoliage/client/resource/Aliases.kt deleted file mode 100644 index ca864c4..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/resource/Aliases.kt +++ /dev/null @@ -1,10 +0,0 @@ -package mods.betterfoliage.client.resource - -import net.minecraft.client.renderer.model.ModelResourceLocation -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.util.ResourceLocation - -typealias Identifier = ResourceLocation -typealias ModelIdentifier = ModelResourceLocation - -typealias Sprite = TextureAtlasSprite diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt b/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt deleted file mode 100644 index d831ef7..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt +++ /dev/null @@ -1,65 +0,0 @@ -package mods.betterfoliage.client.texture - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.client.render.lighting.HSB -import mods.octarinecore.client.resource.* -import mods.octarinecore.common.config.ConfigurableBlockMatcher -import mods.octarinecore.common.config.ModelTextureList -import net.minecraft.block.BlockState -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import org.apache.logging.log4j.Level -import java.lang.Math.min -import java.util.concurrent.CompletableFuture - -const val defaultGrassColor = 0 - -/** Rendering-related information for a grass block. */ -class GrassInfo( - /** Top texture of the grass block. */ - val grassTopTexture: TextureAtlasSprite, - - /** - * Color to use for Short Grass rendering instead of the biome color. - * - * Value is null if the texture is mostly grey (the saturation of its average color is under a configurable limit), - * the average color of the texture otherwise. - */ - val overrideColor: Int? -) - -object GrassRegistry : ModelRenderRegistryRoot() - -object AsyncGrassDiscovery : ConfigurableModelDiscovery() { - override val logger = BetterFoliage.logDetail - override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks - override val modelTextures: List get() = BlockConfig.grassModels.modelList - - override fun processModel(state: BlockState, textures: List, atlas: AtlasFuture): CompletableFuture { - val textureName = textures[0] - val spriteF = atlas.sprite(Identifier(textureName)) - logger.log(Level.DEBUG, "$logName: texture $textureName") - return atlas.mapAfter { - val sprite = spriteF.get() - logger.log(Level.DEBUG, "$logName: block state $state") - logger.log(Level.DEBUG, "$logName: texture $textureName") - val hsb = HSB.fromColor(sprite.averageColor) - val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) { - logger.log(Level.DEBUG, "$logName: brightness ${hsb.brightness}") - logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} >= ${Config.shortGrass.saturationThreshold}, using texture color") - hsb.copy(brightness = min(0.9f, hsb.brightness * 2.0f)).asColor - } else { - logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} < ${Config.shortGrass.saturationThreshold}, using block color") - null - } - GrassInfo(sprite, overrideColor) - } - } - - fun init() { - GrassRegistry.registries.add(this) - BetterFoliage.blockSprites.providers.add(this) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt b/src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt deleted file mode 100644 index 884802a..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt +++ /dev/null @@ -1,85 +0,0 @@ -package mods.betterfoliage.client.texture - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.BetterFoliageMod -import mods.betterfoliage.client.resource.Identifier -import mods.betterfoliage.client.resource.Sprite -import mods.octarinecore.client.resource.* -import mods.octarinecore.stripStart -import mods.octarinecore.client.resource.Atlas -import mods.octarinecore.common.sinkAsync -import net.minecraft.client.particle.ParticleManager -import net.minecraft.resources.IResourceManager -import net.minecraft.util.ResourceLocation -import java.util.concurrent.CompletableFuture - -class FixedSpriteSet(val sprites: List) : SpriteSet { - override val num = sprites.size - override fun get(idx: Int) = sprites[idx % num] -} - -object LeafParticleRegistry : AsyncSpriteProvider { - val targetAtlas = Atlas.PARTICLES - val typeMappings = TextureMatcher() - val particles = hashMapOf() - - operator fun get(type: String) = particles[type] ?: particles["default"]!! - - override fun setup(manager: IResourceManager, particleF: CompletableFuture, atlasFuture: AtlasFuture): StitchPhases { - particles.clear() - val futures = mutableMapOf>>() - - return StitchPhases( - discovery = particleF.sinkAsync { - typeMappings.loadMappings(Identifier(BetterFoliageMod.MOD_ID, "leaf_texture_mappings.cfg")) - (typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType -> - val ids = (0 until 16).map { idx -> Identifier(BetterFoliageMod.MOD_ID, "falling_leaf_${leafType}_$idx") } - val wids = ids.map { Atlas.PARTICLES.wrap(it) } - futures[leafType] = (0 until 16).map { idx -> Identifier(BetterFoliageMod.MOD_ID, "falling_leaf_${leafType}_$idx") } - .filter { manager.hasResource(Atlas.PARTICLES.wrap(it)) } - .map { atlasFuture.sprite(it) } - } - }, - cleanup = atlasFuture.runAfter { - futures.forEach { leafType, spriteFutures -> - val sprites = spriteFutures.filter { !it.isCompletedExceptionally }.map { it.get() } - if (sprites.isNotEmpty()) particles[leafType] = FixedSpriteSet(sprites) - } - if (particles["default"] == null) particles["default"] = FixedSpriteSet(listOf(atlasFuture.missing.get()!!)) - } - ) - } - - fun init() { - BetterFoliage.particleSprites.providers.add(this) - } -} - -class TextureMatcher { - - data class Mapping(val domain: String?, val path: String, val type: String) { - fun matches(iconLocation: ResourceLocation): Boolean { - return (domain == null || domain == iconLocation.namespace) && - iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true) - } - } - - val mappings: MutableList = mutableListOf() - - fun getType(resource: ResourceLocation) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull() - fun getType(iconName: String) = ResourceLocation(iconName).let { getType(it) } - - fun loadMappings(mappingLocation: ResourceLocation) { - mappings.clear() - resourceManager[mappingLocation]?.getLines()?.let { lines -> - lines.filter { !it.startsWith("//") }.filter { !it.isEmpty() }.forEach { line -> - val line2 = line.trim().split('=') - if (line2.size == 2) { - val mapping = line2[0].trim().split(':') - if (mapping.size == 1) mappings.add(Mapping(null, mapping[0].trim(), line2[1].trim())) - else if (mapping.size == 2) mappings.add(Mapping(mapping[0].trim(), mapping[1].trim(), line2[1].trim())) - } - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt b/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt deleted file mode 100644 index dbe004d..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt +++ /dev/null @@ -1,56 +0,0 @@ -package mods.betterfoliage.client.texture - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.HasLogger -import mods.octarinecore.client.resource.* -import mods.octarinecore.common.config.ConfigurableBlockMatcher -import mods.octarinecore.common.config.ModelTextureList -import net.minecraft.block.BlockState -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import java.util.concurrent.CompletableFuture - -const val defaultLeafColor = 0 - -/** Rendering-related information for a leaf block. */ -class LeafInfo( - /** The generated round leaf texture. */ - val roundLeafTexture: TextureAtlasSprite, - - /** Type of the leaf block (configurable by user). */ - val leafType: String, - - /** Average color of the round leaf texture. */ - val averageColor: Int = roundLeafTexture.averageColor -) { - /** [IconSet] of the textures to use for leaf particles emitted from this block. */ - val particleTextures: SpriteSet get() = LeafParticleRegistry[leafType] -} - -object LeafRegistry : ModelRenderRegistryRoot() - -object AsyncLeafDiscovery : ConfigurableModelDiscovery() { - override val logger = BetterFoliage.logDetail - override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks - override val modelTextures: List get() = BlockConfig.leafModels.modelList - - override fun processModel(state: BlockState, textures: List, atlas: AtlasFuture) = defaultRegisterLeaf(Identifier(textures[0]), atlas) - - fun init() { - LeafRegistry.registries.add(this) - BetterFoliage.blockSprites.providers.add(this) - } -} - -fun HasLogger.defaultRegisterLeaf(sprite: Identifier, atlas: AtlasFuture): CompletableFuture { - val leafType = LeafParticleRegistry.typeMappings.getType(sprite) ?: "default" - val generated = GeneratedLeaf(sprite, leafType).register(BetterFoliage.asyncPack) - val roundLeaf = atlas.sprite(generated) - - log(" leaf texture $sprite") - log(" particle $leafType") - return atlas.mapAfter { - LeafInfo(roundLeaf.get(), leafType) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/Utils.kt b/src/main/kotlin/mods/betterfoliage/client/texture/Utils.kt deleted file mode 100644 index e53cd9b..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/texture/Utils.kt +++ /dev/null @@ -1,20 +0,0 @@ -@file:JvmName("Utils") -package mods.betterfoliage.client.texture - -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.client.resource.Atlas -import mods.octarinecore.client.resource.get -import mods.octarinecore.client.resource.loadImage -import net.minecraft.resources.IResourceManager -import java.io.IOException - -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) - val g = (((rgb1 shr 8) and 255) * weight1 + ((rgb2 shr 8) and 255) * weight2) / (weight1 + weight2) - val b = ((rgb1 and 255) * weight1 + (rgb2 and 255) * weight2) / (weight1 + weight2) - val a = (rgb1 shr 24) and 255 - val result = ((a shl 24) or (r shl 16) or (g shl 8) or b).toInt() - return result -} - -fun IResourceManager.loadSprite(id: Identifier) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id") \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/config/BlockConfig.kt b/src/main/kotlin/mods/betterfoliage/config/BlockConfig.kt new file mode 100644 index 0000000..e8e5111 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/config/BlockConfig.kt @@ -0,0 +1,36 @@ +package mods.betterfoliage.config + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.util.Invalidator +import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher +import mods.betterfoliage.resource.discovery.ModelTextureListConfiguration +import net.minecraft.resource.ResourceManager +import net.minecraft.util.Identifier + +class BlockConfig { + private val list = mutableListOf() + + 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 sand = blocks("sand_default.cfg") + val lilypad = blocks("lilypad_default.cfg") + val cactus = blocks("cactus_default.cfg") + val netherrack = blocks("netherrack_blocks_default.cfg") + + private fun blocks(cfgName: String) = ConfigurableBlockMatcher(BetterFoliage.logDetail, Identifier(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) } + private fun models(cfgName: String) = ModelTextureListConfiguration(BetterFoliage.logDetail, Identifier(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) } + + fun reloadConfig(manager: ResourceManager) { + list.forEach { when(it) { + is ConfigurableBlockMatcher -> it.readDefaults(manager) + is ModelTextureListConfiguration -> it.readDefaults(manager) + } } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/config/Delegate.kt b/src/main/kotlin/mods/betterfoliage/config/Delegate.kt new file mode 100644 index 0000000..88135c3 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/config/Delegate.kt @@ -0,0 +1,137 @@ +package mods.betterfoliage.config + +import me.shedaniel.clothconfig2.api.AbstractConfigListEntry +import me.shedaniel.clothconfig2.api.ConfigEntryBuilder +import me.shedaniel.clothconfig2.gui.entries.SubCategoryListEntry +import me.zeroeightsix.fiber.builder.ConfigValueBuilder +import me.zeroeightsix.fiber.tree.ConfigLeaf +import me.zeroeightsix.fiber.tree.ConfigNode +import me.zeroeightsix.fiber.tree.ConfigValue +import net.minecraft.client.resource.language.I18n +import java.util.* +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +const val MAX_LINE_LEN = 30 + +sealed class DelegatingConfigNode(val fiberNode: N) { + abstract fun createClothNode(names: List): AbstractConfigListEntry<*> +} + +abstract class DelegatingConfigValue(fiberNode: ConfigValue) : DelegatingConfigNode>(fiberNode), ReadOnlyProperty + +open class DelegatingConfigGroup(fiberNode: ConfigNode) : DelegatingConfigNode(fiberNode) { + val children = mutableListOf>() + override fun createClothNode(names: List): SubCategoryListEntry { + val builder = ConfigEntryBuilder.create() + .startSubCategory(names.joinToString(".").translate()) + .setTooltip(*names.joinToString(".").translateTooltip()) + .setExpended(false) + children.forEach { builder.add(it.createClothNode(names + it.fiberNode.name!!)) } + return builder.build() + } + operator fun get(name: String) = children.find { it.fiberNode.name == name } +} + +interface DelegatingConfigGroupFactory { + operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty +} + +fun subNode(factory: (ConfigNode)->T) = object : DelegatingConfigGroupFactory { + override operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty { + val childNode = ConfigNode(property.name, null) + val configGroup = factory(childNode) + parent.fiberNode.items.add(childNode) + parent.children.add(configGroup) + return object : ReadOnlyProperty { + override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = configGroup + } + } +} + +interface DelegatingConfigValueFactory { + fun createFiberNode(parent: ConfigNode, name: String): ConfigValue + fun createClothNode(node: ConfigValue, names: List): AbstractConfigListEntry + + operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty { + return object : DelegatingConfigValue(createFiberNode(parent.fiberNode, property.name)) { + override fun createClothNode(names: List) = createClothNode(fiberNode, names) + override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = fiberNode.value!! + }.apply { parent.children.add(this) } + } +} + +fun String.translate() = I18n.translate(this) +fun String.translateTooltip(lineLength: Int = MAX_LINE_LEN) = ("$this.tooltip").translate().let { tooltip -> + 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() }.toTypedArray() +} + +fun boolean( + default: Boolean, + langKey: (List)->String = { it.joinToString(".") }, + valueOverride: (Boolean)->Boolean = { it } +) = object : DelegatingConfigValueFactory { + override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Boolean::class.java) + .withName(name) + .withParent(parent) + .withDefaultValue(default) + .build() + + override fun createClothNode(node: ConfigValue, names: List) = ConfigEntryBuilder.create() + .startBooleanToggle(langKey(names).translate(), node.value!!) + .setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() }) + .setSaveConsumer { node.value = valueOverride(it) } + .build() +} + +fun integer( + default: Int, min: Int, max: Int, + langKey: (List)->String = { it.joinToString(".") }, + valueOverride: (Int)->Int = { it } +) = object : DelegatingConfigValueFactory { + override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Int::class.java) + .withName(name) + .withParent(parent) + .withDefaultValue(default) + .constraints().minNumerical(min).maxNumerical(max).finish() + .build() + + override fun createClothNode(node: ConfigValue, names: List) = ConfigEntryBuilder.create() + .startIntField(langKey(names).translate(), node.value!!) + .setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() }) + .setMin(min).setMax(max) + .setSaveConsumer { node.value = valueOverride(it) } + .build() +} + +fun double( + default: Double, min: Double, max: Double, + langKey: (List)->String = { it.joinToString(".") }, + valueOverride: (Double)->Double = { it } +) = object : DelegatingConfigValueFactory { + override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Double::class.java) + .withName(name) + .withParent(parent) + .withDefaultValue(default) + .constraints().minNumerical(min).maxNumerical(max).finish() + .build() + + override fun createClothNode(node: ConfigValue, names: List) = ConfigEntryBuilder.create() + .startDoubleField(langKey(names).translate(), node.value!!) + .setTooltip(langKey(names).let { if (I18n.hasTranslation("$it.tooltip")) Optional.of(it.translateTooltip()) else Optional.empty() }) + .setMin(min).setMax(max) + .setSaveConsumer { node.value = valueOverride(it) } + .build() +} + +val recurring = { names: List -> "${names.first()}.${names.last()}" } +fun fakeCategory(name: String) = { names: List -> + (listOf(names.first(), name) + names.drop(1)).joinToString(".") +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/config/MainConfig.kt b/src/main/kotlin/mods/betterfoliage/config/MainConfig.kt new file mode 100644 index 0000000..eeafaa1 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/config/MainConfig.kt @@ -0,0 +1,154 @@ +package mods.betterfoliage.config + +import me.zeroeightsix.fiber.tree.ConfigNode +import java.util.* + +interface PopulationConfigData { + val enabled: Boolean + val population: Int + fun enabled(random: Random) = random.nextInt(64) < population && enabled +} + +fun population(default: Int) = integer(default, min = 0, max = 64, langKey = recurring) + +class MainConfig : DelegatingConfigGroup(ConfigNode("root", null)) { + + val enabled by boolean(true, langKey = fakeCategory("global")) + val nVidia by boolean(true, langKey = fakeCategory("global")) + + val leaves by subNode { LeavesConfig(it) } + val shortGrass by subNode { ShortGrassConfig(it) } + val connectedGrass by subNode { ConnectedGrassConfig(it) } + val roundLogs by subNode { RoundLogConfig(it) } + val cactus by subNode { CactusConfig(it) } + val lilypad by subNode { LilypadConfig(it) } + val reed by subNode { ReedConfig(it) } + val algae by subNode { AlgaeConfig(it) } + val coral by subNode { CoralConfig(it) } + val netherrack by subNode { NetherrackConfig(it) } + val fallingLeaves by subNode { FallingLeavesConfig(it) } + val risingSoul by subNode { RisingSoulConfig(it) } +} + +class LeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) { + val enabled by boolean(true, langKey = recurring) + val snowEnabled by boolean(true) + val dense by boolean(false) + val hideInternal by boolean(true) + + val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring) + val vOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring) + val size by double(1.4, min = 0.75, max = 2.5, langKey = recurring) +} + +class ShortGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { + override val enabled by boolean(true, langKey = recurring) + val myceliumEnabled by boolean(true) + val snowEnabled by boolean(true) + val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring) + val heightMin by double(0.6, min = 0.1, max = 2.5, langKey = recurring) + val heightMax by double(0.6, min = 0.1, max = 2.5, langKey = recurring) { it.coerceAtLeast(heightMin) } + val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring) + override val population by population(64) + val useGenerated by boolean(false) + val shaderWind by boolean(true, langKey = recurring) + val saturationThreshold by double(0.1, min = 0.0, max = 1.0) +} + +class ConnectedGrassConfig(node: ConfigNode) : DelegatingConfigGroup(node) { + val enabled by boolean(true, langKey = recurring) + val snowEnabled by boolean(true) +} + +class RoundLogConfig(node: ConfigNode) : DelegatingConfigGroup(node) { + val enabled by boolean(true, langKey = recurring) + + val defaultY by boolean(false) + val connectSolids by boolean(false) + val lenientConnect by boolean(true) + val connectPerpendicular by boolean(true) + val connectGrass by boolean(true) + + val radiusSmall by double(0.25, min = 0.0, max = 0.5) + val radiusLarge by double(0.25, min = 0.0, max = 0.5) { it.coerceAtLeast(radiusSmall) } + val dimming by double(0.7, min = 0.0, max = 1.0) + val zProtection by double(0.99, min = 0.9, max = 1.0) +} + +class CactusConfig(node: ConfigNode) : DelegatingConfigGroup(node) { + val enabled by boolean(true, langKey = recurring) + val size by double(1.3, min = 0.5, max = 1.5, langKey = recurring) + val sizeVariation by double(0.1, min = 0.0, max = 0.5) + val hOffset by double(0.1, min = 0.0, max = 0.5, langKey = recurring) +} + +class LilypadConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { + override val enabled by boolean(true, langKey = recurring) + val hOffset by double(0.1, min = 0.0, max = 0.25, langKey = recurring) + override val population by population(16) +} + +class ReedConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { + override val enabled by boolean(true, langKey = recurring) + val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring) + val heightMin by double(1.7, min = 1.5, max = 3.0, langKey = recurring) + val heightMax by double(2.2, min = 1.5, max = 3.0, langKey = recurring) { it.coerceAtLeast(heightMin) } + override val population by population(32) + val minBiomeTemp by double(0.4, min = 0.0, max = 2.0) + val minBiomeRainfall by double(0.4, min = 0.0, max = 1.0) + val shaderWind by boolean(true, langKey = recurring) +} + +class AlgaeConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { + override val enabled by boolean(true, langKey = recurring) + val hOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring) + val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring) + val heightMin by double(0.5, min = 0.1, max = 1.0, langKey = recurring) + val heightMax by double(0.5, min = 0.1, max = 1.0, langKey = recurring) { it.coerceAtLeast(heightMin) } + override val population by population(48) + val shaderWind by boolean(true, langKey = recurring) +} + +class CoralConfig(node: ConfigNode) : DelegatingConfigGroup(node), PopulationConfigData { + override val enabled by boolean(true, langKey = recurring) + val shallowWater by boolean(false) + val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring) + val vOffset by double(0.1, min = 0.0, max = 0.4, langKey = recurring) + val size by double(0.7, min = 0.5, max = 1.5, langKey = recurring) + val crustSize by double(1.4, min = 0.5, max = 1.5) + val chance by integer(32, min = 0, max = 64) + override val population by population(48) +} + +class NetherrackConfig(node: ConfigNode) : DelegatingConfigGroup(node) { + val enabled by boolean(true, langKey = recurring) + val hOffset by double(0.2, min = 0.0, max = 0.4, langKey = recurring) + val size by double(1.0, min = 0.5, max = 1.5, langKey = recurring) + val heightMin by double(0.6, min = 0.5, max = 1.5, langKey = recurring) + val heightMax by double(0.8, min = 0.5, max = 1.5, langKey = recurring) { it.coerceAtLeast(heightMin) } +} + +class FallingLeavesConfig(node: ConfigNode) : DelegatingConfigGroup(node) { + val enabled by boolean(true, langKey = recurring) + val speed by double(0.05, min = 0.01, max = 0.15) + val windStrength by double(0.5, min = 0.1, max = 2.0) + val stormStrength by double(0.8, min = 0.1, max = 2.0) { it.coerceAtLeast(windStrength) } + val size by double(0.75, min = 0.25, max = 1.5) + val chance by double(0.05, min = 0.001, max = 1.0) + val perturb by double(0.25, min = 0.01, max = 1.0) + val lifetime by double(7.5, min = 1.0, max = 15.0) +} + +class RisingSoulConfig(node: ConfigNode) : DelegatingConfigGroup(node) { + val enabled by boolean(true, langKey = recurring) + val chance by double(0.02, min = 0.001, max = 1.0) + val perturb by double(0.05, min = 0.01, max = 0.25) + val headSize by double(1.0, min = 0.25, max = 1.5) + val trailSize by double(0.75, min = 0.25, max = 1.5) + val opacity by double(0.5, min = 0.05, max = 1.0) + val sizeDecay by double(0.97, min = 0.5, max = 1.0) + val opacityDecay by double(0.97, min = 0.5, max = 1.0) + val lifetime by double(4.0, min = 1.0, max = 15.0) + val trailLength by integer(48, min = 2, max = 128) + val trailDensity by integer(3, min = 1, max = 16) +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/integration/ForestryIntegration.kt b/src/main/kotlin/mods/betterfoliage/integration/ForestryIntegration.kt new file mode 100644 index 0000000..254b31c --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/integration/ForestryIntegration.kt @@ -0,0 +1,110 @@ +package mods.betterfoliage.integration + +/* +val TextureLeaves = ClassRefOld("forestry.arboriculture.models.TextureLeaves") +val TextureLeaves_leafTextures = FieldRefOld(TextureLeaves, "leafTextures", Map) +val TextureLeaves_plain = FieldRefOld(TextureLeaves, "plain", Identifier) +val TextureLeaves_fancy = FieldRefOld(TextureLeaves, "fancy", Identifier) +val TextureLeaves_pollinatedPlain = FieldRefOld(TextureLeaves, "pollinatedPlain", Identifier) +val TextureLeaves_pollinatedFancy = FieldRefOld(TextureLeaves, "pollinatedFancy", Identifier) + + +val TileLeaves = ClassRefOld("forestry.arboriculture.tiles.TileLeaves") +val TileLeaves_getLeaveSprite = MethodRefOld(TileLeaves, "getLeaveSprite", Identifier, boolean) +val PropertyWoodType = ClassRefOld("forestry.arboriculture.blocks.PropertyWoodType") +val IWoodType = ClassRefOld("forestry.api.arboriculture.IWoodType") +val IWoodType_barkTex = MethodRefOld(IWoodType, "getBarkTexture", String) +val IWoodType_heartTex = MethodRefOld(IWoodType, "getHeartTexture", String) + +val PropertyTreeType = ClassRefOld("forestry.arboriculture.blocks.PropertyTreeType") +val IAlleleTreeSpecies = ClassRefOld("forestry.api.arboriculture.IAlleleTreeSpecies") +val ILeafSpriteProvider = ClassRefOld("forestry.api.arboriculture.ILeafSpriteProvider") +val TreeDefinition = ClassRefOld("forestry.arboriculture.genetics.TreeDefinition") + +val IAlleleTreeSpecies_getLeafSpriteProvider = MethodRefOld(IAlleleTreeSpecies, "getLeafSpriteProvider", ILeafSpriteProvider) +val TreeDefinition_species = FieldRefOld(TreeDefinition, "species", IAlleleTreeSpecies) +val ILeafSpriteProvider_getSprite = MethodRefOld(ILeafSpriteProvider, "getSprite", Identifier, boolean, boolean) + +object ForestryIntegration { + init { + } +} + + */ +/* +object ForestryLeafDiscovery : HasLogger, AsyncSpriteProvider, ModelRenderRegistry { + override val logger = BetterFoliage.logDetail + var idToValue = emptyMap() + + override fun get(state: BlockState, world: BlockView, pos: BlockPos): LeafInfo? { + // check variant property (used in decorative leaves) + state.entries.entries.find { + PropertyTreeType.isInstance(it.key) && TreeDefinition.isInstance(it.value) + } ?.let { + val species = it.value[TreeDefinition_species]!! + val spriteProvider = species[IAlleleTreeSpecies_getLeafSpriteProvider]() + val textureLoc = spriteProvider[ILeafSpriteProvider_getSprite](false, MinecraftClient.isFancyGraphicsEnabled()) + return idToValue[textureLoc] + } + + // extract leaf texture information from TileEntity + val tile = world.getBlockEntity(pos) ?: return null + if (!TileLeaves.isInstance(tile)) return null + val textureLoc = tile[TileLeaves_getLeaveSprite](MinecraftClient.isFancyGraphicsEnabled()) + return idToValue[textureLoc] + } + + override fun setup(manager: ResourceManager, bakeryF: CompletableFuture, atlasFuture: AtlasFuture): StitchPhases { + val futures = mutableMapOf>() + + return StitchPhases( + discovery = bakeryF.thenRunAsync { + val allLeaves = TextureLeaves_leafTextures.getStatic() + allLeaves!!.entries.forEach { (type, leaves) -> + log("base leaf type $type") + leaves!! + listOf( + leaves[TextureLeaves_plain], leaves[TextureLeaves_pollinatedPlain], + leaves[TextureLeaves_fancy], leaves[TextureLeaves_pollinatedFancy] + ).forEach { textureLocation -> + futures[textureLocation!!] = defaultRegisterLeaf(textureLocation, atlasFuture) + } + } + }, + cleanup = atlasFuture.runAfter { + idToValue = futures.mapValues { it.value.get() } + } + ) + } +} + +object ForestryLogDiscovery : ModelDiscovery() { + override val logger = BetterFoliage.logDetail + override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? { + // respect class list to avoid triggering on fences, stairs, etc. + if (!BetterFoliageMod.blockConfig.logBlocks.matchesClass(ctx.state.block)) return null + + // find wood type property + val woodType = ctx.state.entries.entries.find { + PropertyWoodType.isInstance(it.key) && IWoodType.isInstance(it.value) + } + if (woodType != null) { + logger.log(Level.DEBUG, "ForestryLogRegistry: block state ${ctx.state}") + logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}") + + // get texture names for wood type + val bark = woodType.value[IWoodType_barkTex]() + val heart = woodType.value[IWoodType_heartTex]() + logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]") + + val heartSprite = atlas.sprite(heart) + val barkSprite = atlas.sprite(bark) + return atlas.mapAfter { + SimpleColumnInfo(AsyncLogDiscovery.getAxis(ctx.state), heartSprite.get(), heartSprite.get(), listOf(barkSprite.get())) + } + } + return null + } +} + + */ diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt b/src/main/kotlin/mods/betterfoliage/integration/RubberIntegration.kt similarity index 64% rename from src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt rename to src/main/kotlin/mods/betterfoliage/integration/RubberIntegration.kt index 87f1ffe..b1d700e 100644 --- a/src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt +++ b/src/main/kotlin/mods/betterfoliage/integration/RubberIntegration.kt @@ -1,58 +1,40 @@ -package mods.betterfoliage.client.integration +package mods.betterfoliage.integration -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.client.render.LogRegistry -import mods.betterfoliage.client.render.column.ColumnTextureInfo -import mods.betterfoliage.client.render.column.SimpleColumnInfo -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.render.Quad -import mods.octarinecore.client.render.lighting.QuadIconResolver -import mods.octarinecore.client.resource.* -import mods.octarinecore.common.rotate -import mods.octarinecore.metaprog.ClassRef -import mods.octarinecore.metaprog.allAvailable -import net.minecraft.client.renderer.model.BlockModel -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.util.Direction -import net.minecraft.util.Direction.* -import net.minecraft.util.ResourceLocation -import net.minecraftforge.fml.ModList -import org.apache.logging.log4j.Level -import java.util.concurrent.CompletableFuture object IC2RubberIntegration { - val BlockRubWood = ClassRef("ic2.core.block.BlockRubWood") +// val BlockRubWood = ClassRefOld("ic2.core.block.BlockRubWood") init { - if (ModList.get().isLoaded("ic2") && allAvailable(BlockRubWood)) { - BetterFoliage.log(Level.INFO, "IC2 rubber support initialized") - LogRegistry.registries.add(IC2LogDiscovery) - BetterFoliage.blockSprites.providers.add(IC2LogDiscovery) - } +// if (ModList.get().isLoaded("ic2") && allAvailable(BlockRubWood)) { +// BetterFoliage.log(Level.INFO, "IC2 rubber support initialized") +// LogRegistry.registries.add(IC2LogDiscovery) +// BetterFoliage.blockSprites.providers.add(IC2LogDiscovery) +// } } } object TechRebornRubberIntegration { - val BlockRubberLog = ClassRef("techreborn.blocks.BlockRubberLog") +// val BlockRubberLog = ClassRefOld("techreborn.blocks.BlockRubberLog") init { - if (ModList.get().isLoaded("techreborn") && allAvailable(BlockRubberLog)) { - BetterFoliage.log(Level.INFO, "TechReborn rubber support initialized") - LogRegistry.registries.add(TechRebornLogDiscovery) - BetterFoliage.blockSprites.providers.add(TechRebornLogDiscovery) - } +// if (ModList.get().isLoaded("techreborn") && allAvailable(BlockRubberLog)) { +// BetterFoliage.log(Level.INFO, "TechReborn rubber support initialized") +// LogRegistry.registries.add(TechRebornLogDiscovery) +// BetterFoliage.blockSprites.providers.add(TechRebornLogDiscovery) +// } } } +/* class RubberLogInfo( axis: Axis?, val spotDir: Direction, - topTexture: TextureAtlasSprite, - bottomTexture: TextureAtlasSprite, - val spotTexture: TextureAtlasSprite, - sideTextures: List + topTexture: Sprite, + bottomTexture: Sprite, + val spotTexture: Sprite, + sideTextures: List ) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) { override val side: QuadIconResolver = { ctx: CombinedContext, idx: Int, quad: Quad -> @@ -70,18 +52,18 @@ object IC2LogDiscovery : ModelDiscovery() { override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? { // check for proper block class, existence of ModelBlock, and "state" blockstate property if (!IC2RubberIntegration.BlockRubWood.isInstance(ctx.state.block)) return null - val blockLoc = ctx.models.firstOrNull() as Pair ?: return null - val type = ctx.state.values.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null + val blockLoc = ctx.models.firstOrNull() as Pair ?: return null + val type = ctx.state.entries.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null // logs with no rubber spot - if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) { + if (blockLoc.derivesFrom(Identifier("block/cube_column"))) { val axis = when(type) { "plain_y" -> Axis.Y "plain_x" -> Axis.X "plain_z" -> Axis.Z else -> null } - val textureNames = listOf("end", "side").map { blockLoc.first.resolveTextureName(it) } + val textureNames = listOf("end", "side").map { blockLoc.first.resolveTexture(it) } if (textureNames.any { it == "missingno" }) return null log("IC2LogSupport: block state ${ctx.state.toString()}") log("IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[1]}") @@ -100,7 +82,7 @@ object IC2LogDiscovery : ModelDiscovery() { "dry_east", "wet_east" -> EAST else -> null } - val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTextureName(it) } + val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTexture(it) } if (textureNames.any { it == "missingno" }) return null log("IC2LogSupport: block state ${ctx.state.toString()}") log("IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}") @@ -122,14 +104,14 @@ object TechRebornLogDiscovery : ModelDiscovery() { override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? { // check for proper block class, existence of ModelBlock if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(ctx.state.block)) return null - val blockLoc = ctx.models.map { it as? Pair }.firstOrNull() ?: return null + val blockLoc = ctx.models.map { it as? Pair }.firstOrNull() ?: return null - val hasSap = ctx.state.values.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null - val sapSide = ctx.state.values.entries.find { it.key.getName() == "sapside" }?.value as? Direction ?: return null + val hasSap = ctx.state.entries.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null + val sapSide = ctx.state.entries.entries.find { it.key.getName() == "sapside" }?.value as? Direction ?: return null log("$logName: block state ${ctx.state}") if (hasSap) { - val textureNames = listOf("end", "side", "sapside").map { blockLoc.first.resolveTextureName(it) } + val textureNames = listOf("end", "side", "sapside").map { blockLoc.first.resolveTexture(it) } log("$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}") if (textureNames.all { it != "missingno" }) { val endSprite = atlas.sprite(textureNames[0]) @@ -140,7 +122,7 @@ object TechRebornLogDiscovery : ModelDiscovery() { } } } else { - val textureNames = listOf("end", "side").map { blockLoc.first.resolveTextureName(it) } + val textureNames = listOf("end", "side").map { blockLoc.first.resolveTexture(it) } log("$logName: end=${textureNames[0]}, side=${textureNames[1]}") if (textureNames.all { it != "missingno" }) { val endSprite = atlas.sprite(textureNames[0]) @@ -152,4 +134,6 @@ object TechRebornLogDiscovery : ModelDiscovery() { } return null } -} \ No newline at end of file +} + + */ diff --git a/src/main/kotlin/mods/betterfoliage/render/AbstractParticle.kt b/src/main/kotlin/mods/betterfoliage/render/AbstractParticle.kt new file mode 100644 index 0000000..9b211eb --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/AbstractParticle.kt @@ -0,0 +1,130 @@ +package mods.betterfoliage.render + +import mods.betterfoliage.util.Double3 +import net.minecraft.client.MinecraftClient +import net.minecraft.client.particle.ParticleTextureSheet +import net.minecraft.client.particle.SpriteBillboardParticle +import net.minecraft.client.render.BufferBuilder +import net.minecraft.client.render.Camera +import net.minecraft.client.texture.Sprite +import net.minecraft.world.World +import kotlin.math.cos +import kotlin.math.sin + + +abstract class AbstractParticle(world: World, x: Double, y: Double, z: Double) : SpriteBillboardParticle(world, x, y, z) { + + companion object { +// @JvmStatic val sin = Array(64) { idx -> Math.sin(PI2 / 64.0 * idx) } +// @JvmStatic val cos = Array(64) { idx -> Math.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(x, y, z) + prevPos.setTo(prevPosX, prevPosY, prevPosZ) + velocity.setTo(velocityX, velocityY, velocityZ) + update() + x = currentPos.x; y = currentPos.y; z = currentPos.z; + velocityX = velocity.x; velocityY = velocity.y; velocityZ = 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) MinecraftClient.getInstance().particleManager.addParticle(this) } + + override fun buildGeometry(buffer: BufferBuilder, camera: Camera, tickDelta: 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, tickDelta) + } + + /** + * 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[sprite] 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 = scale.toDouble(), + rotation: Double = 0.0, + sprite: Sprite = this.sprite, + isMirrored: Boolean = false, + alpha: Float = this.colorAlpha) { + + val minU = (if (isMirrored) sprite.minU else sprite.maxU).toDouble() + val maxU = (if (isMirrored) sprite.maxU else sprite.minU).toDouble() + val minV = sprite.minV.toDouble() + val maxV = sprite.maxV.toDouble() + + val center = currentPos.copy().sub(prevPos).mul(partialTickTime.toDouble()).add(prevPos).sub(cameraX, cameraY, cameraZ) + + val cosRotation = cos(rotation); val sinRotation = sin(rotation) + val v1 = Double3.weight(billboardRot.first, cosRotation * size, billboardRot.second, sinRotation * size) + val v2 = Double3.weight(billboardRot.first, -sinRotation * size, billboardRot.second, cosRotation * size) + + val renderBrightness = this.getColorMultiplier(partialTickTime) + val brHigh = renderBrightness shr 16 and 65535 + val brLow = renderBrightness and 65535 + + worldRenderer + .vertex(center.x - v1.x, center.y - v1.y, center.z - v1.z) + .texture(maxU, maxV) + .color(colorRed, colorGreen, colorBlue, alpha) + .texture(brHigh, brLow) + .next() + + worldRenderer + .vertex(center.x - v2.x, center.y - v2.y, center.z - v2.z) + .texture(maxU, minV) + .color(colorRed, colorGreen, colorBlue, alpha) + .texture(brHigh, brLow) + .next() + + worldRenderer + .vertex(center.x + v1.x, center.y + v1.y, center.z + v1.z) + .texture(minU, minV) + .color(colorRed, colorGreen, colorBlue, alpha) + .texture(brHigh, brLow) + .next() + + worldRenderer + .vertex(center.x + v2.x, center.y + v2.y, center.z + v2.z) + .texture(minU, maxV) + .color(colorRed, colorGreen, colorBlue, alpha) + .texture(brHigh, brLow) + .next() + } + + override fun getType() = ParticleTextureSheet.PARTICLE_SHEET_OPAQUE + + fun setColor(color: Int) { + colorBlue = (color and 255) / 256.0f + colorGreen = ((color shr 8) and 255) / 256.0f + colorRed = ((color shr 16) and 255) / 256.0f + } +} + diff --git a/src/main/kotlin/mods/betterfoliage/render/Misc.kt b/src/main/kotlin/mods/betterfoliage/render/Misc.kt new file mode 100644 index 0000000..a5fd120 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/Misc.kt @@ -0,0 +1,12 @@ +package mods.betterfoliage.render + +import net.minecraft.block.Blocks +import net.minecraft.block.Material +import net.minecraft.world.biome.Biome + +val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT) +val SAND_BLOCKS = listOf(Blocks.SAND, Blocks.RED_SAND) + +val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN) + +val SNOW_MATERIALS = listOf(Material.SNOW, Material.SNOW_BLOCK) \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/OptifineCustomColors.kt b/src/main/kotlin/mods/betterfoliage/render/OptifineCustomColors.kt similarity index 63% rename from src/main/kotlin/mods/betterfoliage/client/integration/OptifineCustomColors.kt rename to src/main/kotlin/mods/betterfoliage/render/OptifineCustomColors.kt index c8011db..0a95bfa 100644 --- a/src/main/kotlin/mods/betterfoliage/client/integration/OptifineCustomColors.kt +++ b/src/main/kotlin/mods/betterfoliage/render/OptifineCustomColors.kt @@ -1,21 +1,18 @@ -package mods.betterfoliage.client.integration +package mods.betterfoliage.render -import mods.betterfoliage.BetterFoliage -import mods.octarinecore.* -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.metaprog.allAvailable -import mods.octarinecore.metaprog.reflectField +import mods.betterfoliage.* +import mods.betterfoliage.util.ThreadLocalDelegate import net.minecraft.block.BlockState -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.model.BakedQuad -import net.minecraft.client.renderer.vertex.DefaultVertexFormats -import net.minecraft.util.Direction.UP +import net.minecraft.client.MinecraftClient +import net.minecraft.client.render.model.BakedQuad import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction.UP import org.apache.logging.log4j.Level /** * Integration for OptiFine custom block colors. */ +/* @Suppress("UNCHECKED_CAST") object OptifineCustomColors { @@ -26,10 +23,10 @@ object OptifineCustomColors { } val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() } - val fakeQuad = BakedQuad(IntArray(0), 1, UP, null, true, DefaultVertexFormats.BLOCK) + val fakeQuad = BakedQuad(IntArray(0), 1, UP, null) fun getBlockColor(ctx: CombinedContext): Int { - val ofColor = if (isColorAvailable && Minecraft.getInstance().gameSettings.reflectField("ofCustomColors") == true) { + val ofColor = if (isColorAvailable && MinecraftClient.getInstance().options.reflectDeclaredField("ofCustomColors") == true) { renderEnv.reset(ctx.state, ctx.pos) CustomColors.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int } else null @@ -46,4 +43,6 @@ class OptifineRenderEnv { fun reset(state: BlockState, pos: BlockPos) { RenderEnv.reset.invoke(wrapped, state, pos) } -} \ No newline at end of file +} + + */ diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt b/src/main/kotlin/mods/betterfoliage/render/ShadersModIntegration.kt similarity index 72% rename from src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt rename to src/main/kotlin/mods/betterfoliage/render/ShadersModIntegration.kt index 53c7b35..b843828 100644 --- a/src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt +++ b/src/main/kotlin/mods/betterfoliage/render/ShadersModIntegration.kt @@ -1,26 +1,19 @@ -package mods.betterfoliage.client.integration +package mods.betterfoliage.render import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.texture.GrassRegistry -import mods.betterfoliage.client.texture.LeafRegistry -import mods.octarinecore.* -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.metaprog.allAvailable -import mods.octarinecore.metaprog.get +import mods.betterfoliage.util.get 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.math.BlockPos -import net.minecraft.world.IEnviromentBlockReader +import net.minecraft.world.ExtendedBlockView import org.apache.logging.log4j.Level.INFO /** * Integration for ShadersMod. */ +/* object ShadersModIntegration { @JvmStatic val isAvailable = allAvailable(SVertexBuilder, SVertexBuilder.pushState, SVertexBuilder.pushNum, SVertexBuilder.pop) @@ -32,9 +25,9 @@ object ShadersModIntegration { * Called from transformed ShadersMod code. * @see mods.betterfoliage.loader.BetterFoliageTransformer */ - @JvmStatic fun getBlockStateOverride(state: BlockState, world: IEnviromentBlockReader, pos: BlockPos): BlockState { - if (LeafRegistry[state, world, pos] != null) return defaultLeaves - if (BlockConfig.crops.matchesClass(state.block)) return defaultGrass + @JvmStatic fun getBlockStateOverride(state: BlockState, world: ExtendedBlockView, pos: BlockPos): BlockState { +// if (LeafRegistry[state, world, pos] != null) return defaultLeaves + if (BetterFoliage.blockConfig.crops.matchesClass(state.block)) return defaultGrass return state } @@ -50,7 +43,7 @@ object ShadersModIntegration { if (isAvailable && enabled) { val buffer = ctx.renderCtx.renderBuffer val sVertexBuilder = buffer[BufferBuilder_sVertexBuilder] - SVertexBuilder.pushState.invoke(sVertexBuilder, ctx.state, ctx.pos, ctx.world, buffer) + SVertexBuilder.pushState.invoke(sVertexBuilder!!, ctx.state, ctx.pos, ctx.world, buffer) func() SVertexBuilder.pop.invoke(sVertexBuilder) } else { @@ -66,3 +59,6 @@ object ShadersModIntegration { inline fun leaves(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) = renderAs(ctx, defaultLeaves, MODEL, enabled, func) } + + + */ diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Cactus.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Cactus.kt new file mode 100644 index 0000000..99f42aa --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Cactus.kt @@ -0,0 +1,94 @@ +package mods.betterfoliage.render.block.vanilla + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.render.lighting.withLighting +import mods.betterfoliage.render.lighting.grassTuftLighting +import mods.betterfoliage.render.lighting.roundLeafLighting +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.resource.discovery.* +import mods.betterfoliage.resource.model.* +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.minecraft.block.BlockState +import net.minecraft.block.CactusBlock +import net.minecraft.client.render.model.BakedModel +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction.DOWN +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Consumer +import java.util.function.Supplier + + +interface CactusKey : BlockRenderKey { + val cactusTop: Identifier + val cactusBottom: Identifier + val cactusSide: Identifier +} + +object StandardCactusDiscovery : ConfigurableModelDiscovery() { + override val logger = BetterFoliage.logDetail + override val matchClasses = SimpleBlockMatcher(CactusBlock::class.java) + override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side")) + + override fun processModel(state: BlockState, textures: List, atlas: Consumer): BlockRenderKey? { + val sprites = textures.map { Identifier(it) } + return CactusModel.Key(sprites[0], sprites[1], sprites[2]) + } +} + +class CactusModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel { + + val crossModels by cactusCrossModels.delegate(key) + val armModels by cactusArmModels.delegate(key) + val armLighting = horizontalDirections.map { grassTuftLighting(it) } + val crossLighting = roundLeafLighting() + + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + (wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context) + if (!BetterFoliage.config.enabled || !BetterFoliage.config.cactus.enabled) return + + val random = randomSupplier.get() + val armSide = random.nextInt() and 3 + + context.withLighting(armLighting[armSide]) { + it.accept(armModels[armSide][random]) + } + context.withLighting(crossLighting) { + it.accept(crossModels[random]) + } + } + + data class Key( + override val cactusTop: Identifier, + override val cactusBottom: Identifier, + override val cactusSide: Identifier + ) : CactusKey { + override fun replace(model: BakedModel, state: BlockState) = CactusModel(this, meshifyStandard(model, state)) + } + + companion object { + val cactusCrossSprite by SpriteDelegate(Atlas.BLOCKS) { + Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus") + } + val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_cactus_arm_$idx") + } + val cactusArmModels = LazyMap(BetterFoliage.modelReplacer) { key: CactusKey -> + val shapes = BetterFoliage.config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) } + val models = tuftModelSet(shapes, Color.white.asInt) { cactusArmSprites[randomI()] } + horizontalDirections.map { side -> + models.transform { move(0.0625 to DOWN).rotate(Rotation.fromUp[side.ordinal]) }.buildTufts() + }.toTypedArray() + } + val cactusCrossModels = LazyMap(BetterFoliage.modelReplacer) { key: CactusKey -> + val models = BetterFoliage.config.cactus.let { config -> + crossModelsRaw(64, config.size, 0.0, 0.0) + .transform { rotateZ(randomD(-config.sizeVariation, config.sizeVariation)) } + } + crossModelsTextured(models, Color.white.asInt, true) { cactusCrossSprite } + } + } +} diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Dirt.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Dirt.kt new file mode 100644 index 0000000..7193b59 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Dirt.kt @@ -0,0 +1,101 @@ +package mods.betterfoliage.render.block.vanilla + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.chunk.BasicBlockCtx +import mods.betterfoliage.render.DIRT_BLOCKS +import mods.betterfoliage.render.SALTWATER_BIOMES +import mods.betterfoliage.render.lighting.withLighting +import mods.betterfoliage.render.lighting.grassTuftLighting +import mods.betterfoliage.render.lighting.reedLighting +import mods.betterfoliage.render.lighting.renderMasquerade +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.resource.discovery.BlockRenderKey +import mods.betterfoliage.resource.discovery.ModelDiscoveryBase +import mods.betterfoliage.resource.discovery.ModelDiscoveryContext +import mods.betterfoliage.resource.generated.CenteredSprite +import mods.betterfoliage.resource.model.* +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.minecraft.block.BlockRenderLayer +import net.minecraft.block.BlockState +import net.minecraft.block.Material +import net.minecraft.client.MinecraftClient +import net.minecraft.client.render.model.BakedModel +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction.UP +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Consumer +import java.util.function.Supplier + +object DirtKey : BlockRenderKey { + override fun replace(model: BakedModel, state: BlockState) = DirtModel(meshifyStandard(model, state)) +} + +object DirtDiscovery : ModelDiscoveryBase() { + override val logger = BetterFoliage.logDetail + + override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer) = + if (ctx.state.block in DIRT_BLOCKS) DirtKey else null +} + +class DirtModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { + + val algaeLighting = grassTuftLighting(UP) + val reedLighting = reedLighting() + + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + + val ctx = BasicBlockCtx(blockView, pos) + val stateUp = ctx.offset(UP).state + val keyUp = BetterFoliage.modelReplacer[stateUp] + + 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 + + if (BetterFoliage.config.connectedGrass.enabled && keyUp is GrassKey) { + val grassBaseModel = (ctx.model(UP) as WrappedBakedModel).wrapped + context.renderMasquerade(grassBaseModel, blockView, stateUp, pos, randomSupplier, context) + } else { + super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + } + + val random = randomSupplier.get() + if (BetterFoliage.config.algae.enabled(random) && isDeepWater) { + context.withLighting(algaeLighting) { + it.accept(algaeModels[random]) + } + } else if (BetterFoliage.config.reed.enabled(random) && isShallowWater && !isSaltWater) { + context.withLighting(reedLighting) { + it.accept(reedModels[random]) + } + } + } + + companion object { + val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_algae_$idx") + } + val reedSprites by SpriteSetDelegate(Atlas.BLOCKS, + idFunc = { idx -> Identifier(BetterFoliage.MOD_ID, "blocks/better_reed_$idx") }, + idRegister = { id -> CenteredSprite(id, aspectHeight = 2).register(BetterFoliage.generatedPack) } + ) + val algaeModels by LazyInvalidatable(BetterFoliage.modelReplacer) { + val shapes = BetterFoliage.config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } + tuftModelSet(shapes, Color.white.asInt) { algaeSprites[randomI()] } + .withOpposites() + .build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false) + + } + val reedModels by LazyInvalidatable(BetterFoliage.modelReplacer) { + val shapes = BetterFoliage.config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) } + tuftModelSet(shapes, Color.white.asInt) { reedSprites[randomI()] } + .withOpposites() + .build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Grass.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Grass.kt new file mode 100644 index 0000000..b902d7e --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Grass.kt @@ -0,0 +1,121 @@ +package mods.betterfoliage.render.block.vanilla + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.chunk.BasicBlockCtx +import mods.betterfoliage.render.SNOW_MATERIALS +import mods.betterfoliage.render.lighting.withLighting +import mods.betterfoliage.render.lighting.grassTuftLighting +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.resource.discovery.* +import mods.betterfoliage.resource.model.* +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.minecraft.block.BlockRenderLayer +import net.minecraft.block.BlockState +import net.minecraft.client.render.model.BakedModel +import net.minecraft.tag.BlockTags +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction.* +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Consumer +import java.util.function.Supplier + +interface GrassKey : BlockRenderKey { + val grassTopTexture: Identifier + + /** + * Color to use for Short Grass rendering instead of the biome color. + * + * Value is null if the texture is mostly grey (the saturation of its average color is under a configurable limit), + * the average color of the texture otherwise. + */ + val overrideColor: Int? +} + +object StandardGrassDiscovery : ConfigurableModelDiscovery() { + override val logger = BetterFoliage.logDetail + override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.grassBlocks + override val modelTextures: List get() = BetterFoliage.blockConfig.grassModels.modelList + + override fun processModel(state: BlockState, textures: List, atlas: Consumer): BlockRenderKey? { + val grassId = Identifier(textures[0]) + log(" block state $state") + log(" texture $grassId") + return GrassBlockModel.Key(grassId, getAndLogColorOverride(grassId, Atlas.BLOCKS, BetterFoliage.config.shortGrass.saturationThreshold)) + } +} + +class GrassBlockModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel { + + val tuftNormal by grassTuftMeshesNormal.delegate(key) + val tuftSnowed by grassTuftMeshesSnowed.delegate(key) + val fullBlock by grassFullBlockMeshes.delegate(key) + + val tuftLighting = grassTuftLighting(UP) + + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + if (!BetterFoliage.config.enabled) return super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + + val ctx = BasicBlockCtx(blockView, pos) + val stateBelow = ctx.state(DOWN) + val stateAbove = ctx.state(UP) + + val isSnowed = stateAbove.material in SNOW_MATERIALS + val connected = BetterFoliage.config.connectedGrass.enabled && + (!isSnowed || BetterFoliage.config.connectedGrass.snowEnabled) && ( + BlockTags.DIRT_LIKE.contains(stateBelow.block) || + BetterFoliage.modelReplacer.getTyped(stateBelow) != null + ) + + val random = randomSupplier.get() + if (connected) { + context.meshConsumer().accept(if (isSnowed) snowFullBlockMeshes[random] else fullBlock[random]) + } else { + super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + } + + if (BetterFoliage.config.shortGrass.enabled(random) && !ctx.isNeighborSolid(UP)) { + context.withLighting(tuftLighting) { + it.accept(if (isSnowed) tuftSnowed[random] else tuftNormal[random]) + } + } + } + + data class Key( + override val grassTopTexture: Identifier, + override val overrideColor: Int? + ) : GrassKey { + override fun replace(model: BakedModel, state: BlockState) = GrassBlockModel(this, meshifyStandard(model, state)) + } + + companion object { + val grassTuftSpritesNormal by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx") + } + val grassTuftSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx") + } + val grassTuftShapes = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey -> + BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } + } + val grassTuftMeshesNormal = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey -> + tuftModelSet(grassTuftShapes[key], key.overrideColor) { idx -> grassTuftSpritesNormal[randomI()] } + .withOpposites() + .build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false) + } + val grassTuftMeshesSnowed = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey -> + tuftModelSet(grassTuftShapes[key], Color.white.asInt) { idx -> grassTuftSpritesSnowed[randomI()] } + .withOpposites() + .build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false) + } + val grassFullBlockMeshes = LazyMap(BetterFoliage.modelReplacer) { key: GrassKey -> + Array(64) { fullCubeTextured(key.grassTopTexture, key.overrideColor) } + } + val snowFullBlockMeshes by LazyInvalidatable(BetterFoliage.modelReplacer) { + Array(64) { fullCubeTextured(Identifier("block/snow"), Color.white.asInt) } + } + } +} diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Leaf.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Leaf.kt new file mode 100644 index 0000000..ee0f504 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Leaf.kt @@ -0,0 +1,116 @@ +package mods.betterfoliage.render.block.vanilla + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.chunk.BasicBlockCtx +import mods.betterfoliage.render.SNOW_MATERIALS +import mods.betterfoliage.render.lighting.withLighting +import mods.betterfoliage.render.lighting.roundLeafLighting +import mods.betterfoliage.render.particle.LeafParticleRegistry +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.resource.discovery.* +import mods.betterfoliage.resource.generated.GeneratedLeafSprite +import mods.betterfoliage.resource.model.* +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED +import net.minecraft.block.BlockState +import net.minecraft.client.render.model.BakedModel +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction.* +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Consumer +import java.util.function.Supplier + +interface LeafKey : BlockRenderKey { + val roundLeafTexture: Identifier + + /** Type of the leaf block (configurable by user). */ + val leafType: String + + /** Average color of the round leaf texture. */ + val overrideColor: Int? +} + +object StandardLeafDiscovery : ConfigurableModelDiscovery() { + override val logger = BetterFoliage.logDetail + override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.leafBlocks + override val modelTextures: List get() = BetterFoliage.blockConfig.leafModels.modelList + + override fun processModel(state: BlockState, textures: List, atlas: Consumer) = + defaultRegisterLeaf(Identifier(textures[0]), atlas) + +} + +fun HasLogger.defaultRegisterLeaf(sprite: Identifier, atlas: Consumer): BlockRenderKey? { + val leafType = LeafParticleRegistry.typeMappings.getType(sprite) ?: "default" + val leafId = GeneratedLeafSprite(sprite, leafType).register(BetterFoliage.generatedPack) + atlas.accept(leafId) + + log(" leaf texture $sprite") + log(" particle $leafType") + + return NormalLeavesModel.Key( + leafId, leafType, + getAndLogColorOverride(leafId, Atlas.BLOCKS, BetterFoliage.config.shortGrass.saturationThreshold) + ) +} + +fun HasLogger.getAndLogColorOverride(sprite: Identifier, atlas: Atlas, threshold: Double): Int? { + val hsb = resourceManager.averageImageColorHSB(sprite, atlas) + return if (hsb.saturation >= threshold) { + log(" brightness ${hsb.brightness}") + log(" saturation ${hsb.saturation} >= ${threshold}, using texture color") + hsb.copy(brightness = 0.9f.coerceAtMost(hsb.brightness * 2.0f)).asColor + } else { + log(" saturation ${hsb.saturation} < ${threshold}, using block color") + null + } +} + +class NormalLeavesModel(val key: Key, wrapped: BakedModel) : WrappedBakedModel(wrapped), FabricBakedModel { + + val leafNormal by leafModelsNormal.delegate(key) + val leafSnowed by leafModelsSnowed.delegate(key) + val leafLighting = roundLeafLighting() + + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + if (!BetterFoliage.config.enabled || !BetterFoliage.config.leaves.enabled) return + + val ctx = BasicBlockCtx(blockView, pos) + val stateAbove = ctx.state(UP) + val isSnowed = stateAbove.material in SNOW_MATERIALS + + val random = randomSupplier.get() + context.withLighting(leafLighting) { + it.accept(leafNormal[random]) + if (isSnowed) it.accept(leafSnowed[random]) + } + } + + data class Key( + override val roundLeafTexture: Identifier, + override val leafType: String, + override val overrideColor: Int? + ) : LeafKey { + override fun replace(model: BakedModel, state: BlockState) = NormalLeavesModel(this, meshifyStandard(model, state, renderLayerOverride = CUTOUT_MIPPED)) + } + + companion object { + val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_leaves_snowed_$idx") + } + val leafModelsBase = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey -> + BetterFoliage.config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) } + } + val leafModelsNormal = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey -> + crossModelsTextured(leafModelsBase[key], key.overrideColor, true) { Atlas.BLOCKS.atlas[key.roundLeafTexture]!! } + } + val leafModelsSnowed = LazyMap(BetterFoliage.modelReplacer) { key: LeafKey -> + crossModelsTextured(leafModelsBase[key], Color.white.asInt, false) { leafSpritesSnowed[it] } + } + } +} diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Lilypad.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Lilypad.kt new file mode 100644 index 0000000..8cd033e --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Lilypad.kt @@ -0,0 +1,68 @@ +package mods.betterfoliage.render.block.vanilla + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.resource.discovery.BlockRenderKey +import mods.betterfoliage.resource.discovery.ModelDiscoveryBase +import mods.betterfoliage.resource.discovery.ModelDiscoveryContext +import mods.betterfoliage.resource.discovery.RenderKeyFactory +import mods.betterfoliage.resource.model.* +import mods.betterfoliage.util.LazyInvalidatable +import mods.betterfoliage.util.get +import mods.betterfoliage.util.semiRandom +import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.minecraft.block.BlockState +import net.minecraft.block.Blocks +import net.minecraft.client.render.model.BakedModel +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction.DOWN +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Consumer +import java.util.function.Supplier + +object LilypadKey : BlockRenderKey { + override fun replace(model: BakedModel, state: BlockState) = LilypadModel(meshifyStandard(model, state)) +} + +object LilyPadDiscovery : ModelDiscoveryBase() { + override val logger = BetterFoliage.logDetail + override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer) = + if (ctx.state.block == Blocks.LILY_PAD) LilypadKey else null +} + +class LilypadModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + if (!BetterFoliage.config.enabled || !BetterFoliage.config.lilypad.enabled) return + + val random = randomSupplier.get() + context.meshConsumer().accept(lilypadRootModels[random]) + if (random.nextInt(64) < BetterFoliage.config.lilypad.population) { + context.meshConsumer().accept(lilypadFlowerModels[random]) + } + } + + companion object { + val lilypadRootSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_roots_$idx") + } + val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_lilypad_flower_$idx") + } + val lilypadRootModels by LazyInvalidatable(BetterFoliage.modelReplacer) { + val shapes = tuftShapeSet(1.0, 1.0, 1.0, BetterFoliage.config.lilypad.hOffset) + tuftModelSet(shapes, Color.white.asInt) { lilypadRootSprites[it] } + .transform { move(2.0 to DOWN) } + .buildTufts() + } + val lilypadFlowerModels by LazyInvalidatable(BetterFoliage.modelReplacer) { + val shapes = tuftShapeSet(0.5, 0.5, 0.5, BetterFoliage.config.lilypad.hOffset) + tuftModelSet(shapes, Color.white.asInt) { lilypadFlowerSprites[it] } + .transform { move(1.0 to DOWN) } + .buildTufts() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Mycelium.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Mycelium.kt new file mode 100644 index 0000000..a7258d7 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Mycelium.kt @@ -0,0 +1,63 @@ +package mods.betterfoliage.render.block.vanilla + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.render.lighting.withLighting +import mods.betterfoliage.render.lighting.grassTuftLighting +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.resource.discovery.BlockRenderKey +import mods.betterfoliage.resource.discovery.ModelDiscoveryBase +import mods.betterfoliage.resource.discovery.ModelDiscoveryContext +import mods.betterfoliage.resource.discovery.RenderKeyFactory +import mods.betterfoliage.resource.model.* +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.minecraft.block.BlockState +import net.minecraft.block.Blocks +import net.minecraft.client.render.model.BakedModel +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction.UP +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Consumer +import java.util.function.Supplier + +object MyceliumKey : BlockRenderKey { + override fun replace(model: BakedModel, state: BlockState) = MyceliumModel(meshifyStandard(model, state)) +} + +object MyceliumDiscovery : ModelDiscoveryBase() { + override val logger = BetterFoliage.logDetail + val myceliumBlocks = listOf(Blocks.MYCELIUM) + override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer) = + if (ctx.state.block in myceliumBlocks) MyceliumKey else null +} + +class MyceliumModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { + + val tuftLighting = grassTuftLighting(UP) + + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + + val random = randomSupplier.get() + if (BetterFoliage.config.enabled && + BetterFoliage.config.shortGrass.let { it.myceliumEnabled && random.nextInt(64) < it.population } && + blockView.getBlockState(pos + UP.offset).isAir + ) { + context.withLighting(tuftLighting) { + it.accept(myceliumTuftModels[random]) + } + } + } + + companion object { + val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx") + } + val myceliumTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) { + val shapes = BetterFoliage.config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } + tuftModelSet(shapes, Color.white.asInt) { idx -> myceliumTuftSprites[randomI()] }.buildTufts() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Netherrack.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Netherrack.kt new file mode 100644 index 0000000..5016b79 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Netherrack.kt @@ -0,0 +1,66 @@ +package mods.betterfoliage.render.block.vanilla + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.render.lighting.withLighting +import mods.betterfoliage.render.lighting.grassTuftLighting +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.resource.discovery.BlockRenderKey +import mods.betterfoliage.resource.discovery.ModelDiscoveryBase +import mods.betterfoliage.resource.discovery.ModelDiscoveryContext +import mods.betterfoliage.resource.discovery.RenderKeyFactory +import mods.betterfoliage.resource.model.* +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.minecraft.block.BlockRenderLayer +import net.minecraft.block.BlockState +import net.minecraft.block.Blocks +import net.minecraft.client.render.model.BakedModel +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction.DOWN +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Consumer +import java.util.function.Supplier + +object NetherrackKey : BlockRenderKey { + override fun replace(model: BakedModel, state: BlockState) = NetherrackModel(meshifyStandard(model, state)) +} + +object NetherrackDiscovery : ModelDiscoveryBase() { + override val logger = BetterFoliage.logDetail + val netherrackBlocks = listOf(Blocks.NETHERRACK) + override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer) = + if (ctx.state.block in netherrackBlocks) NetherrackKey else null +} + +class NetherrackModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { + + val tuftLighting = grassTuftLighting(DOWN) + + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + if (BetterFoliage.config.enabled && + BetterFoliage.config.netherrack.enabled && + blockView.getBlockState(pos + DOWN.offset).isAir + ) { + val random = randomSupplier.get() + context.withLighting(tuftLighting) { + it.accept(netherrackTuftModels[random]) + } + } + } + + companion object { + val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx") + } + val netherrackTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) { + val shapes = BetterFoliage.config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) } + tuftModelSet(shapes, Color.white.asInt) { netherrackTuftSprites[randomI()] } + .transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) } + .withOpposites() + .build(BlockRenderLayer.CUTOUT_MIPPED, flatLighting = false) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/RoundLog.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/RoundLog.kt new file mode 100644 index 0000000..e1cc731 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/RoundLog.kt @@ -0,0 +1,84 @@ +package mods.betterfoliage.render.block.vanilla + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.render.column.* +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.resource.discovery.* +import mods.betterfoliage.resource.model.meshifyStandard +import mods.betterfoliage.util.LazyMap +import mods.betterfoliage.util.get +import mods.betterfoliage.util.tryDefault +import net.minecraft.block.BlockState +import net.minecraft.block.LogBlock +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.texture.SpriteAtlasTexture +import net.minecraft.util.Identifier +import net.minecraft.util.math.Direction.Axis +import java.util.function.Consumer + +object RoundLogOverlayLayer : ColumnRenderLayer() { + override fun getColumnKey(state: BlockState) = BetterFoliage.modelReplacer.getTyped(state) + override val connectSolids: Boolean get() = BetterFoliage.config.roundLogs.connectSolids + override val lenientConnect: Boolean get() = BetterFoliage.config.roundLogs.lenientConnect + override val defaultToY: Boolean get() = BetterFoliage.config.roundLogs.defaultY +} + +object StandardLogDiscovery : ConfigurableModelDiscovery() { + override val logger = BetterFoliage.logDetail + override val matchClasses: ConfigurableBlockMatcher get() = BetterFoliage.blockConfig.logBlocks + override val modelTextures: List get() = BetterFoliage.blockConfig.logModels.modelList + + override fun processModel(state: BlockState, textures: List, atlas: Consumer): BlockRenderKey? { + val axis = getAxis(state) + log(" axis $axis") + return RoundLogModel.Key(axis, Identifier(textures[0]), Identifier(textures[1])) + } + + fun getAxis(state: BlockState): Axis? { + val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?: + state.entries.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString() + return when (axis) { + "x" -> Axis.X + "y" -> Axis.Y + "z" -> Axis.Z + else -> null + } + } +} + +interface RoundLogKey : ColumnBlockKey, BlockRenderKey { + val barkSprite: Identifier + val endSprite: Identifier +} + +class RoundLogModel(val key: Key, wrapped: BakedModel) : ColumnModelBase(wrapped) { + override val enabled: Boolean get() = BetterFoliage.config.enabled && BetterFoliage.config.roundLogs.enabled + override val overlayLayer: ColumnRenderLayer get() = RoundLogOverlayLayer + override val connectPerpendicular: Boolean get() = BetterFoliage.config.roundLogs.connectPerpendicular + + val modelSet by modelSets.delegate(key) + override fun getMeshSet(axis: Axis, quadrant: Int) = modelSet + + data class Key( + override val axis: Axis?, + override val barkSprite: Identifier, + override val endSprite: Identifier + ) : RoundLogKey { + override fun replace(model: BakedModel, state: BlockState) = RoundLogModel(this, meshifyStandard(model, state)) + } + + companion object { + val modelSets = LazyMap(BetterFoliage.modelReplacer) { key: Key -> + val barkSprite = Atlas.BLOCKS.atlas[key.barkSprite]!! + val endSprite = Atlas.BLOCKS.atlas[key.endSprite]!! + BetterFoliage.config.roundLogs.let { config -> + ColumnMeshSet( + config.radiusSmall, config.radiusLarge, config.zProtection, + key.axis ?: Axis.Y, + barkSprite, barkSprite, + endSprite, endSprite + ) + } + } + } +} diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Sand.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Sand.kt new file mode 100644 index 0000000..67527ca --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Sand.kt @@ -0,0 +1,96 @@ +package mods.betterfoliage.render.block.vanilla + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.chunk.CachedBlockCtx +import mods.betterfoliage.render.SALTWATER_BIOMES +import mods.betterfoliage.render.SAND_BLOCKS +import mods.betterfoliage.render.lighting.withLighting +import mods.betterfoliage.render.lighting.grassTuftLighting +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.resource.discovery.BlockRenderKey +import mods.betterfoliage.resource.discovery.ModelDiscoveryBase +import mods.betterfoliage.resource.discovery.ModelDiscoveryContext +import mods.betterfoliage.resource.model.* +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED +import net.minecraft.block.BlockState +import net.minecraft.block.Material +import net.minecraft.client.render.model.BakedModel +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction.UP +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Consumer +import java.util.function.Supplier + +object SandKey : BlockRenderKey { + override fun replace(model: BakedModel, state: BlockState) = SandModel(meshifyStandard(model, state)) +} + +object SandDiscovery : ModelDiscoveryBase() { + override val logger = BetterFoliage.logDetail + + override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer) = + if (ctx.state.block in SAND_BLOCKS) SandKey else null +} + +class SandModel(wrapped: BakedModel) : WrappedBakedModel(wrapped) { + + val coralLighting = allDirections.map { grassTuftLighting(it) }.toTypedArray() + + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + + val ctx = CachedBlockCtx(blockView, pos) + + val random = randomSupplier.get() + if (!BetterFoliage.config.enabled || !BetterFoliage.config.coral.enabled(random)) return + if (ctx.biome.category !in SALTWATER_BIOMES) return + + allDirections.filter { random.nextInt(64) < BetterFoliage.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) context.withLighting(coralLighting[face]) { + it.accept(coralCrustModels[face][random]) + it.accept(coralTuftModels[face][random]) + } + } + } + + companion object { +// val sandModel by LazyInvalidatable(BetterFoliage.modelReplacer) { +// Array(64) { fullCubeTextured(Identifier("block/sand"), Color.white.asInt) } +// } + + val coralTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_coral_$idx") + } + val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx -> + Identifier(BetterFoliage.MOD_ID, "blocks/better_crust_$idx") + } + val coralTuftModels by LazyInvalidatable(BetterFoliage.modelReplacer) { + val shapes = BetterFoliage.config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) } + allDirections.map { face -> + tuftModelSet(shapes, Color.white.asInt) { coralTuftSprites[randomI()] } + .transform { rotate(Rotation.fromUp[face]) } + .withOpposites() + .build(CUTOUT_MIPPED) + }.toTypedArray() + } + val coralCrustModels by LazyInvalidatable(BetterFoliage.modelReplacer) { + allDirections.map { face -> + Array(64) { idx -> + listOf(horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0) + .scale(BetterFoliage.config.coral.crustSize) + .move(0.5 + randomD(0.01, BetterFoliage.config.coral.vOffset) to UP) + .rotate(Rotation.fromUp[face]) + .mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4)) + .sprite(coralCrustSprites[idx]).colorAndIndex(null) + ).build(CUTOUT_MIPPED) + } + }.toTypedArray() + } + } +} diff --git a/src/main/kotlin/mods/betterfoliage/render/column/ColumnMeshSet.kt b/src/main/kotlin/mods/betterfoliage/render/column/ColumnMeshSet.kt new file mode 100644 index 0000000..6bbb9c0 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/column/ColumnMeshSet.kt @@ -0,0 +1,158 @@ +package mods.betterfoliage.render.column + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType +import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.* +import mods.betterfoliage.resource.model.* +import mods.betterfoliage.util.Double3 +import mods.betterfoliage.util.Rotation +import net.minecraft.block.BlockRenderLayer.SOLID +import net.minecraft.client.texture.Sprite +import net.minecraft.util.math.Direction.* + +/** + * Collection of dynamically generated meshes used to render rounded columns. + */ +class ColumnMeshSet( + radiusSmall: Double, + radiusLarge: Double, + zProtection: Double, + val axis: Axis, + val spriteLeft: Sprite, + val spriteRight: Sprite, + val spriteTop: Sprite, + val spriteBottom: Sprite +) { + protected fun sideRounded(radius: Double, yBottom: Double, yTop: Double): List { + val halfRadius = radius * 0.5 + return listOf( + // left side of the diagonal + verticalRectangle(0.0, 0.5, 0.5 - radius, 0.5, yBottom, yTop).clampUV(minU = 0.0, maxU = 0.5 - radius), + verticalRectangle(0.5 - radius, 0.5, 0.5 - halfRadius, 0.5 - halfRadius, yBottom, yTop).clampUV(minU = 0.5 - radius), + // right side of the diagonal + verticalRectangle(0.5 - halfRadius, 0.5 - halfRadius, 0.5, 0.5 - radius, yBottom, yTop).clampUV(maxU = radius - 0.5), + verticalRectangle(0.5, 0.5 - radius, 0.5, 0.0, yBottom, yTop).clampUV(minU = radius - 0.5, maxU = 0.0) + ) + } + + protected fun sideRoundedTransition(radiusBottom: Double, radiusTop: Double, yBottom: Double, yTop: Double): List { + val ySplit = 0.5 * (yBottom + yTop) + val modelTop = sideRounded(radiusTop, yBottom, yTop) + val modelBottom = sideRounded(radiusBottom, yBottom, yTop) + return (modelBottom zip modelTop).map { (quadBottom, quadTop) -> + Quad.mix(quadBottom, quadTop) { vBottom, vTop -> if (vBottom.xyz.y < ySplit) vBottom.copy() else vTop.copy() } + } + } + + protected fun sideSquare(yBottom: Double, yTop: Double) = listOf( + verticalRectangle(0.0, 0.5, 0.5, 0.5, yBottom, yTop).clampUV(minU = 0.0), + verticalRectangle(0.5, 0.5, 0.5, 0.0, yBottom, yTop).clampUV(maxU = 0.0) + ) + + protected fun lidRounded(radius: Double, y: Double, isBottom: Boolean) = Array(4) { quadrant -> + val rotation = baseRotation(axis) + quadrantRotations[quadrant] + val v1 = Vertex(Double3(0.0, y, 0.0), UV(0.0, 0.0)) + val v2 = Vertex(Double3(0.0, y, 0.5), UV(0.0, 0.5)) + val v3 = Vertex(Double3(0.5 - radius, y, 0.5), UV(0.5 - radius, 0.5)) + val v4 = Vertex(Double3(0.5 - radius * 0.5, y, 0.5 - radius * 0.5), UV(0.5, 0.5)) + val v5 = Vertex(Double3(0.5, y, 0.5 - radius), UV(0.5, 0.5 - radius)) + val v6 = Vertex(Double3(0.5, y, 0.0), UV(0.5, 0.0)) + listOf(Quad(v1, v2, v3, v4), Quad(v1, v4, v5, v6)) + .map { it.cycleVertices(if (isBottom xor BetterFoliage.config.nVidia) 0 else 1) } + .map { it.rotate(rotation).rotateUV(quadrant) } + .map { it.sprite(if (isBottom) spriteBottom else spriteTop).colorAndIndex(Color.white.asInt) } + .map { if (isBottom) it.flipped else it } + } + + protected fun lidSquare(y: Double, isBottom: Boolean) = Array(4) { quadrant -> + val rotation = baseRotation(axis) + quadrantRotations[quadrant] + listOf( + horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = y).clampUV(minU = 0.0, minV = 0.0) + .rotate(rotation).rotateUV(quadrant) + .sprite(if (isBottom) spriteBottom else spriteTop).colorAndIndex(Color.white.asInt) + .let { if (isBottom) it.flipped else it } + ) + } + + protected val zProtectionScale = zProtection.let { Double3(it, 1.0, it) } + + protected fun List.extendTop(size: Double) = map { q -> q.clampUV(minV = 0.5 - size).transformV { v -> + if (v.xyz.y > 0.501) v.copy(xyz = v.xyz * zProtectionScale) else v } + } + protected fun List.extendBottom(size: Double) = map { q -> q.clampUV(maxV = -0.5 + size).transformV { v -> + if (v.xyz.y < -0.501) v.copy(xyz = v.xyz * zProtectionScale) else v } + } + protected fun List.buildSides(quadsPerSprite: Int) = Array(4) { quadrant -> + val rotation = baseRotation(axis) + quadrantRotations[quadrant] + this.map { it.rotate(rotation).colorAndIndex(Color.white.asInt) } + .mapIndexed { idx, q -> if (idx % (2 * quadsPerSprite) >= quadsPerSprite) q.sprite(spriteRight) else q.sprite(spriteLeft) } + .build(SOLID, flatLighting = false) + } + + companion object { + fun baseRotation(axis: Axis) = when(axis) { + Axis.X -> Rotation.fromUp[EAST.ordinal] + Axis.Y -> Rotation.fromUp[UP.ordinal] + Axis.Z -> Rotation.fromUp[SOUTH.ordinal] + } + val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it } + } + + // + // Mesh definitions + // 4-element arrays hold prebuild meshes for each of the rotations around the axis + // + val sideSquare = sideSquare(-0.5, 0.5).buildSides(quadsPerSprite = 1) + val sideRoundSmall = sideRounded(radiusSmall, -0.5, 0.5).buildSides(quadsPerSprite = 2) + val sideRoundLarge = sideRounded(radiusLarge, -0.5, 0.5).buildSides(quadsPerSprite = 2) + + val sideExtendTopSquare = sideSquare(0.5, 0.5 + radiusLarge).extendTop(radiusLarge).buildSides(quadsPerSprite = 1) + val sideExtendTopRoundSmall = sideRounded(radiusSmall, 0.5, 0.5 + radiusLarge).extendTop(radiusLarge).buildSides(quadsPerSprite = 2) + val sideExtendTopRoundLarge = sideRounded(radiusLarge, 0.5, 0.5 + radiusLarge).extendTop(radiusLarge).buildSides(quadsPerSprite = 2) + + val sideExtendBottomSquare = sideSquare(-0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 1) + val sideExtendBottomRoundSmall = sideRounded(radiusSmall, -0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 2) + val sideExtendBottomRoundLarge = sideRounded(radiusLarge, -0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 2) + + val lidTopSquare = lidSquare(0.5, false).build(SOLID, flatLighting = false) + val lidTopRoundSmall = lidRounded(radiusSmall, 0.5, false).build(SOLID, flatLighting = false) + val lidTopRoundLarge = lidRounded(radiusLarge, 0.5, false).build(SOLID, flatLighting = false) + + val lidBottomSquare = lidSquare(-0.5, true).build(SOLID, flatLighting = false) + val lidBottomRoundSmall = lidRounded(radiusSmall, -0.5, true).build(SOLID, flatLighting = false) + val lidBottomRoundLarge = lidRounded(radiusLarge, -0.5, true).build(SOLID, flatLighting = false) + + val transitionTop = sideRoundedTransition(radiusLarge, radiusSmall, -0.5, 0.5).buildSides(quadsPerSprite = 2) + val transitionBottom = sideRoundedTransition(radiusSmall, radiusLarge, -0.5, 0.5).buildSides(quadsPerSprite = 2) + + // + // Helper fuctions for lids (block ends) + // + fun flatTop(quadrantTypes: Array, quadrant: Int) = when(quadrantTypes[quadrant]) { + SMALL_RADIUS -> lidTopRoundSmall[quadrant] + LARGE_RADIUS -> lidTopRoundLarge[quadrant] + SQUARE -> lidTopSquare[quadrant] + INVISIBLE -> lidTopSquare[quadrant] + } + + fun flatBottom(quadrantTypes: Array, quadrant: Int) = when(quadrantTypes[quadrant]) { + SMALL_RADIUS -> lidBottomRoundSmall[quadrant] + LARGE_RADIUS -> lidBottomRoundLarge[quadrant] + SQUARE -> lidBottomSquare[quadrant] + INVISIBLE -> lidBottomSquare[quadrant] + } + + fun extendTop(quadrantTypes: Array, quadrant: Int) = when(quadrantTypes[quadrant]) { + SMALL_RADIUS -> sideExtendTopRoundSmall[quadrant] + LARGE_RADIUS -> sideExtendTopRoundLarge[quadrant] + SQUARE -> sideExtendTopSquare[quadrant] + INVISIBLE -> sideExtendTopSquare[quadrant] + } + + fun extendBottom(quadrantTypes: Array, quadrant: Int) = when(quadrantTypes[quadrant]) { + SMALL_RADIUS -> sideExtendBottomRoundSmall[quadrant] + LARGE_RADIUS -> sideExtendBottomRoundLarge[quadrant] + SQUARE -> sideExtendBottomSquare[quadrant] + INVISIBLE -> sideExtendBottomSquare[quadrant] + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/column/ColumnModel.kt b/src/main/kotlin/mods/betterfoliage/render/column/ColumnModel.kt new file mode 100644 index 0000000..c3914db --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/column/ColumnModel.kt @@ -0,0 +1,108 @@ +package mods.betterfoliage.render.column + +import mods.betterfoliage.chunk.CachedBlockCtx +import mods.betterfoliage.chunk.ChunkOverlayManager +import mods.betterfoliage.render.column.ColumnLayerData.NormalRender +import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.* +import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.* +import mods.betterfoliage.resource.model.WrappedBakedModel +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.minecraft.block.BlockState +import net.minecraft.client.render.model.BakedModel +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction.Axis +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Supplier + +abstract class ColumnModelBase(wrapped: BakedModel) : WrappedBakedModel(wrapped) { + abstract val enabled: Boolean + abstract val overlayLayer: ColumnRenderLayer + abstract val connectPerpendicular: Boolean + abstract fun getMeshSet(axis: Axis, quadrant: Int): ColumnMeshSet + + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + val ctx = CachedBlockCtx(blockView, pos) + val roundLog = ChunkOverlayManager.get(overlayLayer, ctx) + + when(roundLog) { + ColumnLayerData.SkipRender -> return + NormalRender -> return super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + ColumnLayerData.ResolveError, null -> { + return super.emitBlockQuads(blockView, state, pos, randomSupplier, context) + } + } + + // 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.emitBlockQuads(blockView, state, pos, randomSupplier, context) + } + + val axis = roundLog.column.axis ?: Axis.Y + val baseRotation = ColumnMeshSet.baseRotation(axis) + ColumnMeshSet.quadrantRotations.forEachIndexed { idx, quadrantRotation -> + // set rotation for the current quadrant + val rotation = baseRotation + quadrantRotation + val meshSet = getMeshSet(axis, idx) + + // disallow sharp discontinuities in the chamfer radius, or tapering-in where inappropriate + if (roundLog.quadrants[idx] == LARGE_RADIUS && + roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] != LARGE_RADIUS && + roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] != LARGE_RADIUS) { + roundLog.quadrants[idx] = SMALL_RADIUS + } + + // select meshes for current quadrant based on connectivity rules + val sideMesh = when (roundLog.quadrants[idx]) { + SMALL_RADIUS -> meshSet.sideRoundSmall[idx] + LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) meshSet.transitionTop[idx] + else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) meshSet.transitionBottom[idx] + else meshSet.sideRoundLarge[idx] + SQUARE -> meshSet.sideSquare[idx] + else -> null + } + + val upMesh = when(roundLog.upType) { + NONSOLID -> meshSet.flatTop(roundLog.quadrants, idx) + PERPENDICULAR -> { + if (!connectPerpendicular) { + meshSet.flatTop(roundLog.quadrants, idx) + } else { + meshSet.extendTop(roundLog.quadrants, idx) + } + } + PARALLEL -> { + if (roundLog.quadrants[idx] discontinuousWith roundLog.quadrantsTop[idx] && + roundLog.quadrants[idx].let { it == SQUARE || it == INVISIBLE } ) + meshSet.flatTop(roundLog.quadrants, idx) + else null + } + else -> null + } + + val downMesh = when(roundLog.downType) { + NONSOLID -> meshSet.flatBottom(roundLog.quadrants, idx) + PERPENDICULAR -> { + if (!connectPerpendicular) { + meshSet.flatBottom(roundLog.quadrants, idx) + } else { + meshSet.extendBottom(roundLog.quadrants, idx) + } + } + PARALLEL -> { + if (roundLog.quadrants[idx] discontinuousWith roundLog.quadrantsBottom[idx] && + roundLog.quadrants[idx].let { it == SQUARE || it == INVISIBLE } ) + meshSet.flatBottom(roundLog.quadrants, idx) + else null + } + else -> null + } + + // render + sideMesh?.let { context.meshConsumer().accept(it) } + upMesh?.let { context.meshConsumer().accept(it) } + downMesh?.let { context.meshConsumer().accept(it) } + + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/column/OverlayLayer.kt b/src/main/kotlin/mods/betterfoliage/render/column/ColumnOverlayLayer.kt similarity index 80% rename from src/main/kotlin/mods/betterfoliage/client/render/column/OverlayLayer.kt rename to src/main/kotlin/mods/betterfoliage/render/column/ColumnOverlayLayer.kt index 678af49..c80ec88 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/column/OverlayLayer.kt +++ b/src/main/kotlin/mods/betterfoliage/render/column/ColumnOverlayLayer.kt @@ -1,21 +1,19 @@ -package mods.betterfoliage.client.render.column +package mods.betterfoliage.render.column -import mods.betterfoliage.client.chunk.ChunkOverlayLayer -import mods.betterfoliage.client.chunk.ChunkOverlayManager -import mods.betterfoliage.client.chunk.dimType -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.* -import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType -import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.* -import mods.betterfoliage.client.render.rotationFromUp -import mods.octarinecore.client.render.BlockCtx -import mods.octarinecore.client.resource.ModelRenderRegistry -import mods.octarinecore.common.* +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.chunk.ChunkOverlayLayer +import mods.betterfoliage.chunk.ChunkOverlayManager +import mods.betterfoliage.chunk.dimType +import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.* +import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType +import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.* +import mods.betterfoliage.chunk.BlockCtx +import mods.betterfoliage.util.* import net.minecraft.block.BlockState -import net.minecraft.util.Direction.Axis -import net.minecraft.util.Direction.AxisDirection import net.minecraft.util.math.BlockPos -import net.minecraft.world.IEnviromentBlockReader +import net.minecraft.util.math.Direction.Axis +import net.minecraft.util.math.Direction.AxisDirection +import net.minecraft.world.ExtendedBlockView /** Index of SOUTH-EAST quadrant. */ const val SE = 0 @@ -26,6 +24,10 @@ const val NW = 2 /** Index of SOUTH-WEST quadrant. */ const val SW = 3 +interface ColumnBlockKey { + val axis: Axis? +} + /** * Sealed class hierarchy for all possible render outcomes */ @@ -35,7 +37,7 @@ sealed class ColumnLayerData { */ @Suppress("ArrayInDataClass") // not used in comparisons anywhere data class SpecialRender( - val column: ColumnTextureInfo, + val column: ColumnBlockKey, val upType: BlockType, val downType: BlockType, val quadrants: Array, @@ -43,7 +45,12 @@ sealed class ColumnLayerData { val quadrantsBottom: Array ) : ColumnLayerData() { enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR } - enum class QuadrantType { SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE } + enum class QuadrantType { + SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE; + infix fun continuousWith(other: QuadrantType) = + this == other || ((this == SQUARE || this == INVISIBLE) && (other == SQUARE || other == INVISIBLE)) + infix fun discontinuousWith(other: QuadrantType) = !continuousWith(other) + } } /** Column block should not be rendered at all */ @@ -56,30 +63,30 @@ sealed class ColumnLayerData { object ResolveError : ColumnLayerData() } - abstract class ColumnRenderLayer : ChunkOverlayLayer { - abstract val registry: ModelRenderRegistry - abstract val blockPredicate: (BlockState)->Boolean abstract val connectSolids: Boolean abstract val lenientConnect: Boolean abstract val defaultToY: Boolean + abstract fun getColumnKey(state: BlockState): ColumnBlockKey? + val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}} - override fun onBlockUpdate(world: IEnviromentBlockReader, pos: BlockPos) { + override fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos) { allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) } } override fun calculate(ctx: BlockCtx): ColumnLayerData { if (allDirections.all { ctx.offset(it).isNormalCube }) return ColumnLayerData.SkipRender - val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError +// val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError + 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 val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender // check log neighborhood - val baseRotation = rotationFromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal] + val baseRotation = Rotation.fromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal] val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0)) val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0)) @@ -167,11 +174,11 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer { */ fun BlockCtx.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType { val offsetRot = offset.rotate(rotation) - val state = state(offsetRot) - return if (!blockPredicate(state)) { + val key = getColumnKey(state(offsetRot)) + return if (key == null) { if (offset(offsetRot).isNormalCube) SOLID else NONSOLID } else { - (registry[state, world, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let { + (key.axis ?: if (BetterFoliage.config.roundLogs.defaultY) Axis.Y else null)?.let { if (it == axis) PARALLEL else PERPENDICULAR } ?: SOLID } diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/CustomLighting.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/CustomLighting.kt new file mode 100644 index 0000000..134acf2 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/lighting/CustomLighting.kt @@ -0,0 +1,140 @@ +package mods.betterfoliage.render.lighting + +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Direction.* +import kotlin.math.abs + +val EPSILON = 0.05 + +interface CustomLighting { + fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean) +} + +interface CustomLightingMeshConsumer { + /** Clear cached block brightness and AO values */ + fun clearLighting() + /** Fill AO/light cache for given face */ + fun fillAoData(lightFace: Direction) + /** Set AO/light values for quad vertex */ + fun setLighting(vIdx: Int, ao: Float, light: Int) + /** Get neighbor block brightness */ + fun brNeighbor(dir: Direction): Int + /** Block brightness value */ + val brSelf: Int + /** Cached AO values for all box face corners */ + val aoFull: FloatArray + /** Cached light values for all box face corners */ + val lightFull: IntArray +} + +/** Custom lighting used for protruding tuft quads (short grass, algae, cactus arms, etc.) */ +fun grassTuftLighting(lightFace: Direction) = object : CustomLighting { + override fun applyLighting(lighting: CustomLightingMeshConsumer, quad : QuadView, flat: Boolean, emissive: Boolean) { + if (flat) lighting.flatForceNeighbor(quad, lightFace) else lighting.smoothWithFaceOverride(quad, lightFace) + } +} + +/** Custom lighting used for round leaves */ +fun roundLeafLighting() = object : CustomLighting { + override fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean) { + if (flat) lighting.flatMax(quad) else lighting.smooth45PreferUp(quad) + } +} + +/** Custom lighting used for reeds */ +fun reedLighting() = object : CustomLighting { + override fun applyLighting(lighting: CustomLightingMeshConsumer, quad: QuadView, flat: Boolean, emissive: Boolean) { + lighting.flatForceNeighbor(quad, UP) + } +} + +/** Flat lighting, use neighbor brightness in the given direction */ +fun CustomLightingMeshConsumer.flatForceNeighbor(quad: QuadView, lightFace: Direction) { + for (vIdx in 0 until 4) { + setLighting(vIdx, 1.0f, brNeighbor(lightFace)) + } +} + +/** Smooth lighting, use *only* AO/light values on the given face (closest corner) */ +fun CustomLightingMeshConsumer.smoothWithFaceOverride(quad: QuadView, lightFace: Direction) { + fillAoData(lightFace) + forEachVertex(quad) { vIdx, x, y, z -> + val cornerUndir = getCornerUndir(x, y, z) + cornerDirFromUndir[lightFace.ordinal][cornerUndir]?.let { aoCorner -> + setLighting(vIdx, aoFull[aoCorner], lightFull[aoCorner]) + } + } +} + +/** + * Smooth lighting scheme for 45-degree quads bisecting the box along 2 opposing face diagonals. + * + * Determine 2 *primary faces* based on the normal direction. + * Take AO/light values *only* from the 2 primary faces *or* the UP direction, + * based on which box corner is closest. Prefer taking values from the top face. + */ +fun CustomLightingMeshConsumer.smooth45PreferUp(quad: QuadView) { + getAngles45(quad)?.let { normalFaces -> + fillAoData(normalFaces.first) + fillAoData(normalFaces.second) + if (normalFaces.first != UP && normalFaces.second != UP) fillAoData(UP) + forEachVertex(quad) { vIdx, x, y, z -> + val isUp = y > 0.5f + val cornerUndir = getCornerUndir(x, y, z) + val preferredFace = if (isUp) UP else normalFaces.minBy { faceDistance(it, x, y, z) } + val aoCorner = cornerDirFromUndir[preferredFace.ordinal][cornerUndir]!! + setLighting(vIdx, aoFull[aoCorner], lightFull[aoCorner]) + } + } +} + +/** Flat lighting, use maximum neighbor brightness at the nearest box corner */ +fun CustomLightingMeshConsumer.flatMax(quad: QuadView) { + forEachVertex(quad) { vIdx, x, y, z -> + val maxBrightness = cornersUndir[getCornerUndir(x, y, z)].maxValueBy { brNeighbor(it) } + setLighting(vIdx, 1.0f, maxBrightness) + } +} + +/** + * If the quad normal approximately bisects 2 axes at a 45 degree angle, + * and is approximately perpendicular to the third, returns the 2 directions + * the quad normal points towards. + * Returns null otherwise. + */ +fun getAngles45(quad: QuadView): Pair? { + val normal = quad.faceNormal() + // one of the components must be close to zero + val zeroAxis = when { + abs(normal.x) < EPSILON -> Axis.X + abs(normal.y) < EPSILON -> Axis.Y + abs(normal.z) < EPSILON -> Axis.Z + else -> return null + } + // the other two must be of similar magnitude + val diff = when(zeroAxis) { + Axis.X -> abs(abs(normal.y) - abs(normal.z)) + Axis.Y -> abs(abs(normal.x) - abs(normal.z)) + Axis.Z -> abs(abs(normal.x) - abs(normal.y)) + } + if (diff > EPSILON) return null + return when(zeroAxis) { + Axis.X -> Pair(if (normal.y > 0.0f) UP else DOWN, if (normal.z > 0.0f) SOUTH else NORTH) + Axis.Y -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.z > 0.0f) SOUTH else NORTH) + Axis.Z -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.y > 0.0f) UP else DOWN) + } +} + +fun faceDistance(face: Direction, x: Float, y: Float, z: Float) = when(face) { + WEST -> x; EAST -> 1.0f - x + DOWN -> y; UP -> 1.0f - y + NORTH -> z; SOUTH -> 1.0f - z +} + +inline fun forEachVertex(quad: QuadView, func: (vIdx: Int, x: Float, y: Float, z: Float)->Unit) { + for (vIdx in 0..3) { + func(vIdx, quad.x(vIdx), quad.y(vIdx), quad.z(vIdx)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/Indigo.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/Indigo.kt new file mode 100644 index 0000000..f19ffc1 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/lighting/Indigo.kt @@ -0,0 +1,53 @@ +package mods.betterfoliage.render.lighting + +import mods.betterfoliage.util.reflectField +import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh +import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator +import net.fabricmc.fabric.impl.client.indigo.renderer.render.* +import net.minecraft.block.BlockState +import net.minecraft.client.MinecraftClient +import net.minecraft.client.render.model.BakedModel +import net.minecraft.util.math.BlockPos +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Consumer +import java.util.function.Supplier + +val MODIFIED_CONSUMER_POOL = ThreadLocal() + +fun TerrainMeshConsumer.modified() = MODIFIED_CONSUMER_POOL.get() ?: let { + val blockInfo = reflectField("blockInfo") + val chunkInfo = reflectField("chunkInfo") + val aoCalc = reflectField("aoCalc") + val transform = reflectField("transform") + ModifiedTerrainMeshConsumer(blockInfo, chunkInfo, aoCalc, transform) +}.apply { MODIFIED_CONSUMER_POOL.set(this) } + +/** + * Render the given model at the given position. + * Mutates the state of the [RenderContext]!! + */ +fun RenderContext.renderMasquerade(model: BakedModel, blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) = when(this) { + is TerrainRenderContext -> { + val blockInfo = reflectField("blockInfo") + blockInfo.prepareForBlock(state, pos, model.useAmbientOcclusion()) + (model as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context) + } + else -> { + (model as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context) + } +} + +/** Execute the provided block with a mesh consumer using the given custom lighting. */ +fun RenderContext.withLighting(lighter: CustomLighting, func: (Consumer)->Unit) = when(this) { + is TerrainRenderContext -> { + val consumer = (meshConsumer() as TerrainMeshConsumer).modified() + consumer.clearLighting() + consumer.lighter = lighter + func(consumer) + consumer.lighter = null + } + else -> func(meshConsumer()) +} diff --git a/src/main/kotlin/mods/betterfoliage/render/particle/FallingLeaves.kt b/src/main/kotlin/mods/betterfoliage/render/particle/FallingLeaves.kt new file mode 100644 index 0000000..764827c --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/particle/FallingLeaves.kt @@ -0,0 +1,112 @@ +package mods.betterfoliage.render.particle + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.ClientWorldLoadCallback +import mods.betterfoliage.render.AbstractParticle +import mods.betterfoliage.render.block.vanilla.LeafKey +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.event.world.WorldTickCallback +import net.minecraft.client.MinecraftClient +import net.minecraft.client.render.BufferBuilder +import net.minecraft.client.world.ClientWorld +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.MathHelper +import net.minecraft.world.World +import java.util.* +import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.sin + +class FallingLeafParticle( + world: World, pos: BlockPos, leafKey: LeafKey +) : AbstractParticle( + world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5 +) { + + companion object { + @JvmStatic val biomeBrightnessMultiplier = 0.5f + } + + var rotationSpeed = randomF(min = PI2 / 80.0, max = PI2 / 50.0) + var rotPositive = true + val isMirrored = randomB() + var wasCollided = false + + init { + angle = randomF(max = PI2) + maxAge = MathHelper.floor(randomD(0.6, 1.0) * BetterFoliage.config.fallingLeaves.lifetime * 20.0) + velocityY = -BetterFoliage.config.fallingLeaves.speed + + scale = BetterFoliage.config.fallingLeaves.size.toFloat() * 0.1f + + val state = world.getBlockState(pos) + val blockColor = MinecraftClient.getInstance().blockColorMap.getColorMultiplier(state, world, pos, 0) + sprite = LeafParticleRegistry[leafKey.leafType][randomI(max = 1024)] + setParticleColor(leafKey.overrideColor, blockColor) + } + + override val isValid: Boolean get() = (sprite != null) + + override fun update() { + if (randomF() > 0.95f) rotPositive = !rotPositive +// if (age > maxAge - 20) colorAlpha = 0.05f * (maxAge - age) + + if (onGround || wasCollided) { + velocity.setTo(0.0, 0.0, 0.0) + if (!wasCollided) { + age = age.coerceAtLeast(maxAge - 20) + wasCollided = true + } + } else { + val cosRotation = cos(angle).toDouble(); val sinRotation = sin(angle).toDouble() + velocity.setTo(cosRotation, 0.0, sinRotation).mul(BetterFoliage.config.fallingLeaves.perturb) + .add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(BetterFoliage.config.fallingLeaves.speed) + angle += if (rotPositive) rotationSpeed else -rotationSpeed + } + } + + override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) { + val tickAngle = angle + partialTickTime * (if (rotPositive) rotationSpeed else -rotationSpeed) + renderParticleQuad(worldRenderer, partialTickTime, rotation = tickAngle.toDouble(), isMirrored = isMirrored) + } + + fun setParticleColor(overrideColor: Int?, blockColor: Int) { + val color = overrideColor ?: blockColor + setColor(color) + } +} + +object LeafWindTracker : WorldTickCallback, ClientWorldLoadCallback { + val random = Random() + val target = Double3.zero + val current = Double3.zero + var nextChange: Long = 0 + + fun changeWindTarget(world: World) { + nextChange = world.time + 120 + random.nextInt(80) + val direction = PI2 * random.nextDouble() + val speed = abs(random.nextGaussian()) * BetterFoliage.config.fallingLeaves.windStrength + + (if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * BetterFoliage.config.fallingLeaves.stormStrength) + target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed) + } + + override fun tick(world: World) { + if (world.isClient) { + // change target wind speed + if (world.time >= nextChange) changeWindTarget(world) + + // change current wind speed + val changeRate = if (world.isRaining) 0.015 else 0.005 + current.add( + (target.x - current.x).minmax(-changeRate, changeRate), + 0.0, + (target.z - current.z).minmax(-changeRate, changeRate) + ) + } + } + + override fun loadWorld(world: ClientWorld) { + changeWindTarget(world) + } +} + diff --git a/src/main/kotlin/mods/betterfoliage/render/particle/LeafParticleRegistry.kt b/src/main/kotlin/mods/betterfoliage/render/particle/LeafParticleRegistry.kt new file mode 100644 index 0000000..1a973c8 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/particle/LeafParticleRegistry.kt @@ -0,0 +1,70 @@ +package mods.betterfoliage.render.particle + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.resource.model.FixedSpriteSet +import mods.betterfoliage.resource.model.SpriteSet +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback +import net.minecraft.client.texture.SpriteAtlasTexture +import net.minecraft.util.Identifier + +object LeafParticleRegistry : ClientSpriteRegistryCallback { + val typeMappings = TextureMatcher() + + val ids = mutableMapOf>() + val spriteSets = mutableMapOf() + + override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) { + ids.clear() + spriteSets.clear() + typeMappings.loadMappings(Identifier(BetterFoliage.MOD_ID, "leaf_texture_mappings.cfg")) + (typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType -> + val validIds = (0 until 16).map { idx -> Identifier(BetterFoliage.MOD_ID, "falling_leaf_${leafType}_$idx") } + .filter { resourceManager.containsResource(Atlas.PARTICLES.wrap(it)) } + ids[leafType] = validIds + validIds.forEach { registry.register(it) } + } + } + + operator fun get(type: String): SpriteSet { + spriteSets[type]?.let { return it } + ids[type]?.let { + return FixedSpriteSet(Atlas.PARTICLES, it).apply { spriteSets[type] = this } + } + return if (type == "default") FixedSpriteSet(Atlas.PARTICLES, emptyList()).apply { spriteSets[type] = this } + else get("default") + } + + init { + ClientSpriteRegistryCallback.event(SpriteAtlasTexture.PARTICLE_ATLAS_TEX).register(this) + } +} + +class TextureMatcher { + + data class Mapping(val domain: String?, val path: String, val type: String) { + fun matches(iconLocation: Identifier): Boolean { + return (domain == null || domain == iconLocation.namespace) && + iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true) + } + } + + val mappings: MutableList = mutableListOf() + + fun getType(resource: Identifier) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull() + fun getType(iconName: String) = Identifier(iconName).let { getType(it) } + + fun loadMappings(mappingLocation: Identifier) { + mappings.clear() + resourceManager[mappingLocation]?.getLines()?.let { lines -> + lines.filter { !it.startsWith("//") }.filter { it.isNotEmpty() }.forEach { line -> + val line2 = line.trim().split('=') + if (line2.size == 2) { + val mapping = line2[0].trim().split(':') + if (mapping.size == 1) mappings.add(Mapping(null, mapping[0].trim(), line2[1].trim())) + else if (mapping.size == 2) mappings.add(Mapping(mapping[0].trim(), mapping[1].trim(), line2[1].trim())) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/particle/RisingSouls.kt b/src/main/kotlin/mods/betterfoliage/render/particle/RisingSouls.kt new file mode 100644 index 0000000..6e75a75 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/particle/RisingSouls.kt @@ -0,0 +1,76 @@ +package mods.betterfoliage.render.particle + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.render.AbstractParticle +import mods.betterfoliage.resource.model.SpriteDelegate +import mods.betterfoliage.resource.model.SpriteSetDelegate +import mods.betterfoliage.util.* +import net.minecraft.client.particle.ParticleTextureSheet +import net.minecraft.client.render.BufferBuilder +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.MathHelper +import net.minecraft.world.World +import java.util.* +import kotlin.math.cos +import kotlin.math.sin + +class RisingSoulParticle( + world: World, pos: BlockPos +) : AbstractParticle( + world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5 +) { + + val particleTrail: Deque = LinkedList() + val initialPhase = randomD(max = PI2) + + init { + velocityY = 0.1 + gravityStrength = 0.0f + sprite = headIcons[randomI(max = 1024)] + maxAge = MathHelper.floor((0.6 + 0.4 * randomD()) * BetterFoliage.config.risingSoul.lifetime * 20.0) + } + + override val isValid: Boolean get() = true + + override fun update() { + val phase = initialPhase + (age.toDouble() * PI2 / 64.0 ) + val cosPhase = cos(phase); val sinPhase = sin(phase) + velocity.setTo(BetterFoliage.config.risingSoul.perturb.let { Double3(cosPhase * it, 0.1, sinPhase * it) }) + + particleTrail.addFirst(currentPos.copy()) + while (particleTrail.size > BetterFoliage.config.risingSoul.trailLength) particleTrail.removeLast() + + if (!BetterFoliage.config.enabled) markDead() + } + + override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) { + var alpha = BetterFoliage.config.risingSoul.opacity.toFloat() + if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f + + renderParticleQuad(worldRenderer, partialTickTime, + size = BetterFoliage.config.risingSoul.headSize * 0.25, + alpha = alpha + ) + + var scale = BetterFoliage.config.risingSoul.trailSize * 0.25 + particleTrail.forEachPairIndexed { idx, current, previous -> + scale *= BetterFoliage.config.risingSoul.sizeDecay + alpha *= BetterFoliage.config.risingSoul.opacityDecay.toFloat() + if (idx % BetterFoliage.config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime, + currentPos = current, + prevPos = previous, + size = scale, + alpha = alpha, + sprite = trackIcon + ) + } + } + + override fun getType() = ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT + + companion object { + val headIcons by SpriteSetDelegate(Atlas.PARTICLES) { idx -> Identifier(BetterFoliage.MOD_ID, "rising_soul_$idx") } + val trackIcon by SpriteDelegate(Atlas.PARTICLES) { Identifier(BetterFoliage.MOD_ID, "soul_track") } + } +} diff --git a/src/main/kotlin/mods/betterfoliage/resource/discovery/BakedModelReplacer.kt b/src/main/kotlin/mods/betterfoliage/resource/discovery/BakedModelReplacer.kt new file mode 100644 index 0000000..eaf2155 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/discovery/BakedModelReplacer.kt @@ -0,0 +1,103 @@ +package mods.betterfoliage.resource.discovery + +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.BlockModelsReloadCallback +import mods.betterfoliage.ModelLoadingCallback +import mods.betterfoliage.util.HasLogger +import mods.betterfoliage.util.Invalidator +import mods.betterfoliage.util.YarnHelper +import mods.betterfoliage.util.get +import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback +import net.minecraft.block.BlockState +import net.minecraft.client.MinecraftClient +import net.minecraft.client.render.block.BlockModels +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.render.model.ModelLoader +import net.minecraft.client.texture.SpriteAtlasTexture +import net.minecraft.resource.ResourceManager +import net.minecraft.util.Identifier +import java.lang.ref.WeakReference +import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.function.Consumer +import java.util.function.Supplier + +// net.minecraft.client.render.block.BlockModels.models +val BlockModels_models = YarnHelper.requiredField>("net.minecraft.class_773", "field_4162", "Ljava/util/Map;") + +/** Threadsafe collector for sprite IDs */ +class SpriteCollectorSync { + val idSet = Collections.synchronizedSet(mutableSetOf()) + fun add(id: Identifier) = id.apply { idSet.add(this) } + fun dump(target: ClientSpriteRegistryCallback.Registry) { idSet.forEach { target.register(it) } } +} + +class BakedModelReplacer : ModelLoadingCallback, ClientSpriteRegistryCallback, BlockModelsReloadCallback, Invalidator, HasLogger { + override val logger get() = BetterFoliage.logDetail + + val discoverers = mutableListOf() + override val callbacks = mutableListOfUnit>>() + + protected var keys = emptyMap() + + operator fun get(state: BlockState) = keys[state] + inline fun getTyped(state: BlockState) = get(state) as? T + + var currentLoader: ModelLoader? = null + + override fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) { + currentLoader = loader + log("reloading block discovery configuration") + BetterFoliage.blockConfig.reloadConfig(manager) + invalidate() + } + + override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) { + log("discovering blocks") + val idSet = Collections.synchronizedSet(mutableSetOf()) + val allKeys = discoverers.map { + // run model discoverers in parallel + CompletableFuture.supplyAsync(Supplier { + it.discover(currentLoader!!, Consumer { idSet.add(it) }) + }, MinecraftClient.getInstance()) + }.map { it.join() } + idSet.forEach { registry.register(it) } + + val result = mutableMapOf() + allKeys.forEach { keys -> + keys.entries.forEach { (state, key) -> + val oldKey = result[state] + if (oldKey != null) log("Replacing $oldKey with $key for state $state") + else log("Adding replacement $key for state $state") + result[state] = key + } + } + + keys = result + } + + override fun reloadBlockModels(blockModels: BlockModels) { + log("block model baking finished") + val modelMap = blockModels[BlockModels_models] as MutableMap + keys.forEach { (state, key) -> + val oldModel = modelMap[state] + if (oldModel == null) log("Cannot find model for state $state, ignoring") + else { + try { + val newModel = key.replace(oldModel, state) + modelMap[state] = newModel + log("Replaced model for state $state with $key") + } catch (e: Exception) { + log("Error creating model for state $state with $key", e) + } + } + } + } + + init { + ModelLoadingCallback.EVENT.register(this) + ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register(this) + BlockModelsReloadCallback.EVENT.register(this) + } +} + diff --git a/src/main/kotlin/mods/betterfoliage/resource/discovery/ConfigurableModelDiscovery.kt b/src/main/kotlin/mods/betterfoliage/resource/discovery/ConfigurableModelDiscovery.kt new file mode 100644 index 0000000..6598c64 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/discovery/ConfigurableModelDiscovery.kt @@ -0,0 +1,53 @@ +package mods.betterfoliage.resource.discovery + +import com.google.common.base.Joiner +import mods.betterfoliage.util.YarnHelper +import mods.betterfoliage.util.get +import mods.betterfoliage.util.stripStart +import net.minecraft.block.BlockState +import net.minecraft.client.render.model.json.JsonUnbakedModel +import net.minecraft.util.Identifier +import java.util.function.Consumer + +// net.minecraft.client.render.model.json.JsonUnbakedModel.parent +val JsonUnbakedModel_parent = YarnHelper.requiredField("net.minecraft.class_793", "field_4253", "Lnet/minecraft/class_793;") +// net.minecraft.client.render.model.json.JsonUnbakedModel.parentId +val JsonUnbakedModel_parentId = YarnHelper.requiredField("net.minecraft.class_793", "field_4247", "Lnet/minecraft/class_2960;") + +fun Pair.derivesFrom(targetLocation: Identifier): Boolean { + if (second.stripStart("models/") == targetLocation) return true + if (first[JsonUnbakedModel_parent] != null && first[JsonUnbakedModel_parentId] != null) + return Pair(first[JsonUnbakedModel_parent]!!, first[JsonUnbakedModel_parentId]!!).derivesFrom(targetLocation) + return false +} + +abstract class ConfigurableModelDiscovery : ModelDiscoveryBase() { + + abstract val matchClasses: IBlockMatcher + abstract val modelTextures: List + + abstract fun processModel(state: BlockState, textures: List, atlas: Consumer): BlockRenderKey? + + override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer): BlockRenderKey? { + val matchClass = matchClasses.matchingClass(ctx.state.block) ?: return null + log("block state ${ctx.state.toString()}") + log(" class ${ctx.state.block.javaClass.name} matches ${matchClass.name}") + + (ctx.models.filter { it.first is JsonUnbakedModel } as List>).forEach { (model, location) -> + val modelMatch = modelTextures.firstOrNull { (model to location).derivesFrom(it.modelLocation) } + if (modelMatch != null) { + log(" model ${model} matches ${modelMatch.modelLocation}") + + val textures = modelMatch.textureNames.map { it to model.resolveTexture(it) } + val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" }) + log(" sprites [$texMapString]") + + if (textures.all { it.second != "missingno" }) { + // found a valid model (all required textures exist) + return processModel(ctx.state, textures.map { it.second }, atlas) + } + } + } + return null + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/common/config/Matchers.kt b/src/main/kotlin/mods/betterfoliage/resource/discovery/Matchers.kt similarity index 61% rename from src/main/kotlin/mods/octarinecore/common/config/Matchers.kt rename to src/main/kotlin/mods/betterfoliage/resource/discovery/Matchers.kt index 33acf59..3377207 100644 --- a/src/main/kotlin/mods/octarinecore/common/config/Matchers.kt +++ b/src/main/kotlin/mods/betterfoliage/resource/discovery/Matchers.kt @@ -1,10 +1,12 @@ -package mods.octarinecore.common.config +package mods.betterfoliage.resource.discovery -import mods.octarinecore.client.resource.getLines -import mods.octarinecore.client.resource.resourceManager -import mods.octarinecore.metaprog.getJavaClass +import mods.betterfoliage.util.getLines +import mods.betterfoliage.util.INTERMEDIARY +import mods.betterfoliage.util.getJavaClass +import net.fabricmc.loader.api.FabricLoader import net.minecraft.block.Block -import net.minecraft.util.ResourceLocation +import net.minecraft.resource.ResourceManager +import net.minecraft.util.Identifier import org.apache.logging.log4j.Logger interface IBlockMatcher { @@ -22,11 +24,10 @@ class SimpleBlockMatcher(vararg val classes: Class<*>) : IBlockMatcher { } } -class ConfigurableBlockMatcher(val logger: Logger, val location: ResourceLocation) : IBlockMatcher { +class ConfigurableBlockMatcher(val logger: Logger, val location: Identifier) : IBlockMatcher { val blackList = mutableListOf>() val whiteList = mutableListOf>() -// override fun convertValue(line: String) = getJavaClass(line) override fun matchesClass(block: Block): Boolean { val blockClass = block.javaClass @@ -42,32 +43,34 @@ class ConfigurableBlockMatcher(val logger: Logger, val location: ResourceLocatio return null } - fun readDefaults() { + fun readDefaults(manager: ResourceManager) { blackList.clear() whiteList.clear() - resourceManager.getAllResources(location).forEach { resource -> - logger.debug("Reading resource $location from pack ${resource.packName}") + manager.getAllResources(location).forEach { resource -> + logger.debug("Reading resource $location from pack ${resource.resourcePackName}") 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) } + if (line.startsWith("-")) getBlockClass(line.substring(1))?.let { blackList.add(it) } + else getBlockClass(line)?.let { whiteList.add(it) } } } } + + fun getBlockClass(name: String) = getJavaClass(FabricLoader.getInstance().mappingResolver.mapClassName(INTERMEDIARY, name)) } -data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List) { - constructor(vararg args: String) : this(ResourceLocation(args[0]), listOf(*args).drop(1)) +data class ModelTextureList(val modelLocation: Identifier, val textureNames: List) { + constructor(vararg args: String) : this(Identifier(args[0]), listOf(*args).drop(1)) } -class ModelTextureListConfiguration(val logger: Logger, val location: ResourceLocation) { +class ModelTextureListConfiguration(val logger: Logger, val location: Identifier) { val modelList = mutableListOf() - fun readDefaults() { - resourceManager.getAllResources(location).forEach { resource -> - logger.debug("Reading resource $location from pack ${resource.packName}") + fun readDefaults(manager: ResourceManager) { + manager.getAllResources(location).forEach { resource -> + logger.debug("Reading resource $location from pack ${resource.resourcePackName}") 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))) + modelList.add(ModelTextureList(Identifier(elements.first()), elements.drop(1))) } } } diff --git a/src/main/kotlin/mods/betterfoliage/resource/discovery/ModelDiscoveryBase.kt b/src/main/kotlin/mods/betterfoliage/resource/discovery/ModelDiscoveryBase.kt new file mode 100644 index 0000000..dc7c1b5 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/discovery/ModelDiscoveryBase.kt @@ -0,0 +1,76 @@ +package mods.betterfoliage.resource.discovery + +import mods.betterfoliage.util.HasLogger +import net.minecraft.block.BlockState +import net.minecraft.client.render.block.BlockModels +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.render.model.ModelLoader +import net.minecraft.client.render.model.UnbakedModel +import net.minecraft.client.render.model.json.JsonUnbakedModel +import net.minecraft.client.render.model.json.ModelVariant +import net.minecraft.client.render.model.json.WeightedUnbakedModel +import net.minecraft.client.texture.SpriteAtlasTexture +import net.minecraft.client.util.ModelIdentifier +import net.minecraft.util.Identifier +import net.minecraft.util.registry.Registry +import java.util.function.Consumer + +typealias RenderKeyFactory = (SpriteAtlasTexture)->BlockRenderKey + +interface BlockRenderKey { + fun replace(model: BakedModel, state: BlockState): BakedModel = model +} + +fun ModelLoader.iterateModels(func: (ModelDiscoveryContext)->Unit) { + Registry.BLOCK.flatMap { block -> + block.stateFactory.states.map { state -> state to BlockModels.getModelId(state) } + }.forEach { (state, stateModelResource) -> + func(ModelDiscoveryContext(this, state, stateModelResource)) + } +} + +/** + * Information about a single [BlockState] and all the [UnbakedModel]s it could render as. + */ +class ModelDiscoveryContext( + loader: ModelLoader, + val state: BlockState, + val modelId: ModelIdentifier +) { + val models = loader.unwrapVariants(loader.getOrLoadModel(modelId) to modelId) + .filter { it.second != loader.getOrLoadModel(ModelLoader.MISSING) } + + fun ModelLoader.unwrapVariants(modelAndLoc: Pair): List> = when(val model = modelAndLoc.first) { + is WeightedUnbakedModel -> (model.variants as List).flatMap { + variant -> unwrapVariants(getOrLoadModel(variant.location) to variant.location) + } + is JsonUnbakedModel -> listOf(modelAndLoc) + else -> emptyList() + } +} + +interface ModelDiscovery { + fun discover(loader: ModelLoader, atlas: Consumer): Map +} + +abstract class ModelDiscoveryBase : ModelDiscovery, HasLogger { + override fun discover(loader: ModelLoader, atlas: Consumer): Map { + val keys = mutableMapOf() + var errors = 0 + + loader.iterateModels { ctx -> + try { + val result = processModel(ctx, atlas) + result?.let { keys[ctx.state] = it } + } catch (e: Exception) { + errors++ + } + } + log("${keys.size} BlockStates discovered, $errors errors") + return keys + } + + abstract fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer): BlockRenderKey? +} + + diff --git a/src/main/kotlin/mods/octarinecore/client/resource/CenteringTextureGenerator.kt b/src/main/kotlin/mods/betterfoliage/resource/generated/CenteredSprite.kt similarity index 69% rename from src/main/kotlin/mods/octarinecore/client/resource/CenteringTextureGenerator.kt rename to src/main/kotlin/mods/betterfoliage/resource/generated/CenteredSprite.kt index ddd9ba2..3323ead 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/CenteringTextureGenerator.kt +++ b/src/main/kotlin/mods/betterfoliage/resource/generated/CenteredSprite.kt @@ -1,8 +1,10 @@ -package mods.octarinecore.client.resource +package mods.betterfoliage.resource.generated -import mods.betterfoliage.client.resource.Identifier -import mods.betterfoliage.client.texture.loadSprite -import net.minecraft.resources.IResourceManager +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.util.bytes +import mods.betterfoliage.util.loadSprite +import net.minecraft.resource.ResourceManager +import net.minecraft.util.Identifier import java.awt.image.BufferedImage import java.lang.Math.max @@ -10,7 +12,7 @@ data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCK fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw) - fun draw(resourceManager: IResourceManager): ByteArray { + fun draw(resourceManager: ResourceManager): ByteArray { val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite)) val frameWidth = baseTexture.width @@ -18,8 +20,8 @@ data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCK val frames = baseTexture.height / frameHeight val size = max(frameWidth, frameHeight) - val resultTexture = BufferedImage(size, size * frames, BufferedImage.TYPE_4BYTE_ABGR) - val graphics = resultTexture.createGraphics() + val result = BufferedImage(size, size * frames, BufferedImage.TYPE_4BYTE_ABGR) + val graphics = result.createGraphics() // iterate all frames for (frame in 0 until frames) { @@ -32,6 +34,6 @@ data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCK graphics.drawImage(resultFrame, 0, size * frame, null) } - return resultTexture.bytes + return result.bytes } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedBlockTexturePack.kt b/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedBlockTexturePack.kt new file mode 100644 index 0000000..d524aa8 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedBlockTexturePack.kt @@ -0,0 +1,92 @@ +package mods.betterfoliage.resource.generated + +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.util.HasLogger +import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener +import net.minecraft.client.resource.ClientResourcePackContainer +import net.minecraft.resource.* +import net.minecraft.resource.ResourcePackContainer.InsertionPosition +import net.minecraft.resource.ResourceType.CLIENT_RESOURCES +import net.minecraft.resource.metadata.ResourceMetadataReader +import net.minecraft.text.LiteralText +import net.minecraft.util.Identifier +import net.minecraft.util.profiler.Profiler +import org.apache.logging.log4j.Logger +import java.io.IOException +import java.lang.IllegalStateException +import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executor +import java.util.function.Predicate +import java.util.function.Supplier + +/** + * [ResourcePack] containing generated block textures + * + * @param[reloadId] Fabric ID of the pack + * @param[nameSpace] Resource namespace of pack + * @param[packName] Friendly name of pack + * @param[packDesc] Description of pack + * @param[logger] Logger to log to when generating resources + */ +class GeneratedBlockTexturePack(val reloadId: Identifier, val nameSpace: String, val packName: String, val packDesc: String, override val logger: Logger) : HasLogger, ResourcePack, IdentifiableResourceReloadListener { + + override fun getName() = reloadId.toString() + override fun getNamespaces(type: ResourceType) = setOf(nameSpace) + override fun parseMetadata(deserializer: ResourceMetadataReader) = null + override fun openRoot(id: String) = null + override fun findResources(type: ResourceType, path: String, maxDepth: Int, filter: Predicate) = emptyList() + override fun close() {} + + protected var manager: ResourceManager? = null + val identifiers: MutableMap = Collections.synchronizedMap(mutableMapOf()) + val resources: MutableMap = Collections.synchronizedMap(mutableMapOf()) + + fun register(key: Any, func: (ResourceManager)->ByteArray): Identifier { + if (manager == null) throw IllegalStateException("Cannot register resources unless resource manager is being reloaded") + identifiers[key]?.let { return it } + + val id = Identifier(nameSpace, UUID.randomUUID().toString()) + val resource = func(manager!!) + + identifiers[key] = id + resources[Atlas.BLOCKS.wrap(id)] = resource + log("generated resource $key -> $id") + return id + } + + override fun open(type: ResourceType, id: Identifier) = + if (type != CLIENT_RESOURCES) null else + try { resources[id]!!.inputStream() } + catch (e: ExecutionException) { (e.cause as? IOException)?.let { throw it } } // rethrow wrapped IOException if present + + override fun contains(type: ResourceType, id: Identifier) = + type == CLIENT_RESOURCES && resources.containsKey(id) + + override fun reload(synchronizer: ResourceReloadListener.Synchronizer, manager: ResourceManager, prepareProfiler: Profiler, applyProfiler: Profiler, prepareExecutor: Executor, applyExecutor: Executor): CompletableFuture { + this.manager = manager + return synchronizer.whenPrepared(null).thenRun { + this.manager = null + identifiers.clear() + resources.clear() + } + } + + override fun getFabricId() = reloadId + + /** + * Supplier for this resource pack. Adds pack as always-on and hidden. + */ + val finder = object : ResourcePackCreator { + val packInfo = ClientResourcePackContainer( + packName, true, Supplier { this@GeneratedBlockTexturePack }, + LiteralText(packName), + LiteralText(packDesc), + ResourcePackCompatibility.COMPATIBLE, InsertionPosition.TOP, true, null + ) + override fun registerContainer(nameToPackMap: MutableMap, packInfoFactory: ResourcePackContainer.Factory) { + (nameToPackMap as MutableMap)[reloadId.toString()] = packInfo + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/GeneratedGrass.kt b/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedGrassSprite.kt similarity index 80% rename from src/main/kotlin/mods/betterfoliage/client/texture/GeneratedGrass.kt rename to src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedGrassSprite.kt index 6454a15..c379f76 100644 --- a/src/main/kotlin/mods/betterfoliage/client/texture/GeneratedGrass.kt +++ b/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedGrassSprite.kt @@ -1,8 +1,8 @@ -package mods.betterfoliage.client.texture +package mods.betterfoliage.resource.generated -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.client.resource.* -import net.minecraft.resources.IResourceManager +import mods.betterfoliage.util.* +import net.minecraft.resource.ResourceManager +import net.minecraft.util.Identifier import java.awt.image.BufferedImage /** @@ -11,12 +11,12 @@ import java.awt.image.BufferedImage * * @param[domain] Resource domain of generator */ -data class GeneratedGrass(val sprite: Identifier, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) { +data class GeneratedGrassSprite(val sprite: Identifier, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) { constructor(sprite: String, isSnowed: Boolean) : this(Identifier(sprite), isSnowed) fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw) - fun draw(resourceManager: IResourceManager): ByteArray { + fun draw(resourceManager: ResourceManager): ByteArray { val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite)) val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR) diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/GeneratedLeaf.kt b/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedLeafSprite.kt similarity index 75% rename from src/main/kotlin/mods/betterfoliage/client/texture/GeneratedLeaf.kt rename to src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedLeafSprite.kt index 2a14bcf..2bc43cf 100644 --- a/src/main/kotlin/mods/betterfoliage/client/texture/GeneratedLeaf.kt +++ b/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedLeafSprite.kt @@ -1,10 +1,10 @@ -package mods.betterfoliage.client.texture +package mods.betterfoliage.resource.generated -import mods.betterfoliage.BetterFoliageMod -import mods.octarinecore.client.resource.* -import net.minecraft.resources.IResource -import net.minecraft.resources.IResourceManager -import net.minecraft.util.ResourceLocation +import mods.betterfoliage.BetterFoliage +import mods.betterfoliage.util.* +import net.minecraft.resource.Resource +import net.minecraft.resource.ResourceManager +import net.minecraft.util.Identifier import java.awt.image.BufferedImage /** @@ -15,11 +15,11 @@ import java.awt.image.BufferedImage * * @param[domain] Resource domain of generator */ -data class GeneratedLeaf(val sprite: ResourceLocation, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) { +data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) { fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw) - fun draw(resourceManager: IResourceManager): ByteArray { + fun draw(resourceManager: ResourceManager): ByteArray { val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite)) val size = baseTexture.width @@ -28,8 +28,8 @@ data class GeneratedLeaf(val sprite: ResourceLocation, val leafType: String, val val maskTexture = (getLeafMask(leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage() fun scale(i: Int) = i * maskTexture!!.width / (size * 2) - val leafTexture = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR) - val graphics = leafTexture.createGraphics() + val result = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR) + val graphics = result.createGraphics() // iterate all frames for (frame in 0 until frames) { @@ -57,7 +57,7 @@ data class GeneratedLeaf(val sprite: ResourceLocation, val leafType: String, val graphics.drawImage(leafFrame, 0, size * frame * 2, null) } - return leafTexture.bytes + return result.bytes } /** @@ -67,7 +67,7 @@ data class GeneratedLeaf(val sprite: ResourceLocation, val leafType: String, val * @param[maxSize] Preferred mask size. */ fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size -> - ResourceLocation(BetterFoliageMod.MOD_ID, "textures/blocks/leafmask_${size}_${type}.png") + Atlas.BLOCKS.wrap(Identifier(BetterFoliage.MOD_ID, "blocks/leafmask_${size}_${type}")) } /** @@ -77,10 +77,10 @@ data class GeneratedLeaf(val sprite: ResourceLocation, val leafType: String, val * @param[maskPath] Location of the texture of the given size * */ - fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->ResourceLocation): IResource? { + fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->Identifier): Resource? { var size = maxSize val sizes = mutableListOf() while(size > 2) { sizes.add(size); size /= 2 } - return sizes.map { resourceManager[maskPath(it)] }.filterNotNull().firstOrNull() + return sizes.findFirst { resourceManager[maskPath(it)] } } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/resource/model/Meshify.kt b/src/main/kotlin/mods/betterfoliage/resource/model/Meshify.kt new file mode 100644 index 0000000..925bbcf --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/model/Meshify.kt @@ -0,0 +1,114 @@ +package mods.betterfoliage.resource.model + +import mods.betterfoliage.util.Double3 +import mods.betterfoliage.util.allDirections +import mods.betterfoliage.util.findFirst +import net.minecraft.block.BlockRenderLayer +import net.minecraft.block.BlockState +import net.minecraft.client.render.VertexFormat +import net.minecraft.client.render.VertexFormatElement +import net.minecraft.client.render.VertexFormatElement.Format.FLOAT +import net.minecraft.client.render.VertexFormatElement.Format.UBYTE +import net.minecraft.client.render.VertexFormatElement.Type.* +import net.minecraft.client.render.VertexFormatElement.Type.UV +import net.minecraft.client.render.VertexFormats +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.render.model.BakedQuad +import net.minecraft.util.math.Direction +import java.lang.Float +import java.util.* + +interface BakedModelConverter { + /** + * Convert baked model. Returns null if conversion unsuccessful (wrong input type). + * @param model Input model + * @param converter Converter to use for converting nested models. + */ + fun convert(model: BakedModel, converter: BakedModelConverter): BakedModel? + companion object { + fun of(func: (BakedModel, BakedModelConverter)->BakedModel?) = object : BakedModelConverter { + override fun convert(model: BakedModel, converter: BakedModelConverter) = func(model, converter) + } + val identity = of { model, _ -> model } + } +} + +/** + * Convert [BakedModel] using the provided list of [BakedModelConverter]s (in order). + * If all converters fail, gives the original model back. + */ +fun List.convert(model: BakedModel) = object : BakedModelConverter { + val converters = this@convert + BakedModelConverter.identity + override fun convert(model: BakedModel, converter: BakedModelConverter) = converters.findFirst { it.convert(model, converter) } +}.let { converterStack -> + // we are guaranteed a result here because of the identity converter + converterStack.convert(model, converterStack)!! +} + +/** List of converters without meaningful configuration that should always be used */ +val COMMON_MESH_CONVERTERS = listOf(WrappedWeightedModel.converter) + +/** + * Convert [BakedModel] into one using fabric-rendering-api [Mesh] instead of the vanilla pipeline. + * @param renderLayerOverride Use the given [BlockRenderLayer] for the [Mesh] + * instead of the one declared by the corresponding [Block] + */ +fun meshifyStandard(model: BakedModel, state: BlockState, renderLayerOverride: BlockRenderLayer? = null) = + (COMMON_MESH_CONVERTERS + WrappedMeshModel.converter(state, renderLayerOverride = renderLayerOverride)).convert(model) + +/** + * Convert a vanilla [BakedModel] into intermediate [Quad]s + * Vertex normals not supported (yet) + * Vertex data elements not aligned to 32 bit boundaries not supported + */ +fun unbakeQuads(model: BakedModel, state: BlockState, random: Random, unshade: Boolean): List { + return (allDirections.toList() + null as Direction?).flatMap { face -> + model.getQuads(state, face, random).mapIndexed { qIdx, bakedQuad -> + var quad = Quad(Vertex(), Vertex(), Vertex(), Vertex(), face = face, colorIndex = bakedQuad.colorIndex, sprite = bakedQuad.sprite) + + val format = quadVertexFormat(bakedQuad) + val stride = format.vertexSizeInteger + format.getIntOffset(POSITION, FLOAT, 3)?.let { posOffset -> + quad = quad.transformVI { vertex, vIdx -> vertex.copy(xyz = Double3( + x = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 0]).toDouble(), + y = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 1]).toDouble(), + z = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 2]).toDouble() + )) } + } + format.getIntOffset(COLOR, UBYTE, 4)?.let { colorOffset -> + quad = quad.transformVI { vertex, vIdx -> vertex.copy( + color = Color(bakedQuad.vertexData[vIdx * stride + colorOffset]) + ) } + } + format.getIntOffset(UV, FLOAT, 2, 0)?.let { uvOffset -> + quad = quad.transformVI { vertex, vIdx -> vertex.copy(uv = UV( + u = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 0]).toDouble(), + v = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 1]).toDouble() + )) } + } + + quad = quad.transformV { it.copy(uv = it.uv.unbake(quad.sprite!!)) }.move(Double3(-0.5, -0.5, -0.5)) + if (unshade) quad = quad.transformV { it.copy(color = it.color * (1.0f / Color.bakeShade(quad.face))) } + quad + } + } +} + +/** Get the byte offset of the [VertexFormatElement] matching the given criteria */ +fun VertexFormat.getByteOffset(type: VertexFormatElement.Type, format: VertexFormatElement.Format, count: Int, index: Int = 0): Int? { + elements.forEachIndexed { idx, element -> + if (element.type == type && element.format == format && element.count == count && element.index == index) + return getElementOffset(idx) + } + return null +} + +/** + * Get the int (32 bit) offset of the [VertexFormatElement] matching the given criteria + * Returns null if the element is not properly aligned + */ +fun VertexFormat.getIntOffset(type: VertexFormatElement.Type, format: VertexFormatElement.Format, count: Int, index: Int = 0) = + getByteOffset(type, format, count, index)?.let { if (it % 4 == 0) it / 4 else null } + +/** Function to determine [VertexFormat] used by [BakedQuad] */ +var quadVertexFormat: (BakedQuad)->VertexFormat = { VertexFormats.POSITION_COLOR_UV_LMAP } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/resource/model/Quads.kt b/src/main/kotlin/mods/betterfoliage/resource/model/Quads.kt new file mode 100644 index 0000000..69f0a19 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/model/Quads.kt @@ -0,0 +1,230 @@ +package mods.betterfoliage.resource.model + +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.util.* +import mods.betterfoliage.util.minmax +import net.fabricmc.fabric.api.renderer.v1.RendererAccess +import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh +import net.minecraft.block.BlockRenderLayer +import net.minecraft.client.texture.MissingSprite +import net.minecraft.client.texture.Sprite +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Direction.* +import java.lang.Math.max +import java.lang.Math.min +import kotlin.math.cos +import kotlin.math.sin +import kotlin.random.Random + +/** + * Vertex UV coordinates + * + * Zero-centered: sprite coordinates fall between (-0.5, 0.5) + */ +data class UV(val u: Double, val v: Double) { + companion object { + val topLeft = UV(-0.5, -0.5) + val topRight = UV(0.5, -0.5) + val bottomLeft = UV(-0.5, 0.5) + val bottomRight = UV(0.5, 0.5) + } + + val rotate: UV get() = UV(v, -u) + + fun rotate(n: Int) = when(n % 4) { + 0 -> copy() + 1 -> UV(v, -u) + 2 -> UV(-u, -v) + else -> UV(-v, u) + } + + fun clamp(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) = + UV(u.minmax(minU, maxU), v.minmax(minV, maxV)) + + fun mirror(mirrorU: Boolean, mirrorV: Boolean) = UV(if (mirrorU) -u else u, if (mirrorV) -v else v) + + fun unbake(sprite: Sprite) = UV( + (u - sprite.minU.toDouble()) / (sprite.maxU - sprite.minU).toDouble() - 0.5, + (v - sprite.minV.toDouble()) / (sprite.maxV - sprite.minV).toDouble() - 0.5 + ) +} + +data class Color(val alpha: Int, val red: Int, val green: Int, val blue: Int) { + constructor(combined: Int) : this(combined shr 24 and 255, combined shr 16 and 255, combined shr 8 and 255, combined and 255) + val asInt get() = (alpha shl 24) or (red shl 16) or (green shl 8) or blue + operator fun times(f: Float) = Color( + alpha, + (f * red.toFloat()).toInt().coerceIn(0 until 256), + (f * green.toFloat()).toInt().coerceIn(0 until 256), + (f * blue.toFloat()).toInt().coerceIn(0 until 256) + ) + companion object { + val white get() = Color(255, 255, 255, 255) + /** Amount of vanilla diffuse lighting applied to face quads */ + fun bakeShade(dir: Direction?) = when(dir) { + DOWN -> 0.5f + NORTH, SOUTH -> 0.8f + EAST, WEST -> 0.6f + else -> 1.0f + } + } +} + +data class HSB(var hue: Float, var saturation: Float, var brightness: Float) { + companion object { + fun fromColor(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]) + } + } + val asColor: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness) +} + +/** + * Model vertex + * + * @param[xyz] x, y, z coordinates + * @param[uv] u, v coordinates + * @param[color] vertex color RGB components + * @param[alpha] vertex color alpha component + */ +data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0), + val uv: UV = UV(0.0, 0.0), + val color: Color = Color.white, + val alpha: Int = 255, + val normal: Double3? = null +) + +/** + * Intermediate (fabric-renderer-api independent) representation of model quad + * Immutable, double-precision + * Zero-centered (both XYZ and UV) coordinates for simpler rotation/mirroring + */ +data class Quad( + val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex, + val sprite: Sprite? = null, + val colorIndex: Int = -1, + val face: Direction? = null +) { + val verts = arrayOf(v1, v2, v3, v4) + + inline fun transformV(trans: (Vertex)-> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) } + inline fun transformVI(trans: (Vertex, Int)-> Vertex): Quad = copy( + v1 = trans(v1, 0), v2 = trans(v2, 1), v3 = trans(v3, 2), v4 = trans(v4, 3) + ) + val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize + + fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) } + fun move(trans: Pair) = move(Double3(trans.second) * trans.first) + fun scale (scale: Double) = transformV { it.copy(xyz = it.xyz * scale) } + fun scale (scale: Double3) = transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) } + fun rotate(rot: Rotation) = transformV { it.copy(xyz = it.xyz.rotate(rot), normal = it.normal?.rotate(rot)) }.copy(face = face?.rotate(rot)) + fun rotateZ(angle: Double) = transformV { it.copy( + xyz = Double3(it.xyz.x * cos(angle) + it.xyz.z * sin(angle), it.xyz.y, it.xyz.z * cos(angle) - it.xyz.x * sin(angle)), + normal = it.normal?.let { normal-> Double3(normal.x * cos(angle) + normal.z * sin(angle), normal.y, normal.z * cos(angle) - normal.x * sin(angle)) } + ) } + + fun scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) } + fun rotateUV(n: Int) = transformV { it.copy(uv = it.uv.rotate(n)) } + fun clampUV(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) = + transformV { it.copy(uv = it.uv.clamp(minU, maxU, minV, maxV)) } + fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) = transformV { it.copy(uv = it.uv.mirror(mirrorU, mirrorV)) } + fun scrambleUV(random: Random, canFlipU: Boolean, canFlipV: Boolean, canRotate: Boolean) = this + .mirrorUV(canFlipU && random.nextBoolean(), canFlipV && random.nextBoolean()) + .let { if (canRotate) it.rotateUV(random.nextInt(4)) else it } + + fun sprite(sprite: Sprite) = copy(sprite = sprite) + fun color(color: Color) = transformV { it.copy(color = color) } + fun color(color: Int) = transformV { it.copy(color = Color(color)) } + fun colorIndex(colorIndex: Int) = copy(colorIndex = colorIndex) + fun colorAndIndex(color: Int?) = color(color ?: Color.white.asInt).colorIndex(if (color == null) 0 else -1) + + val flipped: Quad get() = Quad(v4, v3, v2, v1, sprite, colorIndex) + fun cycleVertices(n: Int) = when(n % 4) { + 1 -> Quad(v2, v3, v4, v1) + 2 -> Quad(v3, v4, v1, v2) + 3 -> Quad(v4, v1, v2, v3) + else -> this.copy() + } + + companion object { + fun mix(first: Quad, second: Quad, vertexFactory: (Vertex, Vertex)-> Vertex) = Quad( + v1 = vertexFactory(first.v1, second.v1), + v2 = vertexFactory(first.v2, second.v2), + v3 = vertexFactory(first.v3, second.v3), + v4 = vertexFactory(first.v4, second.v4) + ) + } +} + +fun List.transform(trans: Quad.()-> Quad) = map { it.trans() } +fun Array>.transform(trans: Quad.(Int)-> Quad) = mapIndexed { idx, qList -> qList.map { it.trans(idx) } }.toTypedArray() + +fun List.withOpposites() = flatMap { listOf(it, it.flipped) } +fun Array>.withOpposites() = map { it.withOpposites() }.toTypedArray() + +/** + * Pour quad data into a fabric-renderer-api Mesh + */ +fun List.build(layer: BlockRenderLayer, noDiffuse: Boolean = false, flatLighting: Boolean = false): Mesh { + val renderer = RendererAccess.INSTANCE.renderer + val material = renderer.materialFinder().blendMode(0, layer).disableAo(0, flatLighting).disableDiffuse(0, noDiffuse).find() + val builder = renderer.meshBuilder() + builder.emitter.apply { + forEach { quad -> + val sprite = quad.sprite ?: Atlas.BLOCKS.atlas[MissingSprite.getMissingSpriteId()]!! + quad.verts.forEachIndexed { idx, vertex -> + pos(idx, (vertex.xyz + Double3(0.5, 0.5, 0.5)).asVec3f) + sprite(idx, 0, + (sprite.maxU - sprite.minU) * (vertex.uv.u.toFloat() + 0.5f) + sprite.minU, + (sprite.maxV - sprite.minV) * (vertex.uv.v.toFloat() + 0.5f) + sprite.minV + ) + spriteColor(idx, 0, vertex.color.asInt) + } + cullFace(quad.face) + colorIndex(quad.colorIndex) + material(material) + emit() + } + } + return builder.build() +} + +fun Array>.build(layer: BlockRenderLayer, noDiffuse: Boolean = false, flatLighting: Boolean = false) = map { it.build(layer, noDiffuse, flatLighting) }.toTypedArray() + +/** + * The model should be positioned so that (0,0,0) is the block center. + * The block extends to (-0.5, 0.5) in all directions (inclusive). + */ +fun verticalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, yBottom: Double, yTop: Double) = Quad( + Vertex(Double3(x1, yBottom, z1), UV.bottomLeft), + Vertex(Double3(x2, yBottom, z2), UV.bottomRight), + Vertex(Double3(x2, yTop, z2), UV.topRight), + Vertex(Double3(x1, yTop, z1), UV.topLeft) +) + +fun horizontalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, y: Double): Quad { + val xMin = min(x1, x2); val xMax = max(x1, x2) + val zMin = min(z1, z2); val zMax = max(z1, z2) + return Quad( + Vertex(Double3(xMin, y, zMin), UV.topLeft), + Vertex(Double3(xMin, y, zMax), UV.bottomLeft), + Vertex(Double3(xMax, y, zMax), UV.bottomRight), + Vertex(Double3(xMax, y, zMin), UV.topRight) + ) +} + +fun faceQuad(face: Direction): Quad { + val base = face.vec * 0.5 + val top = boxFaces[face].top * 0.5 + val left = boxFaces[face].left * 0.5 + return Quad( + Vertex(base + top + left, UV.topLeft), + Vertex(base - top + left, UV.bottomLeft), + Vertex(base - top - left, UV.bottomRight), + Vertex(base + top - left, UV.topRight), + face = face + ) +} + +fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(cos(it), 0.0, sin(it)) } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/resource/model/SpriteSets.kt b/src/main/kotlin/mods/betterfoliage/resource/model/SpriteSets.kt new file mode 100644 index 0000000..c3b1a2e --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/model/SpriteSets.kt @@ -0,0 +1,70 @@ +package mods.betterfoliage.resource.model + +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.util.get +import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback +import net.minecraft.client.MinecraftClient +import net.minecraft.client.texture.MissingSprite +import net.minecraft.client.texture.Sprite +import net.minecraft.client.texture.SpriteAtlasTexture +import net.minecraft.util.Identifier +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +interface SpriteSet { + val num: Int + operator fun get(idx: Int): Sprite +} + +class FixedSpriteSet(val sprites: List) : SpriteSet { + override val num = sprites.size + override fun get(idx: Int) = sprites[idx % num] + + constructor(atlas: Atlas, ids: List) : this( + ids.mapNotNull { atlas.atlas[it] }.let { sprites -> + if (sprites.isNotEmpty()) sprites else listOf(atlas.atlas[MissingSprite.getMissingSpriteId()]!!) + } + ) +} + +class SpriteDelegate(val atlas: Atlas, val idFunc: ()->Identifier) : ReadOnlyProperty, ClientSpriteRegistryCallback { + private var id: Identifier? = null + private var value: Sprite? = null + + init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) } + + override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) { + id = idFunc(); value = null + registry.register(id) + } + + override fun getValue(thisRef: Any, property: KProperty<*>): Sprite { + value?.let { return it } + synchronized(this) { + value?.let { return it } + atlas.atlas[id!!]!!.let { value = it; return it } + } + } +} + +class SpriteSetDelegate(val atlas: Atlas, val idRegister: (Identifier)->Identifier = { it }, val idFunc: (Int)->Identifier) : ReadOnlyProperty, ClientSpriteRegistryCallback { + private var idList: List = emptyList() + private var spriteSet: SpriteSet? = null + init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) } + + override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) { + spriteSet = null + val manager = MinecraftClient.getInstance().resourceManager + idList = (0 until 16).map(idFunc).filter { manager.containsResource(atlas.wrap(it)) }.map(idRegister) + idList.forEach { registry.register(it) } + } + + override fun getValue(thisRef: Any, property: KProperty<*>): SpriteSet { + spriteSet?.let { return it } + synchronized(this) { + spriteSet?.let { return it } + spriteSet = FixedSpriteSet(atlas, idList) + return spriteSet!! + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/resource/model/TuftMeshes.kt b/src/main/kotlin/mods/betterfoliage/resource/model/TuftMeshes.kt new file mode 100644 index 0000000..d90f530 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/model/TuftMeshes.kt @@ -0,0 +1,74 @@ +package mods.betterfoliage.resource.model + +import mods.betterfoliage.util.Atlas +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.renderer.v1.RendererAccess +import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh +import net.minecraft.block.BlockRenderLayer +import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED +import net.minecraft.client.texture.Sprite +import net.minecraft.util.Identifier +import net.minecraft.util.math.Direction.UP + +data class TuftShapeKey( + val size: Double, + val height: Double, + val offset: Double3, + val flipU1: Boolean, + val flipU2: Boolean +) + +fun tuftShapeSet(size: Double, heightMin: Double, heightMax: Double, hOffset: Double): Array { + return Array(64) { idx -> + TuftShapeKey( + size, + randomD(heightMin, heightMax), + xzDisk(idx) * randomD(hOffset / 2.0, hOffset), + randomB(), + randomB() + ) + } +} + +fun tuftQuadSingle(size: Double, height: Double, flipU: Boolean) = + verticalRectangle(x1 = -0.5 * size, z1 = 0.5 * size, x2 = 0.5 * size, z2 = -0.5 * size, yBottom = 0.5, yTop = 0.5 + height) + .mirrorUV(flipU, false) + +fun tuftModelSet(shapes: Array, overrideColor: Int?, spriteGetter: (Int)->Sprite) = 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.colorAndIndex(overrideColor) } + .map { it.sprite(spriteGetter(idx)) } +}.toTypedArray() + +fun fullCubeTextured(spriteId: Identifier, overrideColor: Int?, scrambleUV: Boolean = true): Mesh { + val sprite = Atlas.BLOCKS.atlas[spriteId]!! + return allDirections.map { faceQuad(it) } + .map { if (!scrambleUV) it else it.rotateUV(randomI(max = 4)) } + .map { it.sprite(sprite) } + .map { it.colorAndIndex(overrideColor) } + .build(BlockRenderLayer.SOLID) +} + +fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Array> { + return Array(num) { idx -> + listOf( + verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41), + verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41) + .rotate(rot(UP)) + ).map { it.scale(size) } + .map { it.move(xzDisk(idx) * hOffset) } + .map { it.move(UP.vec * randomD(-1.0, 1.0) * vOffset) } + } +} + +fun crossModelsTextured(leafBase: Array>, overrideColor: Int?, scrambleUV: Boolean, spriteGetter: (Int)->Sprite) = leafBase.map { leaf -> + leaf.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it } + .map { it.colorAndIndex(overrideColor) } + .mapIndexed { idx, quad -> quad.sprite(spriteGetter(idx)) } + .withOpposites().build(CUTOUT_MIPPED) +}.toTypedArray() + +fun Array>.buildTufts() = withOpposites().build(CUTOUT_MIPPED) \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/resource/model/VanillaWrappers.kt b/src/main/kotlin/mods/betterfoliage/resource/model/VanillaWrappers.kt new file mode 100644 index 0000000..87e6d4a --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/model/VanillaWrappers.kt @@ -0,0 +1,86 @@ +package mods.betterfoliage.resource.model + +import mods.betterfoliage.util.YarnHelper +import mods.betterfoliage.util.get +import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh +import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.minecraft.block.BlockRenderLayer +import net.minecraft.block.BlockState +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.render.model.BasicBakedModel +import net.minecraft.client.render.model.WeightedBakedModel +import net.minecraft.item.ItemStack +import net.minecraft.util.WeightedPicker +import net.minecraft.util.math.BlockPos +import net.minecraft.world.ExtendedBlockView +import java.util.* +import java.util.function.Supplier + +// net.minecraft.client.render.model.WeightedBakedModel.totalWeight +val WeightedBakedModel_totalWeight = YarnHelper.requiredField("net.minecraft.class_1097", "field_5433", "I") +// net.minecraft.client.render.model.WeightedBakedModel.models +val WeightedBakedModel_models = YarnHelper.requiredField>("net.minecraft.class_1097", "field_5434", "Ljava/util/List;") +// net.minecraft.client.render.model.WeightedBakedModel.ModelEntry.model +val WeightedBakedModelEntry_model = YarnHelper.requiredField("net.minecraft.class_1097\$class_1099", "field_5437", "Lnet/minecraft/class_1087;") +// net.minecraft.util.WeightedPicker.Entry.weight +val WeightedPickerEntry_weight = YarnHelper.requiredField("net.minecraft.class_3549\$class_3550", "field_15774", "I") + +abstract class WrappedBakedModel(val wrapped: BakedModel) : BakedModel by wrapped, FabricBakedModel { + override fun isVanillaAdapter() = false + + override fun emitItemQuads(stack: ItemStack, randomSupplier: Supplier, context: RenderContext) { + (wrapped as FabricBakedModel).emitItemQuads(stack, randomSupplier, context) + } + + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + (wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context) + } +} + +class WrappedMeshModel(wrapped: BasicBakedModel, val mesh: Mesh) : WrappedBakedModel(wrapped) { + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + context.meshConsumer().accept(mesh) + } + + companion object { + /** + * Converter for [BasicBakedModel] instances. + * @param state [BlockState] to use when querying [BakedModel] + * @param unshade undo vanilla diffuse lighting when unbaking the [BakedModel] + * @param noDiffuse disable diffuse lighting when baking the [Mesh] + * @param renderLayerOverride [BlockRenderLayer] to use instead of the one declared by the corresponding [Block] + */ + fun converter(state: BlockState, unshade: Boolean = false, noDiffuse: Boolean = true, renderLayerOverride: BlockRenderLayer? = null) = BakedModelConverter.of { model, _ -> + if (model is BasicBakedModel) { + val mesh = unbakeQuads(model, state, Random(42L), unshade).build( + layer = renderLayerOverride ?: state.block.renderLayer, + noDiffuse = noDiffuse, + flatLighting = !model.useAmbientOcclusion() + ) + WrappedMeshModel(model, mesh) + } else null + } + } +} + +class WrappedWeightedModel(wrapped: WeightedBakedModel, transformer: BakedModelConverter) : WrappedBakedModel(wrapped) { + val totalWeight = wrapped[WeightedBakedModel_totalWeight] as Int + val models = wrapped[WeightedBakedModel_models]!!.map { entry -> + Entry(transformer.convert(entry[WeightedBakedModelEntry_model]!!, transformer)!!, entry[WeightedPickerEntry_weight]!!) + } + + override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier, context: RenderContext) { + (WeightedPicker.getRandom(randomSupplier.get(), models, totalWeight).model as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context) + } + + class Entry(val model: BakedModel, weight: Int) : WeightedPicker.Entry(weight) + + companion object { + val converter = object : BakedModelConverter { + override fun convert(model: BakedModel, converter: BakedModelConverter) = + (model as? WeightedBakedModel)?.let { WrappedWeightedModel(it, converter) } + } + } +} + diff --git a/src/main/kotlin/mods/betterfoliage/util/Caching.kt b/src/main/kotlin/mods/betterfoliage/util/Caching.kt new file mode 100644 index 0000000..3180bef --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/util/Caching.kt @@ -0,0 +1,60 @@ +package mods.betterfoliage.util + +import java.lang.ref.WeakReference +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +interface Invalidator { + fun invalidate() { + val iterator = callbacks.iterator() + while(iterator.hasNext()) iterator.next().let { callback -> + callback.get()?.invoke() ?: iterator.remove() + } + } + val callbacks: MutableListUnit>> + fun onInvalidate(callback: ()->Unit) { + callbacks.add(WeakReference(callback)) + } +} + +class LazyInvalidatable(invalidator: Invalidator, val valueFactory: ()->V): ReadOnlyProperty { + init { invalidator.onInvalidate { value = null } } + + var value: V? = null + + override fun getValue(thisRef: Any, property: KProperty<*>): V { + value?.let { return it } + return synchronized(this) { + value?.let { return it } + valueFactory().apply { value = this } + } + } +} + +class LazyMap(val invalidator: Invalidator, val valueFactory: (K)->V) { + init { invalidator.onInvalidate { values.clear() } } + + val values = mutableMapOf() + + operator fun get(key: K): V { + values[key]?.let { return it } + return synchronized(values) { + values[key]?.let { return it } + valueFactory(key).apply { values[key] = this } + } + } + operator fun set(key: K, value: V) { values[key] = value } + + fun delegate(key: K) = Delegate(key) + + inner class Delegate(val key: K) : ReadOnlyProperty { + init { invalidator.onInvalidate { cached = null } } + + private var cached: V? = null + + override fun getValue(thisRef: Any, property: KProperty<*>): V { + cached?.let { return it } + get(key).let { cached = it; return it } + } + } +} diff --git a/src/main/kotlin/mods/betterfoliage/util/Collections.kt b/src/main/kotlin/mods/betterfoliage/util/Collections.kt new file mode 100644 index 0000000..3ced728 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/util/Collections.kt @@ -0,0 +1,63 @@ +package mods.betterfoliage.util + +import java.util.* + +/** + * Starting with the second element of this [Iterable] until the last, call the supplied lambda with + * the parameters (index, element, previous element). + */ +inline fun Iterable.forEachPairIndexed(func: (Int, T, T)->Unit) { + var previous: T? = null + forEachIndexed { idx, current -> + if (previous != null) func(idx, current, previous!!) + previous = current + } +} + +inline fun > Pair.minBy(func: (T)->C) = + if (func(first) < func(second)) first else second + +inline fun > Pair.maxBy(func: (T)->C) = + if (func(first) > func(second)) first else second + +inline fun > Triple.maxValueBy(func: (T)->C): C { + var result = func(first) + func(second).let { if (it > result) result = it } + func(third).let { if (it > result) result = it } + return result +} + +@Suppress("UNCHECKED_CAST") +inline fun Map.filterValuesNotNull() = filterValues { it != null } as Map + +inline fun Iterable.findFirst(func: (T)->R?): R? { + forEach { func(it)?.let { return it } } + return null +} + +inline fun List>.filterIsInstanceFirst(cls: Class) = filter { it.first is A2 } as List> + +/** Cross product of this [Iterable] with the parameter. */ +fun Iterable.cross(other: Iterable) = flatMap { a -> other.map { b -> a to b } } + +inline fun Iterable.mapAs(transform: (C) -> R) = map { transform(it as C) } + +inline fun forEachNested(list1: Iterable, list2: Iterable, func: (T1, T2)-> Unit) = + list1.forEach { e1 -> + list2.forEach { e2 -> + func(e1, e2) + } + } + +/** Mutating version of _map_. Replace each element of the list with the result of the given transformation. */ +inline fun MutableList.replace(transform: (T) -> T) = forEachIndexed { idx, value -> this[idx] = transform(value) } + +/** Exchange the two elements of the list with the given indices */ +inline fun MutableList.exchange(idx1: Int, idx2: Int) { + val e = this[idx1] + this[idx1] = this[idx2] + this[idx2] = e +} + +/** Return a random element from the array using the provided random generator */ +inline operator fun Array.get(random: Random) = get(random.nextInt(Int.MAX_VALUE) % size) diff --git a/src/main/kotlin/mods/octarinecore/common/Futures.kt b/src/main/kotlin/mods/betterfoliage/util/Futures.kt similarity index 70% rename from src/main/kotlin/mods/octarinecore/common/Futures.kt rename to src/main/kotlin/mods/betterfoliage/util/Futures.kt index 6e3afe3..b2919b9 100644 --- a/src/main/kotlin/mods/octarinecore/common/Futures.kt +++ b/src/main/kotlin/mods/betterfoliage/util/Futures.kt @@ -1,6 +1,6 @@ -package mods.octarinecore.common +package mods.betterfoliage.util -import net.minecraft.client.Minecraft +import net.minecraft.client.MinecraftClient import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletionStage import java.util.function.Consumer @@ -9,7 +9,7 @@ import java.util.function.Function fun completedVoid() = CompletableFuture.completedFuture(null)!! fun CompletionStage.map(func: (T)->U) = thenApply(Function(func)).toCompletableFuture()!! -fun CompletionStage.mapAsync(func: (T)->U) = thenApplyAsync(Function(func), Minecraft.getInstance()).toCompletableFuture()!! +fun CompletionStage.mapAsync(func: (T)->U) = thenApplyAsync(Function(func), MinecraftClient.getInstance()).toCompletableFuture()!! fun CompletionStage.sink(func: (T)->Unit) = thenAccept(Consumer(func)).toCompletableFuture()!! -fun CompletionStage.sinkAsync(func: (T)->Unit) = thenAcceptAsync(Consumer(func), Minecraft.getInstance()).toCompletableFuture()!! \ No newline at end of file +fun CompletionStage.sinkAsync(func: (T)->Unit) = thenAcceptAsync(Consumer(func), MinecraftClient.getInstance()).toCompletableFuture()!! \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/common/Geometry.kt b/src/main/kotlin/mods/betterfoliage/util/Geometry.kt similarity index 83% rename from src/main/kotlin/mods/octarinecore/common/Geometry.kt rename to src/main/kotlin/mods/betterfoliage/util/Geometry.kt index f82390b..7674d19 100644 --- a/src/main/kotlin/mods/octarinecore/common/Geometry.kt +++ b/src/main/kotlin/mods/betterfoliage/util/Geometry.kt @@ -1,27 +1,25 @@ -package mods.octarinecore.common +package mods.betterfoliage.util -import mods.octarinecore.cross -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.client.util.math.Vector3f import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Direction.Axis.* +import net.minecraft.util.math.Direction.AxisDirection +import net.minecraft.util.math.Direction.AxisDirection.* +import net.minecraft.util.math.Direction.* // ================================ // Axes and directions // ================================ val axes = listOf(X, Y, Z) val axisDirs = listOf(POSITIVE, NEGATIVE) -val Direction.dir: AxisDirection get() = axisDirection -val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" } val allDirections = Direction.values() val horizontalDirections = listOf(NORTH, SOUTH, EAST, WEST) val allDirOffsets = allDirections.map { Int3(it) } -val Pair.face: Direction get() = when(this) { - X to POSITIVE -> EAST; X to NEGATIVE -> WEST; - Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN; - Z to POSITIVE -> SOUTH; else -> NORTH; +val Pair.face: Direction get() = when(this) { + X to POSITIVE -> EAST; X to NEGATIVE -> WEST + Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN + Z to POSITIVE -> SOUTH; else -> NORTH } val Direction.perpendiculars: List get() = axes.filter { it != this.axis }.cross(axisDirs).map { it.face } @@ -41,15 +39,15 @@ val ROTATION_MATRIX: Array 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(vector.x.toDouble() * scale, vector.y.toDouble() * scale, vector.z.toDouble() * scale) +val Direction.vec: Double3 get() = Double3(vector.x.toDouble(), vector.y.toDouble(), vector.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.vector.x.toDouble(), dir.vector.y.toDouble(), dir.vector.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) = @@ -94,15 +92,16 @@ data class Double3(var x: Double, var y: Double, var z: Double) { val length: Double get() = Math.sqrt(x * x + y * y + z * z) val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) } val nearestCardinal: Direction get() = nearestAngle(this, allDirections.asIterable()) { it.vec }.first + val asVec3f: Vector3f get() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat()) } /** 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.vector.x, dir.vector.y, dir.vector.z) constructor(offset: Pair) : this( - offset.first * offset.second.directionVec.x, - offset.first * offset.second.directionVec.y, - offset.first * offset.second.directionVec.z + offset.first * offset.second.vector.x, + offset.first * offset.second.vector.y, + offset.first * offset.second.vector.z ) companion object { val zero = Int3(0, 0, 0) @@ -111,9 +110,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) = 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.vector.x, + y + other.first * other.second.vector.y, + z + other.first * other.second.vector.z ) operator fun unaryMinus() = Int3(-x, -y, -z) operator fun minus(other: Int3) = Int3(x - other.x, y - other.y, z - other.z) @@ -162,7 +161,8 @@ class Rotation(val forward: Array, val reverse: Array) { Array(6) { idx -> other.reverse[reverse[idx].ordinal] } ) operator fun unaryMinus() = Rotation(reverse, forward) - operator fun times(num: Int) = when(num % 4) { 1 -> this; 2 -> this + this; 3 -> -this; else -> identity } + operator fun times(num: Int) = when(num % 4) { 1 -> this; 2 -> this + this; 3 -> -this; else -> identity + } inline fun rotatedComponent(dir: Direction, x: Int, y: Int, z: Int) = when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0 } @@ -173,8 +173,15 @@ class Rotation(val forward: Array, val reverse: Array) { // Forge rotation matrix is left-hand val rot90 = Array(6) { idx -> Rotation(allDirections[idx].opposite.rotations, allDirections[idx].rotations) } val identity = Rotation(allDirections, allDirections) + val fromUp = arrayOf( + rot90[EAST.ordinal] * 2, + identity, + rot90[WEST.ordinal], + rot90[EAST.ordinal], + rot90[SOUTH.ordinal], + rot90[NORTH.ordinal] + ) } - } // ================================ @@ -184,8 +191,14 @@ class Rotation(val forward: Array, val reverse: Array) { inline operator fun Array.get(face: Direction): T = get(face.ordinal) data class BoxFace(val top: Direction, val left: Direction) { - + val bottom get() = top.opposite + val right get() = left.opposite val allCorners = listOf(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite) + + val tl get() = top to left + val tr get() = top to right + val bl get() = bottom to left + val br get() = bottom to right } val boxFaces = allDirections.map { when(it) { DOWN -> BoxFace(SOUTH, WEST) diff --git a/src/main/kotlin/mods/betterfoliage/util/Misc.kt b/src/main/kotlin/mods/betterfoliage/util/Misc.kt new file mode 100644 index 0000000..3cb2356 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/util/Misc.kt @@ -0,0 +1,78 @@ +@file:Suppress("NOTHING_TO_INLINE") +package mods.betterfoliage.util + +import net.minecraft.text.LiteralText +import net.minecraft.text.Style +import net.minecraft.util.Formatting +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.Logger +import java.lang.Math.* +import kotlin.reflect.KProperty + +const val PI2 = 2.0 * PI + +/** Strip the given prefix off the start of the string, if present */ +inline fun String.stripStart(str: String, ignoreCase: Boolean = true) = if (startsWith(str, ignoreCase)) substring(str.length) else this +inline fun String.stripEnd(str: String, ignoreCase: Boolean = true) = if (endsWith(str, ignoreCase)) substring(0, length - str.length) else this + +/** Strip the given prefix off the start of the resource path, if present */ +inline fun Identifier.stripStart(str: String) = Identifier(namespace, path.stripStart(str)) +inline fun Identifier.stripEnd(str: String) = Identifier(namespace, path.stripEnd(str)) + +/** + * Property-level delegate backed by a [ThreadLocal]. + * + * @param[init] Lambda to get initial value + */ +class ThreadLocalDelegate(init: () -> T) { + var tlVal = ThreadLocal.withInitial(init) + operator fun getValue(thisRef: Any?, property: KProperty<*>): T = tlVal.get() + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { tlVal.set(value) } +} + +/** Call the supplied lambda and return its result, or the given default value if an exception is thrown. */ +fun tryDefault(default: T, work: ()->T) = try { work() } catch (e: Throwable) { default } + +/** + * Return this [Double] value if it lies between the two limits. If outside, return the + * minimum/maximum value correspondingly. + */ +fun Double.minmax(minVal: Double, maxVal: Double) = min(max(this, minVal), maxVal) + +/** + * Return this [Int] value if it lies between the two limits. If outside, return the + * minimum/maximum value correspondingly. + */ +fun Int.minmax(minVal: Int, maxVal: Int) = min(max(this, minVal), maxVal) + +fun nextPowerOf2(x: Int): Int { + return 1 shl (if (x == 0) 0 else 32 - Integer.numberOfLeadingZeros(x - 1)) +} + +/** + * Check if the Chunk containing the given [BlockPos] is loaded. + * Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances. + */ +//fun IWorldReader.isBlockLoaded(pos: BlockPos) = when { +// this is World -> isBlockLoaded(pos, false) +// this is RenderChunkCache -> isworld.isBlockLoaded(pos, false) +// Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkCache).world.isBlockLoaded(pos, false) +// else -> false +//} + +interface HasLogger { + val logger: Logger + val logName: String get() = this::class.simpleName!! + fun log(msg: String) = log(Level.INFO, msg) + fun log(level: Level, msg: String) = logger.log(level, "[$logName] $msg") + fun log(msg: String, e: Throwable) = log(Level.WARN, msg, e) + fun log(level: Level, msg: String, e: Throwable) = logger.log(level, "[$logName] $msg", e) +} + +fun textComponent(msg: String, color: Formatting = Formatting.GRAY): LiteralText { + val style = Style().apply { this.color = color } + return LiteralText(msg).apply { this.style = style } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/util/Random.kt b/src/main/kotlin/mods/betterfoliage/util/Random.kt new file mode 100644 index 0000000..8d3958b --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/util/Random.kt @@ -0,0 +1,20 @@ +package mods.betterfoliage.util + +import net.minecraft.util.math.BlockPos +import kotlin.random.Random + +val random = Random(System.nanoTime()) + +fun randomB() = random.nextBoolean() +fun randomI(min: Int = 0, max: Int = Int.MAX_VALUE) = random.nextInt(min, max) +fun randomL(min: Long = 0, max: Long = Long.MAX_VALUE) = random.nextLong(min, max) +fun randomF(min: Double = 0.0, max: Double = 1.0) = random.nextDouble(min, max).toFloat() +fun randomD(min: Double = 0.0, max: Double = 1.0) = if (min == max) min else random.nextDouble(min, max) + +fun semiRandom(x: Int, y: Int, z: Int, seed: Int): Int { + var value = (x * x + y * y + z * z + x * y + y * z + z * x + (seed * seed)) + value = (3 * x * value + 5 * y * value + 7 * z * value + (11 * seed)) + return value shr 4 +} + +//fun BlockPos.semiRandom(seed: Int) = semiRandom(x, y, z, seed) \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/util/Reflection.kt b/src/main/kotlin/mods/betterfoliage/util/Reflection.kt new file mode 100644 index 0000000..7fcfde4 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/util/Reflection.kt @@ -0,0 +1,106 @@ +package mods.betterfoliage.util + +import java.lang.reflect.Field +import java.lang.reflect.Method +import net.fabricmc.loader.api.FabricLoader +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.LogManager +import java.lang.Exception + +/** Get a Java class with the given name. */ +fun getJavaClass(name: String) = tryDefault(null) { Class.forName(name) } + +/** Get the field with the given name and type using reflection. */ +fun Any.reflectField(name: String) = getFieldRecursive(this::class.java, name).let { + it.isAccessible = true + it.get(this) as T +} + +fun getFieldRecursive(cls: Class<*>, name: String): Field = try { + cls.getDeclaredField(name) +} catch (e: NoSuchFieldException) { + cls.superclass?.let { getFieldRecursive(it, name) } ?: throw e +} + +fun getMethodRecursive(cls: Class<*>, name: String): Method = try { + cls.declaredMethods.find { it.name == name } ?: throw NoSuchMethodException() +} catch (e: NoSuchMethodException) { + cls.superclass?.let { getMethodRecursive(it, name) } ?: throw e +} + + +interface FieldRef { + val field: Field? + + /** Get this field using reflection. */ + operator fun get(receiver: Any?) = field?.get(receiver) as T? + + /** Get this static field using reflection. */ + fun getStatic() = get(null) + + /** Get this field using reflection. */ + fun set(receiver: Any?, obj: Any?) { field?.set(receiver, obj) } +} + +interface MethodRef { + val method: Method? + + /** Invoke this method using reflection. */ + operator fun invoke(receiver: Any, vararg args: Any?) = method?.invoke(receiver, *args) as T + + /** Invoke this static method using reflection. */ + fun invokeStatic(vararg args: Any) = method?.invoke(null, *args) +} + +const val INTERMEDIARY = "intermediary" + +object YarnHelper { + val logger = LogManager.getLogger() + val resolver = FabricLoader.getInstance().mappingResolver + + fun requiredField(className: String, fieldName: String, descriptor: String) = Field(false, className, fieldName, descriptor) + fun requiredMethod(className: String, methodName: String, descriptor: String, vararg params: String) = Method(false, className, methodName, descriptor) + + class Field(val optional: Boolean, val className: String, val fieldName: String, descriptor: String) : FieldRef { + override val field = FabricLoader.getInstance().mappingResolver.let { resolver -> + try { + val classMapped = resolver.mapClassName(INTERMEDIARY, className) + val fieldMapped = resolver.mapFieldName(INTERMEDIARY, className, fieldName, descriptor) + Class.forName(classMapped)?.let { cls -> getFieldRecursive(cls, fieldMapped).apply { isAccessible = true } } + } catch (e: Exception) { + logger.log( + if (optional) Level.DEBUG else Level.ERROR, + "Could not resolve field $className.$fieldName ( $descriptor ): ${e.message}" + ) + if (optional) null else throw e + } + } + } + + class Method(val optional: Boolean, val className: String, val methodName: String, descriptor: String) : MethodRef { + override val method = FabricLoader.getInstance().mappingResolver.let { resolver -> + try { + val classMapped = resolver.mapClassName(INTERMEDIARY, className) + val methodMapped = resolver.mapMethodName(INTERMEDIARY, className, methodName, descriptor) + Class.forName(classMapped)?.let { cls -> getMethodRecursive(cls, methodMapped).apply { isAccessible = true } } + } catch (e: Exception) { + logger.log( + if (optional) Level.DEBUG else Level.ERROR, + "Could not resolve field $className.$methodName ( $descriptor ): ${e.message}" + ) + if (optional) null else throw e + } + } + } +} + +//fun Any.isInstance(cls: ClassRefOld<*>) = cls.isInstance(this) + +interface ReflectionCallable { + operator fun invoke(vararg args: Any): T +} +inline operator fun Any.get(field: FieldRef) = field.get(this) +inline operator fun Any.set(field: FieldRef, value: T) = field.set(this, value) +inline operator fun Any.get(methodRef: MethodRef) = object : ReflectionCallable { + override fun invoke(vararg args: Any) = methodRef.invoke(this@get, *args) +} diff --git a/src/main/kotlin/mods/betterfoliage/util/Resources.kt b/src/main/kotlin/mods/betterfoliage/util/Resources.kt new file mode 100644 index 0000000..591abc1 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/util/Resources.kt @@ -0,0 +1,30 @@ +package mods.betterfoliage.util + +import net.minecraft.client.MinecraftClient +import net.minecraft.resource.ReloadableResourceManager +import net.minecraft.resource.Resource +import net.minecraft.resource.ResourceManager +import net.minecraft.util.Identifier + +/** Concise getter for the Minecraft resource manager. */ +val resourceManager: ReloadableResourceManager get() = + MinecraftClient.getInstance().resourceManager as ReloadableResourceManager + +/** Append a string to the [ResourceLocation]'s path. */ +operator fun Identifier.plus(str: String) = Identifier(namespace, path + str) + +/** Index operator to get a resource. */ +operator fun ResourceManager.get(domain: String, path: String): Resource? = get(Identifier(domain, path)) +/** Index operator to get a resource. */ +operator fun ResourceManager.get(location: Identifier): Resource? = tryDefault(null) { getResource(location) } + +/** Get the lines of a text resource. */ +fun Resource.getLines(): List { + val result = arrayListOf() + inputStream.bufferedReader().useLines { it.forEach { result.add(it) } } + return result +} + + + + diff --git a/src/main/kotlin/mods/betterfoliage/util/Sprites.kt b/src/main/kotlin/mods/betterfoliage/util/Sprites.kt new file mode 100644 index 0000000..9ae0f16 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/util/Sprites.kt @@ -0,0 +1,84 @@ +package mods.betterfoliage.util + +import mods.betterfoliage.resource.model.HSB +import net.minecraft.client.MinecraftClient +import net.minecraft.client.texture.Sprite +import net.minecraft.client.texture.SpriteAtlasTexture +import net.minecraft.resource.Resource +import net.minecraft.resource.ResourceManager +import net.minecraft.util.Identifier +import java.awt.image.BufferedImage +import java.io.ByteArrayOutputStream +import java.io.IOException +import javax.imageio.ImageIO +import kotlin.math.atan2 + +enum class Atlas(val basePath: String, val resourceId: Identifier) { + BLOCKS("textures", SpriteAtlasTexture.BLOCK_ATLAS_TEX), + PARTICLES("textures/particle", SpriteAtlasTexture.PARTICLE_ATLAS_TEX); + + /** Get the fully-qualified resource name for sprites belonging to this atlas*/ + fun wrap(resource: Identifier) = Identifier(resource.namespace, "$basePath/${resource.path}.png") + + /** Get the short resource name for sprites belonging to this atlas*/ + fun unwrap(resource: Identifier) = resource.stripStart("$basePath/").stripEnd(".png") + + /** Reference to the atlas itself */ + val atlas: SpriteAtlasTexture get() = MinecraftClient.getInstance().textureManager.getTexture(resourceId) as SpriteAtlasTexture +} + +operator fun SpriteAtlasTexture.get(res: Identifier): Sprite? = getSprite(res) +operator fun SpriteAtlasTexture.get(name: String): Sprite? = getSprite(Identifier(name)) + +fun ResourceManager.loadSprite(id: Identifier) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id") + +fun Resource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream) + +/** Index operator to get the RGB value of a pixel. */ +operator fun BufferedImage.get(x: Int, y: Int) = this.getRGB(x, y) +/** Index operator to set the RGB value of a pixel. */ +operator fun BufferedImage.set(x: Int, y: Int, value: Int) = this.setRGB(x, y, value) + +val BufferedImage.bytes: ByteArray get() = + ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() } + +/** + * Calculate the average color of an image. + * + * Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average), + * and the result transformed back to the RGB color space. + */ +fun ResourceManager.averageImageColorHSB(id: Identifier, atlas: Atlas) = loadSprite(atlas.wrap(id)).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.height) { + val pixel = image.get(x, y) + val alpha = (pixel shr 24) and 255 + val hsb = HSB.fromColor(pixel) + if (alpha == 255) { + numOpaque++ + sumHueX += Math.cos((hsb.hue.toDouble() - 0.5) * PI2) + sumHueY += Math.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() + HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat()) +} + +/** 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) + val g = (((rgb1 shr 8) and 255) * weight1 + ((rgb2 shr 8) and 255) * weight2) / (weight1 + weight2) + val b = ((rgb1 and 255) * weight1 + (rgb2 and 255) * weight2) / (weight1 + weight2) + val a = (rgb1 shr 24) and 255 + val result = ((a shl 24) or (r shl 16) or (g shl 8) or b) + return result +} diff --git a/src/main/kotlin/mods/betterfoliage/util/Winding.kt b/src/main/kotlin/mods/betterfoliage/util/Winding.kt new file mode 100644 index 0000000..612054f --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/util/Winding.kt @@ -0,0 +1,102 @@ +package mods.betterfoliage.util + +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Direction.* + +fun Pair.ccwWinding() = arrayOf(first to second, first.opposite to second, first.opposite to second.opposite, first to second.opposite) + +typealias BoxCorner = Triple +fun BoxCorner.equalsUnordered(other: BoxCorner) = contains(other.first) && contains(other.second) && contains(other.third) + +fun BoxCorner.contains(dir: Direction) = first == dir || second == dir || third == dir + +fun Array.findIdx(corner: BoxCorner): Int? { + forEachIndexed { idx, test -> if (test.contains(corner.first) && test.contains(corner.second) && test.contains(corner.third)) return idx } + return null +} +fun Array.findIdx(predicate: (BoxCorner)->Boolean): Int? { + forEachIndexed { idx, test -> if (predicate(test)) return idx } + return null +} + +/** + * Decribes the winding order for vanilla AO data. + * 1st index: light face ordinal + * 2nd index: index in AO data array + * value: pair of [Direction]s that along with light face fully describe the corner + * the AO data belongs to + */ +val aoWinding = allDirections.map { when(it) { + DOWN -> (SOUTH to WEST).ccwWinding() + UP -> (SOUTH to EAST).ccwWinding() + NORTH -> (WEST to UP).ccwWinding() + SOUTH -> (UP to WEST).ccwWinding() + WEST -> (SOUTH to UP).ccwWinding() + EAST -> (SOUTH to DOWN).ccwWinding() +} } + +/** + * Indexing for undirected box corners (component order does not matter). + * Array contains [Direction] triplets fully defining the corner. + */ +val cornersUndir = Array(8) { idx -> Triple( + if (idx and 1 != 0) EAST else WEST, + if (idx and 2 != 0) UP else DOWN, + if (idx and 4 != 0) SOUTH else NORTH +) } + +/** + * Reverse lookup for [cornersUndir]. Index 3 times with the corner's cardinal directions. + * A null value indicates an invalid corner (multiple indexing along the same axis) + */ +val cornersUndirIdx = Array(6) { idx1 -> Array(6) { idx2 -> Array(6) { idx3 -> + cornersUndir.findIdx(BoxCorner(byId(idx1), byId(idx2), byId(idx3))) +} } } + +/** + * Get corner index for vertex coordinates + */ +fun getCornerUndir(x: Float, y: Float, z: Float): Int { + var result = 0 + if (x > 0.5f) result += 1 + if (y > 0.5f) result += 2 + if (z > 0.5f) result += 4 + return result +} + +/** + * Indexing scheme for directed box corners. + * The first direction - the face - matters, the other two are unordered. + * 1:1 correspondence with possible AO values. + * Array contains triplets defining the corner fully. + */ +val cornersDir = Array(24) { idx -> + val faceIdx = idx / 4 + val windingIdx = idx % 4 + val winding = aoWinding[faceIdx][windingIdx] + BoxCorner(byId(faceIdx), winding.first, winding.second) +} + +/** + * Reverse lookup for [cornersDir]. + * 1st index: primary face + * 2nd index: undirected corner index. + * value: directed corner index + * A null value indicates an invalid corner (primary face not shared by corner). + */ +val cornerDirFromUndir = Array(6) { faceIdx -> Array(8) { undirIdx -> + val face = byId(faceIdx); val corner = cornersUndir[undirIdx] + if (!corner.contains(face)) null else cornersDir.findIdx { it.first == face && it.equalsUnordered(corner) } +} } + +/** + * Reverse lookup for [cornersDir]. + * 1st index: primary face + * 2nd index: AO value index on the face. + * value: directed corner index + */ +val cornerDirFromAo = Array(6) { faceIdx -> IntArray(4) { aoIdx -> + val face = byId(faceIdx); val corner = aoWinding[face.ordinal][aoIdx].let { Triple(face, it.first, it.second) } + cornersDir.findIdx { it.first == face && it.equalsUnordered(corner) }!! +} } + diff --git a/src/main/kotlin/mods/octarinecore/CommonRefs.kt b/src/main/kotlin/mods/octarinecore/CommonRefs.kt deleted file mode 100644 index 5149bbb..0000000 --- a/src/main/kotlin/mods/octarinecore/CommonRefs.kt +++ /dev/null @@ -1,69 +0,0 @@ -package mods.octarinecore - -import mods.octarinecore.metaprog.ClassRef -import mods.octarinecore.metaprog.ClassRef.Companion.void -import mods.octarinecore.metaprog.FieldRef -import mods.octarinecore.metaprog.MethodRef -import net.minecraft.block.Block -import net.minecraft.block.BlockState -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.client.renderer.chunk.ChunkRenderCache -import net.minecraft.client.renderer.model.BakedQuad -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.util.BlockRenderLayer -import net.minecraft.util.ResourceLocation -import net.minecraft.util.math.BlockPos -import net.minecraft.world.IBlockReader -import net.minecraft.world.IEnviromentBlockReader -import java.util.* - -// Java -val String = ClassRef("java.lang.String") -val Map = ClassRef>("java.util.Map") -val List = ClassRef>("java.util.List") -val Random = ClassRef("java.util.Random") - -// Minecraft -val IBlockReader = ClassRef("net.minecraft.world.IBlockReader") -val IEnvironmentBlockReader = ClassRef("net.minecraft.world.IEnviromentBlockReader") -val BlockState = ClassRef("net.minecraft.block.BlockState") -val BlockPos = ClassRef("net.minecraft.util.math.BlockPos") -val BlockRenderLayer = ClassRef("net.minecraft.util.BlockRenderLayer") -val Block = ClassRef("net.minecraft.block.Block") - -val TextureAtlasSprite = ClassRef("net.minecraft.client.renderer.texture.TextureAtlasSprite") -val BufferBuilder = ClassRef("net.minecraft.client.renderer.BufferBuilder") -val BufferBuilder_setSprite = MethodRef(BufferBuilder, "setSprite", void, TextureAtlasSprite) -val BufferBuilder_sVertexBuilder = FieldRef(BufferBuilder, "sVertexBuilder", SVertexBuilder) -val BlockRendererDispatcher = ClassRef("net.minecraft.client.renderer.BlockRendererDispatcher") -val ChunkRenderCache = ClassRef("net.minecraft.client.renderer.chunk.ChunkRenderCache") -val ResourceLocation = ClassRef("net.minecraft.util.ResourceLocation") -val BakedQuad = ClassRef("net.minecraft.client.renderer.model.BakedQuad") - -// Optifine -val OptifineClassTransformer = ClassRef("optifine.OptiFineClassTransformer") -val BlockPosM = ClassRef("net.optifine.BlockPosM") -object ChunkCacheOF : ClassRef("net.optifine.override.ChunkCacheOF") { - val chunkCache = FieldRef(this, "chunkCache", ChunkRenderCache) -} - -object RenderEnv : ClassRef("net.optifine.render.RenderEnv") { - val reset = MethodRef(this, "reset", void, BlockState, BlockPos) -} - -// Optifine custom colors -val IColorizer = ClassRef("net.optifine.CustomColors\$IColorizer") -object CustomColors : ClassRef("net.optifine.CustomColors") { - val getColorMultiplier = MethodRef(this, "getColorMultiplier", int, BakedQuad, BlockState, IEnvironmentBlockReader, BlockPos, RenderEnv) -} - -// Optifine shaders -object SVertexBuilder : ClassRef("net.optifine.shaders.SVertexBuilder") { - val pushState = MethodRef(this, "pushEntity", void, BlockState, BlockPos, IEnvironmentBlockReader, BufferBuilder) - val pushNum = MethodRef(this, "pushEntity", void, long) - val pop = MethodRef(this, "popEntity", void) -} - - - diff --git a/src/main/kotlin/mods/octarinecore/Utils.kt b/src/main/kotlin/mods/octarinecore/Utils.kt deleted file mode 100644 index f21411d..0000000 --- a/src/main/kotlin/mods/octarinecore/Utils.kt +++ /dev/null @@ -1,129 +0,0 @@ -@file:JvmName("Utils") -@file:Suppress("NOTHING_TO_INLINE") -package mods.octarinecore - -import net.minecraft.tileentity.TileEntity -import net.minecraft.util.ResourceLocation -import net.minecraft.util.math.BlockPos -import net.minecraft.world.* -import org.apache.logging.log4j.Level -import org.apache.logging.log4j.Logger -import kotlin.reflect.KProperty -import java.lang.Math.* - -const val PI2 = 2.0 * PI - -/** Strip the given prefix off the start of the string, if present */ -inline fun String.stripStart(str: String, ignoreCase: Boolean = true) = if (startsWith(str, ignoreCase)) substring(str.length) else this -inline fun String.stripEnd(str: String, ignoreCase: Boolean = true) = if (endsWith(str, ignoreCase)) substring(0, length - str.length) else this - -/** Strip the given prefix off the start of the resource path, if present */ -inline fun ResourceLocation.stripStart(str: String) = ResourceLocation(namespace, path.stripStart(str)) -inline fun ResourceLocation.stripEnd(str: String) = ResourceLocation(namespace, path.stripEnd(str)) - -/** Mutating version of _map_. Replace each element of the list with the result of the given transformation. */ -inline fun MutableList.replace(transform: (T) -> T) = forEachIndexed { idx, value -> this[idx] = transform(value) } - -/** Exchange the two elements of the list with the given indices */ -inline fun MutableList.exchange(idx1: Int, idx2: Int) { - val e = this[idx1] - this[idx1] = this[idx2] - this[idx2] = e -} - -/** Cross product of this [Iterable] with the parameter. */ -fun Iterable.cross(other: Iterable) = flatMap { a -> other.map { b -> a to b } } - -inline fun Iterable.mapAs(transform: (C) -> R) = map { transform(it as C) } - -inline fun forEachNested(list1: Iterable, list2: Iterable, func: (T1, T2)-> Unit) = - list1.forEach { e1 -> - list2.forEach { e2 -> - func(e1, e2) - } - } - -@Suppress("UNCHECKED_CAST") -inline fun Map.filterValuesNotNull() = filterValues { it != null } as Map - -inline fun Iterable.findFirst(func: (T)->R?): R? { - forEach { func(it)?.let { return it } } - return null -} - -/** - * Property-level delegate backed by a [ThreadLocal]. - * - * @param[init] Lambda to get initial value - */ -class ThreadLocalDelegate(init: () -> T) { - var tlVal = ThreadLocal.withInitial(init) - operator fun getValue(thisRef: Any?, property: KProperty<*>): T = tlVal.get() - operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { tlVal.set(value) } -} - -/** - * Starting with the second element of this [Iterable] until the last, call the supplied lambda with - * the parameters (index, element, previous element). - */ -inline fun Iterable.forEachPairIndexed(func: (Int, T, T)->Unit) { - var previous: T? = null - forEachIndexed { idx, current -> - if (previous != null) func(idx, current, previous!!) - previous = current - } -} - -/** Call the supplied lambda and return its result, or the given default value if an exception is thrown. */ -fun tryDefault(default: T, work: ()->T) = try { work() } catch (e: Throwable) { default } - -/** Return a random [Double] value between the given two limits (inclusive min, exclusive max). */ -fun random(min: Double, max: Double) = Math.random().let { min + (max - min) * it } - -fun semiRandom(x: Int, y: Int, z: Int, seed: Int): Int { - var value = (x * x + y * y + z * z + x * y + y * z + z * x + (seed * seed)) and 63 - value = (3 * x * value + 5 * y * value + 7 * z * value + (11 * seed)) and 63 - return value -} -/** - * Return this [Double] value if it lies between the two limits. If outside, return the - * minimum/maximum value correspondingly. - */ -fun Double.minmax(minVal: Double, maxVal: Double) = min(max(this, minVal), maxVal) - -/** - * Return this [Int] value if it lies between the two limits. If outside, return the - * minimum/maximum value correspondingly. - */ -fun Int.minmax(minVal: Int, maxVal: Int) = min(max(this, minVal), maxVal) - -fun nextPowerOf2(x: Int): Int { - return 1 shl (if (x == 0) 0 else 32 - Integer.numberOfLeadingZeros(x - 1)) -} - -/** - * Check if the Chunk containing the given [BlockPos] is loaded. - * Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances. - */ -//fun IWorldReader.isBlockLoaded(pos: BlockPos) = when { -// this is World -> isBlockLoaded(pos, false) -// this is RenderChunkCache -> isworld.isBlockLoaded(pos, false) -// Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkCache).world.isBlockLoaded(pos, false) -// else -> false -//} - - -/** - * Get the [TileEntity] at the given position, suppressing exceptions. - * Also returns null if the chunk is unloaded, which can happen because of multithreaded rendering. - */ -fun IWorldReader.getTileEntitySafe(pos: BlockPos): TileEntity? = tryDefault(null as TileEntity?) { - if (isBlockLoaded(pos)) getTileEntity(pos) else null -} - -interface HasLogger { - val logger: Logger - val logName: String get() = this::class.simpleName!! - fun log(msg: String) = log(Level.DEBUG, msg) - fun log(level: Level, msg: String) = logger.log(level, "[$logName] $msg") -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/KeyHandler.kt b/src/main/kotlin/mods/octarinecore/client/KeyHandler.kt deleted file mode 100644 index f6edbad..0000000 --- a/src/main/kotlin/mods/octarinecore/client/KeyHandler.kt +++ /dev/null @@ -1,22 +0,0 @@ -package mods.octarinecore.client - -import net.minecraft.client.settings.KeyBinding -import net.minecraftforge.client.event.InputEvent -import net.minecraftforge.common.MinecraftForge -import net.minecraftforge.eventbus.api.SubscribeEvent -import net.minecraftforge.fml.client.registry.ClientRegistry - -class KeyHandler(val modId: String, val defaultKey: Int, val lang: String, val action: (InputEvent.KeyInputEvent)->Unit) { - - val keyBinding = KeyBinding(lang, defaultKey, modId) - - init { - ClientRegistry.registerKeyBinding(keyBinding) - MinecraftForge.EVENT_BUS.register(this) - } - - @SubscribeEvent - fun handleKeyPress(event: InputEvent.KeyInputEvent) { - if (keyBinding.isPressed) action(event) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/gui/Utils.kt b/src/main/kotlin/mods/octarinecore/client/gui/Utils.kt deleted file mode 100644 index 4cfedbd..0000000 --- a/src/main/kotlin/mods/octarinecore/client/gui/Utils.kt +++ /dev/null @@ -1,22 +0,0 @@ -@file:JvmName("Utils") -package mods.octarinecore.client.gui - -import net.minecraft.util.text.StringTextComponent -import net.minecraft.util.text.Style -import net.minecraft.util.text.TextFormatting -import net.minecraft.util.text.TextFormatting.AQUA -import net.minecraft.util.text.TextFormatting.GRAY - -fun stripTooltipDefaultText(tooltip: MutableList) { - 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 } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/AbstractEntityFX.kt b/src/main/kotlin/mods/octarinecore/client/render/AbstractEntityFX.kt deleted file mode 100644 index 3fc07bf..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/AbstractEntityFX.kt +++ /dev/null @@ -1,129 +0,0 @@ -package mods.octarinecore.client.render - -import mods.octarinecore.PI2 -import mods.octarinecore.common.Double3 -import net.minecraft.client.Minecraft -import net.minecraft.client.particle.IParticleRenderType -import net.minecraft.client.particle.Particle -import net.minecraft.client.particle.SpriteTexturedParticle -import net.minecraft.client.renderer.ActiveRenderInfo -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.entity.Entity -import net.minecraft.world.World - -abstract class AbstractEntityFX(world: World, 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) } - @JvmStatic val cos = Array(64) { idx -> Math.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 - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/BlockContext.kt b/src/main/kotlin/mods/octarinecore/client/render/BlockContext.kt deleted file mode 100644 index 4299e35..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/BlockContext.kt +++ /dev/null @@ -1,71 +0,0 @@ -package mods.octarinecore.client.render - -import mods.octarinecore.common.* -import mods.octarinecore.semiRandom -import net.minecraft.block.Block -import net.minecraft.block.BlockState -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.util.BlockRenderLayer -import net.minecraft.util.Direction -import net.minecraft.util.math.BlockPos -import net.minecraft.world.IBlockReader -import net.minecraft.world.IEnviromentBlockReader -import net.minecraft.world.World -import net.minecraft.world.biome.Biome -import java.util.* - -/** - * Represents the block being rendered. Has properties and methods to query the neighborhood of the block in - * block-relative coordinates. - */ -interface BlockCtx { - val world: IEnviromentBlockReader - val pos: BlockPos - - fun offset(dir: Direction) = offset(dir.offset) - fun offset(offset: Int3): BlockCtx - - val state: BlockState get() = world.getBlockState(pos) - fun state(dir: Direction) = world.getBlockState(pos + dir.offset) - fun state(offset: Int3) = world.getBlockState(pos + offset) - - val biome: Biome get() = world.getBiome(pos) - - val isNormalCube: Boolean get() = state.isNormalCube(world, pos) - - fun shouldSideBeRendered(side: Direction) = Block.shouldSideBeRendered(state, world, pos, side) - - /** Get a semi-random value based on the block coordinate and the given seed. */ - fun semiRandom(seed: Int) = semiRandom(pos.x, pos.y, pos.z, seed) - - /** Get an array of semi-random values based on the block coordinate. */ - fun semiRandomArray(num: Int): Array = Array(num) { semiRandom(it) } -} - -open class BasicBlockCtx( - override val world: IEnviromentBlockReader, - override val pos: BlockPos -) : BlockCtx { - override var state: BlockState = world.getBlockState(pos) - protected set - override fun offset(offset: Int3) = BasicBlockCtx(world, pos + offset) - fun cache() = CachedBlockCtx(world, pos) -} - -open class CachedBlockCtx(world: IEnviromentBlockReader, pos: BlockPos) : BasicBlockCtx(world, pos) { - var neighbors = Array(6) { world.getBlockState(pos + allDirections[it].offset) } - override var biome: Biome = world.getBiome(pos) - override fun state(dir: Direction) = neighbors[dir.ordinal] -} - - -data class RenderCtx( - val dispatcher: BlockRendererDispatcher, - val renderBuffer: BufferBuilder, - val layer: BlockRenderLayer, - val random: Random -) { - fun render(worldBlock: BlockCtx) = dispatcher.renderBlock(worldBlock.state, worldBlock.pos, worldBlock.world, renderBuffer, random, null) -} - diff --git a/src/main/kotlin/mods/octarinecore/client/render/CombinedContext.kt b/src/main/kotlin/mods/octarinecore/client/render/CombinedContext.kt deleted file mode 100644 index d6b6570..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/CombinedContext.kt +++ /dev/null @@ -1,101 +0,0 @@ -package mods.octarinecore.client.render - -import mods.betterfoliage.client.render.canRenderInCutout -import mods.betterfoliage.client.render.isCutout -import mods.octarinecore.BufferBuilder -import mods.octarinecore.BufferBuilder_setSprite -import mods.octarinecore.client.render.lighting.* -import mods.octarinecore.common.Double3 -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.Rotation -import mods.octarinecore.common.plus -import mods.octarinecore.metaprog.get -import mods.octarinecore.metaprog.set -import net.minecraft.block.Blocks -import net.minecraft.fluid.Fluids -import net.minecraft.util.Direction -import net.minecraft.util.math.BlockPos -import net.minecraft.world.IEnviromentBlockReader -import net.minecraft.world.LightType -import net.minecraft.world.biome.Biomes - -class CombinedContext( - val blockCtx: BlockCtx, val renderCtx: RenderCtx, val lightingCtx: DefaultLightingCtx -) : BlockCtx by blockCtx, LightingCtx by lightingCtx { - - var hasRendered = false - - fun render(force: Boolean = false) = renderCtx.let { - if (force || state.canRenderInLayer(it.layer) || (state.canRenderInCutout() && it.layer.isCutout)) { - it.render(blockCtx) - hasRendered = true - } - Unit - } - - fun exchange(moddedOffset: Int3, targetOffset: Int3) = CombinedContext( - BasicBlockCtx(OffsetEnvBlockReader(blockCtx.world, pos + moddedOffset, pos + targetOffset), pos), - renderCtx, - lightingCtx - ) - - val isCutout = renderCtx.layer.isCutout - - /** Get the centerpoint of the block being rendered. */ - val blockCenter: Double3 get() = Double3(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5) - - /** Holds final vertex data before it goes to the [Tessellator]. */ - val temp = RenderVertex() - - fun render( - model: Model, - rotation: Rotation = Rotation.identity, - translation: Double3 = blockCenter, - forceFlat: Boolean = false, - quadFilter: (Int, Quad) -> Boolean = { _, _ -> true }, - icon: QuadIconResolver, - postProcess: PostProcessLambda = noPost - ) { - lightingCtx.modelRotation = rotation - model.quads.forEachIndexed { quadIdx, quad -> - if (quadFilter(quadIdx, quad)) { - val drawIcon = icon(this, quadIdx, quad) - if (drawIcon != null) { - // let OptiFine know the texture we're using, so it can - // transform UV coordinates to quad-relative - BufferBuilder_setSprite.invoke(renderCtx.renderBuffer, drawIcon) - - quad.verts.forEachIndexed { vertIdx, vert -> - temp.init(vert).rotate(lightingCtx.modelRotation).translate(translation) - val shader = if (lightingCtx.aoEnabled && !forceFlat) vert.aoShader else vert.flatShader - shader.shade(lightingCtx, temp) - temp.postProcess(this, quadIdx, quad, vertIdx, vert) - temp.setIcon(drawIcon) - - renderCtx.renderBuffer - .pos(temp.x, temp.y, temp.z) - .color(temp.red, temp.green, temp.blue, 1.0f) - .tex(temp.u, temp.v) - .lightmap(temp.brightness shr 16 and 65535, temp.brightness and 65535) - .endVertex() - } - } - } - } - hasRendered = true - } -} - -val allFaces: (Direction) -> Boolean = { true } -val topOnly: (Direction) -> Boolean = { it == Direction.UP } - -/** Perform no post-processing */ -val noPost: PostProcessLambda = { _, _, _, _, _ -> } - -object NonNullWorld : IEnviromentBlockReader { - override fun getBlockState(pos: BlockPos) = Blocks.AIR.defaultState - override fun getLightFor(type: LightType, pos: BlockPos) = 0 - override fun getFluidState(pos: BlockPos) = Fluids.EMPTY.defaultState - override fun getTileEntity(pos: BlockPos) = null - override fun getBiome(pos: BlockPos) = Biomes.THE_VOID -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/Model.kt b/src/main/kotlin/mods/octarinecore/client/render/Model.kt deleted file mode 100644 index f9e5e19..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/Model.kt +++ /dev/null @@ -1,147 +0,0 @@ -package mods.octarinecore.client.render - -import mods.octarinecore.client.render.lighting.* -import mods.octarinecore.common.* -import mods.octarinecore.minmax -import mods.octarinecore.replace -import net.minecraft.util.Direction -import java.lang.Math.max -import java.lang.Math.min - -/** - * Vertex UV coordinates - * - * Zero-centered: coordinates fall between (-0.5, 0.5) (inclusive) - */ -data class UV(val u: Double, val v: Double) { - companion object { - val topLeft = UV(-0.5, -0.5) - val topRight = UV(0.5, -0.5) - val bottomLeft = UV(-0.5, 0.5) - val bottomRight = UV(0.5, 0.5) - } - - val rotate: UV get() = UV(v, -u) - - fun rotate(n: Int) = when(n % 4) { - 0 -> copy() - 1 -> UV(v, -u) - 2 -> UV(-u, -v) - else -> UV(-v, u) - } - - fun clamp(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) = - UV(u.minmax(minU, maxU), v.minmax(minV, maxV)) - - fun mirror(mirrorU: Boolean, mirrorV: Boolean) = UV(if (mirrorU) -u else u, if (mirrorV) -v else v) -} - -/** - * Model vertex - * - * @param[xyz] x, y, z coordinates - * @param[uv] u, v coordinates - * @param[aoShader] [ModelLighter] instance to use with AO rendering - * @param[flatShader] [ModelLighter] instance to use with non-AO rendering - */ -data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0), - val uv: UV = UV(0.0, 0.0), - val aoShader: ModelLighter = NoLighting, - val flatShader: ModelLighter = NoLighting) - -/** - * Model quad - */ -data class Quad(val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex) { - val verts = arrayOf(v1, v2, v3, v4) - inline fun transformV(trans: (Vertex)->Vertex): Quad = transformVI { vertex, idx -> trans(vertex) } - inline fun transformVI(trans: (Vertex, Int)->Vertex): Quad = - Quad(trans(v1, 0), trans(v2, 1), trans(v3, 2), trans(v4, 3)) - val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize - - fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) } - fun move(trans: Pair) = move(Double3(trans.second) * trans.first) - fun scale (scale: Double) = transformV { it.copy(xyz = it.xyz * scale) } - fun scale (scale: Double3) = transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) } - fun scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) } - fun rotate(rot: Rotation) = transformV { - it.copy(xyz = it.xyz.rotate(rot), aoShader = it.aoShader.rotate(rot), flatShader = it.flatShader.rotate(rot)) - } - fun rotateUV(n: Int) = transformV { it.copy(uv = it.uv.rotate(n)) } - fun clampUV(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) = - transformV { it.copy(uv = it.uv.clamp(minU, maxU, minV, maxV)) } - fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) = transformV { it.copy(uv = it.uv.mirror(mirrorU, mirrorV)) } - fun setAoShader(factory: ShaderFactory, predicate: (Vertex, Int)->Boolean = { v, vi -> true }) = - transformVI { vertex, idx -> - if (!predicate(vertex, idx)) vertex else vertex.copy(aoShader = factory(this@Quad, vertex)) - } - fun setFlatShader(factory: ShaderFactory, predicate: (Vertex, Int)->Boolean = { v, vi -> true }) = - transformVI { vertex, idx -> - if (!predicate(vertex, idx)) vertex else vertex.copy(flatShader = factory(this@Quad, vertex)) - } - fun setFlatShader(shader: ModelLighter) = transformVI { vertex, idx -> vertex.copy(flatShader = shader) } - val flipped: Quad get() = Quad(v4, v3, v2, v1) - - fun cycleVertices(n: Int) = when(n % 4) { - 1 -> Quad(v2, v3, v4, v1) - 2 -> Quad(v3, v4, v1, v2) - 3 -> Quad(v4, v1, v2, v3) - else -> this.copy() - } -} - -/** - * Model. The basic unit of rendering blocks with OctarineCore. - * - * The model should be positioned so that (0,0,0) is the block center. - * The block extends to (-0.5, 0.5) in all directions (inclusive). - */ -class Model() { - constructor(other: List) : this() { quads.addAll(other) } - val quads = mutableListOf() - - fun Quad.add() = quads.add(this) - fun Iterable.addAll() = forEach { quads.add(it) } - - fun transformQ(trans: (Quad)->Quad) = quads.replace(trans) - fun transformV(trans: (Vertex)->Vertex) = quads.replace{ it.transformV(trans) } - - fun verticalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, yBottom: Double, yTop: Double) = Quad( - Vertex(Double3(x1, yBottom, z1), UV.bottomLeft), - Vertex(Double3(x2, yBottom, z2), UV.bottomRight), - Vertex(Double3(x2, yTop, z2), UV.topRight), - Vertex(Double3(x1, yTop, z1), UV.topLeft) - ) - - fun horizontalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, y: Double): Quad { - val xMin = min(x1, x2); val xMax = max(x1, x2) - val zMin = min(z1, z2); val zMax = max(z1, z2) - return Quad( - Vertex(Double3(xMin, y, zMin), UV.topLeft), - Vertex(Double3(xMin, y, zMax), UV.bottomLeft), - Vertex(Double3(xMax, y, zMax), UV.bottomRight), - Vertex(Double3(xMax, y, zMin), UV.topRight) - ) - } - - fun faceQuad(face: Direction): Quad { - val base = face.vec * 0.5 - val top = boxFaces[face].top * 0.5 - val left = boxFaces[face].left * 0.5 - return Quad( - Vertex(base + top + left, UV.topLeft), - Vertex(base - top + left, UV.bottomLeft), - Vertex(base - top - left, UV.bottomRight), - Vertex(base + top - left, UV.topRight) - ) - } -} - -val fullCube = Model().apply { - allDirections.forEach { - faceQuad(it) - .setAoShader(faceOrientedAuto(corner = cornerAo(it.axis), edge = null)) - .setFlatShader(faceOrientedAuto(corner = cornerFlat, edge = null)) - .add() - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/RenderDecorator.kt b/src/main/kotlin/mods/octarinecore/client/render/RenderDecorator.kt deleted file mode 100644 index 15ac383..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/RenderDecorator.kt +++ /dev/null @@ -1,45 +0,0 @@ -@file:JvmName("RendererHolder") -package mods.octarinecore.client.render - -import mods.betterfoliage.client.render.canRenderInCutout -import mods.betterfoliage.client.render.isCutout -import mods.octarinecore.ThreadLocalDelegate -import mods.octarinecore.client.resource.ResourceHandler -import mods.octarinecore.common.Double3 -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.allDirOffsets -import mods.octarinecore.common.plus -import mods.octarinecore.semiRandom -import net.minecraft.block.Block -import net.minecraft.block.BlockState -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.client.renderer.color.BlockColors -import net.minecraft.util.BlockRenderLayer -import net.minecraft.util.Direction -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.MathHelper -import net.minecraft.world.IEnviromentBlockReader -import net.minecraft.world.biome.Biome -import net.minecraftforge.client.model.data.IModelData -import net.minecraftforge.eventbus.api.IEventBus -import java.util.* -import kotlin.math.abs - -abstract class RenderDecorator(modId: String, modBus: IEventBus) : ResourceHandler(modId, modBus) { - - open val renderOnCutout: Boolean get() = true - open val onlyOnCutout: Boolean get() = false - - // ============================ - // Custom rendering - // ============================ - abstract fun isEligible(ctx: CombinedContext): Boolean - abstract fun render(ctx: CombinedContext) - -} - -data class BlockData(val state: BlockState, val color: Int, val brightness: Int) - - diff --git a/src/main/kotlin/mods/octarinecore/client/render/lighting/Lighting.kt b/src/main/kotlin/mods/octarinecore/client/render/lighting/Lighting.kt deleted file mode 100644 index 5b39066..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/lighting/Lighting.kt +++ /dev/null @@ -1,146 +0,0 @@ -package mods.octarinecore.client.render.lighting - -import mods.octarinecore.client.render.* -import mods.octarinecore.common.* -import net.minecraft.util.Direction -import net.minecraft.util.Direction.* -import java.lang.Math.min - -typealias EdgeShaderFactory = (Direction, Direction) -> ModelLighter -typealias CornerShaderFactory = (Direction, Direction, Direction) -> ModelLighter -typealias ShaderFactory = (Quad, Vertex) -> ModelLighter - -/** Holds lighting values for block corners as calculated by vanilla Minecraft rendering. */ -class CornerLightData { - var valid = false - var brightness = 0 - var red: Float = 0.0f - var green: Float = 0.0f - var blue: Float = 0.0f - - fun reset() { valid = false } - - fun set(brightness: Int, red: Float, green: Float, blue: Float) { - if (valid) return - this.valid = true - this.brightness = brightness - this.red = red - this.green = green - this.blue = blue - } - - fun set(brightness: Int, colorMultiplier: Float) { - this.valid = true - this.brightness = brightness - this.red = colorMultiplier - this.green = colorMultiplier - this.blue = colorMultiplier - } - - companion object { - val black: CornerLightData get() = CornerLightData() - } -} - -/** - * Instances of this interface are associated with [Model] vertices, and used to apply brightness and color - * values to a [RenderVertex]. - */ -interface ModelLighter { - /** - * Set shading values of a [RenderVertex] - * - * @param[context] context that can be queried for lighting data in a [Model]-relative frame of reference - * @param[vertex] the [RenderVertex] to manipulate - */ - fun shade(context: LightingCtx, vertex: RenderVertex) - - /** - * Return a new rotated version of this [ModelLighter]. Used during [Model] setup when rotating the model itself. - */ - fun rotate(rot: Rotation): ModelLighter - - /** Set all lighting values on the [RenderVertex] to match the given [CornerLightData]. */ - fun RenderVertex.shade(shading: CornerLightData) { - brightness = shading.brightness; red = shading.red; green = shading.green; blue = shading.blue - } - - /** Set the lighting values on the [RenderVertex] to a weighted average of the two [CornerLightData] instances. */ - fun RenderVertex.shade(shading1: CornerLightData, shading2: CornerLightData, weight1: Float = 0.5f, weight2: Float = 0.5f) { - red = min(shading1.red * weight1 + shading2.red * weight2, 1.0f) - green = min(shading1.green * weight1 + shading2.green * weight2, 1.0f) - blue = min(shading1.blue * weight1 + shading2.blue * weight2, 1.0f) - brightness = brWeighted(shading1.brightness, weight1, shading2.brightness, weight2) - } - - /** - * Set the lighting values on the [RenderVertex] directly. - * - * @param[brightness] packed brightness value - * @param[color] packed color value - */ - fun RenderVertex.shade(brightness: Int, color: Int) { - this.brightness = brightness; setColor(color) - } -} - -/** - * Returns a [ModelLighter] resolver for quads that point towards one of the 6 block faces. - * The resolver works the following way: - * - determines which face the _quad_ normal points towards (if not overridden) - * - determines the distance of the _vertex_ to the corners and edge midpoints on that block face - * - if _corner_ is given, and the _vertex_ is closest to a block corner, returns the [ModelLighter] created by _corner_ - * - if _edge_ is given, and the _vertex_ is closest to an edge midpoint, returns the [ModelLighter] created by _edge_ - * - * @param[overrideFace] assume the given face instead of going by the _quad_ normal - * @param[corner] [ModelLighter] instantiation lambda for corner vertices - * @param[edge] [ModelLighter] instantiation lambda for edge midpoint vertices - */ -fun faceOrientedAuto(overrideFace: Direction? = null, - corner: CornerShaderFactory? = null, - edge: EdgeShaderFactory? = null) = - fun(quad: Quad, vertex: Vertex): ModelLighter { - val quadFace = overrideFace ?: quad.normal.nearestCardinal - val nearestCorner = nearestPosition(vertex.xyz, boxFaces[quadFace].allCorners) { - (quadFace.vec + it.first.vec + it.second.vec) * 0.5 - } - val nearestEdge = nearestPosition(vertex.xyz, quadFace.perpendiculars) { - (quadFace.vec + it.vec) * 0.5 - } - if (edge != null && (nearestEdge.second < nearestCorner.second || corner == null)) - return edge(quadFace, nearestEdge.first) - else return corner!!(quadFace, nearestCorner.first.first, nearestCorner.first.second) - } - -/** - * Returns a ModelLighter resolver for quads that point towards one of the 12 block edges. - * The resolver works the following way: - * - determines which edge the _quad_ normal points towards (if not overridden) - * - determines which face midpoint the _vertex_ is closest to, of the 2 block faces that share this edge - * - determines which block corner _of this face_ the _vertex_ is closest to - * - returns the [ModelLighter] created by _corner_ - * - * @param[overrideEdge] assume the given edge instead of going by the _quad_ normal - * @param[corner] ModelLighter instantiation lambda - */ -fun edgeOrientedAuto(overrideEdge: Pair? = null, - corner: CornerShaderFactory) = - fun(quad: Quad, vertex: Vertex): ModelLighter { - val edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first - val nearestFace = nearestPosition(vertex.xyz, edgeDir.toList()) { it.vec }.first - val nearestCorner = nearestPosition(vertex.xyz, boxFaces[nearestFace].allCorners) { - (nearestFace.vec + it.first.vec + it.second.vec) * 0.5 - }.first - return corner(nearestFace, nearestCorner.first, nearestCorner.second) - } - -fun faceOrientedInterpolate(overrideFace: Direction? = null) = - fun(quad: Quad, vertex: Vertex): ModelLighter { - val resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir -> - val axis = axes.find { it != face.axis && it != edgeDir.axis }!! - val vec = Double3((axis to AxisDirection.POSITIVE).face) - val pos = vertex.xyz.dot(vec) - EdgeInterpolateFallback(face, edgeDir, pos) - }) - return resolver(quad, vertex) - } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/lighting/LightingContext.kt b/src/main/kotlin/mods/octarinecore/client/render/lighting/LightingContext.kt deleted file mode 100644 index d253d41..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/lighting/LightingContext.kt +++ /dev/null @@ -1,111 +0,0 @@ -package mods.octarinecore.client.render.lighting - -import mods.octarinecore.client.render.BlockCtx -import mods.octarinecore.common.* -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.BlockModelRenderer -import net.minecraft.util.Direction -import net.minecraft.world.IEnviromentBlockReader -import java.util.* - -val Direction.aoMultiplier: Float get() = when(this) { - Direction.UP -> 1.0f - Direction.DOWN -> 0.5f - Direction.NORTH, Direction.SOUTH -> 0.8f - Direction.EAST, Direction.WEST -> 0.6f -} - -interface LightingCtx { - val modelRotation: Rotation - val blockContext: BlockCtx - val aoEnabled: Boolean - - val brightness get() = brightness(Int3.zero) - val color get() = color(Int3.zero) - fun brightness(face: Direction) = brightness(face.offset) - fun color(face: Direction) = color(face.offset) - - fun brightness(offset: Int3) = offset.rotate(modelRotation).let { - blockContext.state(it).getPackedLightmapCoords(blockContext.world, blockContext.pos + it) - } - fun color(offset: Int3) = blockContext.offset(offset.rotate(modelRotation)).let { Minecraft.getInstance().blockColors.getColor(it.state, it.world, it.pos, 0) } - - fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData -} - -class DefaultLightingCtx(blockContext: BlockCtx) : LightingCtx { - override var modelRotation = Rotation.identity - - override var aoEnabled = false - protected set - override var blockContext: BlockCtx = blockContext - protected set - override var brightness = brightness(Int3.zero) - protected set - override var color = color(Int3.zero) - protected set - - override fun brightness(face: Direction) = brightness(face.offset) - override fun color(face: Direction) = color(face.offset) - - // smooth lighting stuff - val lightingData = Array(6) { FaceLightData(allDirections[it]) } - override fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData = lightingData[face.rotate(modelRotation)].let { faceData -> - if (!faceData.isValid) faceData.update(blockContext, faceData.face.aoMultiplier) - return faceData[corner1.rotate(modelRotation), corner2.rotate(modelRotation)] - } - - fun reset(blockContext: BlockCtx) { - this.blockContext = blockContext - brightness = brightness(Int3.zero) - color = color(Int3.zero) - modelRotation = Rotation.identity - lightingData.forEach { it.isValid = false } - aoEnabled = Minecraft.isAmbientOcclusionEnabled() -// allDirections.forEach { lightingData[it].update(blockContext, it.aoMultiplier) } - } -} - -private val vanillaAOFactory = BlockModelRenderer.AmbientOcclusionFace::class.java.let { - it.getDeclaredConstructor(BlockModelRenderer::class.java).apply { isAccessible = true } -}.let { ctor -> { ctor.newInstance(Minecraft.getInstance().blockRendererDispatcher.blockModelRenderer) } } - -class FaceLightData(val face: Direction) { - val topDir = boxFaces[face].top - val leftDir = boxFaces[face].left - - val topLeft = CornerLightData() - val topRight = CornerLightData() - val bottomLeft = CornerLightData() - val bottomRight = CornerLightData() - - val vanillaOrdered = when(face) { - Direction.DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight) - Direction.UP -> listOf(bottomRight, topRight, topLeft, bottomLeft) - Direction.NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft) - Direction.SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight) - Direction.WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft) - Direction.EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight) - } - - val delegate = vanillaAOFactory() - var isValid = false - - fun update(blockCtx: BlockCtx, multiplier: Float) { - val quadBounds = FloatArray(12) - val flags = BitSet(3).apply { set(0) } - delegate.updateVertexBrightness(blockCtx.world, blockCtx.state, blockCtx.pos, face, quadBounds, flags) - vanillaOrdered.forEachIndexed { idx, corner -> corner.set(delegate.vertexBrightness[idx], delegate.vertexColorMultiplier[idx] * multiplier) } - isValid = true - } - - operator fun get(dir1: Direction, dir2: Direction): CornerLightData { - val isTop = topDir == dir1 || topDir == dir2 - val isLeft = leftDir == dir1 || leftDir == dir2 - return if (isTop) { - if (isLeft) topLeft else topRight - } else { - if (isLeft) bottomLeft else bottomRight - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/lighting/ModelLighters.kt b/src/main/kotlin/mods/octarinecore/client/render/lighting/ModelLighters.kt deleted file mode 100644 index 2cfa656..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/lighting/ModelLighters.kt +++ /dev/null @@ -1,152 +0,0 @@ -package mods.octarinecore.client.render.lighting - -import mods.octarinecore.common.* -import net.minecraft.util.Direction - - -const val defaultCornerDimming = 0.5f -const val defaultEdgeDimming = 0.8f - -// ================================ -// Shader instantiation lambdas -// ================================ -fun cornerAo(fallbackAxis: Direction.Axis): CornerShaderFactory = { face, dir1, dir2 -> - val fallbackDir = listOf(face, dir1, dir2).find { it.axis == fallbackAxis }!! - CornerSingleFallback(face, dir1, dir2, fallbackDir) -} -val cornerFlat = { face: Direction, dir1: Direction, dir2: Direction -> FaceFlat(face) } -fun cornerAoTri(func: (CornerLightData, CornerLightData)-> CornerLightData) = { face: Direction, dir1: Direction, dir2: Direction -> - CornerTri(face, dir1, dir2, func) -} -val cornerAoMaxGreen = cornerAoTri { s1, s2 -> if (s1.green > s2.green) s1 else s2 } - -fun cornerInterpolate(edgeAxis: Direction.Axis, weight: Float, dimming: Float): CornerShaderFactory = { dir1, dir2, dir3 -> - val edgeDir = listOf(dir1, dir2, dir3).find { it.axis == edgeAxis }!! - val faceDirs = listOf(dir1, dir2, dir3).filter { it.axis != edgeAxis } - CornerInterpolateDimming(faceDirs[0], faceDirs[1], edgeDir, weight, dimming) -} - -// ================================ -// Shaders -// ================================ -object NoLighting : ModelLighter { - override fun shade(context: LightingCtx, vertex: RenderVertex) = vertex.shade(CornerLightData.black) - override fun rotate(rot: Rotation) = this -} - -class CornerSingleFallback(val face: Direction, val dir1: Direction, val dir2: Direction, val fallbackDir: Direction, val fallbackDimming: Float = defaultCornerDimming) : ModelLighter { - val offset = Int3(fallbackDir) - override fun shade(context: LightingCtx, vertex: RenderVertex) { - val shading = context.lighting(face, dir1, dir2) - if (shading.valid) - vertex.shade(shading) - else { - vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming) - } - } - override fun rotate(rot: Rotation) = CornerSingleFallback(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), fallbackDir.rotate(rot), fallbackDimming) -} - -inline fun accumulate(v1: CornerLightData?, v2: CornerLightData?, func: ((CornerLightData, CornerLightData)-> CornerLightData)): CornerLightData? { - val v1ok = v1 != null && v1.valid - val v2ok = v2 != null && v2.valid - if (v1ok && v2ok) return func(v1!!, v2!!) - if (v1ok) return v1 - if (v2ok) return v2 - return null -} - -class CornerTri(val face: Direction, val dir1: Direction, val dir2: Direction, - val func: ((CornerLightData, CornerLightData)-> CornerLightData)) : ModelLighter { - override fun shade(context: LightingCtx, vertex: RenderVertex) { - var acc = accumulate( - context.lighting(face, dir1, dir2), - context.lighting(dir1, face, dir2), - func) - acc = accumulate( - acc, - context.lighting(dir2, face, dir1), - func) - vertex.shade(acc ?: CornerLightData.black) - } - override fun rotate(rot: Rotation) = CornerTri(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), func) -} - -class EdgeInterpolateFallback(val face: Direction, val edgeDir: Direction, val pos: Double, val fallbackDimming: Float = defaultEdgeDimming): ModelLighter { - val offset = Int3(edgeDir) - val edgeAxis = axes.find { it != face.axis && it != edgeDir.axis }!! - val weightN = (0.5 - pos).toFloat() - val weightP = (0.5 + pos).toFloat() - - override fun shade(context: LightingCtx, vertex: RenderVertex) { - val shadingP = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.POSITIVE).face) - val shadingN = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.NEGATIVE).face) - if (!shadingP.valid && !shadingN.valid) { - return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming) - } - if (!shadingP.valid) return vertex.shade(shadingN) - if (!shadingN.valid) return vertex.shade(shadingP) - vertex.shade(shadingP, shadingN, weightP, weightN) - } - override fun rotate(rot: Rotation) = EdgeInterpolateFallback(face.rotate(rot), edgeDir.rotate(rot), pos) -} - -class CornerInterpolateDimming(val face1: Direction, val face2: Direction, val edgeDir: Direction, - val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming) : ModelLighter { - val offset = Int3(edgeDir) - override fun shade(context: LightingCtx, vertex: RenderVertex) { - var shading1 = context.lighting(face1, edgeDir, face2) - var shading2 = context.lighting(face2, edgeDir, face1) - var weight1 = weight - var weight2 = 1.0f - weight - if (!shading1.valid && !shading2.valid) { - return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming) - } - if (!shading1.valid) { shading1 = shading2; weight1 *= dimming } - if (!shading2.valid) { shading2 = shading1; weight2 *= dimming } - vertex.shade(shading1, shading2, weight1, weight2) - } - - override fun rotate(rot: Rotation) = - CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming) -} - -class FaceCenter(val face: Direction): ModelLighter { - override fun shade(context: LightingCtx, vertex: RenderVertex) { - vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f; - val b = IntArray(4) - boxFaces[face].allCorners.forEachIndexed { idx, corner -> - val shading = context.lighting(face, corner.first, corner.second) - vertex.red += shading.red - vertex.green += shading.green - vertex.blue += shading.blue - b[idx] = shading.brightness - } - vertex.apply { red *= 0.25f; green *= 0.25f; blue *= 0.25f } - vertex.brightness = brSum(0.25f, *b) - } - override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot)) -} - -class FaceFlat(val face: Direction): ModelLighter { - override fun shade(context: LightingCtx, vertex: RenderVertex) { - vertex.shade(context.brightness(face.offset), context.color(Int3.zero)) - } - override fun rotate(rot: Rotation): ModelLighter = FaceFlat(face.rotate(rot)) -} - -class FlatOffset(val offset: Int3): ModelLighter { - override fun shade(context: LightingCtx, vertex: RenderVertex) { - vertex.brightness = context.brightness(offset) - vertex.setColor(context.color(offset)) - } - override fun rotate(rot: Rotation): ModelLighter = this -} - -class FlatOffsetNoColor(val offset: Int3): ModelLighter { - override fun shade(context: LightingCtx, vertex: RenderVertex) { - vertex.brightness = context.brightness(offset) - vertex.red = 1.0f; vertex.green = 1.0f; vertex.blue = 1.0f - } - override fun rotate(rot: Rotation): ModelLighter = this -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/lighting/PixelFormat.kt b/src/main/kotlin/mods/octarinecore/client/render/lighting/PixelFormat.kt deleted file mode 100644 index 9168075..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/lighting/PixelFormat.kt +++ /dev/null @@ -1,5 +0,0 @@ -@file:JvmName("PixelFormat") -package mods.octarinecore.client.render.lighting - -import java.awt.Color - diff --git a/src/main/kotlin/mods/octarinecore/client/render/lighting/Vertex.kt b/src/main/kotlin/mods/octarinecore/client/render/lighting/Vertex.kt deleted file mode 100644 index f69023a..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/lighting/Vertex.kt +++ /dev/null @@ -1,146 +0,0 @@ -package mods.octarinecore.client.render.lighting - -import mods.octarinecore.client.render.CombinedContext -import mods.octarinecore.client.render.Quad -import mods.octarinecore.client.render.Vertex -import mods.octarinecore.common.Double3 -import mods.octarinecore.common.Rotation -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.util.Direction.* -import java.awt.Color - -typealias QuadIconResolver = (CombinedContext, Int, Quad) -> TextureAtlasSprite? -typealias PostProcessLambda = RenderVertex.(CombinedContext, Int, Quad, Int, Vertex) -> Unit - -@Suppress("NOTHING_TO_INLINE") -class RenderVertex { - var x: Double = 0.0 - var y: Double = 0.0 - var z: Double = 0.0 - var u: Double = 0.0 - var v: Double = 0.0 - var brightness: Int = 0 - var red: Float = 0.0f - var green: Float = 0.0f - var blue: Float = 0.0f - - val rawData = IntArray(7) - - fun init(vertex: Vertex, rot: Rotation, trans: Double3): RenderVertex { - val result = vertex.xyz.rotate(rot) + trans - x = result.x; y = result.y; z = result.z - return this - } - fun init(vertex: Vertex): RenderVertex { - x = vertex.xyz.x; y = vertex.xyz.y; z = vertex.xyz.z; - u = vertex.uv.u; v = vertex.uv.v - return this - } - fun translate(trans: Double3): RenderVertex { x += trans.x; y += trans.y; z += trans.z; return this } - fun rotate(rot: Rotation): RenderVertex { - if (rot === Rotation.identity) return this - val rotX = rot.rotatedComponent(EAST, x, y, z) - val rotY = rot.rotatedComponent(UP, x, y, z) - val rotZ = rot.rotatedComponent(SOUTH, x, y, z) - x = rotX; y = rotY; z = rotZ - return this - } - inline fun rotateUV(n: Int): RenderVertex { - when (n % 4) { - 1 -> { val t = v; v = -u; u = t; return this } - 2 -> { u = -u; v = -v; return this } - 3 -> { val t = -v; v = u; u = t; return this } - else -> { return this } - } - } - inline fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) { - if (mirrorU) u = -u - if (mirrorV) v = -v - } - inline fun setIcon(icon: TextureAtlasSprite): RenderVertex { - u = (icon.maxU - icon.minU) * (u + 0.5) + icon.minU - v = (icon.maxV - icon.minV) * (v + 0.5) + icon.minV - return this - } - - inline fun setGrey(level: Float) { - val grey = Math.min((red + green + blue) * 0.333f * level, 1.0f) - red = grey; green = grey; blue = grey - } - inline fun multiplyColor(color: Int) { - red *= (color shr 16 and 255) / 256.0f - green *= (color shr 8 and 255) / 256.0f - blue *= (color and 255) / 256.0f - } - inline fun setColor(color: Int) { - red = (color shr 16 and 255) / 256.0f - green = (color shr 8 and 255) / 256.0f - blue = (color and 255) / 256.0f - } - -} - -/** List of bit-shift offsets in packed brightness values where meaningful (4-bit) data is contained. */ -var brightnessComponents = listOf(20, 4) - -/** Multiply the components of this packed brightness value with the given [Float]. */ -infix fun Int.brMul(f: Float): Int { - val weight = (f * 256.0f).toInt() - var result = 0 - brightnessComponents.forEach { shift -> - val raw = (this shr shift) and 15 - val weighted = (raw) * weight / 256 - result = result or (weighted shl shift) - } - return result -} - -/** Multiply the components of this packed color value with the given [Float]. */ -infix fun Int.colorMul(f: Float): Int { - val weight = (f * 256.0f).toInt() - val red = (this shr 16 and 255) * weight / 256 - val green = (this shr 8 and 255) * weight / 256 - val blue = (this and 255) * weight / 256 - return (red shl 16) or (green shl 8) or blue -} - -/** Sum the components of all packed brightness values given. */ -fun brSum(multiplier: Float?, vararg brightness: Int): Int { - val sum = Array(brightnessComponents.size) { 0 } - brightnessComponents.forEachIndexed { idx, shift -> brightness.forEach { br -> - val comp = (br shr shift) and 15 - sum[idx] += comp - } } - var result = 0 - brightnessComponents.forEachIndexed { idx, shift -> - val comp = if (multiplier == null) - ((sum[idx]) shl shift) - else - ((sum[idx].toFloat() * multiplier).toInt() shl shift) - result = result or comp - } - return result -} - -fun brWeighted(br1: Int, weight1: Float, br2: Int, weight2: Float): Int { - val w1int = (weight1 * 256.0f + 0.5f).toInt() - val w2int = (weight2 * 256.0f + 0.5f).toInt() - var result = 0 - brightnessComponents.forEachIndexed { idx, shift -> - val comp1 = (br1 shr shift) and 15 - val comp2 = (br2 shr shift) and 15 - val compWeighted = (comp1 * w1int + comp2 * w2int) / 256 - result = result or ((compWeighted and 15) shl shift) - } - return result -} - -data class HSB(var hue: Float, var saturation: Float, var brightness: Float) { - companion object { - fun fromColor(color: Int): HSB { - val hsbVals = Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null) - return HSB(hsbVals[0], hsbVals[1], hsbVals[2]) - } - } - val asColor: Int get() = Color.HSBtoRGB(hue, saturation, brightness) -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/resource/AsyncSpriteProviderManager.kt b/src/main/kotlin/mods/octarinecore/client/resource/AsyncSpriteProviderManager.kt deleted file mode 100644 index 9f920ef..0000000 --- a/src/main/kotlin/mods/octarinecore/client/resource/AsyncSpriteProviderManager.kt +++ /dev/null @@ -1,87 +0,0 @@ -package mods.octarinecore.client.resource - -import mods.betterfoliage.client.resource.Identifier -import mods.betterfoliage.client.resource.Sprite -import mods.octarinecore.common.map -import net.minecraft.client.renderer.texture.AtlasTexture -import net.minecraft.client.renderer.texture.MissingTextureSprite -import net.minecraft.profiler.IProfiler -import net.minecraft.resources.IResourceManager -import java.util.* -import java.util.concurrent.CompletableFuture - -/** - * Main entry point to atlas manipulation. Called from mixins that wrap [AtlasTexture.stitch] calls. - * - * 1. All registered providers receive an [AsyncSpriteProvider.setup] call. Providers can set up their - * processing chain at this point, but should not do anything yet except configuration and housekeeping. - * 2. The [CompletableFuture] of the stitch source finishes, starting the "discovery" phase. Providers - * may register sprites in the [AtlasFuture]. - * 3. After all providers finish their discovery, the atlas is stitched. - * 4. The [AtlasFuture] finishes, starting the "cleanup" phase. All [AtlasFuture.runAfter] and - * [AtlasFuture.mapAfter] tasks are processed. - * 5. After all providers finish their cleanup, we return to the original code path. - */ -class AsnycSpriteProviderManager(val profilerSection: String) { - - val providers = mutableListOf>() - - /** - * Needed in order to keep the actual [AtlasTexture.stitch] call in the original method, in case - * other modders want to modify it too. - */ - class StitchWrapper(val idList: Iterable, val onComplete: (AtlasTexture.SheetData)->Unit) { - fun complete(sheet: AtlasTexture.SheetData) = onComplete(sheet) - } - - @Suppress("UNCHECKED_CAST") - fun prepare(sourceObj: Any, atlas: AtlasTexture, manager: IResourceManager, idList: Iterable, profiler: IProfiler): StitchWrapper { - profiler.startSection(profilerSection) - - val source = CompletableFuture() - val atlasFuture = AtlasFuture(idList) - - val phases = providers.map { it.setup(manager, source, atlasFuture) } - source.complete(sourceObj as SOURCE) - phases.forEach { it.discovery.get() } - - return StitchWrapper(atlasFuture.idSet) { sheet -> - atlasFuture.complete(sheet) - phases.forEach { it.cleanup.get() } - profiler.endSection() - } - } -} - -/** - * Provides a way for [AsyncSpriteProvider]s to register sprites to receive [CompletableFuture]s. - * Tracks sprite ids that need to be stitched. - */ -class AtlasFuture(initial: Iterable) { - val idSet = Collections.synchronizedSet(mutableSetOf().apply { addAll(initial) }) - protected val sheet = CompletableFuture() - protected val finished = CompletableFuture() - - fun complete(sheetData: AtlasTexture.SheetData) { - sheet.complete(sheetData) - finished.complete(null) - } - - fun sprite(id: String) = sprite(Identifier(id)) - fun sprite(id: Identifier): CompletableFuture { - idSet.add(id) - return sheet.map { sheetData -> sheetData.sprites.find { it.name == id } ?: throw IllegalStateException("Atlas does not contain $id") } - } - val missing = sheet.map { sheetData -> sheetData.sprites.find { it.name == MissingTextureSprite.getLocation() } } - fun mapAfter(supplier: ()->T): CompletableFuture = finished.map{ supplier() } - fun runAfter(action: ()->Unit): CompletableFuture = finished.thenRun(action) -} - -class StitchPhases( - val discovery: CompletableFuture, - val cleanup: CompletableFuture -) - -interface AsyncSpriteProvider { - fun setup(manager: IResourceManager, source: CompletableFuture, atlas: AtlasFuture): StitchPhases -} diff --git a/src/main/kotlin/mods/octarinecore/client/resource/ModelDiscovery.kt b/src/main/kotlin/mods/octarinecore/client/resource/ModelDiscovery.kt deleted file mode 100644 index 6e7f613..0000000 --- a/src/main/kotlin/mods/octarinecore/client/resource/ModelDiscovery.kt +++ /dev/null @@ -1,132 +0,0 @@ -package mods.octarinecore.client.resource - -import com.google.common.base.Joiner -import mods.betterfoliage.client.resource.ModelIdentifier -import mods.octarinecore.HasLogger -import mods.octarinecore.client.render.BlockCtx -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.config.IBlockMatcher -import mods.octarinecore.common.config.ModelTextureList -import mods.octarinecore.common.plus -import mods.octarinecore.findFirst -import mods.octarinecore.common.sinkAsync -import net.minecraft.block.BlockState -import net.minecraft.client.renderer.BlockModelShapes -import net.minecraft.client.renderer.model.BlockModel -import net.minecraft.client.renderer.model.IUnbakedModel -import net.minecraft.client.renderer.model.ModelBakery -import net.minecraft.client.renderer.model.VariantList -import net.minecraft.resources.IResourceManager -import net.minecraft.util.ResourceLocation -import net.minecraft.util.math.BlockPos -import net.minecraft.world.IBlockReader -import net.minecraftforge.registries.ForgeRegistries -import java.util.concurrent.CompletableFuture - -interface ModelRenderRegistry { - operator fun get(ctx: BlockCtx) = get(ctx.state, ctx.world, ctx.pos) - operator fun get(ctx: BlockCtx, offset: Int3) = get(ctx.state(offset), ctx.world, ctx.pos + offset) - operator fun get(state: BlockState, world: IBlockReader, pos: BlockPos): T? -} - -abstract class ModelRenderRegistryRoot : ModelRenderRegistry { - val registries = mutableListOf>() - override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = registries.findFirst { it[state, world, pos] } -} - -/** - * Information about a single BlockState and all the IUnbakedModel it could render as. - */ -class ModelDiscoveryContext( - bakery: ModelBakery, - val state: BlockState, - val modelId: ModelIdentifier -) { - val models = bakery.unwrapVariants(bakery.getUnbakedModel(modelId) to modelId) - .filter { it.second != bakery.getUnbakedModel(ModelBakery.MODEL_MISSING) } - - fun ModelBakery.unwrapVariants(modelAndLoc: Pair): List> = when(val model = modelAndLoc.first) { - is VariantList -> model.variantList.flatMap { variant -> unwrapVariants(getUnbakedModel(variant.modelLocation) to variant.modelLocation) } - is BlockModel -> listOf(modelAndLoc) - else -> emptyList() - } -} - -abstract class ModelDiscovery : HasLogger, AsyncSpriteProvider, ModelRenderRegistry { - - var modelData: Map = emptyMap() - protected set - - override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = modelData[state] - - abstract fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? - - override fun setup(manager: IResourceManager, bakeryF: CompletableFuture, atlas: AtlasFuture): StitchPhases { - val modelDataTemp = mutableMapOf>() - - return StitchPhases( - discovery = bakeryF.sinkAsync { bakery -> - var errors = 0 - bakery.iterateModels { ctx -> - try { - processModel(ctx, atlas)?.let { modelDataTemp[ctx.state] = it } - } catch (e: Exception) { - errors++ - } - } - log("${modelDataTemp.size} BlockStates discovered, $errors errors") - }, - cleanup = atlas.runAfter { - modelData = modelDataTemp.filterValues { !it.isCompletedExceptionally }.mapValues { it.value.get() } - val errors = modelDataTemp.values.filter { it.isCompletedExceptionally }.size - log("${modelData.size} BlockStates loaded, $errors errors") - } - ) - } - - fun ModelBakery.iterateModels(func: (ModelDiscoveryContext)->Unit) { - ForgeRegistries.BLOCKS.flatMap { block -> - block.stateContainer.validStates.map { state -> state to BlockModelShapes.getModelLocation(state) } - }.forEach { (state, stateModelResource) -> - func(ModelDiscoveryContext(this, state, stateModelResource)) - } - } -} - -abstract class ConfigurableModelDiscovery : ModelDiscovery() { - - abstract val matchClasses: IBlockMatcher - abstract val modelTextures: List - - abstract fun processModel(state: BlockState, textures: List, atlas: AtlasFuture): CompletableFuture? - - override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? { - val matchClass = matchClasses.matchingClass(ctx.state.block) ?: return null - log("block state ${ctx.state.toString()}") - log(" class ${ctx.state.block.javaClass.name} matches ${matchClass.name}") - - if (ctx.models.isEmpty()) { - log(" no models found") - return null - } - - ctx.models.filter { it.first is BlockModel }.forEach { (model, location) -> - model as BlockModel - val modelMatch = modelTextures.firstOrNull { (model to location).derivesFrom(it.modelLocation) } - if (modelMatch != null) { - log(" model ${model} matches ${modelMatch.modelLocation}") - - val textures = modelMatch.textureNames.map { it to model.resolveTextureName(it) } - val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" }) - log(" sprites [$texMapString]") - - if (textures.all { it.second != "missingno" }) { - // found a valid model (all required textures exist) - return processModel(ctx.state, textures.map { it.second}, atlas) - } - } - } - return null - } - -} diff --git a/src/main/kotlin/mods/octarinecore/client/resource/ResourceGeneration.kt b/src/main/kotlin/mods/octarinecore/client/resource/ResourceGeneration.kt deleted file mode 100644 index 43f8c4b..0000000 --- a/src/main/kotlin/mods/octarinecore/client/resource/ResourceGeneration.kt +++ /dev/null @@ -1,85 +0,0 @@ -package mods.octarinecore.client.resource - -import mods.betterfoliage.client.resource.Identifier -import mods.octarinecore.HasLogger -import mods.octarinecore.common.completedVoid -import mods.octarinecore.common.map -import net.minecraft.client.renderer.model.ModelBakery -import net.minecraft.client.resources.ClientResourcePackInfo -import net.minecraft.resources.* -import net.minecraft.resources.ResourcePackType.CLIENT_RESOURCES -import net.minecraft.resources.data.IMetadataSectionSerializer -import net.minecraft.util.text.StringTextComponent -import org.apache.logging.log4j.Logger -import java.io.IOException -import java.lang.IllegalStateException -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ExecutionException -import java.util.function.Predicate -import java.util.function.Supplier - -/** - * [IResourcePack] containing generated resources - * - * @param[name] Name of the resource pack - * @param[generators] List of resource generators - */ -class GeneratedBlockTexturePack(val nameSpace: String, val packName: String, override val logger: Logger) : HasLogger, IResourcePack, AsyncSpriteProvider { - - override fun getName() = packName - override fun getResourceNamespaces(type: ResourcePackType) = setOf(nameSpace) - override fun getMetadata(deserializer: IMetadataSectionSerializer) = null - override fun getRootResourceStream(id: String) = null - override fun getAllResourceLocations(type: ResourcePackType, path: String, maxDepth: Int, filter: Predicate) = emptyList() - override fun close() {} - - protected var manager: CompletableFuture? = null - val identifiers = Collections.synchronizedMap(mutableMapOf()) - val resources = Collections.synchronizedMap(mutableMapOf>()) - - fun register(key: Any, func: (IResourceManager)->ByteArray): Identifier { - if (manager == null) throw IllegalStateException("Cannot register resources unless block textures are being reloaded") - identifiers[key]?.let { return it } - - val id = Identifier(nameSpace, UUID.randomUUID().toString()) - val resource = manager!!.map { func(it) } - - identifiers[key] = id - resources[Atlas.BLOCKS.wrap(id)] = resource - log("generated resource $key -> $id") - return id - } - - override fun getResourceStream(type: ResourcePackType, id: Identifier) = - if (type != CLIENT_RESOURCES) null else - try { resources[id]!!.get().inputStream() } - catch (e: ExecutionException) { (e.cause as? IOException)?.let { throw it } } // rethrow wrapped IOException if present - - override fun resourceExists(type: ResourcePackType, id: Identifier) = - type == CLIENT_RESOURCES && resources.containsKey(id) - - override fun setup(manager: IResourceManager, bakeryF: CompletableFuture, atlas: AtlasFuture): StitchPhases { - this.manager = CompletableFuture.completedFuture(manager) - return StitchPhases( - completedVoid(), - atlas.runAfter { - this.manager = null - identifiers.clear() - resources.clear() - } - ) - } - - val finder = object : IPackFinder { - val packInfo = ClientResourcePackInfo( - packName, true, Supplier { this@GeneratedBlockTexturePack }, - StringTextComponent(packName), - StringTextComponent("Generated block textures resource pack"), - PackCompatibility.COMPATIBLE, ResourcePackInfo.Priority.TOP, true, null, true - ) - override fun addPackInfosToMap(nameToPackMap: MutableMap, packInfoFactory: ResourcePackInfo.IFactory) { - (nameToPackMap as MutableMap).put(packName, packInfo) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt b/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt deleted file mode 100644 index 5ecb390..0000000 --- a/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt +++ /dev/null @@ -1,160 +0,0 @@ -package mods.octarinecore.client.resource - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.client.resource.Identifier -import mods.betterfoliage.client.resource.Sprite -import mods.octarinecore.client.render.Model -import mods.octarinecore.common.Double3 -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.completedVoid -import mods.octarinecore.common.sink -import mods.octarinecore.stripEnd -import mods.octarinecore.stripStart -import net.minecraft.resources.IResourceManager -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.MathHelper -import net.minecraft.world.IWorld -import net.minecraft.world.gen.SimplexNoiseGenerator -import net.minecraftforge.client.event.TextureStitchEvent -import net.minecraftforge.event.world.WorldEvent -import net.minecraftforge.eventbus.api.IEventBus -import net.minecraftforge.eventbus.api.SubscribeEvent -import net.minecraftforge.fml.config.ModConfig -import java.util.* -import java.util.concurrent.CompletableFuture -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty - -enum class Atlas(val basePath: String) { - BLOCKS("textures"), - PARTICLES("textures/particle"); - - fun wrap(resource: Identifier) = Identifier(resource.namespace, "$basePath/${resource.path}.png") - fun unwrap(resource: Identifier) = resource.stripStart("$basePath/").stripEnd(".png") - fun matches(event: TextureStitchEvent) = event.map.basePath == basePath -} - -// ============================ -// Resource types -// ============================ -interface IConfigChangeListener { fun onConfigChange() } -interface IWorldLoadListener { fun onWorldLoad(world: IWorld) } - -/** - * Base class for declarative resource handling. - * - * Resources are automatically reloaded/recalculated when the appropriate events are fired. - * - * @param[modId] mod ID associated with this handler (used to filter config change events) - */ -open class ResourceHandler( - val modId: String, - val modBus: IEventBus, - val targetAtlas: Atlas = Atlas.BLOCKS -) { - - val resources = mutableListOf() - // ============================ - // Self-registration - // ============================ - init { modBus.register(this) } - - // ============================ - // Resource declarations - // ============================ - fun sprite(id: Identifier) = sprite { id } - fun sprite(idFunc: ()->Identifier) = AsyncSpriteDelegate(idFunc).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) } - fun spriteSet(idFunc: (Int)->Identifier) = AsyncSpriteSet(idFunc).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) } - fun spriteSetTransformed(check: (Int)->Identifier, register: (Identifier)->Identifier) = - AsyncSpriteSet(check, register).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) } - fun model(init: Model.()->Unit) = ModelHolder(init).apply { resources.add(this) } - fun modelSet(num: Int, init: Model.(Int)->Unit) = ModelSet(num, init).apply { resources.add(this) } - fun vectorSet(num: Int, init: (Int)-> Double3) = VectorSet(num, init).apply { resources.add(this) } - fun simplexNoise() = SimplexNoise().apply { resources.add(this) } - - // ============================ - // Event registration - // ============================ - @SubscribeEvent - fun handleModConfigChange(event: ModConfig.ModConfigEvent) { - resources.forEach { (it as? IConfigChangeListener)?.onConfigChange() } - } - - @SubscribeEvent - fun handleWorldLoad(event: WorldEvent.Load) = - resources.forEach { (it as? IWorldLoadListener)?.onWorldLoad(event.world) } -} - -// ============================ -// Resource container classes -// ============================ -class AsyncSpriteDelegate(val idFunc: ()->Identifier) : ReadOnlyProperty, AsyncSpriteProvider { - protected lateinit var value: Sprite - override fun getValue(thisRef: Any, property: KProperty<*>) = value - - override fun setup(manager: IResourceManager, sourceF: CompletableFuture, atlas: AtlasFuture): StitchPhases { - sourceF.thenRun { - val sprite = atlas.sprite(idFunc()) - atlas.runAfter { - sprite.handle { sprite, error -> value = sprite ?: atlas.missing.get()!! } - } - } - return StitchPhases(completedVoid(), completedVoid()) - } -} - -interface SpriteSet { - val num: Int - operator fun get(idx: Int): Sprite -} - -class AsyncSpriteSet(val idFunc: (Int)->Identifier, val transform: (Identifier)->Identifier = { it }) : AsyncSpriteProvider { - var num = 0 - protected set - protected var sprites: List = emptyList() - - override fun setup(manager: IResourceManager, sourceF: CompletableFuture, atlas: AtlasFuture): StitchPhases { - var list: List> = emptyList() - - return StitchPhases( - discovery = sourceF.sink { - list = (0 until 16).map { idFunc(it) } - .filter { manager.hasResource( Atlas.BLOCKS.wrap(it)) } - .map { transform(it) } - .map { atlas.sprite(it) } - }, - cleanup = atlas.runAfter { - sprites = list.filter { !it.isCompletedExceptionally }.map { it.get() } - if (sprites.isEmpty()) sprites = listOf(atlas.missing.get()!!) - num = sprites.size - } - ) - } - operator fun get(idx: Int) = sprites[idx % num] -} - -class ModelHolder(val init: Model.()->Unit): IConfigChangeListener { - var model: Model = Model() - override fun onConfigChange() { model = Model().apply(init) } -} - -class ModelSet(val num: Int, val init: Model.(Int)->Unit): IConfigChangeListener { - val models = Array(num) { Model() } - override fun onConfigChange() { (0 until num).forEach { models[it] = Model().apply{ init(it) } } } - operator fun get(idx: Int) = models[idx % num] -} - -class VectorSet(val num: Int, val init: (Int)->Double3): IConfigChangeListener { - val models = Array(num) { Double3.zero } - override fun onConfigChange() { (0 until num).forEach { models[it] = init(it) } } - operator fun get(idx: Int) = models[idx % num] -} - -class SimplexNoise : IWorldLoadListener { - var noise = SimplexNoiseGenerator(Random()) - override fun onWorldLoad(world: IWorld) { noise = SimplexNoiseGenerator(Random(world.worldInfo.seed)) - } - operator fun get(x: Int, z: Int) = MathHelper.floor((noise.getValue(x.toDouble(), z.toDouble()) + 1.0) * 32.0) - operator fun get(pos: Int3) = get(pos.x, pos.z) - operator fun get(pos: BlockPos) = get(pos.x, pos.z) -} diff --git a/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt b/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt deleted file mode 100644 index ccb310b..0000000 --- a/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt +++ /dev/null @@ -1,108 +0,0 @@ -@file:JvmName("Utils") -package mods.octarinecore.client.resource - -import mods.octarinecore.PI2 -import mods.octarinecore.client.render.lighting.HSB -import mods.octarinecore.stripStart -import mods.octarinecore.tryDefault -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.model.BlockModel -import net.minecraft.client.renderer.texture.AtlasTexture -import net.minecraft.client.renderer.texture.MissingTextureSprite -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.resources.IResource -import net.minecraft.resources.IResourceManager -import net.minecraft.resources.SimpleReloadableResourceManager -import net.minecraft.util.ResourceLocation -import java.awt.image.BufferedImage -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.InputStream -import java.lang.Math.* -import javax.imageio.ImageIO -import kotlin.math.atan2 - -/** Concise getter for the Minecraft resource manager. */ -val resourceManager: SimpleReloadableResourceManager - get() = - Minecraft.getInstance().resourceManager as SimpleReloadableResourceManager - -/** Append a string to the [ResourceLocation]'s path. */ -operator fun ResourceLocation.plus(str: String) = ResourceLocation(namespace, path + str) - -/** 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. */ -operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryDefault(null) { getResource(location) } - -/** Index operator to get a texture sprite. */ -operator fun AtlasTexture.get(res: ResourceLocation): TextureAtlasSprite? = getSprite(res) -operator fun AtlasTexture.get(name: String): TextureAtlasSprite? = getSprite(ResourceLocation(name)) - -val missingSprite: TextureAtlasSprite get() = MissingTextureSprite.func_217790_a() - -/** Load an image resource. */ -fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream) - -/** Get the lines of a text resource. */ -fun IResource.getLines(): List { - val result = arrayListOf() - inputStream.bufferedReader().useLines { it.forEach { result.add(it) } } - return result -} - -/** Index operator to get the RGB value of a pixel. */ -operator fun BufferedImage.get(x: Int, y: Int) = this.getRGB(x, y) -/** Index operator to set the RGB value of a pixel. */ -operator fun BufferedImage.set(x: Int, y: Int, value: Int) = this.setRGB(x, y, value) - -/** Get an [InputStream] to an image object in PNG format. */ -val BufferedImage.asStream: InputStream get() = - ByteArrayInputStream(ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() }) -val BufferedImage.bytes: ByteArray get() = - ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() } -/** - * Calculate the average color of a texture. - * - * Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average), - * and the result transformed back to the RGB color space. - */ -val TextureAtlasSprite.averageColor: Int get() { - var numOpaque = 0 - var sumHueX = 0.0 - var sumHueY = 0.0 - var sumSaturation = 0.0f - var sumBrightness = 0.0f - for (x in 0 until width) - for (y in 0 until height) { - val pixel = getPixelRGBA(0, x, y) - val alpha = (pixel shr 24) and 255 - val hsb = HSB.fromColor(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()).asColor -} - -/** - * Get the actual location of a texture from the name of its [TextureAtlasSprite]. - */ -fun textureLocation(iconName: String) = ResourceLocation(iconName).let { - if (it.path.startsWith("mcpatcher")) it - else ResourceLocation(it.namespace, "textures/${it.path}") -} - -fun Pair.derivesFrom(targetLocation: ResourceLocation): Boolean { - if (second.stripStart("models/") == targetLocation) return true - if (first.parent != null && first.parentLocation != null) - return Pair(first.parent!!, first.parentLocation!!).derivesFrom(targetLocation) - return false -} diff --git a/src/main/kotlin/mods/octarinecore/common/config/DelegatingConfig.kt b/src/main/kotlin/mods/octarinecore/common/config/DelegatingConfig.kt deleted file mode 100644 index 75e3cfb..0000000 --- a/src/main/kotlin/mods/octarinecore/common/config/DelegatingConfig.kt +++ /dev/null @@ -1,89 +0,0 @@ -@file:JvmName("DelegatingConfigKt") - -package mods.octarinecore.common.config - -import mods.octarinecore.metaprog.reflectDelegates -import mods.octarinecore.metaprog.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, 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 : ReadOnlyProperty { - lateinit var configValue: ForgeConfigSpec.ConfigValue - 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 - 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() { - 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() { - 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() { - 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() { - 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) diff --git a/src/main/kotlin/mods/octarinecore/metaprog/Reflection.kt b/src/main/kotlin/mods/octarinecore/metaprog/Reflection.kt deleted file mode 100644 index e9720b5..0000000 --- a/src/main/kotlin/mods/octarinecore/metaprog/Reflection.kt +++ /dev/null @@ -1,182 +0,0 @@ -@file:JvmName("Reflection") -package mods.octarinecore.metaprog - -import java.lang.reflect.Field -import java.lang.reflect.Method -import mods.octarinecore.metaprog.Namespace.* -import mods.octarinecore.tryDefault -import kotlin.reflect.KCallable -import kotlin.reflect.KFunction - -/** Get a Java class with the given name. */ -fun getJavaClass(name: String) = tryDefault(null) { Class.forName(name) } - -/** Get the field with the given name and type using reflection. */ -inline fun Any.reflectField(field: String): T? = - tryDefault(null) { this.javaClass.getDeclaredField(field) }?.let { - it.isAccessible = true - it.get(this) as T - } - -/** Get the static field with the given name and type using reflection. */ -inline fun Class<*>.reflectStaticField(field: String): T? = - tryDefault(null) { this.getDeclaredField(field) }?.let { - it.isAccessible = true - it.get(null) as T - } - -/** - * Get all nested _object_s of this _object_ with reflection. - * - * @return [Pair]s of (name, instance) - */ -val Any.reflectNestedObjects: List> get() = this.javaClass.declaredClasses.map { - tryDefault(null) { it.name.split("$")[1] to it.getField("INSTANCE").get(null) } -}.filterNotNull() - -/** - * Get all fields of this instance that match (or subclass) any of the given classes. - * - * @param[types] classes to look for - * @return [Pair]s of (field name, instance) - */ -fun Any.reflectFieldsOfType(vararg types: Class<*>) = this.javaClass.declaredFields - .filter { field -> types.any { it.isAssignableFrom(field.type) } } - .map { field -> field.name to field.let { it.isAccessible = true; it.get(this) } } - .filterNotNull() - -fun Any.reflectFields(type: Class) = this.javaClass.declaredFields - .filter { field -> type.isAssignableFrom(field.type) } - .map { field -> field.name to field.let { it.isAccessible = true; it.get(this) as T } } - -fun Any.reflectDelegates(type: Class) = this.javaClass.declaredFields - .filter { field -> type.isAssignableFrom(field.type) && field.name.contains("$") } - .map { field -> field.name.split("$")[0] to field.let { it.isAccessible = true; it.get(this) } as T } - -enum class Namespace { MCP, SRG } - -abstract class Resolvable { - abstract fun resolve(): T? - val element: T? by lazy { resolve() } -} - -/** Return true if all given elements are found. */ -fun allAvailable(vararg codeElement: Resolvable<*>) = codeElement.all { it.element != null } - -/** - * Reference to a class. - * - * @param[name] MCP name of the class - */ -open class ClassRef(val name: String) : Resolvable>() { - - companion object { - val int = ClassRefPrimitive("I", Int::class.java) - val long = ClassRefPrimitive("J", Long::class.java) - val float = ClassRefPrimitive("F", Float::class.java) - val boolean = ClassRefPrimitive("Z", Boolean::class.java) - val void = ClassRefPrimitive("V", Void::class.java) - } - - open fun asmDescriptor(namespace: Namespace) = "L${name.replace(".", "/")};" - - override fun resolve() = getJavaClass(name) as Class? - - fun isInstance(obj: Any) = element?.isInstance(obj) ?: false -} - -/** - * Reference to a primitive type. - * - * @param[name] ASM descriptor of this primitive type - * @param[clazz] class of this primitive type - */ -class ClassRefPrimitive(name: String, val clazz: Class?) : ClassRef(name) { - override fun asmDescriptor(namespace: Namespace) = name - override fun resolve() = clazz -} - -/** - * Reference to a method. - * - * @param[parentClass] reference to the class containing the method - * @param[mcpName] MCP name of the method - * @param[srgName] SRG name of the method - * @param[returnType] reference to the return type - * @param[returnType] references to the argument types - */ -class MethodRef(val parentClass: ClassRef<*>, - val mcpName: String, - val srgName: String, - val returnType: ClassRef, - vararg val argTypes: ClassRef<*> -) : Resolvable() { - constructor(parentClass: ClassRef<*>, mcpName: String, returnType: ClassRef, vararg argTypes: ClassRef<*>) : - this(parentClass, mcpName, mcpName, returnType, *argTypes) - - fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName } - fun asmDescriptor(namespace: Namespace) = "(${argTypes.map { it.asmDescriptor(namespace) }.fold(""){ s1, s2 -> s1 + s2 } })${returnType.asmDescriptor(namespace)}" - - override fun resolve(): Method? = - if (parentClass.element == null || argTypes.any { it.element == null }) null - else { - val args = argTypes.map { it.element!! }.toTypedArray() - listOf(srgName, mcpName).map { tryDefault(null) { - parentClass.element!!.getDeclaredMethod(it, *args) - }}.filterNotNull().firstOrNull() - ?.apply { isAccessible = true } - } - - /** Invoke this method using reflection. */ - operator fun invoke(receiver: Any, vararg args: Any?) = element?.invoke(receiver, *args) as T - - /** Invoke this static method using reflection. */ - fun invokeStatic(vararg args: Any) = element?.invoke(null, *args) -} - -/** - * Reference to a field. - * - * @param[parentClass] reference to the class containing the field - * @param[mcpName] MCP name of the field - * @param[srgName] SRG name of the field - * @param[type] reference to the field type - */ -class FieldRef(val parentClass: ClassRef<*>, - val mcpName: String, - val srgName: String, - val type: ClassRef -) : Resolvable() { - constructor(parentClass: ClassRef<*>, mcpName: String, type: ClassRef) : this(parentClass, mcpName, mcpName, type) - - fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName } - fun asmDescriptor(namespace: Namespace) = type.asmDescriptor(namespace) - - override fun resolve(): Field? = - if (parentClass.element == null) null - else { - listOf(srgName, mcpName).mapNotNull { tryDefault(null) { - parentClass.element!!.getDeclaredField(it) - } }.firstOrNull() - ?.apply{ isAccessible = true } - } - - /** Get this field using reflection. */ - operator fun get(receiver: Any?) = element?.get(receiver) as T - - /** Get this static field using reflection. */ - fun getStatic() = get(null) - - fun set(receiver: Any?, obj: Any?) { element?.set(receiver, obj) } -} - - -fun Any.isInstance(cls: ClassRef<*>) = cls.isInstance(this) -interface ReflectionCallable { - operator fun invoke(vararg args: Any): T -} -inline operator fun Any.get(field: FieldRef) = field.get(this) -inline operator fun Any.set(field: FieldRef, value: T) = field.set(this, value) -inline operator fun Any.get(methodRef: MethodRef) = object : ReflectionCallable { - override fun invoke(vararg args: Any) = methodRef.invoke(this@get, args) -} \ No newline at end of file diff --git a/src/main/kotlin/net/fabricmc/fabric/impl/client/indigo/renderer/render/ModifiedTerrainMeshConsumer.kt b/src/main/kotlin/net/fabricmc/fabric/impl/client/indigo/renderer/render/ModifiedTerrainMeshConsumer.kt new file mode 100644 index 0000000..46a7ad4 --- /dev/null +++ b/src/main/kotlin/net/fabricmc/fabric/impl/client/indigo/renderer/render/ModifiedTerrainMeshConsumer.kt @@ -0,0 +1,103 @@ +package net.fabricmc.fabric.impl.client.indigo.renderer.render + +import mods.betterfoliage.render.lighting.CustomLightingMeshConsumer +import mods.betterfoliage.render.lighting.CustomLighting +import mods.betterfoliage.util.* +import net.fabricmc.fabric.api.renderer.v1.render.RenderContext +import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator +import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl +import net.minecraft.util.math.Direction + +val AoCalculator_computeFace = YarnHelper.requiredMethod( + "net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator", "computeFace", + "(Lnet/minecraft/util/math/Direction;Z)Lnet/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoFaceData;" +) +val AoFaceData_toArray = YarnHelper.requiredMethod( + "net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoFaceData", "toArray", + "([F[I[I)V" +) + +open class ModifiedTerrainMeshConsumer( + blockInfo: TerrainBlockRenderInfo, + chunkInfo: ChunkRenderInfo, + aoCalc: AoCalculator, + transform: RenderContext.QuadTransform +) : TerrainMeshConsumer(blockInfo, chunkInfo, aoCalc, transform), CustomLightingMeshConsumer { + + /** Custom lighting to use */ + var lighter: CustomLighting? = null + + /** Cache validity for AO/light values */ + protected val aoValid = Array(6) { false } + override val aoFull = FloatArray(24) + override val lightFull = IntArray(24) + + /** Cached block brightness values for all neighbors */ + val brNeighbor = IntArray(6) + /** Cache validity for block brightness values (neighbors + self) */ + val brValid = Array(7) { false } + + override var brSelf: Int = -1 + get() { if (brValid[6]) return field else + field = blockInfo.blockView.getBlockState(blockInfo.blockPos).getBlockBrightness(blockInfo.blockView, blockInfo.blockPos) + brValid[6] = true + return field + } + protected set + + override fun brNeighbor(dir: Direction): Int { + if (brValid[dir.ordinal]) return brNeighbor[dir.ordinal] + blockInfo.blockView.getBlockState(blockInfo.blockPos) + .getBlockBrightness(blockInfo.blockView, blockInfo.blockPos + dir.offset) + .let { brNeighbor[dir.ordinal] = it; brValid[dir.ordinal] = true; return it } + } + + override fun clearLighting() { + for (idx in 0 until 6) { + aoValid[idx] = false + brValid[idx] = false + } + brValid[6] = false + } + + override fun fillAoData(lightFace: Direction) { + if (!aoValid[lightFace.ordinal]) { + AoFaceData_toArray.invoke(AoCalculator_computeFace.invoke(aoCalc, lightFace, true), aoFull, lightFull, cornerDirFromAo[lightFace.ordinal]) + aoValid[lightFace.ordinal] = true + } + } + + override fun setLighting(vIdx: Int, ao: Float, light: Int) { + aoCalc.ao[vIdx] = ao + aoCalc.light[vIdx] = light + } + + override fun applyOffsets(quad: MutableQuadViewImpl) { + // Moved farther back in the pipeline, after custom lighting is applied + // Might possibly mess emissive multitexturing up, but seems to be OK for now + } + + override fun tesselateFlat(q: MutableQuadViewImpl, renderLayer: Int, blockColorIndex: Int) { + lighter?.applyLighting(this, q, flat = true, emissive = false) + super.applyOffsets(q) + super.tesselateSmooth(q, renderLayer, blockColorIndex) + } + + override fun tesselateFlatEmissive(q: MutableQuadViewImpl, renderLayer: Int, blockColorIndex: Int, lightmaps: IntArray) { + lighter?.applyLighting(this, q, flat = true, emissive = true) + super.applyOffsets(q) + super.tesselateSmoothEmissive(q, renderLayer, blockColorIndex) + } + + override fun tesselateSmooth(q: MutableQuadViewImpl, renderLayer: Int, blockColorIndex: Int) { + lighter?.applyLighting(this, q, flat = false, emissive = false) + super.applyOffsets(q) + super.tesselateSmooth(q, renderLayer, blockColorIndex) + } + + override fun tesselateSmoothEmissive(q: MutableQuadViewImpl, renderLayer: Int, blockColorIndex: Int) { + lighter?.applyLighting(this, q, flat = false, emissive = true) + super.applyOffsets(q) + super.tesselateSmoothEmissive(q, renderLayer, blockColorIndex) + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF index 813534a..fe1ba61 100644 --- a/src/main/resources/META-INF/MANIFEST.MF +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -1,6 +1,2 @@ Manifest-Version: 1.0 -Specification-Title: BetterFoliage -Implementation-Title: BetterFoliage -Specification-Vendor: octarine-noise -Implementation-Vendor: octarine-noise -MixinConnector: mods.betterfoliage.MixinConnector +Implementation-Title: betterfoliage diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg deleted file mode 100644 index 366fe2b..0000000 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ /dev/null @@ -1,10 +0,0 @@ -public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace -public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace -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 diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml deleted file mode 100644 index 66386ef..0000000 --- a/src/main/resources/META-INF/mods.toml +++ /dev/null @@ -1,14 +0,0 @@ -modLoader="kotlinfml" -loaderVersion="[1.4,)" -issueTrackerURL="https://github.com/octarine-noise/BetterFoliage/issues" - -[[mods]] - modId="betterfoliage" - version="${version}" - displayName="Better Foliage" - authors="octarine-noise" - description=''' -Leafier leaves and grassier grass. - ''' - displayURL="https://www.curseforge.com/minecraft/mc-mods/better-foliage" - side="CLIENT" \ No newline at end of file diff --git a/src/main/resources/assets/betterfoliage/grass_blocks_default.cfg b/src/main/resources/assets/betterfoliage/grass_blocks_default.cfg index eb0a164..5c6cad2 100644 --- a/src/main/resources/assets/betterfoliage/grass_blocks_default.cfg +++ b/src/main/resources/assets/betterfoliage/grass_blocks_default.cfg @@ -1,2 +1,3 @@ // Vanilla -net.minecraft.block.GrassBlock +//net.minecraft.block.GrassBlock +net.minecraft.class_2372 diff --git a/src/main/resources/assets/betterfoliage/icon.png b/src/main/resources/assets/betterfoliage/icon.png new file mode 100644 index 0000000..2931efb Binary files /dev/null and b/src/main/resources/assets/betterfoliage/icon.png differ diff --git a/src/main/resources/assets/betterfoliage/lang/en_us.json b/src/main/resources/assets/betterfoliage/lang/en_us.json new file mode 100644 index 0000000..afb6dc7 --- /dev/null +++ b/src/main/resources/assets/betterfoliage/lang/en_us.json @@ -0,0 +1,257 @@ +{ + "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.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.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.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." +} diff --git a/src/main/resources/assets/betterfoliage/lang/en_us.lang b/src/main/resources/assets/betterfoliage/lang/en_us.lang deleted file mode 100644 index 2012834..0000000 --- a/src/main/resources/assets/betterfoliage/lang/en_us.lang +++ /dev/null @@ -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. diff --git a/src/main/resources/assets/betterfoliage/lang/ko_kr.json b/src/main/resources/assets/betterfoliage/lang/ko_kr.json new file mode 100644 index 0000000..1a9f90c --- /dev/null +++ b/src/main/resources/assets/betterfoliage/lang/ko_kr.json @@ -0,0 +1,216 @@ +{ + "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 일경우, 나무 블럭이 수직선에대한 렌더링을 할수가 없습니다. 그렇지 아니하면, 네모난 블럭으로 렌더링 될것임니다." +} diff --git a/src/main/resources/assets/betterfoliage/lang/ko_kr.lang b/src/main/resources/assets/betterfoliage/lang/ko_kr.lang deleted file mode 100644 index 7003d63..0000000 --- a/src/main/resources/assets/betterfoliage/lang/ko_kr.lang +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/main/resources/assets/betterfoliage/lang/ru_ru.json b/src/main/resources/assets/betterfoliage/lang/ru_ru.json new file mode 100644 index 0000000..df0e3e5 --- /dev/null +++ b/src/main/resources/assets/betterfoliage/lang/ru_ru.json @@ -0,0 +1,215 @@ +{ + "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-бой (мерцание). Попробуйте установить его как можно выше, для устранения мерцания." +} diff --git a/src/main/resources/assets/betterfoliage/lang/ru_ru.lang b/src/main/resources/assets/betterfoliage/lang/ru_ru.lang deleted file mode 100644 index 54fb9f9..0000000 --- a/src/main/resources/assets/betterfoliage/lang/ru_ru.lang +++ /dev/null @@ -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-бой (мерцание). Попробуйте установить его как можно выше, для устранения мерцания. diff --git a/src/main/resources/assets/betterfoliage/leaves_blocks_default.cfg b/src/main/resources/assets/betterfoliage/leaves_blocks_default.cfg index 839b864..e9b215f 100644 --- a/src/main/resources/assets/betterfoliage/leaves_blocks_default.cfg +++ b/src/main/resources/assets/betterfoliage/leaves_blocks_default.cfg @@ -1,2 +1,3 @@ // Vanilla -net.minecraft.block.LeavesBlock +//net.minecraft.block.LeavesBlock +net.minecraft.class_2397 diff --git a/src/main/resources/assets/betterfoliage/log_models_default.cfg b/src/main/resources/assets/betterfoliage/log_models_default.cfg index ddccc01..a6ef581 100644 --- a/src/main/resources/assets/betterfoliage/log_models_default.cfg +++ b/src/main/resources/assets/betterfoliage/log_models_default.cfg @@ -1,12 +1,4 @@ // Vanilla -block/column_side,end,end,side -block/cube_column,end,end,side -block/cube_all,all,all,all - -// Agricultural Revolution -agriculturalrevolution:block/palmlog,top,top,texture - -// Lithos Core -block/column_top,end,end,side_a,side_b -block/column_side_x,end,end,side_a,side_b -block/column_side_z,end,end,side_a,side_b +block/column_side,side,end +block/cube_column,side,end +block/cube_all,all,all diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_0.png b/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_0.png index a45f67f..d8579d0 100644 Binary files a/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_0.png and b/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_0.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_1.png b/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_1.png index f1ed5de..75b9720 100644 Binary files a/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_1.png and b/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_1.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_2.png b/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_2.png index 7313aaf..a5cb063 100644 Binary files a/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_2.png and b/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_2.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_3.png b/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_3.png index 915f85b..a0c7d3d 100644 Binary files a/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_3.png and b/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_3.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_4.png b/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_4.png index c5d9d31..b9914b9 100644 Binary files a/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_4.png and b/src/main/resources/assets/betterfoliage/textures/blocks/better_grass_long_4.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/soul_track.png.mcmeta b/src/main/resources/assets/betterfoliage/textures/particle/soul_track.png.mcmeta similarity index 100% rename from src/main/resources/assets/betterfoliage/textures/blocks/soul_track.png.mcmeta rename to src/main/resources/assets/betterfoliage/textures/particle/soul_track.png.mcmeta diff --git a/src/main/resources/betterfoliage.common.mixins.json b/src/main/resources/betterfoliage.mixins.json similarity index 65% rename from src/main/resources/betterfoliage.common.mixins.json rename to src/main/resources/betterfoliage.mixins.json index 221df73..0c4559b 100644 --- a/src/main/resources/betterfoliage.common.mixins.json +++ b/src/main/resources/betterfoliage.mixins.json @@ -1,7 +1,7 @@ { "required": true, "package": "mods.betterfoliage.mixin", - "refmap": "betterfoliage.refmap.json", + "refmap": "betterfoliage-refmap.json", "compatibilityLevel": "JAVA_8", "minVersion": "0.8-SNAPSHOT", "mixins": [ @@ -9,10 +9,11 @@ "client": [ "MixinBlock", "MixinBlockState", - "MixinChunkRender", + "MixinBlockModels", "MixinClientWorld", - "MixinModelBakery", - "MixinParticleManager" + "MixinClientChunkManager", + "MixinClientChunkManagerChunkMap", + "MixinModelLoader" ], "server": [ ], diff --git a/src/main/resources/betterfoliage.optifine.mixins.json b/src/main/resources/betterfoliage.optifine.mixins.json deleted file mode 100644 index 28a4e18..0000000 --- a/src/main/resources/betterfoliage.optifine.mixins.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "required": true, - "package": "mods.betterfoliage.mixin", - "refmap": "betterfoliage.refmap.json", - "compatibilityLevel": "JAVA_8", - "minVersion": "0.8-SNAPSHOT", - "mixins": [ - ], - "client": [ - "MixinOptifineChunkRender", - "MixinShadersBlockModelRenderer", - "MixinOptifineBlockUtils" - ], - "server": [ - ], - "injectors": { - "defaultRequire": 1 - } -} \ No newline at end of file diff --git a/src/main/resources/betterfoliage.vanilla.mixins.json b/src/main/resources/betterfoliage.vanilla.mixins.json deleted file mode 100644 index 4608c01..0000000 --- a/src/main/resources/betterfoliage.vanilla.mixins.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "required": true, - "package": "mods.betterfoliage.mixin", - "refmap": "betterfoliage.refmap.json", - "compatibilityLevel": "JAVA_8", - "minVersion": "0.8-SNAPSHOT", - "mixins": [ - ], - "client": [ - "MixinChunkRenderVanilla" - ], - "server": [ - ], - "injectors": { - "defaultRequire": 1 - } -} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..91d7ecb --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,47 @@ +{ + "schemaVersion": 1, + + "id": "betterfoliage", + "version": "${version}", + "name": "Better Foliage", + "description": "Leafier leaves and grassier grass.", + "license": "MIT", + "authors": ["octarine-noise"], + "contributors": ["Meringue"], + "icon": "assets/betterfoliage/icon.png", + "contact": { + "homepage": "https://www.curseforge.com/minecraft/mc-mods/better-foliage", + "issues": "https://github.com/octarine-noise/BetterFoliage/issues" + }, + + "environment": "client", + "custom": { + "modmenu:clientsideOnly": true + }, + + "entrypoints": { + "client": [ + { + "adapter": "kotlin", + "value": "mods.betterfoliage.BetterFoliage" + } + ], + "modmenu": [ + { + "adapter": "kotlin", + "value": "mods.betterfoliage.BetterFoliage" + } + ] + }, + + "mixins": [ + "betterfoliage.mixins.json" + ], + + "depends": { + "fabricloader": ">=0.7.3", + "fabric": "*", + "fabric-language-kotlin": "*", + "minecraft": "1.14.4" + } +} \ No newline at end of file diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info deleted file mode 100644 index fff3bf9..0000000 --- a/src/main/resources/mcmod.info +++ /dev/null @@ -1,8 +0,0 @@ -[{ - "modid": "betterfoliage", - "name": "Better Foliage", - "version": "$version", - "mcversion": "$mcversion", - "description": "Leafier leaves and grassier grass", - "authorList" : ["octarine-noise (code)", "Meringue (textures)"] -}] \ No newline at end of file