Compare commits
12 Commits
kotlin-1.1
...
1.14.4-Fab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d741338d46 | ||
|
|
01f697d2d3 | ||
|
|
4c439dccd2 | ||
|
|
df50f61b0d | ||
|
|
2252fb3b42 | ||
|
|
4efa831296 | ||
|
|
a3d99c3076 | ||
|
|
c4ee768025 | ||
|
|
b4f18c1e1d | ||
|
|
2a06c18884 | ||
|
|
2ba99f40e7 | ||
|
|
46cbe64328 |
62
build.gradle
62
build.gradle
@@ -1,62 +0,0 @@
|
|||||||
apply plugin: "net.minecraftforge.gradle.forge"
|
|
||||||
apply plugin: 'kotlin'
|
|
||||||
|
|
||||||
archivesBaseName = jarName
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
name = "forge"
|
|
||||||
url = "http://files.minecraftforge.net/maven"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath "net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT"
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
jcenter()
|
|
||||||
maven {
|
|
||||||
name = "shadowfacts"
|
|
||||||
url = "https://maven.shadowfacts.net/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile "net.shadowfacts:Forgelin:$forgelin_version"
|
|
||||||
}
|
|
||||||
|
|
||||||
minecraft {
|
|
||||||
version = mc_version + "-" + forge_version
|
|
||||||
mappings = mcp_mappings
|
|
||||||
runDir = 'run'
|
|
||||||
}
|
|
||||||
processResources {
|
|
||||||
from(sourceSets.main.resources) { exclude 'mcmod.info' }
|
|
||||||
from(sourceSets.main.resources) { include 'mcmod.info' expand 'version':version, 'mcversion':minecraft.version }
|
|
||||||
|
|
||||||
into "${buildDir}/classes/main"
|
|
||||||
}
|
|
||||||
|
|
||||||
def manifestCfg = {
|
|
||||||
attributes "FMLCorePlugin": "mods.betterfoliage.loader.BetterFoliageLoader"
|
|
||||||
attributes "FMLCorePluginContainsFMLMod": "mods.betterfoliage.BetterFoliageMod"
|
|
||||||
attributes "FMLAT": "BetterFoliage_at.cfg"
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
manifest manifestCfg
|
|
||||||
exclude "optifine"
|
|
||||||
}
|
|
||||||
|
|
||||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
|
||||||
classifier = 'sources'
|
|
||||||
manifest manifestCfg
|
|
||||||
from(sourceSets.main.kotlin)
|
|
||||||
from(sourceSets.main.resources) { exclude 'mcmod.info' }
|
|
||||||
from(sourceSets.main.resources) { include 'mcmod.info' expand 'version':version, 'mcversion':minecraft.version }
|
|
||||||
}
|
|
||||||
69
build.gradle.kts
Normal file
69
build.gradle.kts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import net.fabricmc.loom.task.RemapJarTask
|
||||||
|
import org.ajoberstar.grgit.Grgit
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
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.ajoberstar.grgit")
|
||||||
|
|
||||||
|
val gitHash = (project.ext.get("grgit") as Grgit).head().abbreviatedId
|
||||||
|
val semVer = "${project.version}+$gitHash"
|
||||||
|
val jarName = "BetterFoliage-$semVer-Fabric-${properties["mcVersion"]}"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven("http://maven.fabricmc.net/")
|
||||||
|
maven("https://minecraft.curseforge.com/api/maven")
|
||||||
|
maven("http://maven.modmuss50.me/")
|
||||||
|
maven("https://grondag-repo.appspot.com").credentials { username = "guest"; password = "" }
|
||||||
|
maven("https://jitpack.io")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
"minecraft"("com.mojang:minecraft:${properties["mcVersion"]}")
|
||||||
|
"mappings"("net.fabricmc:yarn:${properties["yarnMappings"]}:v2")
|
||||||
|
|
||||||
|
// basic Fabric stuff
|
||||||
|
"modImplementation"("net.fabricmc:fabric-loader:${properties["loaderVersion"]}")
|
||||||
|
"modImplementation"("net.fabricmc.fabric-api:fabric-api:${properties["fabricVersion"]}")
|
||||||
|
"modImplementation"("net.fabricmc:fabric-language-kotlin:${properties["fabricKotlinVersion"]}")
|
||||||
|
|
||||||
|
// configuration handling
|
||||||
|
"modImplementation"("io.github.prospector:modmenu:${properties["modMenuVersion"]}")
|
||||||
|
listOf("modImplementation", "include").forEach { configuration ->
|
||||||
|
configuration("me.shedaniel.cloth:config-2:${properties["clothConfigVersion"]}")
|
||||||
|
configuration("me.zeroeightsix:fiber:${properties["fiberVersion"]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canvas Renderer
|
||||||
|
// "modImplementation"("grondag:canvas:0.7.+")
|
||||||
|
|
||||||
|
// Optifabric
|
||||||
|
"modImplementation"("com.github.modmuss50:OptiFabric:df03dc2c22")
|
||||||
|
"implementation"("org.zeroturnaround:zt-zip:1.13")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
get("main").ext["refMap"] = "betterfoliage.refmap.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
target.compilations.configureEach {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
kotlinOptions.freeCompilerArgs += listOf("-Xno-param-assertions", "-Xno-call-assertions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.getByName<ProcessResources>("processResources") {
|
||||||
|
filesMatching("fabric.mod.json") { expand(mutableMapOf("version" to semVer)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.getByName<RemapJarTask>("remapJar") {
|
||||||
|
archiveName = "$jarName.jar"
|
||||||
|
}
|
||||||
@@ -1,11 +1,21 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx2G
|
||||||
|
org.gradle.daemon=false
|
||||||
|
|
||||||
group = com.github.octarine-noise
|
group = com.github.octarine-noise
|
||||||
jarName = BetterFoliage-MC1.12
|
name = betterfoliage
|
||||||
|
jarName = BetterFoliage-Forge
|
||||||
|
|
||||||
version = 2.3.1
|
version = 2.5.0
|
||||||
|
|
||||||
mc_version = 1.12.2
|
mcVersion = 1.14.4
|
||||||
forge_version = 14.23.5.2847
|
yarnMappings=1.14.4+build.15
|
||||||
mcp_mappings = stable_39
|
loaderVersion=0.7.3+build.176
|
||||||
|
fabricVersion=0.4.2+build.246-1.14
|
||||||
|
loomVersion=0.2.6-SNAPSHOT
|
||||||
|
|
||||||
kotlin_version = 1.3.40
|
kotlinVersion=1.3.60
|
||||||
forgelin_version = 1.8.4
|
fabricKotlinVersion=1.3.60+build.1
|
||||||
|
|
||||||
|
clothConfigVersion=1.8
|
||||||
|
modMenuVersion=1.7.6+build.115
|
||||||
|
fiberVersion=0.8.0-2
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
51
gradlew
vendored
51
gradlew
vendored
@@ -1,5 +1,21 @@
|
|||||||
#!/usr/bin/env sh
|
#!/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
|
## Gradle start up script for UN*X
|
||||||
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
|||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
@@ -109,8 +125,8 @@ if $darwin; then
|
|||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
@@ -138,19 +154,19 @@ if $cygwin ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -159,14 +175,9 @@ save () {
|
|||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# 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"
|
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" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
18
gradlew.bat
vendored
18
gradlew.bat
vendored
@@ -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
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
|||||||
set APP_HOME=%DIRNAME%
|
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.
|
@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
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
rootProject.name = 'BetterFoliage'
|
|
||||||
|
|
||||||
8
settings.gradle.kts
Normal file
8
settings.gradle.kts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
maven("https://maven.fabricmc.net/")
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rootProject.name = "betterfoliage"
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package mods.betterfoliage.loader;
|
|
||||||
|
|
||||||
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@IFMLLoadingPlugin.TransformerExclusions({
|
|
||||||
"mods.betterfoliage.loader",
|
|
||||||
"mods.octarinecore.metaprog",
|
|
||||||
"kotlin"
|
|
||||||
})
|
|
||||||
@IFMLLoadingPlugin.MCVersion("1.12.2")
|
|
||||||
@IFMLLoadingPlugin.SortingIndex(1400)
|
|
||||||
public class BetterFoliageLoader implements IFMLLoadingPlugin {
|
|
||||||
@Override
|
|
||||||
public String[] getASMTransformerClass() {
|
|
||||||
return new String[] { "mods.betterfoliage.loader.BetterFoliageTransformer" };
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getModContainerClass() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSetupClass() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectData(Map<String, Object> data) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAccessTransformerClass() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
44
src/main/java/mods/betterfoliage/mixin/MixinBlock.java
Normal file
44
src/main/java/mods/betterfoliage/mixin/MixinBlock.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.Hooks;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
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(Block.class)
|
||||||
|
public class MixinBlock {
|
||||||
|
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, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/java/mods/betterfoliage/mixin/MixinBlockModels.java
Normal file
17
src/main/java/mods/betterfoliage/mixin/MixinBlockModels.java
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/mods/betterfoliage/mixin/MixinBlockState.java
Normal file
27
src/main/java/mods/betterfoliage/mixin/MixinBlockState.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.Hooks;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixin to override the result of {@link BlockState}.getAmbientOcclusionLightValue().
|
||||||
|
*
|
||||||
|
* Needed to avoid excessive darkening of Round Logs at the corners, now that they are not full blocks.
|
||||||
|
*/
|
||||||
|
@Mixin(BlockState.class)
|
||||||
|
@SuppressWarnings({"UnnecessaryQualifiedMemberReference", "deprecation"})
|
||||||
|
public class MixinBlockState {
|
||||||
|
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, BlockView reader, BlockPos pos) {
|
||||||
|
return Hooks.getAmbientOcclusionLightValueOverride(block.getAmbientOcclusionLightLevel(state, reader, pos), state);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WorldChunk> ci) {
|
||||||
|
ClientChunkLoadCallback.EVENT.invoker().loadChunk(ci.getReturnValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<WorldChunk> ci) {
|
||||||
|
ClientChunkLoadCallback.EVENT.invoker().unloadChunk(oldChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/main/java/mods/betterfoliage/mixin/MixinClientWorld.java
Normal file
50
src/main/java/mods/betterfoliage/mixin/MixinClientWorld.java
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.ClientWorldLoadCallback;
|
||||||
|
import mods.betterfoliage.Hooks;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
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.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.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@Mixin(ClientWorld.class)
|
||||||
|
public class MixinClientWorld {
|
||||||
|
|
||||||
|
private static final String ctor = "<init>(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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/java/mods/betterfoliage/mixin/MixinModelLoader.java
Normal file
28
src/main/java/mods/betterfoliage/mixin/MixinModelLoader.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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) {
|
||||||
|
// use the same trick fabric-api does to get around the no-mixins-in-constructors policy
|
||||||
|
if (id == MISSING) {
|
||||||
|
ModelLoadingCallback.EVENT.invoker().beginLoadModels((ModelLoader) (Object) this, resourceManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package optifine;
|
|
||||||
|
|
||||||
import net.minecraft.launchwrapper.IClassTransformer;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLClassLoader;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipFile;
|
|
||||||
|
|
||||||
public class OptifineTransformerDevWrapper implements IClassTransformer {
|
|
||||||
|
|
||||||
public static String OPTIFINE_CLASSNAME = "optifine/OptiFineClassTransformer.class";
|
|
||||||
private ZipFile ofZip = null;
|
|
||||||
|
|
||||||
public OptifineTransformerDevWrapper() {
|
|
||||||
Stream<URL> loaderSources = Arrays.stream(((URLClassLoader) getClass().getClassLoader()).getURLs());
|
|
||||||
Optional<URL> optifineURL = loaderSources.filter(this::isOptifineJar).findFirst();
|
|
||||||
optifineURL.ifPresent(url -> ofZip = getZip(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ZipFile getZip(URL url) {
|
|
||||||
try {
|
|
||||||
return new ZipFile(new File(url.toURI()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isOptifineJar(URL url) {
|
|
||||||
ZipFile zip = getZip(url);
|
|
||||||
return zip != null && zip.getEntry(OPTIFINE_CLASSNAME) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] transform(String name, String transformedName, byte[] basicClass) {
|
|
||||||
if (ofZip == null) return basicClass;
|
|
||||||
ZipEntry replacement = ofZip.getEntry(name.replace(".", "/") + ".class");
|
|
||||||
if (replacement == null) return basicClass;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return readAll(ofZip.getInputStream(replacement));
|
|
||||||
} catch (IOException e) {
|
|
||||||
return basicClass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] readAll(InputStream is) throws IOException {
|
|
||||||
byte[] buf = new byte[4096];
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
int len;
|
|
||||||
do {
|
|
||||||
len = is.read(buf, 0, 4096);
|
|
||||||
if (len > 0) bos.write(buf, 0, len);
|
|
||||||
} while (len > -1);
|
|
||||||
is.close();
|
|
||||||
return bos.toByteArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package optifine;
|
|
||||||
|
|
||||||
import net.minecraft.launchwrapper.ITweaker;
|
|
||||||
import net.minecraft.launchwrapper.LaunchClassLoader;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class OptifineTweakerDevWrapper implements ITweaker {
|
|
||||||
@Override
|
|
||||||
public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectIntoClassLoader(LaunchClassLoader classLoader) {
|
|
||||||
classLoader.registerTransformer("optifine.OptifineTransformerDevWrapper");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getLaunchTarget() {
|
|
||||||
return "net.minecraft.client.main.Main";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getLaunchArguments() {
|
|
||||||
return new String[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
88
src/main/kotlin/mods/betterfoliage/BetterFoliage.kt
Normal file
88
src/main/kotlin/mods/betterfoliage/BetterFoliage.kt
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package mods.betterfoliage
|
||||||
|
|
||||||
|
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.ResourceManagerHelper
|
||||||
|
import net.fabricmc.loader.api.FabricLoader
|
||||||
|
import net.minecraft.client.MinecraftClient
|
||||||
|
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
|
||||||
|
import org.apache.logging.log4j.util.PropertiesUtil
|
||||||
|
import java.io.File
|
||||||
|
import java.io.PrintStream
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
object BetterFoliage : ClientModInitializer {
|
||||||
|
const val MOD_ID = "betterfoliage"
|
||||||
|
|
||||||
|
var logger = LogManager.getLogger()
|
||||||
|
var logDetail = SimpleLogger(
|
||||||
|
"BetterFoliage",
|
||||||
|
Level.DEBUG,
|
||||||
|
false, false, true, false,
|
||||||
|
"yyyy-MM-dd HH:mm:ss",
|
||||||
|
null,
|
||||||
|
PropertiesUtil(Properties()),
|
||||||
|
PrintStream(File(FabricLoader.getInstance().gameDirectory, "logs/betterfoliage.log").apply {
|
||||||
|
parentFile.mkdirs()
|
||||||
|
if (!exists()) createNewFile()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
val configFile get() = File(FabricLoader.getInstance().configDirectory, "BetterFoliage.json")
|
||||||
|
|
||||||
|
val config = MainConfig().apply {
|
||||||
|
if (configFile.exists()) JanksonSettings().deserialize(fiberNode, configFile.inputStream())
|
||||||
|
else JanksonSettings().serialize(fiberNode, configFile.outputStream(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val blockConfig = BlockConfig()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package mods.betterfoliage
|
|
||||||
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.betterfoliage.client.isAfterPostInit
|
|
||||||
import net.minecraftforge.common.config.Configuration
|
|
||||||
import net.minecraftforge.fml.common.FMLCommonHandler
|
|
||||||
import net.minecraftforge.fml.common.Mod
|
|
||||||
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent
|
|
||||||
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent
|
|
||||||
import net.minecraftforge.fml.common.network.NetworkCheckHandler
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import org.apache.logging.log4j.Level.DEBUG
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
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(
|
|
||||||
modid = BetterFoliageMod.MOD_ID,
|
|
||||||
name = BetterFoliageMod.MOD_NAME,
|
|
||||||
acceptedMinecraftVersions = BetterFoliageMod.MC_VERSIONS,
|
|
||||||
guiFactory = BetterFoliageMod.GUI_FACTORY,
|
|
||||||
dependencies = "after:forgelin",
|
|
||||||
modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter",
|
|
||||||
clientSideOnly = true
|
|
||||||
)
|
|
||||||
object BetterFoliageMod {
|
|
||||||
|
|
||||||
const val MOD_ID = "betterfoliage"
|
|
||||||
const val MOD_NAME = "Better Foliage"
|
|
||||||
const val DOMAIN = "betterfoliage"
|
|
||||||
const val LEGACY_DOMAIN = "bettergrassandleaves"
|
|
||||||
const val MC_VERSIONS = "[1.12]"
|
|
||||||
const val GUI_FACTORY = "mods.betterfoliage.client.gui.ConfigGuiFactory"
|
|
||||||
|
|
||||||
lateinit var log: Logger
|
|
||||||
lateinit var logDetail: Logger
|
|
||||||
|
|
||||||
var config: Configuration? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
// inject pack into default list at construction time to get domains enumerated
|
|
||||||
// there's no 2nd resource reload pass anymore
|
|
||||||
Client.generatorPack.inject()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Mod.EventHandler
|
|
||||||
fun preInit(event: FMLPreInitializationEvent) {
|
|
||||||
log = event.modLog
|
|
||||||
val logDetailFile = File(event.modConfigurationDirectory.parentFile, "logs/betterfoliage.log").apply {
|
|
||||||
parentFile.mkdirs()
|
|
||||||
if (!exists()) createNewFile()
|
|
||||||
}
|
|
||||||
logDetail = SimpleLogger(
|
|
||||||
"BetterFoliage",
|
|
||||||
DEBUG,
|
|
||||||
false, false, true, false,
|
|
||||||
"yyyy-MM-dd HH:mm:ss",
|
|
||||||
null,
|
|
||||||
PropertiesUtil(Properties()),
|
|
||||||
PrintStream(logDetailFile)
|
|
||||||
)
|
|
||||||
config = Configuration(event.suggestedConfigurationFile, null, true)
|
|
||||||
|
|
||||||
Config.attach(config!!)
|
|
||||||
Client.init()
|
|
||||||
Client.log(INFO, "BetterFoliage initialized")
|
|
||||||
isAfterPostInit = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Mod is cosmetic only, always allow connection. */
|
|
||||||
@NetworkCheckHandler
|
|
||||||
fun checkVersion(mods: Map<String, String>, side: Side) = true
|
|
||||||
}
|
|
||||||
29
src/main/kotlin/mods/betterfoliage/CommonRefs.kt
Normal file
29
src/main/kotlin/mods/betterfoliage/CommonRefs.kt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package mods.betterfoliage
|
||||||
|
|
||||||
|
|
||||||
|
// Optifine
|
||||||
|
//val OptifineClassTransformer = ClassRefOld<Any>("optifine.OptiFineClassTransformer")
|
||||||
|
//val BlockPosM = ClassRefOld<Any>("net.optifine.BlockPosM")
|
||||||
|
//object ChunkCacheOF : ClassRefOld<Any>("net.optifine.override.ChunkCacheOF") {
|
||||||
|
// val chunkCache = FieldRefOld(this, "chunkCache", ChunkRendererRegion)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//object RenderEnv : ClassRefOld<Any>("net.optifine.render.RenderEnv") {
|
||||||
|
// val reset = MethodRefOld(this, "reset", void, BlockState, BlockPos)
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Optifine custom colors
|
||||||
|
//val IColorizer = ClassRefOld<Any>("net.optifine.CustomColors\$IColorizer")
|
||||||
|
//object CustomColors : ClassRefOld<Any>("net.optifine.CustomColors") {
|
||||||
|
// val getColorMultiplier = MethodRefOld(this, "getColorMultiplier", int, BakedQuad, BlockState, ExtendedBlockView, BlockPos, RenderEnv)
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Optifine shaders
|
||||||
|
//object SVertexBuilder : ClassRefOld<Any>("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)
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
72
src/main/kotlin/mods/betterfoliage/Events.kt
Normal file
72
src/main/kotlin/mods/betterfoliage/Events.kt
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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<ClientChunkLoadCallback> = 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<ClientWorldLoadCallback> = EventFactory.createArrayBacked(ClientWorldLoadCallback::class.java) { listeners ->
|
||||||
|
object : ClientWorldLoadCallback {
|
||||||
|
override fun loadWorld(world: ClientWorld) { listeners.forEach { it.loadWorld(world) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired after [BlockModels.reload] finishes.
|
||||||
|
*/
|
||||||
|
interface BlockModelsReloadCallback {
|
||||||
|
fun reloadBlockModels(blockModels: BlockModels)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val EVENT: Event<BlockModelsReloadCallback> = 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<ModelLoadingCallback> = EventFactory.createArrayBacked(ModelLoadingCallback::class.java) { listeners ->
|
||||||
|
object : ModelLoadingCallback {
|
||||||
|
override fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) {
|
||||||
|
listeners.forEach { it.beginLoadModels(loader, manager) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/main/kotlin/mods/betterfoliage/Hooks.kt
Normal file
67
src/main/kotlin/mods/betterfoliage/Hooks.kt
Normal file
@@ -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<RoundLogKey>(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<LeafKey>(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)
|
||||||
|
}
|
||||||
55
src/main/kotlin/mods/betterfoliage/chunk/BlockContext.kt
Normal file
55
src/main/kotlin/mods/betterfoliage/chunk/BlockContext.kt
Normal file
@@ -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<BlockState>(6) { world.getBlockState(pos + allDirections[it].offset) }
|
||||||
|
override var biome: Biome = world.getBiome(pos)
|
||||||
|
override fun state(dir: Direction) = neighbors[dir.ordinal]
|
||||||
|
}
|
||||||
50
src/main/kotlin/mods/betterfoliage/chunk/OffsetBlockView.kt
Normal file
50
src/main/kotlin/mods/betterfoliage/chunk/OffsetBlockView.kt
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package mods.betterfoliage.chunk
|
||||||
|
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.minecraft.world.BlockView
|
||||||
|
import net.minecraft.world.ExtendedBlockView
|
||||||
|
import net.minecraft.world.LightType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegating [IBlockAccess] that fakes a _modified_ location to return values from a _target_ location.
|
||||||
|
* All other locations are handled normally.
|
||||||
|
*
|
||||||
|
* @param[original] the [IBlockAccess] that is delegated to
|
||||||
|
*/
|
||||||
|
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
|
||||||
|
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 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 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 getBlockEntity(pos: BlockPos) = original.getBlockEntity(actualPos(pos))
|
||||||
|
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporarily replaces the [IBlockReader] used by this [BlockContext] and the corresponding [ExtendedRenderBlocks]
|
||||||
|
* to use an [OffsetEnvBlockReader] while executing this lambda.
|
||||||
|
*
|
||||||
|
* @param[modded] the _modified_ location
|
||||||
|
* @param[target] the _target_ location
|
||||||
|
* @param[func] the lambda to execute
|
||||||
|
*/
|
||||||
|
//inline fun <reified T> BlockContext.withOffset(modded: Int3, target: Int3, func: () -> T): T {
|
||||||
|
// val original = reader!!
|
||||||
|
// reader = OffsetEnvBlockReader(original, pos + modded, pos + target)
|
||||||
|
// val result = func()
|
||||||
|
// reader = original
|
||||||
|
// return result
|
||||||
|
//}
|
||||||
124
src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt
Normal file
124
src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package mods.betterfoliage.chunk
|
||||||
|
|
||||||
|
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.ExtendedBlockView
|
||||||
|
import net.minecraft.world.ViewableWorld
|
||||||
|
import net.minecraft.world.World
|
||||||
|
import net.minecraft.world.chunk.WorldChunk
|
||||||
|
import net.minecraft.world.dimension.DimensionType
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.List
|
||||||
|
import kotlin.collections.MutableMap
|
||||||
|
import kotlin.collections.associateWith
|
||||||
|
import kotlin.collections.forEach
|
||||||
|
import kotlin.collections.mutableListOf
|
||||||
|
import kotlin.collections.mutableMapOf
|
||||||
|
import kotlin.collections.set
|
||||||
|
|
||||||
|
// net.minecraft.world.chunk.WorldChunk.world
|
||||||
|
val WorldChunk_world = YarnHelper.requiredField<World>("net.minecraft.class_2818", "field_12858", "Lnet/minecraft/class_1937;")
|
||||||
|
// net.minecraft.client.render.chunk.ChunkRendererRegion.world
|
||||||
|
val ChunkRendererRegion_world = YarnHelper.requiredField<World>("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!")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position
|
||||||
|
*/
|
||||||
|
interface ChunkOverlayLayer<T> {
|
||||||
|
fun calculate(ctx: BlockCtx): T
|
||||||
|
fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
|
||||||
|
*/
|
||||||
|
object ChunkOverlayManager : ClientChunkLoadCallback, ClientWorldLoadCallback {
|
||||||
|
|
||||||
|
init {
|
||||||
|
ClientWorldLoadCallback.EVENT.register(this)
|
||||||
|
ClientChunkLoadCallback.EVENT.register(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>()
|
||||||
|
val layers = mutableListOf<ChunkOverlayLayer<*>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the overlay data for a given layer and position
|
||||||
|
*
|
||||||
|
* @param layer Overlay layer to query
|
||||||
|
* @param reader World to use if calculation of overlay value is necessary
|
||||||
|
* @param pos Block position
|
||||||
|
*/
|
||||||
|
fun <T> get(layer: ChunkOverlayLayer<T>, ctx: BlockCtx): T? {
|
||||||
|
val data = chunkData[ctx.world.dimType]?.get(ChunkPos(ctx.pos)) ?: return null
|
||||||
|
data.get(layer, ctx.pos).let { value ->
|
||||||
|
if (value !== ChunkOverlayData.UNCALCULATED) return value
|
||||||
|
val newValue = layer.calculate(ctx)
|
||||||
|
data.set(layer, ctx.pos, newValue)
|
||||||
|
return newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the overlay data for a given layer and position
|
||||||
|
*
|
||||||
|
* @param layer Overlay layer to clear
|
||||||
|
* @param pos Block position
|
||||||
|
*/
|
||||||
|
fun <T> clear(dimension: DimensionType, layer: ChunkOverlayLayer<T>, pos: BlockPos) {
|
||||||
|
chunkData[dimension]?.get(ChunkPos(pos))?.clear(layer, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBlockChange(world: ClientWorld, pos: BlockPos) {
|
||||||
|
if (chunkData[world.dimType]?.containsKey(ChunkPos(pos)) == true) {
|
||||||
|
layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadChunk(chunk: WorldChunk) {
|
||||||
|
chunk[WorldChunk_world]!!.dimType.let { dim ->
|
||||||
|
val data = chunkData[dim] ?: mutableMapOf<ChunkPos, ChunkOverlayData>().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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
|
||||||
|
val BlockPos.isValid: Boolean get() = y in validYRange
|
||||||
|
val rawData = layers.associateWith { emptyOverlay() }
|
||||||
|
fun <T> get(layer: ChunkOverlayLayer<T>, pos: BlockPos): T? = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T? else null
|
||||||
|
fun <T> set(layer: ChunkOverlayLayer<T>, pos: BlockPos, data: T) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data) else null
|
||||||
|
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, UNCALCULATED) else null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val UNCALCULATED = object {}
|
||||||
|
fun emptyOverlay() = Array(16) { Array(16) { Array<Any?>(256) { UNCALCULATED }}}
|
||||||
|
val validYRange = 0 until 256
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
package mods.betterfoliage.client
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
|
||||||
import mods.betterfoliage.client.gui.ConfigGuiFactory
|
|
||||||
import mods.betterfoliage.client.integration.*
|
|
||||||
import mods.betterfoliage.client.render.*
|
|
||||||
import mods.betterfoliage.client.texture.*
|
|
||||||
import mods.octarinecore.client.KeyHandler
|
|
||||||
import mods.octarinecore.client.gui.textComponent
|
|
||||||
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
|
|
||||||
import mods.octarinecore.client.resource.CenteringTextureGenerator
|
|
||||||
import mods.octarinecore.client.resource.GeneratorPack
|
|
||||||
import net.minecraft.block.Block
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.Minecraft
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.util.text.TextComponentTranslation
|
|
||||||
import net.minecraft.util.text.TextFormatting
|
|
||||||
import net.minecraftforge.fml.client.FMLClientHandler
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* This and all other singletons are annotated [SideOnly] to avoid someone accidentally partially
|
|
||||||
* initializing the mod on a server environment.
|
|
||||||
*/
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object Client {
|
|
||||||
|
|
||||||
lateinit var renderers: List<AbstractBlockRenderingHandler>
|
|
||||||
val suppressRenderErrors = mutableSetOf<IBlockState>()
|
|
||||||
|
|
||||||
// texture generation stuff
|
|
||||||
val genGrass = GrassGenerator("bf_gen_grass")
|
|
||||||
val genLeaves = LeafGenerator("bf_gen_leaves")
|
|
||||||
val genReeds = CenteringTextureGenerator("bf_gen_reeds", 1, 2)
|
|
||||||
|
|
||||||
val generatorPack = GeneratorPack(
|
|
||||||
"Better Foliage generated",
|
|
||||||
genGrass,
|
|
||||||
genLeaves,
|
|
||||||
genReeds
|
|
||||||
)
|
|
||||||
|
|
||||||
fun init() {
|
|
||||||
// init renderers
|
|
||||||
renderers = listOf(
|
|
||||||
RenderGrass(),
|
|
||||||
RenderMycelium(),
|
|
||||||
RenderLeaves(),
|
|
||||||
RenderCactus(),
|
|
||||||
RenderLilypad(),
|
|
||||||
RenderReeds(),
|
|
||||||
RenderAlgae(),
|
|
||||||
RenderCoral(),
|
|
||||||
RenderLog(),
|
|
||||||
RenderNetherrack(),
|
|
||||||
RenderConnectedGrass(),
|
|
||||||
RenderConnectedGrassLog()
|
|
||||||
)
|
|
||||||
|
|
||||||
// init other singletons
|
|
||||||
val singletons = listOf(
|
|
||||||
StandardCactusRegistry,
|
|
||||||
LeafParticleRegistry,
|
|
||||||
ChunkOverlayManager,
|
|
||||||
LeafWindTracker,
|
|
||||||
RisingSoulTextures
|
|
||||||
)
|
|
||||||
|
|
||||||
// init mod integrations
|
|
||||||
val integrations = listOf(
|
|
||||||
ShadersModIntegration,
|
|
||||||
OptifineCustomColors,
|
|
||||||
ForestryIntegration,
|
|
||||||
IC2RubberIntegration,
|
|
||||||
TechRebornRubberIntegration
|
|
||||||
)
|
|
||||||
|
|
||||||
// add basic block support instances as last
|
|
||||||
GrassRegistry.addRegistry(StandardGrassRegistry)
|
|
||||||
LeafRegistry.addRegistry(StandardLeafRegistry)
|
|
||||||
LogRegistry.addRegistry(StandardLogRegistry)
|
|
||||||
|
|
||||||
// init config hotkey
|
|
||||||
val configKey = KeyHandler(BetterFoliageMod.MOD_NAME, 66, "key.betterfoliage.gui") {
|
|
||||||
FMLClientHandler.instance().showGuiScreen(
|
|
||||||
ConfigGuiFactory.createBFConfigGui(Minecraft.getMinecraft().currentScreen)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun log(level: Level, msg: String) {
|
|
||||||
BetterFoliageMod.log.log(level, "[BetterFoliage] $msg")
|
|
||||||
BetterFoliageMod.logDetail.log(level, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun logDetail(msg: String) {
|
|
||||||
BetterFoliageMod.logDetail.log(Level.DEBUG, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun logRenderError(state: IBlockState, location: BlockPos) {
|
|
||||||
if (state in suppressRenderErrors) return
|
|
||||||
suppressRenderErrors.add(state)
|
|
||||||
|
|
||||||
val blockName = Block.REGISTRY.getNameForObject(state.block).toString()
|
|
||||||
val blockLoc = "${location.x},${location.y},${location.z}"
|
|
||||||
Minecraft.getMinecraft().ingameGUI.chatGUI.printChatMessage(TextComponentTranslation(
|
|
||||||
"betterfoliage.rendererror",
|
|
||||||
textComponent(blockName, TextFormatting.GOLD),
|
|
||||||
textComponent(blockLoc, TextFormatting.GOLD)
|
|
||||||
))
|
|
||||||
logDetail("Error rendering block $state at $blockLoc")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
@file:JvmName("Hooks")
|
|
||||||
@file:SideOnly(Side.CLIENT)
|
|
||||||
package mods.betterfoliage.client
|
|
||||||
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.betterfoliage.client.render.*
|
|
||||||
import mods.betterfoliage.loader.Refs
|
|
||||||
import mods.octarinecore.client.render.blockContext
|
|
||||||
import mods.octarinecore.client.resource.LoadModelDataEvent
|
|
||||||
import mods.octarinecore.common.plus
|
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
|
||||||
import net.minecraft.block.Block
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.Minecraft
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.init.Blocks
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.BlockRenderLayer.CUTOUT
|
|
||||||
import net.minecraft.util.BlockRenderLayer.CUTOUT_MIPPED
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraft.world.World
|
|
||||||
import net.minecraftforge.client.model.ModelLoader
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
|
|
||||||
var isAfterPostInit = false
|
|
||||||
val isOptifinePresent = allAvailable(Refs.OptifineClassTransformer)
|
|
||||||
|
|
||||||
fun doesSideBlockRenderingOverride(original: Boolean, blockAccess: IBlockAccess, pos: BlockPos, side: EnumFacing): Boolean {
|
|
||||||
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(blockAccess.getBlockState(pos).block));
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isOpaqueCubeOverride(original: Boolean, state: IBlockState): Boolean {
|
|
||||||
// caution: blocks are initialized and the method called during startup
|
|
||||||
if (!isAfterPostInit) return original
|
|
||||||
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAmbientOcclusionLightValueOverride(original: Float, state: IBlockState): Float {
|
|
||||||
if (Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block)) return Config.roundLogs.dimming;
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUseNeighborBrightnessOverride(original: Boolean, state: IBlockState): Boolean {
|
|
||||||
return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block));
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) {
|
|
||||||
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 &&
|
|
||||||
Config.blocks.leavesClasses.matchesClass(state.block) &&
|
|
||||||
world.isAirBlock(pos + down1) &&
|
|
||||||
Math.random() < Config.fallingLeaves.chance) {
|
|
||||||
EntityFallingLeavesFX(world, pos).addIfValid()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onAfterLoadModelDefinitions(loader: ModelLoader) {
|
|
||||||
MinecraftForge.EVENT_BUS.post(LoadModelDataEvent(loader))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun renderWorldBlock(dispatcher: BlockRendererDispatcher,
|
|
||||||
state: IBlockState,
|
|
||||||
pos: BlockPos,
|
|
||||||
blockAccess: IBlockAccess,
|
|
||||||
worldRenderer: BufferBuilder,
|
|
||||||
layer: BlockRenderLayer
|
|
||||||
): Boolean {
|
|
||||||
val doBaseRender = state.canRenderInLayer(layer) || (layer == targetCutoutLayer && state.canRenderInLayer(otherCutoutLayer))
|
|
||||||
blockContext.let { ctx ->
|
|
||||||
ctx.set(blockAccess, pos)
|
|
||||||
Client.renderers.forEach { renderer ->
|
|
||||||
if (renderer.isEligible(ctx)) {
|
|
||||||
// render on the block's default layer
|
|
||||||
// also render on the cutout layer if the renderer requires it
|
|
||||||
if (doBaseRender || (renderer.addToCutout && layer == targetCutoutLayer)) {
|
|
||||||
return renderer.render(ctx, dispatcher, worldRenderer, layer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (doBaseRender) dispatcher.renderBlock(state, pos, blockAccess, worldRenderer) else false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun canRenderBlockInLayer(block: Block, state: IBlockState, layer: BlockRenderLayer) = block.canRenderInLayer(state, layer) || layer == targetCutoutLayer
|
|
||||||
|
|
||||||
val targetCutoutLayer: BlockRenderLayer get() = if (Minecraft.getMinecraft().gameSettings.mipmapLevels > 0) CUTOUT_MIPPED else CUTOUT
|
|
||||||
val otherCutoutLayer: BlockRenderLayer get() = if (Minecraft.getMinecraft().gameSettings.mipmapLevels > 0) CUTOUT else CUTOUT_MIPPED
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
package mods.betterfoliage.client.chunk
|
|
||||||
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.multiplayer.WorldClient
|
|
||||||
import net.minecraft.entity.Entity
|
|
||||||
import net.minecraft.entity.player.EntityPlayer
|
|
||||||
import net.minecraft.util.SoundCategory
|
|
||||||
import net.minecraft.util.SoundEvent
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.util.math.ChunkPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraft.world.IWorldEventListener
|
|
||||||
import net.minecraft.world.World
|
|
||||||
import net.minecraft.world.chunk.EmptyChunk
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.event.world.ChunkEvent
|
|
||||||
import net.minecraftforge.event.world.WorldEvent
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position
|
|
||||||
*/
|
|
||||||
interface ChunkOverlayLayer<T> {
|
|
||||||
abstract fun calculate(world: IBlockAccess, pos: BlockPos): T
|
|
||||||
abstract fun onBlockUpdate(world: IBlockAccess, pos: BlockPos)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
|
|
||||||
*/
|
|
||||||
object ChunkOverlayManager : IBlockUpdateListener {
|
|
||||||
init {
|
|
||||||
Client.log(Level.INFO, "Initializing client overlay manager")
|
|
||||||
MinecraftForge.EVENT_BUS.register(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
val chunkData = mutableMapOf<ChunkPos, ChunkOverlayData>()
|
|
||||||
val layers = mutableListOf<ChunkOverlayLayer<*>>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the overlay data for a given layer and position
|
|
||||||
*
|
|
||||||
* @param layer Overlay layer to query
|
|
||||||
* @param world World to use if calculation of overlay value is necessary
|
|
||||||
* @param pos Block position
|
|
||||||
*/
|
|
||||||
fun <T> get(layer: ChunkOverlayLayer<T>, world: IBlockAccess, pos: BlockPos): T? {
|
|
||||||
val data = chunkData[ChunkPos(pos)] ?: return null
|
|
||||||
data.get(layer, pos).let { value ->
|
|
||||||
if (value !== ChunkOverlayData.UNCALCULATED) return value
|
|
||||||
val newValue = layer.calculate(world, pos)
|
|
||||||
data.set(layer, pos, newValue)
|
|
||||||
return newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the overlay data for a given layer and position
|
|
||||||
*
|
|
||||||
* @param layer Overlay layer to clear
|
|
||||||
* @param pos Block position
|
|
||||||
*/
|
|
||||||
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) {
|
|
||||||
chunkData[ChunkPos(pos)]?.clear(layer, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun notifyBlockUpdate(world: World, pos: BlockPos, oldState: IBlockState, newState: IBlockState, flags: Int) {
|
|
||||||
if (chunkData.containsKey(ChunkPos(pos))) layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handleLoadWorld(event: WorldEvent.Load) {
|
|
||||||
if (event.world is WorldClient) {
|
|
||||||
event.world.addEventListener(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handleLoadChunk(event: ChunkEvent.Load) {
|
|
||||||
if (event.world is WorldClient && event.chunk !is EmptyChunk) {
|
|
||||||
chunkData[event.chunk.pos] = ChunkOverlayData(layers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handleUnloadChunk(event: ChunkEvent.Unload) {
|
|
||||||
if (event.world is WorldClient) {
|
|
||||||
chunkData.remove(event.chunk.pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
|
|
||||||
val BlockPos.isValid: Boolean get() = y in validYRange
|
|
||||||
val rawData = layers.associateWith { emptyOverlay() }
|
|
||||||
fun <T> get(layer: ChunkOverlayLayer<T>, pos: BlockPos): T? = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T? else null
|
|
||||||
fun <T> set(layer: ChunkOverlayLayer<T>, pos: BlockPos, data: T) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data) else null
|
|
||||||
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, UNCALCULATED) else null
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val UNCALCULATED = object {}
|
|
||||||
fun emptyOverlay() = Array(16) { Array(16) { Array<Any?>(256) { UNCALCULATED }}}
|
|
||||||
val validYRange = 0 until 256
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IWorldEventListener helper subclass
|
|
||||||
* No-op for everything except notifyBlockUpdate()
|
|
||||||
*/
|
|
||||||
interface IBlockUpdateListener : IWorldEventListener {
|
|
||||||
override fun playSoundToAllNearExcept(player: EntityPlayer?, soundIn: SoundEvent, category: SoundCategory, x: Double, y: Double, z: Double, volume: Float, pitch: Float) {}
|
|
||||||
override fun onEntityAdded(entityIn: Entity) {}
|
|
||||||
override fun broadcastSound(soundID: Int, pos: BlockPos, data: Int) {}
|
|
||||||
override fun playEvent(player: EntityPlayer?, type: Int, blockPosIn: BlockPos, data: Int) {}
|
|
||||||
override fun onEntityRemoved(entityIn: Entity) {}
|
|
||||||
override fun notifyLightSet(pos: BlockPos) {}
|
|
||||||
override fun spawnParticle(particleID: Int, ignoreRange: Boolean, xCoord: Double, yCoord: Double, zCoord: Double, xSpeed: Double, ySpeed: Double, zSpeed: Double, vararg parameters: Int) {}
|
|
||||||
override fun spawnParticle(id: Int, ignoreRange: Boolean, minimiseParticleLevel: Boolean, x: Double, y: Double, z: Double, xSpeed: Double, ySpeed: Double, zSpeed: Double, vararg parameters: Int) {}
|
|
||||||
override fun playRecord(soundIn: SoundEvent, pos: BlockPos) {}
|
|
||||||
override fun sendBlockBreakProgress(breakerId: Int, pos: BlockPos, progress: Int) {}
|
|
||||||
override fun markBlockRangeForRenderUpdate(x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int) {}
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
package mods.betterfoliage.client.config
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.gui.BiomeListConfigEntry
|
|
||||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
|
||||||
import mods.octarinecore.common.config.*
|
|
||||||
import net.minecraft.client.Minecraft
|
|
||||||
import net.minecraft.world.biome.Biome
|
|
||||||
import net.minecraftforge.fml.client.event.ConfigChangedEvent
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.lwjgl.opengl.GL11
|
|
||||||
|
|
||||||
// BetterFoliage-specific property delegates
|
|
||||||
private val OBSOLETE = ObsoleteConfigProperty()
|
|
||||||
private fun featureEnable() = boolean(true).lang("enabled")
|
|
||||||
fun biomeList(defaults: (Biome) -> Boolean) = intList {
|
|
||||||
Biome.REGISTRY
|
|
||||||
.filter { it != null && defaults(it) }
|
|
||||||
.map { Biome.REGISTRY.getIDForObject(it) }
|
|
||||||
.toTypedArray()
|
|
||||||
}.apply { guiClass = BiomeListConfigEntry::class.java }
|
|
||||||
|
|
||||||
// Biome filter methods
|
|
||||||
private fun Biome.filterTemp(min: Float?, max: Float?) = (min == null || min <= defaultTemperature) && (max == null || max >= defaultTemperature)
|
|
||||||
private fun Biome.filterRain(min: Float?, max: Float?) = (min == null || min <= rainfall) && (max == null || max >= rainfall)
|
|
||||||
private fun Biome.filterClass(vararg name: String) = name.any { it in this.javaClass.name.toLowerCase() }
|
|
||||||
|
|
||||||
// Config singleton
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAIN) {
|
|
||||||
|
|
||||||
var enabled by boolean(true)
|
|
||||||
var nVidia by boolean(GL11.glGetString(GL11.GL_VENDOR).toLowerCase().contains("nvidia"))
|
|
||||||
|
|
||||||
object shaders {
|
|
||||||
val leavesId by long(min = 1, max = 65535, default = ShadersModIntegration.leavesDefaultBlockId.toInt())
|
|
||||||
val grassId by long(min = 1, max = 65535, default = ShadersModIntegration.grassDefaultBlockId.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
object blocks {
|
|
||||||
val leavesClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "leaves_blocks_default.cfg")
|
|
||||||
val leavesModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "leaves_models_default.cfg", 1)
|
|
||||||
val grassClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "grass_blocks_default.cfg")
|
|
||||||
val grassModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "grass_models_default.cfg", 1)
|
|
||||||
val mycelium = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "mycelium_blocks_default.cfg")
|
|
||||||
val dirt = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "dirt_default.cfg")
|
|
||||||
val crops = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "crop_default.cfg")
|
|
||||||
val logClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "log_blocks_default.cfg")
|
|
||||||
val logModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "log_models_default.cfg", 3)
|
|
||||||
val sand = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "sand_default.cfg")
|
|
||||||
val lilypad = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "lilypad_default.cfg")
|
|
||||||
val cactus = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "cactus_default.cfg")
|
|
||||||
val netherrack = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "netherrack_blocks_default.cfg")
|
|
||||||
|
|
||||||
val leavesWhitelist = OBSOLETE
|
|
||||||
val leavesBlacklist = OBSOLETE
|
|
||||||
val grassWhitelist = OBSOLETE
|
|
||||||
val grassBlacklist = OBSOLETE
|
|
||||||
val logsWhitelist = OBSOLETE
|
|
||||||
val logsBlacklist = OBSOLETE
|
|
||||||
}
|
|
||||||
|
|
||||||
object leaves {
|
|
||||||
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 {
|
|
||||||
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 hangingGrass {
|
|
||||||
// var enabled by featureEnable()
|
|
||||||
// var distance by distanceLimit()
|
|
||||||
// var size by double(min=0.25, max=1.5, default=0.75).lang("size")
|
|
||||||
// var separation by double(max=0.5, default=0.25)
|
|
||||||
// }
|
|
||||||
|
|
||||||
object connectedGrass {
|
|
||||||
val enabled by boolean(true)
|
|
||||||
val snowEnabled by boolean(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
object roundLogs {
|
|
||||||
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 float(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 {
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
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 biomes by biomeList { it.filterTemp(0.4f, null) && it.filterRain(0.4f, null) }
|
|
||||||
val shaderWind by boolean(true).lang("shaderWind")
|
|
||||||
}
|
|
||||||
|
|
||||||
object algae {
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
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 float(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 float(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
val forceReloadOptions = listOf(
|
|
||||||
blocks.leavesClasses,
|
|
||||||
blocks.leavesModels,
|
|
||||||
blocks.grassClasses,
|
|
||||||
blocks.grassModels,
|
|
||||||
shortGrass["saturationThreshold"]!!
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun onChange(event: ConfigChangedEvent.PostConfigChangedEvent) {
|
|
||||||
if (hasChanged(forceReloadOptions))
|
|
||||||
Minecraft.getMinecraft().refreshResources()
|
|
||||||
else
|
|
||||||
Minecraft.getMinecraft().renderGlobal.loadRenderers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui
|
|
||||||
|
|
||||||
import mods.octarinecore.client.gui.IdListConfigEntry
|
|
||||||
import net.minecraft.world.biome.Biome
|
|
||||||
import net.minecraftforge.fml.client.config.GuiConfig
|
|
||||||
import net.minecraftforge.fml.client.config.GuiConfigEntries
|
|
||||||
import net.minecraftforge.fml.client.config.IConfigElement
|
|
||||||
|
|
||||||
/** Toggleable list of all defined biomes. */
|
|
||||||
class BiomeListConfigEntry(
|
|
||||||
owningScreen: GuiConfig,
|
|
||||||
owningEntryList: GuiConfigEntries,
|
|
||||||
configElement: IConfigElement)
|
|
||||||
: IdListConfigEntry<Biome>(owningScreen, owningEntryList, configElement) {
|
|
||||||
|
|
||||||
override val baseSet: List<Biome> get() = Biome.REGISTRY.filterNotNull()
|
|
||||||
override val Biome.itemId: Int get() = Biome.REGISTRY.getIDForObject(this)
|
|
||||||
override val Biome.itemName: String get() = this.biomeName
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import net.minecraft.client.Minecraft
|
|
||||||
import net.minecraft.client.gui.GuiScreen
|
|
||||||
import net.minecraftforge.fml.client.IModGuiFactory
|
|
||||||
import net.minecraftforge.fml.client.config.GuiConfig
|
|
||||||
|
|
||||||
class ConfigGuiFactory : IModGuiFactory {
|
|
||||||
|
|
||||||
override fun initialize(minecraftInstance: Minecraft?) { }
|
|
||||||
override fun hasConfigGui() = true
|
|
||||||
override fun runtimeGuiCategories() = hashSetOf<IModGuiFactory.RuntimeOptionCategoryElement>()
|
|
||||||
override fun createConfigGui(parentScreen: GuiScreen?) = createBFConfigGui(parentScreen)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun createBFConfigGui(parentScreen: GuiScreen?) = GuiConfig(
|
|
||||||
parentScreen,
|
|
||||||
Config.rootGuiElements,
|
|
||||||
BetterFoliageMod.MOD_ID,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
BetterFoliageMod.MOD_NAME
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
package mods.betterfoliage.client.integration
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.betterfoliage.client.render.LogRegistry
|
|
||||||
import mods.betterfoliage.client.render.StandardLogRegistry
|
|
||||||
import mods.betterfoliage.client.render.column.ColumnTextureInfo
|
|
||||||
import mods.betterfoliage.client.render.column.SimpleColumnInfo
|
|
||||||
import mods.betterfoliage.client.texture.LeafInfo
|
|
||||||
import mods.betterfoliage.client.texture.LeafRegistry
|
|
||||||
import mods.betterfoliage.client.texture.StandardLeafKey
|
|
||||||
import mods.betterfoliage.loader.Refs
|
|
||||||
import mods.octarinecore.client.resource.ModelRenderKey
|
|
||||||
import mods.octarinecore.client.resource.ModelRenderRegistry
|
|
||||||
import mods.octarinecore.client.resource.ModelRenderRegistryBase
|
|
||||||
import mods.octarinecore.getTileEntitySafe
|
|
||||||
import mods.octarinecore.metaprog.ClassRef
|
|
||||||
import mods.octarinecore.metaprog.FieldRef
|
|
||||||
import mods.octarinecore.metaprog.MethodRef
|
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.Minecraft
|
|
||||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation
|
|
||||||
import net.minecraft.util.ResourceLocation
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent
|
|
||||||
import net.minecraftforge.client.model.IModel
|
|
||||||
import net.minecraftforge.fml.common.Loader
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.EventPriority
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
import kotlin.collections.Map
|
|
||||||
import kotlin.collections.component1
|
|
||||||
import kotlin.collections.component2
|
|
||||||
import kotlin.collections.emptyMap
|
|
||||||
import kotlin.collections.find
|
|
||||||
import kotlin.collections.forEach
|
|
||||||
import kotlin.collections.get
|
|
||||||
import kotlin.collections.listOf
|
|
||||||
import kotlin.collections.mapValues
|
|
||||||
import kotlin.collections.mutableMapOf
|
|
||||||
import kotlin.collections.set
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object ForestryIntegration {
|
|
||||||
|
|
||||||
val TextureLeaves = ClassRef("forestry.arboriculture.models.TextureLeaves")
|
|
||||||
val TeLleafTextures = FieldRef(TextureLeaves, "leafTextures", Refs.Map)
|
|
||||||
val TeLplain = FieldRef(TextureLeaves, "plain", Refs.ResourceLocation)
|
|
||||||
val TeLfancy = FieldRef(TextureLeaves, "fancy", Refs.ResourceLocation)
|
|
||||||
val TeLpollplain = FieldRef(TextureLeaves, "pollinatedPlain", Refs.ResourceLocation)
|
|
||||||
val TeLpollfancy = FieldRef(TextureLeaves, "pollinatedFancy", Refs.ResourceLocation)
|
|
||||||
val TileLeaves = ClassRef("forestry.arboriculture.tiles.TileLeaves")
|
|
||||||
val TiLgetLeaveSprite = MethodRef(TileLeaves, "getLeaveSprite", Refs.ResourceLocation, ClassRef.boolean)
|
|
||||||
|
|
||||||
val PropertyWoodType = ClassRef("forestry.arboriculture.blocks.PropertyWoodType")
|
|
||||||
val IWoodType = ClassRef("forestry.api.arboriculture.IWoodType")
|
|
||||||
val barkTex = MethodRef(IWoodType, "getBarkTexture", Refs.String)
|
|
||||||
val heartTex = MethodRef(IWoodType, "getHeartTexture", Refs.String)
|
|
||||||
|
|
||||||
val PropertyTreeType = ClassRef("forestry.arboriculture.blocks.PropertyTreeType")
|
|
||||||
val TreeDefinition = ClassRef("forestry.arboriculture.genetics.TreeDefinition")
|
|
||||||
val IAlleleTreeSpecies = ClassRef("forestry.api.arboriculture.IAlleleTreeSpecies")
|
|
||||||
val ILeafSpriteProvider = ClassRef("forestry.api.arboriculture.ILeafSpriteProvider")
|
|
||||||
val TdSpecies = FieldRef(TreeDefinition, "species", IAlleleTreeSpecies)
|
|
||||||
val getLeafSpriteProvider = MethodRef(IAlleleTreeSpecies, "getLeafSpriteProvider", ILeafSpriteProvider)
|
|
||||||
val getSprite = MethodRef(ILeafSpriteProvider, "getSprite", Refs.ResourceLocation, ClassRef.boolean, ClassRef.boolean)
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (Loader.isModLoaded("forestry") && allAvailable(TiLgetLeaveSprite, getLeafSpriteProvider, getSprite)) {
|
|
||||||
Client.log(Level.INFO, "Forestry support initialized")
|
|
||||||
LeafRegistry.addRegistry(ForestryLeafRegistry)
|
|
||||||
LogRegistry.addRegistry(ForestryLogRegistry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object ForestryLeafRegistry : ModelRenderRegistry<LeafInfo> {
|
|
||||||
val logger = BetterFoliageMod.logDetail
|
|
||||||
val textureToKey = mutableMapOf<ResourceLocation, ModelRenderKey<LeafInfo>>()
|
|
||||||
var textureToValue = emptyMap<ResourceLocation, LeafInfo>()
|
|
||||||
|
|
||||||
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos): LeafInfo? {
|
|
||||||
// check variant property (used in decorative leaves)
|
|
||||||
state.properties.entries.find {
|
|
||||||
ForestryIntegration.PropertyTreeType.isInstance(it.key) && ForestryIntegration.TreeDefinition.isInstance(it.value)
|
|
||||||
} ?.let {
|
|
||||||
val species = ForestryIntegration.TdSpecies.get(it.value)
|
|
||||||
val spriteProvider = ForestryIntegration.getLeafSpriteProvider.invoke(species!!)
|
|
||||||
val textureLoc = ForestryIntegration.getSprite.invoke(spriteProvider!!, false, Minecraft.isFancyGraphicsEnabled())
|
|
||||||
return textureToValue[textureLoc]
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract leaf texture information from TileEntity
|
|
||||||
val tile = world.getTileEntitySafe(pos) ?: return null
|
|
||||||
if (!ForestryIntegration.TileLeaves.isInstance(tile)) return null
|
|
||||||
val textureLoc = ForestryIntegration.TiLgetLeaveSprite.invoke(tile, Minecraft.isFancyGraphicsEnabled()) ?: return null
|
|
||||||
return textureToValue[textureLoc]
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handlePreStitch(event: TextureStitchEvent.Pre) {
|
|
||||||
textureToValue = emptyMap()
|
|
||||||
|
|
||||||
val allLeaves = ForestryIntegration.TeLleafTextures.getStatic() as Map<*, *>
|
|
||||||
allLeaves.entries.forEach {
|
|
||||||
logger.log(Level.DEBUG, "ForestryLeavesSupport: base leaf type ${it.key.toString()}")
|
|
||||||
listOf(
|
|
||||||
ForestryIntegration.TeLplain.get(it.value) as ResourceLocation,
|
|
||||||
ForestryIntegration.TeLfancy.get(it.value) as ResourceLocation,
|
|
||||||
ForestryIntegration.TeLpollplain.get(it.value) as ResourceLocation,
|
|
||||||
ForestryIntegration.TeLpollfancy.get(it.value) as ResourceLocation
|
|
||||||
).forEach { textureLocation ->
|
|
||||||
val key = StandardLeafKey(logger, textureLocation.toString()).apply { onPreStitch(event.map) }
|
|
||||||
textureToKey[textureLocation] = key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent(priority = EventPriority.LOW)
|
|
||||||
fun handlePostStitch(event: TextureStitchEvent.Post) {
|
|
||||||
textureToValue = textureToKey.mapValues { (_, key) -> key.resolveSprites(event.map) }
|
|
||||||
textureToKey.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object ForestryLogRegistry : ModelRenderRegistryBase<ColumnTextureInfo>() {
|
|
||||||
override val logger = BetterFoliageMod.logDetail
|
|
||||||
|
|
||||||
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
|
|
||||||
// respect class list to avoid triggering on fences, stairs, etc.
|
|
||||||
if (!Config.blocks.logClasses.matchesClass(state.block)) return null
|
|
||||||
|
|
||||||
// find wood type property
|
|
||||||
val woodType = state.properties.entries.find {
|
|
||||||
ForestryIntegration.PropertyWoodType.isInstance(it.key) && ForestryIntegration.IWoodType.isInstance(it.value)
|
|
||||||
} ?: return null
|
|
||||||
|
|
||||||
logger.log(Level.DEBUG, "ForestryLogRegistry: block state $state")
|
|
||||||
logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}")
|
|
||||||
|
|
||||||
// get texture names for wood type
|
|
||||||
val bark = ForestryIntegration.barkTex.invoke(woodType.value) as String?
|
|
||||||
val heart = ForestryIntegration.heartTex.invoke(woodType.value) as String?
|
|
||||||
|
|
||||||
logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]")
|
|
||||||
if (bark != null && heart != null) return SimpleColumnInfo.Key(logger, StandardLogRegistry.getAxis(state), listOf(heart, heart, bark))
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package mods.betterfoliage.client.integration
|
|
||||||
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.loader.Refs
|
|
||||||
import mods.octarinecore.ThreadLocalDelegate
|
|
||||||
import mods.octarinecore.client.render.BlockContext
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
|
||||||
import mods.octarinecore.metaprog.reflectField
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.Minecraft
|
|
||||||
import net.minecraft.client.renderer.block.model.BakedQuad
|
|
||||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration for OptiFine custom block colors.
|
|
||||||
*/
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object OptifineCustomColors {
|
|
||||||
|
|
||||||
val isColorAvailable = allAvailable(
|
|
||||||
Refs.CustomColors, Refs.getColorMultiplier
|
|
||||||
)
|
|
||||||
|
|
||||||
init {
|
|
||||||
Client.log(Level.INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
|
|
||||||
}
|
|
||||||
|
|
||||||
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }
|
|
||||||
val fakeQuad = BakedQuad(IntArray(0), 1, EnumFacing.UP, null, true, DefaultVertexFormats.BLOCK)
|
|
||||||
|
|
||||||
fun getBlockColor(ctx: BlockContext): Int {
|
|
||||||
val ofColor = if (isColorAvailable && Minecraft.getMinecraft().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
|
|
||||||
renderEnv.reset(ctx.blockState(Int3.zero), ctx.pos)
|
|
||||||
Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, renderEnv.wrapped) as? Int
|
|
||||||
} else null
|
|
||||||
return if (ofColor == null || ofColor == -1) ctx.blockData(Int3.zero).color else ofColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class OptifineRenderEnv {
|
|
||||||
val wrapped: Any = Refs.RenderEnv.element!!.getDeclaredConstructor(
|
|
||||||
Refs.IBlockState.element, Refs.BlockPos.element
|
|
||||||
).let {
|
|
||||||
it.isAccessible = true
|
|
||||||
it.newInstance(null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun reset(state: IBlockState, pos: BlockPos) {
|
|
||||||
Refs.RenderEnv_reset.invoke(wrapped, state, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
package mods.betterfoliage.client.integration
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
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.Quad
|
|
||||||
import mods.octarinecore.client.render.QuadIconResolver
|
|
||||||
import mods.octarinecore.client.render.ShadingContext
|
|
||||||
import mods.octarinecore.client.render.blockContext
|
|
||||||
import mods.octarinecore.client.resource.*
|
|
||||||
import mods.octarinecore.common.rotate
|
|
||||||
import mods.octarinecore.metaprog.ClassRef
|
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.ResourceLocation
|
|
||||||
import net.minecraftforge.client.model.IModel
|
|
||||||
import net.minecraftforge.fml.common.Loader
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object IC2RubberIntegration {
|
|
||||||
|
|
||||||
val BlockRubWood = ClassRef("ic2.core.block.BlockRubWood")
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (Loader.isModLoaded("ic2") && allAvailable(BlockRubWood)) {
|
|
||||||
Client.log(Level.INFO, "IC2 rubber support initialized")
|
|
||||||
LogRegistry.addRegistry(IC2LogSupport)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object TechRebornRubberIntegration {
|
|
||||||
|
|
||||||
val BlockRubberLog = ClassRef("techreborn.blocks.BlockRubberLog")
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (Loader.isModLoaded("techreborn") && allAvailable(BlockRubberLog)) {
|
|
||||||
Client.log(Level.INFO, "TechReborn rubber support initialized")
|
|
||||||
LogRegistry.addRegistry(TechRebornLogSupport)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RubberLogInfo(
|
|
||||||
axis: EnumFacing.Axis?,
|
|
||||||
val spotDir: EnumFacing,
|
|
||||||
topTexture: TextureAtlasSprite,
|
|
||||||
bottomTexture: TextureAtlasSprite,
|
|
||||||
val spotTexture: TextureAtlasSprite,
|
|
||||||
sideTextures: List<TextureAtlasSprite>
|
|
||||||
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
|
|
||||||
|
|
||||||
override val side: QuadIconResolver = { ctx: ShadingContext, idx: Int, quad: Quad ->
|
|
||||||
val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
|
|
||||||
if (worldFace == spotDir) spotTexture else {
|
|
||||||
val sideIdx = if (this.sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
|
|
||||||
this.sideTextures[sideIdx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Key(override val logger: Logger, val axis: EnumFacing.Axis?, val spotDir: EnumFacing, val textures: List<String>): ModelRenderKey<ColumnTextureInfo> {
|
|
||||||
override fun resolveSprites(atlas: TextureMap) = RubberLogInfo(
|
|
||||||
axis,
|
|
||||||
spotDir,
|
|
||||||
atlas[textures[0]] ?: atlas.missingSprite,
|
|
||||||
atlas[textures[1]] ?: atlas.missingSprite,
|
|
||||||
atlas[textures[2]] ?: atlas.missingSprite,
|
|
||||||
textures.drop(3).map { atlas[it] ?: atlas.missingSprite }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object IC2LogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
|
|
||||||
override val logger = BetterFoliageMod.logDetail
|
|
||||||
|
|
||||||
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
|
|
||||||
// check for proper block class, existence of ModelBlock, and "state" blockstate property
|
|
||||||
if (!IC2RubberIntegration.BlockRubWood.isInstance(state.block)) return null
|
|
||||||
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
|
|
||||||
val type = state.properties.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
|
|
||||||
|
|
||||||
// logs with no rubber spot
|
|
||||||
if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) {
|
|
||||||
val axis = when(type) {
|
|
||||||
"plain_y" -> EnumFacing.Axis.Y
|
|
||||||
"plain_x" -> EnumFacing.Axis.X
|
|
||||||
"plain_z" -> EnumFacing.Axis.Z
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
|
|
||||||
if (textureNames.any { it == "missingno" }) return null
|
|
||||||
logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}")
|
|
||||||
logger.log(Level.DEBUG, "IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[2]}")
|
|
||||||
return SimpleColumnInfo.Key(logger, axis, textureNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
// logs with rubber spot
|
|
||||||
val spotDir = when(type) {
|
|
||||||
"dry_north", "wet_north" -> EnumFacing.NORTH
|
|
||||||
"dry_south", "wet_south" -> EnumFacing.SOUTH
|
|
||||||
"dry_west", "wet_west" -> EnumFacing.WEST
|
|
||||||
"dry_east", "wet_east" -> EnumFacing.EAST
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTextureName(it) }
|
|
||||||
if (textureNames.any { it == "missingno" }) return null
|
|
||||||
logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}")
|
|
||||||
logger.log(Level.DEBUG, "IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
|
|
||||||
return if (spotDir != null) RubberLogInfo.Key(logger, EnumFacing.Axis.Y, spotDir, textureNames) else SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object TechRebornLogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
|
|
||||||
override val logger = BetterFoliageMod.logDetail
|
|
||||||
|
|
||||||
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
|
|
||||||
// check for proper block class, existence of ModelBlock
|
|
||||||
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(state.block)) return null
|
|
||||||
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
|
|
||||||
|
|
||||||
val hasSap = state.properties.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
|
|
||||||
val sapSide = state.properties.entries.find { it.key.getName() == "sapside" }?.value as? EnumFacing ?: return null
|
|
||||||
|
|
||||||
logger.log(Level.DEBUG, "$logName: block state $state")
|
|
||||||
if (hasSap) {
|
|
||||||
val textureNames = listOf("end", "end", "sapside", "side").map { blockLoc.first.resolveTextureName(it) }
|
|
||||||
logger.log(Level.DEBUG, "$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
|
|
||||||
if (textureNames.all { it != "missingno" }) return RubberLogInfo.Key(logger, EnumFacing.Axis.Y, sapSide, textureNames)
|
|
||||||
} else {
|
|
||||||
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
|
|
||||||
logger.log(Level.DEBUG, "$logName: end=${textureNames[0]}, side=${textureNames[2]}")
|
|
||||||
if (textureNames.all { it != "missingno" })return SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package mods.betterfoliage.client.integration
|
|
||||||
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.betterfoliage.loader.Refs
|
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
|
||||||
import net.minecraft.block.Block
|
|
||||||
import net.minecraft.block.BlockTallGrass
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.init.Blocks
|
|
||||||
import net.minecraft.util.EnumBlockRenderType
|
|
||||||
import net.minecraft.util.EnumBlockRenderType.MODEL
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration for ShadersMod.
|
|
||||||
*/
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object ShadersModIntegration {
|
|
||||||
|
|
||||||
@JvmStatic val isAvailable = allAvailable(Refs.sVertexBuilder, Refs.pushEntity_state, Refs.pushEntity_num, Refs.popEntity)
|
|
||||||
|
|
||||||
val grassDefaultBlockId = blockIdFor(Blocks.TALLGRASS.defaultState.withProperty(BlockTallGrass.TYPE, BlockTallGrass.EnumType.GRASS))
|
|
||||||
val leavesDefaultBlockId = blockIdFor(Blocks.LEAVES.defaultState)
|
|
||||||
fun blockIdFor(blockState: IBlockState) = Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535
|
|
||||||
|
|
||||||
// fun entityDataFor(blockState: IBlockState) =
|
|
||||||
// (Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535) //or
|
|
||||||
// ((blockState.renderType.ordinal.toLong() and 65535) shl 16) or
|
|
||||||
// (blockState.block.getMetaFromState(blockState).toLong() shl 32)
|
|
||||||
|
|
||||||
fun logEntityData(name: String, blockState: IBlockState) {
|
|
||||||
val blockId = Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535
|
|
||||||
val meta = blockState.renderType.ordinal.toLong() and 65535
|
|
||||||
val renderType = blockState.renderType.ordinal.toLong() and 65535
|
|
||||||
Client.log(INFO, "ShadersMod integration for $name")
|
|
||||||
Client.log(INFO, " blockState=$blockState")
|
|
||||||
Client.log(INFO, " blockId=$blockId, meta=$meta, type=$renderType")
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Called from transformed ShadersMod code.
|
|
||||||
* @see mods.betterfoliage.loader.BetterFoliageTransformer
|
|
||||||
*/
|
|
||||||
@JvmStatic fun getBlockIdOverride(original: Long, blockState: IBlockState): Long {
|
|
||||||
if (Config.blocks.leavesClasses.matchesClass(blockState.block)) return Config.shaders.leavesId
|
|
||||||
if (Config.blocks.crops.matchesClass(blockState.block)) return Config.shaders.grassId
|
|
||||||
return original
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
Client.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Quads rendered inside this block will use the given block entity data in shader programs. */
|
|
||||||
inline fun renderAs(blockId: Long, renderType: EnumBlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) {
|
|
||||||
val blockData = blockId or (renderType.ordinal shl 16).toLong()
|
|
||||||
if ((isAvailable && enabled)) {
|
|
||||||
val vertexBuilder = Refs.sVertexBuilder.get(renderer)!!
|
|
||||||
Refs.pushEntity_num.invoke(vertexBuilder, blockId)
|
|
||||||
func()
|
|
||||||
Refs.popEntity.invoke(vertexBuilder)
|
|
||||||
} else {
|
|
||||||
func()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Quads rendered inside this block will use the given block entity data in shader programs. */
|
|
||||||
inline fun renderAs(state: IBlockState, renderType: EnumBlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
|
||||||
renderAs(blockIdFor(state), renderType, renderer, enabled, func)
|
|
||||||
|
|
||||||
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
|
|
||||||
inline fun grass(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
|
||||||
renderAs(Config.shaders.grassId, MODEL, renderer, enabled, func)
|
|
||||||
|
|
||||||
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
|
|
||||||
inline fun leaves(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
|
||||||
renderAs(Config.shaders.leavesId, MODEL, renderer, enabled, func)
|
|
||||||
}
|
|
||||||
@@ -1,136 +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.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.world.WorldEvent
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
import net.minecraftforge.fml.common.gameevent.TickEvent
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.lwjgl.opengl.GL11
|
|
||||||
import java.lang.Math.*
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
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 {
|
|
||||||
particleMaxAge = 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 blockColor = Minecraft.getMinecraft().blockColors.colorMultiplier(state, world, pos, 0)
|
|
||||||
val leafInfo = LeafRegistry[state, world, pos]
|
|
||||||
if (leafInfo != null) {
|
|
||||||
particleTexture = leafInfo.particleTextures[rand.nextInt(1024)]
|
|
||||||
calculateParticleColor(leafInfo.averageColor, blockColor)
|
|
||||||
} else {
|
|
||||||
particleTexture = LeafParticleRegistry["default"][rand.nextInt(1024)]
|
|
||||||
setColor(blockColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val isValid: Boolean get() = (particleTexture != null)
|
|
||||||
|
|
||||||
override fun update() {
|
|
||||||
if (rand.nextFloat() > 0.95f) rotPositive = !rotPositive
|
|
||||||
if (particleAge > particleMaxAge - 20) particleAlpha = 0.05f * (particleMaxAge - particleAge)
|
|
||||||
|
|
||||||
if (onGround || wasCollided) {
|
|
||||||
velocity.setTo(0.0, 0.0, 0.0)
|
|
||||||
if (!wasCollided) {
|
|
||||||
particleAge = Math.max(particleAge, particleMaxAge - 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
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.worldTime + 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.getMinecraft().world?.let { world ->
|
|
||||||
// change target wind speed
|
|
||||||
if (world.worldInfo.worldTime >= 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) }
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.octarinecore.client.render.AbstractEntityFX
|
|
||||||
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.math.BlockPos
|
|
||||||
import net.minecraft.util.math.MathHelper
|
|
||||||
import net.minecraft.world.World
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
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<Double3> = LinkedList<Double3>()
|
|
||||||
val initialPhase = rand.nextInt(64)
|
|
||||||
|
|
||||||
init {
|
|
||||||
motionY = 0.1
|
|
||||||
particleGravity = 0.0f
|
|
||||||
particleTexture = RisingSoulTextures.headIcons[rand.nextInt(256)]
|
|
||||||
particleMaxAge = 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 + particleAge) % 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
|
|
||||||
if (particleAge > particleMaxAge - 40) alpha *= (particleMaxAge - particleAge) / 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
|
|
||||||
if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime,
|
|
||||||
currentPos = current,
|
|
||||||
prevPos = previous,
|
|
||||||
size = scale,
|
|
||||||
alpha = alpha,
|
|
||||||
icon = RisingSoulTextures.trackIcon.icon!!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
val headIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/rising_soul_%d")
|
|
||||||
val trackIcon = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/soul_track")
|
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
|
||||||
Client.log(INFO, "Registered ${headIcons.num} soul particle textures")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.common.Double3
|
|
||||||
import mods.octarinecore.exchange
|
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
import org.lwjgl.opengl.GL11
|
|
||||||
|
|
||||||
/** 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))
|
|
||||||
)
|
|
||||||
.setAoShader(
|
|
||||||
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming)),
|
|
||||||
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)))
|
|
||||||
.setAoShader(
|
|
||||||
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming)),
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
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.*
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.Rotation
|
|
||||||
import net.minecraft.block.material.Material
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val noise = simplexNoise()
|
|
||||||
|
|
||||||
val algaeIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_algae_%d")
|
|
||||||
val algaeModels = modelSet(64, RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax))
|
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
|
||||||
Client.log(INFO, "Registered ${algaeIcons.num} algae textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.algae.enabled &&
|
|
||||||
ctx.blockState(up2).material == Material.WATER &&
|
|
||||||
ctx.blockState(up1).material == Material.WATER &&
|
|
||||||
Config.blocks.dirt.matchesClass(ctx.block) &&
|
|
||||||
ctx.biomeId in Config.algae.biomes &&
|
|
||||||
noise[ctx.pos] < Config.algae.population
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
if (!layer.isCutout) return baseRender
|
|
||||||
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
val rand = ctx.semiRandomArray(3)
|
|
||||||
|
|
||||||
ShadersModIntegration.grass(renderer, Config.algae.shaderWind) {
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
algaeModels[rand[2]],
|
|
||||||
Rotation.identity,
|
|
||||||
icon = { _, qi, _ -> algaeIcons[rand[qi and 1]]!! },
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.betterfoliage.client.render.column.ColumnTextureInfo
|
|
||||||
import mods.betterfoliage.client.render.column.SimpleColumnInfo
|
|
||||||
import mods.octarinecore.client.render.*
|
|
||||||
import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.Rotation
|
|
||||||
import mods.octarinecore.common.config.ModelTextureList
|
|
||||||
import mods.octarinecore.common.config.SimpleBlockMatcher
|
|
||||||
import net.minecraft.block.BlockCactus
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
|
|
||||||
object StandardCactusRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
|
|
||||||
override val logger = BetterFoliageMod.logDetail
|
|
||||||
override val matchClasses = SimpleBlockMatcher(BlockCactus::class.java)
|
|
||||||
override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side"))
|
|
||||||
override fun processModel(state: IBlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, Axis.Y, textures)
|
|
||||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val cactusStemRadius = 0.4375
|
|
||||||
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
|
|
||||||
|
|
||||||
val iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus")
|
|
||||||
val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus_arm_%d")
|
|
||||||
|
|
||||||
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 afterPreStitch() {
|
|
||||||
Client.log(Level.INFO, "Registered ${iconArm.num} cactus arm textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext): Boolean =
|
|
||||||
Config.enabled && Config.cactus.enabled &&
|
|
||||||
Config.blocks.cactus.matchesClass(ctx.block)
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
// render the whole block on the cutout layer
|
|
||||||
if (!layer.isCutout) return false
|
|
||||||
|
|
||||||
// get AO data
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
val icons = StandardCactusRegistry[ctx] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
modelStem.model,
|
|
||||||
Rotation.identity,
|
|
||||||
icon = { ctx, qi, q -> when(qi) {
|
|
||||||
0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q)
|
|
||||||
} },
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
modelCross[ctx.random(0)],
|
|
||||||
Rotation.identity,
|
|
||||||
icon = { _, _, _ -> iconCross.icon!!},
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
modelArm[ctx.random(1)],
|
|
||||||
cactusArmRotation[ctx.random(2) % 4],
|
|
||||||
icon = { _, _, _ -> iconArm[ctx.random(3)]!!},
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
|
|
||||||
import mods.octarinecore.client.render.BlockContext
|
|
||||||
import mods.octarinecore.client.render.withOffset
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.forgeDirsHorizontal
|
|
||||||
import mods.octarinecore.common.offset
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.connectedGrass.enabled &&
|
|
||||||
Config.blocks.dirt.matchesClass(ctx.block) &&
|
|
||||||
Config.blocks.grassClasses.matchesClass(ctx.block(up1)) &&
|
|
||||||
(Config.connectedGrass.snowEnabled || !ctx.blockState(up2).isSnow)
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
// if the block sides are not visible anyway, render normally
|
|
||||||
if (forgeDirsHorizontal.all { ctx.blockState(it.offset).isOpaqueCube }) return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
|
|
||||||
if (ctx.isSurroundedBy { it.isOpaqueCube } ) return false
|
|
||||||
return ctx.withOffset(Int3.zero, up1) {
|
|
||||||
ctx.withOffset(up1, up2) {
|
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
|
|
||||||
import mods.octarinecore.client.render.BlockContext
|
|
||||||
import mods.octarinecore.client.render.withOffset
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.offset
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val grassCheckDirs = listOf(EAST, WEST, NORTH, SOUTH)
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
|
|
||||||
Config.blocks.dirt.matchesClass(ctx.block) &&
|
|
||||||
Config.blocks.logClasses.matchesClass(ctx.block(up1))
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
val grassDir = grassCheckDirs.find {
|
|
||||||
Config.blocks.grassClasses.matchesClass(ctx.block(it.offset))
|
|
||||||
} ?: return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
|
|
||||||
return ctx.withOffset(Int3.zero, grassDir.offset) {
|
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.octarinecore.client.render.*
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.forgeDirOffsets
|
|
||||||
import mods.octarinecore.common.forgeDirs
|
|
||||||
import mods.octarinecore.random
|
|
||||||
import net.minecraft.block.material.Material
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing.Axis
|
|
||||||
import net.minecraft.util.EnumFacing.UP
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val noise = simplexNoise()
|
|
||||||
|
|
||||||
val coralIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_coral_%d")
|
|
||||||
val crustIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_crust_%d")
|
|
||||||
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 afterPreStitch() {
|
|
||||||
Client.log(INFO, "Registered ${coralIcons.num} coral textures")
|
|
||||||
Client.log(INFO, "Registered ${crustIcons.num} coral crust textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.coral.enabled &&
|
|
||||||
(ctx.blockState(up2).material == Material.WATER || Config.coral.shallowWater) &&
|
|
||||||
ctx.blockState(up1).material == Material.WATER &&
|
|
||||||
Config.blocks.sand.matchesClass(ctx.block) &&
|
|
||||||
ctx.biomeId in Config.coral.biomes &&
|
|
||||||
noise[ctx.pos] < Config.coral.population
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
if (!layer.isCutout) return baseRender
|
|
||||||
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
forgeDirs.forEachIndexed { idx, face ->
|
|
||||||
if (!ctx.blockState(forgeDirOffsets[idx]).isOpaqueCube && blockContext.random(idx) < Config.coral.chance) {
|
|
||||||
var variation = blockContext.random(6)
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
coralModels[variation++],
|
|
||||||
rotationFromUp[idx],
|
|
||||||
icon = { _, qi, _ -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!},
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
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.texture.GrassRegistry
|
|
||||||
import mods.octarinecore.client.render.*
|
|
||||||
import mods.octarinecore.common.*
|
|
||||||
import mods.octarinecore.random
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumBlockRenderType
|
|
||||||
import net.minecraft.util.EnumBlockRenderType.MODEL
|
|
||||||
import net.minecraft.util.EnumFacing.Axis
|
|
||||||
import net.minecraft.util.EnumFacing.UP
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
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 = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_grass_long_%d")
|
|
||||||
val snowedIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_grass_snowed_%d")
|
|
||||||
val normalGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:blocks/tallgrass", "snowed" to false))
|
|
||||||
val snowedGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:blocks/tallgrass", "snowed" to true))
|
|
||||||
|
|
||||||
val grassModels = modelSet(64, grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax))
|
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
|
||||||
Client.log(INFO, "Registered ${normalIcons.num} grass textures")
|
|
||||||
Client.log(INFO, "Registered ${snowedIcons.num} snowed grass textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled &&
|
|
||||||
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
|
|
||||||
GrassRegistry[ctx] != null
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
// render the whole block on the cutout layer
|
|
||||||
if (!layer.isCutout) return false
|
|
||||||
|
|
||||||
val isConnected = ctx.block(down1).let {
|
|
||||||
Config.blocks.dirt.matchesClass(it) ||
|
|
||||||
Config.blocks.grassClasses.matchesClass(it)
|
|
||||||
}
|
|
||||||
val isSnowed = ctx.blockState(up1).isSnow
|
|
||||||
val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
|
|
||||||
|
|
||||||
val grass = GrassRegistry[ctx]
|
|
||||||
if (grass == null) {
|
|
||||||
// shouldn't happen
|
|
||||||
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
|
|
||||||
return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
}
|
|
||||||
val blockColor = OptifineCustomColors.getBlockColor(ctx)
|
|
||||||
|
|
||||||
if (connectedGrass) {
|
|
||||||
// get full AO data
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
// check occlusion
|
|
||||||
val isHidden = forgeDirs.map { ctx.blockState(it.offset).isOpaqueCube }
|
|
||||||
|
|
||||||
// render full grass block
|
|
||||||
ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), MODEL, renderer) {
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
fullCube,
|
|
||||||
quadFilter = { qi, _ -> !isHidden[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 {
|
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
|
|
||||||
// get AO data only for block top
|
|
||||||
modelRenderer.updateShading(Int3.zero, topOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Config.shortGrass.grassEnabled) return true
|
|
||||||
if (isSnowed && !Config.shortGrass.snowEnabled) return true
|
|
||||||
if (ctx.blockState(up1).isOpaqueCube) return true
|
|
||||||
if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return true
|
|
||||||
|
|
||||||
// render grass quads
|
|
||||||
val iconset = if (isSnowed) snowedIcons else normalIcons
|
|
||||||
val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon
|
|
||||||
val rand = ctx.semiRandomArray(2)
|
|
||||||
|
|
||||||
ShadersModIntegration.grass(renderer, Config.shortGrass.shaderWind) {
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
grassModels[rand[0]],
|
|
||||||
Rotation.identity,
|
|
||||||
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
|
|
||||||
icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen.icon!! else iconset[rand[qi and 1]]!! },
|
|
||||||
postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
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.texture.LeafRegistry
|
|
||||||
import mods.octarinecore.PI2
|
|
||||||
import mods.octarinecore.client.render.*
|
|
||||||
import mods.octarinecore.common.Double3
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.Rotation
|
|
||||||
import mods.octarinecore.common.vec
|
|
||||||
import mods.octarinecore.random
|
|
||||||
import net.minecraft.block.material.Material
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing.DOWN
|
|
||||||
import net.minecraft.util.EnumFacing.UP
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import java.lang.Math.cos
|
|
||||||
import java.lang.Math.sin
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
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 = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_leaves_snowed_%d")
|
|
||||||
|
|
||||||
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: BlockContext) =
|
|
||||||
Config.enabled &&
|
|
||||||
Config.leaves.enabled &&
|
|
||||||
LeafRegistry[ctx] != null &&
|
|
||||||
!(Config.leaves.hideInternal && ctx.isSurroundedBy { it.isFullCube || it.material == Material.LEAVES } )
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
val isSnowed = ctx.blockState(up1).material.let {
|
|
||||||
it == Material.SNOW || it == Material.CRAFTED_SNOW
|
|
||||||
}
|
|
||||||
val leafInfo = LeafRegistry[ctx]
|
|
||||||
if (leafInfo == null) {
|
|
||||||
// shouldn't happen
|
|
||||||
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
|
|
||||||
return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
}
|
|
||||||
val blockColor = OptifineCustomColors.getBlockColor(ctx)
|
|
||||||
|
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
if (!layer.isCutout) return true
|
|
||||||
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
ShadersModIntegration.leaves(renderer) {
|
|
||||||
val rand = ctx.semiRandomArray(2)
|
|
||||||
(if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation ->
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
leavesModel.model,
|
|
||||||
rotation,
|
|
||||||
ctx.blockCenter + perturbs[rand[0]],
|
|
||||||
icon = { _, _, _ -> leafInfo.roundLeafTexture },
|
|
||||||
postProcess = { _, _, _, _, _ ->
|
|
||||||
rotateUV(rand[1])
|
|
||||||
multiplyColor(blockColor)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isSnowed && Config.leaves.snowEnabled) modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
leavesModel.model,
|
|
||||||
Rotation.identity,
|
|
||||||
ctx.blockCenter + perturbs[rand[0]],
|
|
||||||
icon = { _, _, _ -> snowedIcon[rand[1]]!! },
|
|
||||||
postProcess = whitewash
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
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.*
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.Rotation
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing.DOWN
|
|
||||||
import net.minecraft.util.EnumFacing.UP
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
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 = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_roots_%d")
|
|
||||||
val flowerIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_flower_%d")
|
|
||||||
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset }
|
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
|
||||||
Client.log(Level.INFO, "Registered ${rootIcon.num} lilypad root textures")
|
|
||||||
Client.log(Level.INFO, "Registered ${flowerIcon.num} lilypad flower textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext): Boolean =
|
|
||||||
Config.enabled && Config.lilypad.enabled &&
|
|
||||||
Config.blocks.lilypad.matchesClass(ctx.block)
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
// render the whole block on the cutout layer
|
|
||||||
if (!layer.isCutout) return false
|
|
||||||
|
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
val rand = ctx.semiRandomArray(5)
|
|
||||||
|
|
||||||
ShadersModIntegration.grass(renderer) {
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
rootModel.model,
|
|
||||||
Rotation.identity,
|
|
||||||
ctx.blockCenter.add(perturbs[rand[2]]),
|
|
||||||
forceFlat = true,
|
|
||||||
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! },
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rand[3] < Config.lilypad.flowerChance) modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
flowerModel.model,
|
|
||||||
Rotation.identity,
|
|
||||||
ctx.blockCenter.add(perturbs[rand[4]]),
|
|
||||||
forceFlat = true,
|
|
||||||
icon = { _, _, _ -> flowerIcon[rand[0]]!! },
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
|
||||||
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.octarinecore.client.render.BlockContext
|
|
||||||
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.BlockLog
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.util.EnumFacing.Axis
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
|
|
||||||
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
override val addToCutout: Boolean get() = false
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.roundLogs.enabled &&
|
|
||||||
Config.blocks.logClasses.matchesClass(ctx.block)
|
|
||||||
|
|
||||||
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<ColumnTextureInfo> get() = LogRegistry
|
|
||||||
override val blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) }
|
|
||||||
override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logClasses.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
|
|
||||||
}
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object LogRegistry : ModelRenderRegistryRoot<ColumnTextureInfo>()
|
|
||||||
|
|
||||||
object StandardLogRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
|
|
||||||
override val logger = BetterFoliageMod.logDetail
|
|
||||||
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.logClasses
|
|
||||||
override val modelTextures: List<ModelTextureList> get() = Config.blocks.logModels.list
|
|
||||||
override fun processModel(state: IBlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, getAxis(state), textures)
|
|
||||||
|
|
||||||
fun getAxis(state: IBlockState): Axis? {
|
|
||||||
val axis = tryDefault(null) { state.getValue(BlockLog.LOG_AXIS).toString() } ?:
|
|
||||||
state.properties.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
|
|
||||||
return when (axis) {
|
|
||||||
"x" -> Axis.X
|
|
||||||
"y" -> Axis.Y
|
|
||||||
"z" -> Axis.Z
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
|
|
||||||
import mods.octarinecore.client.render.BlockContext
|
|
||||||
import mods.octarinecore.client.render.modelRenderer
|
|
||||||
import mods.octarinecore.client.render.noPost
|
|
||||||
import mods.octarinecore.common.Double3
|
|
||||||
import mods.octarinecore.common.Rotation
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_mycel_%d")
|
|
||||||
val myceliumModel = modelSet(64, RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax))
|
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
|
||||||
Client.log(INFO, "Registered ${myceliumIcon.num} mycelium textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext): Boolean {
|
|
||||||
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
|
|
||||||
return Config.blocks.mycelium.matchesClass(ctx.block)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
// render the whole block on the cutout layer
|
|
||||||
if (!layer.isCutout) return false
|
|
||||||
|
|
||||||
val isSnowed = ctx.blockState(up1).isSnow
|
|
||||||
|
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
|
|
||||||
if (isSnowed && !Config.shortGrass.snowEnabled) return true
|
|
||||||
if (ctx.blockState(up1).isOpaqueCube) return true
|
|
||||||
|
|
||||||
val rand = ctx.semiRandomArray(2)
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
myceliumModel[rand[0]],
|
|
||||||
Rotation.identity,
|
|
||||||
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
|
|
||||||
icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]]!! },
|
|
||||||
postProcess = if (isSnowed) whitewash else noPost
|
|
||||||
)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.octarinecore.client.render.*
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.Rotation
|
|
||||||
import mods.octarinecore.random
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val netherrackIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_netherrack_%d")
|
|
||||||
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 afterPreStitch() {
|
|
||||||
Client.log(INFO, "Registered ${netherrackIcon.num} netherrack textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext): Boolean {
|
|
||||||
if (!Config.enabled || !Config.netherrack.enabled) return false
|
|
||||||
return Config.blocks.netherrack.matchesClass(ctx.block)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
if (!layer.isCutout) return baseRender
|
|
||||||
|
|
||||||
if (ctx.blockState(down1).isOpaqueCube) return baseRender
|
|
||||||
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
val rand = ctx.semiRandomArray(2)
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
netherrackModel[rand[0]],
|
|
||||||
Rotation.identity,
|
|
||||||
icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]]!! },
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
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.*
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.Rotation
|
|
||||||
import mods.octarinecore.random
|
|
||||||
import net.minecraft.block.material.Material
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing.UP
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val noise = simplexNoise()
|
|
||||||
val reedIcons = iconSet(Client.genReeds.generatedResource("${BetterFoliageMod.LEGACY_DOMAIN}:blocks/better_reed_%d"))
|
|
||||||
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 afterPreStitch() {
|
|
||||||
Client.log(Level.INFO, "Registered ${reedIcons.num} reed textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.reed.enabled &&
|
|
||||||
ctx.blockState(up2).material == Material.AIR &&
|
|
||||||
ctx.blockState(up1).material == Material.WATER &&
|
|
||||||
Config.blocks.dirt.matchesClass(ctx.block) &&
|
|
||||||
ctx.biomeId in Config.reed.biomes &&
|
|
||||||
noise[ctx.pos] < Config.reed.population
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
if (!layer.isCutout) return baseRender
|
|
||||||
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
val iconVar = ctx.random(1)
|
|
||||||
ShadersModIntegration.grass(renderer, Config.reed.shaderWind) {
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
reedModels[ctx.random(0)],
|
|
||||||
Rotation.identity,
|
|
||||||
forceFlat = true,
|
|
||||||
icon = { _, _, _ -> reedIcons[iconVar]!! },
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
@file:JvmName("Utils")
|
|
||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
import mods.octarinecore.PI2
|
|
||||||
import mods.octarinecore.client.render.*
|
|
||||||
import mods.octarinecore.common.Double3
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.Rotation
|
|
||||||
import mods.octarinecore.common.times
|
|
||||||
import net.minecraft.block.Block
|
|
||||||
import net.minecraft.block.material.Material
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
|
|
||||||
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 IBlockState.isSnow: Boolean get() = material.let { it == Material.SNOW || it == Material.CRAFTED_SNOW }
|
|
||||||
|
|
||||||
fun Quad.toCross(rotAxis: EnumFacing, trans: (Quad)->Quad) =
|
|
||||||
(0..3).map { rotIdx ->
|
|
||||||
trans(rotate(Rotation.rot90[rotAxis.ordinal] * rotIdx).mirrorUV(rotIdx > 1, false))
|
|
||||||
}
|
|
||||||
fun Quad.toCross(rotAxis: EnumFacing) = toCross(rotAxis) { it }
|
|
||||||
|
|
||||||
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(Math.cos(it), 0.0, Math.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 IBlockState.canRenderInLayer(layer: BlockRenderLayer) = this.block.canRenderInLayer(this, layer)
|
|
||||||
fun IBlockState.canRenderInCutout() = this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT) || this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT_MIPPED)
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render.column
|
|
||||||
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayLayer
|
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
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.*
|
|
||||||
import mods.octarinecore.client.resource.ModelRenderRegistry
|
|
||||||
import mods.octarinecore.common.*
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumBlockRenderType
|
|
||||||
import net.minecraft.util.EnumBlockRenderType.MODEL
|
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) {
|
|
||||||
|
|
||||||
/** 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: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
|
|
||||||
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx.world!!, ctx.pos)
|
|
||||||
when(roundLog) {
|
|
||||||
ColumnLayerData.SkipRender -> return true
|
|
||||||
ColumnLayerData.NormalRender -> return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
ColumnLayerData.ResolveError, null -> {
|
|
||||||
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
|
|
||||||
return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get AO data
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal]
|
|
||||||
renderAs(ctx.blockState(Int3.zero), MODEL, renderer) {
|
|
||||||
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) modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
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) modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
upModel,
|
|
||||||
rotation,
|
|
||||||
icon = upIcon,
|
|
||||||
postProcess = { _, _, _, _, _ ->
|
|
||||||
if (isLidUp) {
|
|
||||||
rotateUV(idx + if (roundLog.column.axis == Axis.X) 1 else 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (downModel != null) modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
downModel,
|
|
||||||
rotation,
|
|
||||||
icon = downIcon,
|
|
||||||
postProcess = { _, _, _, _, _ ->
|
|
||||||
if (isLidDown) {
|
|
||||||
rotateUV((if (roundLog.column.axis == Axis.X) 0 else 3) - idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render.column
|
|
||||||
|
|
||||||
import mods.octarinecore.client.render.QuadIconResolver
|
|
||||||
import mods.octarinecore.client.render.blockContext
|
|
||||||
import mods.octarinecore.client.resource.ModelRenderKey
|
|
||||||
import mods.octarinecore.client.resource.get
|
|
||||||
import mods.octarinecore.common.rotate
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
interface ColumnTextureInfo {
|
|
||||||
val axis: EnumFacing.Axis?
|
|
||||||
val top: QuadIconResolver
|
|
||||||
val bottom: QuadIconResolver
|
|
||||||
val side: QuadIconResolver
|
|
||||||
}
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
open class SimpleColumnInfo(
|
|
||||||
override val axis: EnumFacing.Axis?,
|
|
||||||
val topTexture: TextureAtlasSprite,
|
|
||||||
val bottomTexture: TextureAtlasSprite,
|
|
||||||
val sideTextures: List<TextureAtlasSprite>
|
|
||||||
) : 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) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
|
|
||||||
val sideIdx = if (sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0
|
|
||||||
sideTextures[sideIdx]
|
|
||||||
}
|
|
||||||
|
|
||||||
class Key(override val logger: Logger, val axis: EnumFacing.Axis?, val textures: List<String>) : ModelRenderKey<ColumnTextureInfo> {
|
|
||||||
override fun resolveSprites(atlas: TextureMap) = SimpleColumnInfo(
|
|
||||||
axis,
|
|
||||||
atlas[textures[0]] ?: atlas.missingSprite,
|
|
||||||
atlas[textures[1]] ?: atlas.missingSprite,
|
|
||||||
textures.drop(2).map { atlas[it] ?: atlas.missingSprite }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package mods.betterfoliage.client.texture
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.octarinecore.client.render.BlockContext
|
|
||||||
import mods.octarinecore.client.render.HSB
|
|
||||||
import mods.octarinecore.client.resource.*
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
|
||||||
import mods.octarinecore.common.config.IBlockMatcher
|
|
||||||
import mods.octarinecore.common.config.ModelTextureList
|
|
||||||
import mods.octarinecore.findFirst
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
import java.lang.Math.min
|
|
||||||
|
|
||||||
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<GrassInfo>()
|
|
||||||
|
|
||||||
object StandardGrassRegistry : ModelRenderRegistryConfigurable<GrassInfo>() {
|
|
||||||
override val logger = BetterFoliageMod.logDetail
|
|
||||||
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.grassClasses
|
|
||||||
override val modelTextures: List<ModelTextureList> get() = Config.blocks.grassModels.list
|
|
||||||
override fun processModel(state: IBlockState, textures: List<String>) = StandardGrassKey(logger, textures[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
class StandardGrassKey(override val logger: Logger, val textureName: String) : ModelRenderKey<GrassInfo> {
|
|
||||||
override fun resolveSprites(atlas: TextureMap): GrassInfo {
|
|
||||||
val logName = "StandardGrassKey"
|
|
||||||
val texture = atlas[textureName] ?: atlas.missingSprite
|
|
||||||
logger.log(Level.DEBUG, "$logName: texture $textureName")
|
|
||||||
val hsb = HSB.fromColor(texture.averageColor ?: defaultGrassColor)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
return GrassInfo(texture, overrideColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package mods.betterfoliage.client.texture
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.octarinecore.client.resource.*
|
|
||||||
import mods.octarinecore.stripStart
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
|
||||||
import net.minecraft.util.ResourceLocation
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.EventPriority
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
|
|
||||||
object LeafParticleRegistry {
|
|
||||||
val typeMappings = TextureMatcher()
|
|
||||||
val particles = hashMapOf<String, IconSet>()
|
|
||||||
|
|
||||||
operator fun get(type: String) = particles[type] ?: particles["default"]!!
|
|
||||||
|
|
||||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
|
||||||
|
|
||||||
@SubscribeEvent(priority = EventPriority.HIGH)
|
|
||||||
fun handleLoadModelData(event: LoadModelDataEvent) {
|
|
||||||
particles.clear()
|
|
||||||
typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.DOMAIN, "leaf_texture_mappings.cfg"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handlePreStitch(event: TextureStitchEvent.Pre) {
|
|
||||||
val allTypes = (typeMappings.mappings.map { it.type } + "default").distinct()
|
|
||||||
allTypes.forEach { leafType ->
|
|
||||||
val particleSet = IconSet("betterfoliage", "blocks/falling_leaf_${leafType}_%d").apply { onPreStitch(event.map) }
|
|
||||||
if (leafType == "default" || particleSet.num > 0) particles[leafType] = particleSet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handlePostStitch(event: TextureStitchEvent.Post) {
|
|
||||||
particles.forEach { (_, particleSet) -> particleSet.onPostStitch(event.map) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Mapping> = 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()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package mods.betterfoliage.client.texture
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.octarinecore.client.render.BlockContext
|
|
||||||
import mods.octarinecore.client.resource.*
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
|
||||||
import mods.octarinecore.common.config.IBlockMatcher
|
|
||||||
import mods.octarinecore.common.config.ModelTextureList
|
|
||||||
import mods.octarinecore.findFirst
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.ResourceLocation
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.EventPriority
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
|
|
||||||
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 ?: defaultLeafColor
|
|
||||||
) {
|
|
||||||
/** [IconSet] of the textures to use for leaf particles emitted from this block. */
|
|
||||||
val particleTextures: IconSet get() = LeafParticleRegistry[leafType]
|
|
||||||
}
|
|
||||||
|
|
||||||
object LeafRegistry : ModelRenderRegistryRoot<LeafInfo>()
|
|
||||||
|
|
||||||
object StandardLeafRegistry : ModelRenderRegistryConfigurable<LeafInfo>() {
|
|
||||||
override val logger = BetterFoliageMod.logDetail
|
|
||||||
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.leavesClasses
|
|
||||||
override val modelTextures: List<ModelTextureList> get() = Config.blocks.leavesModels.list
|
|
||||||
override fun processModel(state: IBlockState, textures: List<String>) = StandardLeafKey(logger, textures[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
class StandardLeafKey(override val logger: Logger, val textureName: String) : ModelRenderKey<LeafInfo> {
|
|
||||||
lateinit var leafType: String
|
|
||||||
lateinit var generated: ResourceLocation
|
|
||||||
|
|
||||||
override fun onPreStitch(atlas: TextureMap) {
|
|
||||||
val logName = "StandardLeafKey"
|
|
||||||
leafType = LeafParticleRegistry.typeMappings.getType(textureName) ?: "default"
|
|
||||||
generated = Client.genLeaves.generatedResource(textureName, "type" to leafType)
|
|
||||||
atlas.registerSprite(generated)
|
|
||||||
|
|
||||||
logger.log(Level.DEBUG, "$logName: leaf texture $textureName")
|
|
||||||
logger.log(Level.DEBUG, "$logName: particle $leafType")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resolveSprites(atlas: TextureMap) = LeafInfo(atlas[generated] ?: atlas.missingSprite, leafType)
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
@file:JvmName("Utils")
|
|
||||||
package mods.betterfoliage.client.texture
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
36
src/main/kotlin/mods/betterfoliage/config/BlockConfig.kt
Normal file
36
src/main/kotlin/mods/betterfoliage/config/BlockConfig.kt
Normal file
@@ -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<Any>()
|
||||||
|
|
||||||
|
val leafBlocks = blocks("leaves_blocks_default.cfg")
|
||||||
|
val leafModels = models("leaves_models_default.cfg")
|
||||||
|
val grassBlocks = blocks("grass_blocks_default.cfg")
|
||||||
|
val grassModels = models("grass_models_default.cfg")
|
||||||
|
val mycelium = blocks("mycelium_blocks_default.cfg")
|
||||||
|
// val dirt = blocks("dirt_default.cfg")
|
||||||
|
val crops = blocks("crop_default.cfg")
|
||||||
|
val logBlocks = blocks("log_blocks_default.cfg")
|
||||||
|
val logModels = models("log_models_default.cfg")
|
||||||
|
val 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)
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/main/kotlin/mods/betterfoliage/config/Delegate.kt
Normal file
137
src/main/kotlin/mods/betterfoliage/config/Delegate.kt
Normal file
@@ -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<N: ConfigLeaf>(val fiberNode: N) {
|
||||||
|
abstract fun createClothNode(names: List<String>): AbstractConfigListEntry<*>
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DelegatingConfigValue<T>(fiberNode: ConfigValue<T>) : DelegatingConfigNode<ConfigValue<T>>(fiberNode), ReadOnlyProperty<DelegatingConfigGroup, T>
|
||||||
|
|
||||||
|
open class DelegatingConfigGroup(fiberNode: ConfigNode) : DelegatingConfigNode<ConfigNode>(fiberNode) {
|
||||||
|
val children = mutableListOf<DelegatingConfigNode<*>>()
|
||||||
|
override fun createClothNode(names: List<String>): 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<T> {
|
||||||
|
operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T: DelegatingConfigGroup> subNode(factory: (ConfigNode)->T) = object : DelegatingConfigGroupFactory<T> {
|
||||||
|
override operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> {
|
||||||
|
val childNode = ConfigNode(property.name, null)
|
||||||
|
val configGroup = factory(childNode)
|
||||||
|
parent.fiberNode.items.add(childNode)
|
||||||
|
parent.children.add(configGroup)
|
||||||
|
return object : ReadOnlyProperty<DelegatingConfigGroup, T> {
|
||||||
|
override fun getValue(thisRef: DelegatingConfigGroup, property: KProperty<*>) = configGroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DelegatingConfigValueFactory<T> {
|
||||||
|
fun createFiberNode(parent: ConfigNode, name: String): ConfigValue<T>
|
||||||
|
fun createClothNode(node: ConfigValue<T>, names: List<String>): AbstractConfigListEntry<T>
|
||||||
|
|
||||||
|
operator fun provideDelegate(parent: DelegatingConfigGroup, property: KProperty<*>): ReadOnlyProperty<DelegatingConfigGroup, T> {
|
||||||
|
return object : DelegatingConfigValue<T>(createFiberNode(parent.fiberNode, property.name)) {
|
||||||
|
override fun createClothNode(names: List<String>) = 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>)->String = { it.joinToString(".") },
|
||||||
|
valueOverride: (Boolean)->Boolean = { it }
|
||||||
|
) = object : DelegatingConfigValueFactory<Boolean> {
|
||||||
|
override fun createFiberNode(parent: ConfigNode, name: String) = ConfigValueBuilder(Boolean::class.java)
|
||||||
|
.withName(name)
|
||||||
|
.withParent(parent)
|
||||||
|
.withDefaultValue(default)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun createClothNode(node: ConfigValue<Boolean>, names: List<String>) = 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>)->String = { it.joinToString(".") },
|
||||||
|
valueOverride: (Int)->Int = { it }
|
||||||
|
) = object : DelegatingConfigValueFactory<Int> {
|
||||||
|
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<Int>, names: List<String>) = 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>)->String = { it.joinToString(".") },
|
||||||
|
valueOverride: (Double)->Double = { it }
|
||||||
|
) = object : DelegatingConfigValueFactory<Double> {
|
||||||
|
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<Double>, names: List<String>) = 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<String> -> "${names.first()}.${names.last()}" }
|
||||||
|
fun fakeCategory(name: String) = { names: List<String> ->
|
||||||
|
(listOf(names.first(), name) + names.drop(1)).joinToString(".")
|
||||||
|
}
|
||||||
154
src/main/kotlin/mods/betterfoliage/config/MainConfig.kt
Normal file
154
src/main/kotlin/mods/betterfoliage/config/MainConfig.kt
Normal file
@@ -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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package mods.betterfoliage.integration
|
||||||
|
|
||||||
|
/*
|
||||||
|
val TextureLeaves = ClassRefOld<Any>("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<Any>("forestry.arboriculture.tiles.TileLeaves")
|
||||||
|
val TileLeaves_getLeaveSprite = MethodRefOld(TileLeaves, "getLeaveSprite", Identifier, boolean)
|
||||||
|
val PropertyWoodType = ClassRefOld<Any>("forestry.arboriculture.blocks.PropertyWoodType")
|
||||||
|
val IWoodType = ClassRefOld<Any>("forestry.api.arboriculture.IWoodType")
|
||||||
|
val IWoodType_barkTex = MethodRefOld(IWoodType, "getBarkTexture", String)
|
||||||
|
val IWoodType_heartTex = MethodRefOld(IWoodType, "getHeartTexture", String)
|
||||||
|
|
||||||
|
val PropertyTreeType = ClassRefOld<Any>("forestry.arboriculture.blocks.PropertyTreeType")
|
||||||
|
val IAlleleTreeSpecies = ClassRefOld<Any>("forestry.api.arboriculture.IAlleleTreeSpecies")
|
||||||
|
val ILeafSpriteProvider = ClassRefOld<Any>("forestry.api.arboriculture.ILeafSpriteProvider")
|
||||||
|
val TreeDefinition = ClassRefOld<Any>("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<ModelLoader>, ModelRenderRegistry<LeafInfo> {
|
||||||
|
override val logger = BetterFoliage.logDetail
|
||||||
|
var idToValue = emptyMap<Identifier, LeafInfo>()
|
||||||
|
|
||||||
|
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<ModelLoader>, atlasFuture: AtlasFuture): StitchPhases {
|
||||||
|
val futures = mutableMapOf<Identifier, CompletableFuture<LeafInfo>>()
|
||||||
|
|
||||||
|
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<ColumnTextureInfo>() {
|
||||||
|
override val logger = BetterFoliage.logDetail
|
||||||
|
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
29
src/main/kotlin/mods/betterfoliage/integration/ModMenu.kt
Normal file
29
src/main/kotlin/mods/betterfoliage/integration/ModMenu.kt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package mods.betterfoliage.integration
|
||||||
|
|
||||||
|
import io.github.prospector.modmenu.api.ModMenuApi
|
||||||
|
import me.shedaniel.clothconfig2.api.ConfigBuilder
|
||||||
|
import me.zeroeightsix.fiber.JanksonSettings
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
|
import net.minecraft.client.MinecraftClient
|
||||||
|
import net.minecraft.client.gui.screen.Screen
|
||||||
|
import net.minecraft.client.resource.language.I18n
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
object ModMenu : ModMenuApi {
|
||||||
|
override fun getModId() = BetterFoliage.MOD_ID
|
||||||
|
|
||||||
|
override fun getConfigScreenFactory() = Function { screen: Screen ->
|
||||||
|
val builder = ConfigBuilder.create()
|
||||||
|
.setParentScreen(screen)
|
||||||
|
.setTitle(I18n.translate("betterfoliage.title"))
|
||||||
|
BetterFoliage.config.createClothNode(listOf("betterfoliage")).value.forEach { rootOption ->
|
||||||
|
builder.getOrCreateCategory("main").addEntry(rootOption)
|
||||||
|
}
|
||||||
|
builder.savingRunnable = Runnable {
|
||||||
|
JanksonSettings().serialize(BetterFoliage.config.fiberNode, BetterFoliage.configFile.outputStream(), false)
|
||||||
|
BetterFoliage.modelReplacer.invalidate()
|
||||||
|
MinecraftClient.getInstance().worldRenderer.reload()
|
||||||
|
}
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package mods.betterfoliage.integration
|
||||||
|
|
||||||
|
|
||||||
|
object IC2RubberIntegration {
|
||||||
|
|
||||||
|
// val BlockRubWood = ClassRefOld<Any>("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)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object TechRebornRubberIntegration {
|
||||||
|
|
||||||
|
// val BlockRubberLog = ClassRefOld<Any>("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)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
class RubberLogInfo(
|
||||||
|
axis: Axis?,
|
||||||
|
val spotDir: Direction,
|
||||||
|
topTexture: Sprite,
|
||||||
|
bottomTexture: Sprite,
|
||||||
|
val spotTexture: Sprite,
|
||||||
|
sideTextures: List<Sprite>
|
||||||
|
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
|
||||||
|
|
||||||
|
override val side: QuadIconResolver = { ctx: CombinedContext, idx: Int, quad: Quad ->
|
||||||
|
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation)
|
||||||
|
if (worldFace == spotDir) spotTexture else {
|
||||||
|
val sideIdx = if (this.sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
|
||||||
|
this.sideTextures[sideIdx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object IC2LogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
|
||||||
|
override val logger = BetterFoliage.logDetail
|
||||||
|
|
||||||
|
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
|
||||||
|
// 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<JsonUnbakedModel, Identifier> ?: 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(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.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]}")
|
||||||
|
val endSprite = atlas.sprite(textureNames[0])
|
||||||
|
val sideSprite = atlas.sprite(textureNames[1])
|
||||||
|
return atlas.mapAfter {
|
||||||
|
SimpleColumnInfo(axis, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logs with rubber spot
|
||||||
|
val spotDir = when(type) {
|
||||||
|
"dry_north", "wet_north" -> NORTH
|
||||||
|
"dry_south", "wet_south" -> SOUTH
|
||||||
|
"dry_west", "wet_west" -> WEST
|
||||||
|
"dry_east", "wet_east" -> EAST
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
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]}")
|
||||||
|
val upSprite = atlas.sprite(textureNames[0])
|
||||||
|
val downSprite = atlas.sprite(textureNames[1])
|
||||||
|
val sideSprite = atlas.sprite(textureNames[2])
|
||||||
|
val spotSprite = atlas.sprite(textureNames[3])
|
||||||
|
return if (spotDir != null) atlas.mapAfter {
|
||||||
|
RubberLogInfo(Axis.Y, spotDir, upSprite.get(), downSprite.get(), spotSprite.get(), listOf(sideSprite.get()))
|
||||||
|
} else atlas.mapAfter {
|
||||||
|
SimpleColumnInfo(Axis.Y, upSprite.get(), downSprite.get(), listOf(sideSprite.get()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object TechRebornLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
|
||||||
|
override val logger = BetterFoliage.logDetail
|
||||||
|
|
||||||
|
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
|
||||||
|
// 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<JsonUnbakedModel, Identifier> }.firstOrNull() ?: 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.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])
|
||||||
|
val sideSprite = atlas.sprite(textureNames[1])
|
||||||
|
val sapSprite = atlas.sprite(textureNames[2])
|
||||||
|
return atlas.mapAfter {
|
||||||
|
RubberLogInfo(Axis.Y, sapSide, endSprite.get(), endSprite.get(), sapSprite.get(), listOf(sideSprite.get()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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])
|
||||||
|
val sideSprite = atlas.sprite(textureNames[1])
|
||||||
|
return atlas.mapAfter {
|
||||||
|
SimpleColumnInfo(Axis.Y, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
package mods.betterfoliage.loader
|
|
||||||
|
|
||||||
import mods.octarinecore.metaprog.Transformer
|
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
|
||||||
import net.minecraftforge.fml.relauncher.FMLLaunchHandler
|
|
||||||
import org.objectweb.asm.ClassWriter
|
|
||||||
import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES
|
|
||||||
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
|
|
||||||
import org.objectweb.asm.Opcodes.*
|
|
||||||
|
|
||||||
class BetterFoliageTransformer : Transformer() {
|
|
||||||
|
|
||||||
val isOptifinePresent = allAvailable(Refs.OptifineClassTransformer)
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (FMLLaunchHandler.side().isClient) setupClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setupClient() {
|
|
||||||
// where: WorldClient.showBarrierParticles(), right after invoking Block.randomDisplayTick
|
|
||||||
// what: invoke BF code for every random display tick
|
|
||||||
// why: allows us to catch random display ticks, without touching block code
|
|
||||||
transformMethod(Refs.showBarrierParticles) {
|
|
||||||
find(invokeRef(Refs.randomDisplayTick))?.insertAfter {
|
|
||||||
log.info("[BetterFoliageLoader] Applying random display tick call hook")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
varinsn(ALOAD, 11)
|
|
||||||
varinsn(ALOAD, 7)
|
|
||||||
invokeStatic(Refs.onRandomDisplayTick)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply random display tick call hook!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: BlockStateContainer$StateImplementation.getAmbientOcclusionLightValue()
|
|
||||||
// what: invoke BF code to overrule AO transparency value
|
|
||||||
// why: allows us to have light behave properly on non-solid log blocks
|
|
||||||
transformMethod(Refs.getAmbientOcclusionLightValue) {
|
|
||||||
find(FRETURN)?.insertBefore {
|
|
||||||
log.info("[BetterFoliageLoader] Applying getAmbientOcclusionLightValue() override")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
invokeStatic(Refs.getAmbientOcclusionLightValueOverride)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply getAmbientOcclusionLightValue() override!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: BlockStateContainer$StateImplementation.useNeighborBrightness()
|
|
||||||
// what: invoke BF code to overrule _useNeighborBrightness_
|
|
||||||
// why: allows us to have light behave properly on non-solid log blocks
|
|
||||||
transformMethod(Refs.useNeighborBrightness) {
|
|
||||||
find(IRETURN)?.insertBefore {
|
|
||||||
log.info("[BetterFoliageLoader] Applying useNeighborBrightness() override")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
invokeStatic(Refs.useNeighborBrightnessOverride)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply useNeighborBrightness() override!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: BlockStateContainer$StateImplementation.doesSideBlockRendering()
|
|
||||||
// what: invoke BF code to overrule condition
|
|
||||||
// why: allows us to make log blocks non-solid
|
|
||||||
transformMethod(Refs.doesSideBlockRendering) {
|
|
||||||
find(IRETURN)?.insertBefore {
|
|
||||||
log.info("[BetterFoliageLoader] Applying doesSideBlockRendering() override")
|
|
||||||
varinsn(ALOAD, 1)
|
|
||||||
varinsn(ALOAD, 2)
|
|
||||||
varinsn(ALOAD, 3)
|
|
||||||
invokeStatic(Refs.doesSideBlockRenderingOverride)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply doesSideBlockRendering() override!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: BlockStateContainer$StateImplementation.isOpaqueCube()
|
|
||||||
// what: invoke BF code to overrule condition
|
|
||||||
// why: allows us to make log blocks non-solid
|
|
||||||
transformMethod(Refs.isOpaqueCube) {
|
|
||||||
find(IRETURN)?.insertBefore {
|
|
||||||
log.info("[BetterFoliageLoader] Applying isOpaqueCube() override")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
invokeStatic(Refs.isOpaqueCubeOverride)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply isOpaqueCube() override!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: ModelLoader.setupModelRegistry(), right before the textures are loaded
|
|
||||||
// what: invoke handler code with ModelLoader instance
|
|
||||||
// why: allows us to iterate the unbaked models in ModelLoader in time to register textures
|
|
||||||
transformMethod(Refs.setupModelRegistry) {
|
|
||||||
find(invokeName("addAll"))?.insertAfter {
|
|
||||||
log.info("[BetterFoliageLoader] Applying ModelLoader lifecycle callback")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
invokeStatic(Refs.onAfterLoadModelDefinitions)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply ModelLoader lifecycle callback!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: RenderChunk.rebuildChunk()
|
|
||||||
// what: replace call to BlockRendererDispatcher.renderBlock()
|
|
||||||
// why: allows us to perform additional rendering for each block
|
|
||||||
// what: invoke code to overrule result of Block.canRenderInLayer()
|
|
||||||
// why: allows us to render transparent quads for blocks which are only on the SOLID layer
|
|
||||||
transformMethod(Refs.rebuildChunk) {
|
|
||||||
applyWriterFlags(COMPUTE_FRAMES, COMPUTE_MAXS)
|
|
||||||
find(invokeRef(Refs.renderBlock))?.replace {
|
|
||||||
log.info("[BetterFoliageLoader] Applying RenderChunk block render override")
|
|
||||||
varinsn(ALOAD, if (isOptifinePresent) 22 else 20)
|
|
||||||
invokeStatic(Refs.renderWorldBlock)
|
|
||||||
}
|
|
||||||
if (isOptifinePresent) {
|
|
||||||
find(varinsn(ISTORE, 23))?.insertAfter {
|
|
||||||
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (Optifine)")
|
|
||||||
varinsn(ALOAD, 19)
|
|
||||||
varinsn(ALOAD, 18)
|
|
||||||
varinsn(ALOAD, 22)
|
|
||||||
invokeStatic(Refs.canRenderBlockInLayer)
|
|
||||||
varinsn(ISTORE, 23)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
find(invokeRef(Refs.canRenderInLayer))?.replace {
|
|
||||||
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (non-Optifine)")
|
|
||||||
invokeStatic(Refs.canRenderBlockInLayer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace
|
|
||||||
// what: make constructor public
|
|
||||||
// why: use vanilla AO calculation at will without duplicating code
|
|
||||||
transformMethod(Refs.AOF_constructor) {
|
|
||||||
log.info("[BetterFoliageLoader] Setting AmbientOcclusionFace constructor public")
|
|
||||||
makePublic()
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: shadersmod.client.SVertexBuilder.pushEntity()
|
|
||||||
// what: invoke code to overrule block data
|
|
||||||
// why: allows us to change the block ID seen by shader programs
|
|
||||||
transformMethod(Refs.pushEntity_state) {
|
|
||||||
find(invokeRef(Refs.pushEntity_num))?.insertBefore {
|
|
||||||
log.info("[BetterFoliageLoader] Applying SVertexBuilder.pushEntity() block ID override")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
invokeStatic(Refs.getBlockIdOverride)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply SVertexBuilder.pushEntity() block ID override!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package mods.betterfoliage.loader
|
|
||||||
|
|
||||||
import mods.octarinecore.metaprog.ClassRef
|
|
||||||
import mods.octarinecore.metaprog.FieldRef
|
|
||||||
import mods.octarinecore.metaprog.MethodRef
|
|
||||||
import net.minecraftforge.fml.relauncher.FMLInjectionData
|
|
||||||
|
|
||||||
/** Singleton object holding references to foreign code elements. */
|
|
||||||
object Refs {
|
|
||||||
val mcVersion = FMLInjectionData.data()[4].toString()
|
|
||||||
|
|
||||||
// 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 IBlockAccess = ClassRef("net.minecraft.world.IBlockAccess")
|
|
||||||
val IBlockState = ClassRef("net.minecraft.block.state.IBlockState")
|
|
||||||
val BlockStateBase = ClassRef("net.minecraft.block.state.BlockStateBase")
|
|
||||||
val BlockPos = ClassRef("net.minecraft.util.math.BlockPos")
|
|
||||||
val MutableBlockPos = ClassRef("net.minecraft.util.math.BlockPos\$MutableBlockPos")
|
|
||||||
val BlockRenderLayer = ClassRef("net.minecraft.util.BlockRenderLayer")
|
|
||||||
val EnumFacing = ClassRef("net.minecraft.util.EnumFacing")
|
|
||||||
|
|
||||||
val World = ClassRef("net.minecraft.world.World")
|
|
||||||
val WorldClient = ClassRef("net.minecraft.client.multiplayer.WorldClient")
|
|
||||||
val ChunkCache = ClassRef("net.minecraft.world.ChunkCache")
|
|
||||||
val showBarrierParticles = MethodRef(WorldClient, "showBarrierParticles", "func_184153_a", ClassRef.void, ClassRef.int, ClassRef.int, ClassRef.int, ClassRef.int, Random, ClassRef.boolean, MutableBlockPos)
|
|
||||||
|
|
||||||
val Block = ClassRef("net.minecraft.block.Block")
|
|
||||||
val StateImplementation = ClassRef("net.minecraft.block.state.BlockStateContainer\$StateImplementation")
|
|
||||||
val canRenderInLayer = MethodRef(Block, "canRenderInLayer", ClassRef.boolean, IBlockState, BlockRenderLayer)
|
|
||||||
val getAmbientOcclusionLightValue = MethodRef(StateImplementation, "getAmbientOcclusionLightValue", "func_185892_j", ClassRef.float)
|
|
||||||
val useNeighborBrightness = MethodRef(StateImplementation, "useNeighborBrightness", "func_185916_f", ClassRef.boolean)
|
|
||||||
val doesSideBlockRendering = MethodRef(StateImplementation, "doesSideBlockRendering", ClassRef.boolean, IBlockAccess, BlockPos, EnumFacing)
|
|
||||||
val isOpaqueCube = MethodRef(StateImplementation, "isOpaqueCube", "func_185914_p", ClassRef.boolean)
|
|
||||||
val randomDisplayTick = MethodRef(Block, "randomDisplayTick", "func_180655_c", ClassRef.void, IBlockState, World, BlockPos, Random)
|
|
||||||
|
|
||||||
val BlockModelRenderer = ClassRef("net.minecraft.client.renderer.BlockModelRenderer")
|
|
||||||
val AmbientOcclusionFace = ClassRef("net.minecraft.client.renderer.BlockModelRenderer\$AmbientOcclusionFace")
|
|
||||||
val ChunkCompileTaskGenerator = ClassRef("net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator")
|
|
||||||
val BufferBuilder = ClassRef("net.minecraft.client.renderer.BufferBuilder")
|
|
||||||
val AOF_constructor = MethodRef(AmbientOcclusionFace, "<init>", ClassRef.void, BlockModelRenderer)
|
|
||||||
|
|
||||||
val RenderChunk = ClassRef("net.minecraft.client.renderer.chunk.RenderChunk")
|
|
||||||
val rebuildChunk = MethodRef(RenderChunk, "rebuildChunk", "func_178581_b", ClassRef.void, ClassRef.float, ClassRef.float, ClassRef.float, ChunkCompileTaskGenerator)
|
|
||||||
|
|
||||||
val BlockRendererDispatcher = ClassRef("net.minecraft.client.renderer.BlockRendererDispatcher")
|
|
||||||
val renderBlock = MethodRef(BlockRendererDispatcher, "renderBlock", "func_175018_a", ClassRef.boolean, IBlockState, BlockPos, IBlockAccess, BufferBuilder)
|
|
||||||
|
|
||||||
val TextureAtlasSprite = ClassRef("net.minecraft.client.renderer.texture.TextureAtlasSprite")
|
|
||||||
|
|
||||||
val IRegistry = ClassRef("net.minecraft.util.registry.IRegistry")
|
|
||||||
val ModelLoader = ClassRef("net.minecraftforge.client.model.ModelLoader")
|
|
||||||
val stateModels = FieldRef(ModelLoader, "stateModels", Map)
|
|
||||||
val setupModelRegistry = MethodRef(ModelLoader, "setupModelRegistry", "func_177570_a", IRegistry)
|
|
||||||
|
|
||||||
val IModel = ClassRef("net.minecraftforge.client.model.IModel")
|
|
||||||
val ModelBlock = ClassRef("net.minecraft.client.renderer.block.model.ModelBlock")
|
|
||||||
val ResourceLocation = ClassRef("net.minecraft.util.ResourceLocation")
|
|
||||||
val ModelResourceLocation = ClassRef("net.minecraft.client.renderer.block.model.ModelResourceLocation")
|
|
||||||
val VanillaModelWrapper = ClassRef("net.minecraftforge.client.model.ModelLoader\$VanillaModelWrapper")
|
|
||||||
val model_VMW = FieldRef(VanillaModelWrapper, "model", ModelBlock)
|
|
||||||
val location_VMW = FieldRef(VanillaModelWrapper, "location", ModelBlock)
|
|
||||||
val WeightedRandomModel = ClassRef("net.minecraftforge.client.model.ModelLoader\$WeightedRandomModel")
|
|
||||||
val models_WRM = FieldRef(WeightedRandomModel, "models", List)
|
|
||||||
val MultiModel = ClassRef("net.minecraftforge.client.model.MultiModel")
|
|
||||||
val base_MM = FieldRef(MultiModel, "base", IModel)
|
|
||||||
val MultipartModel = ClassRef("net.minecraftforge.client.model.ModelLoader\$MultipartModel")
|
|
||||||
val partModels_MPM = FieldRef(MultipartModel, "partModels", List)
|
|
||||||
|
|
||||||
val BakedQuad = ClassRef("net.minecraft.client.renderer.block.model.BakedQuad")
|
|
||||||
|
|
||||||
val resetChangedState = MethodRef(ClassRef("net.minecraftforge.common.config.Configuration"), "resetChangedState", ClassRef.void)
|
|
||||||
|
|
||||||
|
|
||||||
// Better Foliage
|
|
||||||
val BetterFoliageHooks = ClassRef("mods.betterfoliage.client.Hooks")
|
|
||||||
val getAmbientOcclusionLightValueOverride = MethodRef(BetterFoliageHooks, "getAmbientOcclusionLightValueOverride", ClassRef.float, ClassRef.float, IBlockState)
|
|
||||||
val useNeighborBrightnessOverride = MethodRef(BetterFoliageHooks, "getUseNeighborBrightnessOverride", ClassRef.boolean, ClassRef.boolean, IBlockState)
|
|
||||||
val doesSideBlockRenderingOverride = MethodRef(BetterFoliageHooks, "doesSideBlockRenderingOverride", ClassRef.boolean, ClassRef.boolean, IBlockAccess, BlockPos, EnumFacing)
|
|
||||||
val isOpaqueCubeOverride = MethodRef(BetterFoliageHooks, "isOpaqueCubeOverride", ClassRef.boolean, ClassRef.boolean, IBlockState)
|
|
||||||
val onRandomDisplayTick = MethodRef(BetterFoliageHooks, "onRandomDisplayTick", ClassRef.void, World, IBlockState, BlockPos)
|
|
||||||
val onAfterLoadModelDefinitions = MethodRef(BetterFoliageHooks, "onAfterLoadModelDefinitions", ClassRef.void, ModelLoader)
|
|
||||||
val onAfterBakeModels = MethodRef(BetterFoliageHooks, "onAfterBakeModels", ClassRef.void, Map)
|
|
||||||
val renderWorldBlock = MethodRef(BetterFoliageHooks, "renderWorldBlock", ClassRef.boolean, BlockRendererDispatcher, IBlockState, BlockPos, IBlockAccess, BufferBuilder, BlockRenderLayer)
|
|
||||||
val canRenderBlockInLayer = MethodRef(BetterFoliageHooks, "canRenderBlockInLayer", ClassRef.boolean, Block, IBlockState, BlockRenderLayer)
|
|
||||||
|
|
||||||
// Optifine
|
|
||||||
val OptifineClassTransformer = ClassRef("optifine.OptiFineClassTransformer")
|
|
||||||
val OptifineChunkCache = ClassRef("net.optifine.override.ChunkCacheOF")
|
|
||||||
val CCOFChunkCache = FieldRef(OptifineChunkCache, "chunkCache", ChunkCache)
|
|
||||||
|
|
||||||
val getBlockId = MethodRef(BlockStateBase, "getBlockId", ClassRef.int);
|
|
||||||
val getMetadata = MethodRef(BlockStateBase, "getMetadata", ClassRef.int);
|
|
||||||
|
|
||||||
// Optifine
|
|
||||||
val RenderEnv = ClassRef("net.optifine.render.RenderEnv")
|
|
||||||
val RenderEnv_reset = MethodRef(RenderEnv, "reset", ClassRef.void, IBlockState, BlockPos)
|
|
||||||
val quadSprite = FieldRef(BufferBuilder, "quadSprite", TextureAtlasSprite)
|
|
||||||
|
|
||||||
// Optifine: custom colors
|
|
||||||
val CustomColors = ClassRef("net.optifine.CustomColors")
|
|
||||||
val getColorMultiplier = MethodRef(CustomColors, "getColorMultiplier", ClassRef.int, BakedQuad, IBlockState, IBlockAccess, BlockPos, RenderEnv)
|
|
||||||
|
|
||||||
// Optifine: shaders
|
|
||||||
val SVertexBuilder = ClassRef("net.optifine.shaders.SVertexBuilder")
|
|
||||||
val sVertexBuilder = FieldRef(BufferBuilder, "sVertexBuilder", SVertexBuilder)
|
|
||||||
val pushEntity_state = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, IBlockState, BlockPos, IBlockAccess, BufferBuilder)
|
|
||||||
val pushEntity_num = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, ClassRef.long)
|
|
||||||
val popEntity = MethodRef(SVertexBuilder, "popEntity", ClassRef.void)
|
|
||||||
|
|
||||||
val ShadersModIntegration = ClassRef("mods.betterfoliage.client.integration.ShadersModIntegration")
|
|
||||||
val getBlockIdOverride = MethodRef(ShadersModIntegration, "getBlockIdOverride", ClassRef.long, ClassRef.long, IBlockState)
|
|
||||||
}
|
|
||||||
130
src/main/kotlin/mods/betterfoliage/render/AbstractParticle.kt
Normal file
130
src/main/kotlin/mods/betterfoliage/render/AbstractParticle.kt
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
12
src/main/kotlin/mods/betterfoliage/render/Misc.kt
Normal file
12
src/main/kotlin/mods/betterfoliage/render/Misc.kt
Normal file
@@ -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)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package mods.betterfoliage.render
|
||||||
|
|
||||||
|
import mods.betterfoliage.*
|
||||||
|
import mods.betterfoliage.util.ThreadLocalDelegate
|
||||||
|
import net.minecraft.block.BlockState
|
||||||
|
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 {
|
||||||
|
|
||||||
|
val isColorAvailable = allAvailable(CustomColors, CustomColors.getColorMultiplier)
|
||||||
|
|
||||||
|
init {
|
||||||
|
BetterFoliage.log(Level.INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
|
||||||
|
}
|
||||||
|
|
||||||
|
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }
|
||||||
|
val fakeQuad = BakedQuad(IntArray(0), 1, UP, null)
|
||||||
|
|
||||||
|
fun getBlockColor(ctx: CombinedContext): Int {
|
||||||
|
val ofColor = if (isColorAvailable && MinecraftClient.getInstance().options.reflectDeclaredField<Boolean>("ofCustomColors") == true) {
|
||||||
|
renderEnv.reset(ctx.state, ctx.pos)
|
||||||
|
CustomColors.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int
|
||||||
|
} else null
|
||||||
|
return if (ofColor == null || ofColor == -1) ctx.lightingCtx.color else ofColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OptifineRenderEnv {
|
||||||
|
val wrapped: Any = RenderEnv.element!!.getDeclaredConstructor(BlockState.element, BlockPos.element).let {
|
||||||
|
it.isAccessible = true
|
||||||
|
it.newInstance(null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset(state: BlockState, pos: BlockPos) {
|
||||||
|
RenderEnv.reset.invoke(wrapped, state, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package mods.betterfoliage.render
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
|
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.util.math.BlockPos
|
||||||
|
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)
|
||||||
|
|
||||||
|
val defaultLeaves = Blocks.OAK_LEAVES.defaultState
|
||||||
|
val defaultGrass = Blocks.TALL_GRASS.defaultState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from transformed ShadersMod code.
|
||||||
|
* @see mods.betterfoliage.loader.BetterFoliageTransformer
|
||||||
|
*/
|
||||||
|
@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
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
BetterFoliage.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun renderAs(ctx: CombinedContext, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) =
|
||||||
|
renderAs(ctx, ctx.state, renderType, enabled, func)
|
||||||
|
|
||||||
|
/** Quads rendered inside this block will use the given block entity data in shader programs. */
|
||||||
|
inline fun renderAs(ctx: CombinedContext, state: BlockState, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) {
|
||||||
|
if (isAvailable && enabled) {
|
||||||
|
val buffer = ctx.renderCtx.renderBuffer
|
||||||
|
val sVertexBuilder = buffer[BufferBuilder_sVertexBuilder]
|
||||||
|
SVertexBuilder.pushState.invoke(sVertexBuilder!!, ctx.state, ctx.pos, ctx.world, buffer)
|
||||||
|
func()
|
||||||
|
SVertexBuilder.pop.invoke(sVertexBuilder)
|
||||||
|
} else {
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
|
||||||
|
inline fun grass(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
|
||||||
|
renderAs(ctx, defaultGrass, MODEL, enabled, func)
|
||||||
|
|
||||||
|
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
|
||||||
|
inline fun leaves(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
|
||||||
|
renderAs(ctx, defaultLeaves, MODEL, enabled, func)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
@@ -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<String>, atlas: Consumer<Identifier>): 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<Random>, 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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/main/kotlin/mods/betterfoliage/render/block/vanilla/Dirt.kt
Normal file
101
src/main/kotlin/mods/betterfoliage/render/block/vanilla/Dirt.kt
Normal file
@@ -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<Identifier>) =
|
||||||
|
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<Random>, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/main/kotlin/mods/betterfoliage/render/block/vanilla/Grass.kt
Normal file
121
src/main/kotlin/mods/betterfoliage/render/block/vanilla/Grass.kt
Normal file
@@ -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<ModelTextureList> get() = BetterFoliage.blockConfig.grassModels.modelList
|
||||||
|
|
||||||
|
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): 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<Random>, 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<GrassKey>(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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
src/main/kotlin/mods/betterfoliage/render/block/vanilla/Leaf.kt
Normal file
116
src/main/kotlin/mods/betterfoliage/render/block/vanilla/Leaf.kt
Normal file
@@ -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<ModelTextureList> get() = BetterFoliage.blockConfig.leafModels.modelList
|
||||||
|
|
||||||
|
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>) =
|
||||||
|
defaultRegisterLeaf(Identifier(textures[0]), atlas)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun HasLogger.defaultRegisterLeaf(sprite: Identifier, atlas: Consumer<Identifier>): 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<Random>, 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] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Identifier>) =
|
||||||
|
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<Random>, 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Identifier>) =
|
||||||
|
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<Random>, 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Identifier>) =
|
||||||
|
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<Random>, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ColumnBlockKey>(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<ModelTextureList> get() = BetterFoliage.blockConfig.logModels.modelList
|
||||||
|
|
||||||
|
override fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Identifier>) =
|
||||||
|
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<Random>, 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Quad> {
|
||||||
|
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<Quad> {
|
||||||
|
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<Quad>.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<Quad>.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<Quad>.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<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
|
||||||
|
SMALL_RADIUS -> lidTopRoundSmall[quadrant]
|
||||||
|
LARGE_RADIUS -> lidTopRoundLarge[quadrant]
|
||||||
|
SQUARE -> lidTopSquare[quadrant]
|
||||||
|
INVISIBLE -> lidTopSquare[quadrant]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flatBottom(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
|
||||||
|
SMALL_RADIUS -> lidBottomRoundSmall[quadrant]
|
||||||
|
LARGE_RADIUS -> lidBottomRoundLarge[quadrant]
|
||||||
|
SQUARE -> lidBottomSquare[quadrant]
|
||||||
|
INVISIBLE -> lidBottomSquare[quadrant]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extendTop(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
|
||||||
|
SMALL_RADIUS -> sideExtendTopRoundSmall[quadrant]
|
||||||
|
LARGE_RADIUS -> sideExtendTopRoundLarge[quadrant]
|
||||||
|
SQUARE -> sideExtendTopSquare[quadrant]
|
||||||
|
INVISIBLE -> sideExtendTopSquare[quadrant]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extendBottom(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
|
||||||
|
SMALL_RADIUS -> sideExtendBottomRoundSmall[quadrant]
|
||||||
|
LARGE_RADIUS -> sideExtendBottomRoundLarge[quadrant]
|
||||||
|
SQUARE -> sideExtendBottomSquare[quadrant]
|
||||||
|
INVISIBLE -> sideExtendBottomSquare[quadrant]
|
||||||
|
}
|
||||||
|
}
|
||||||
108
src/main/kotlin/mods/betterfoliage/render/column/ColumnModel.kt
Normal file
108
src/main/kotlin/mods/betterfoliage/render/column/ColumnModel.kt
Normal file
@@ -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<Random>, 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) }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,19 @@
|
|||||||
package mods.betterfoliage.client.render.column
|
package mods.betterfoliage.render.column
|
||||||
|
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayLayer
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
import mods.betterfoliage.chunk.ChunkOverlayLayer
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.chunk.ChunkOverlayManager
|
||||||
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
|
import mods.betterfoliage.chunk.dimType
|
||||||
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
|
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
|
||||||
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
|
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
|
||||||
import mods.betterfoliage.client.render.rotationFromUp
|
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
|
||||||
import mods.octarinecore.client.render.BlockContext
|
import mods.betterfoliage.chunk.BlockCtx
|
||||||
import mods.octarinecore.client.resource.ModelRenderRegistry
|
import mods.betterfoliage.util.*
|
||||||
import mods.octarinecore.common.Int3
|
import net.minecraft.block.BlockState
|
||||||
import mods.octarinecore.common.Rotation
|
|
||||||
import mods.octarinecore.common.face
|
|
||||||
import mods.octarinecore.common.plus
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.world.IBlockAccess
|
import net.minecraft.util.math.Direction.Axis
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
import net.minecraft.util.math.Direction.AxisDirection
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
import net.minecraft.world.ExtendedBlockView
|
||||||
|
|
||||||
/** Index of SOUTH-EAST quadrant. */
|
/** Index of SOUTH-EAST quadrant. */
|
||||||
const val SE = 0
|
const val SE = 0
|
||||||
@@ -29,18 +24,20 @@ const val NW = 2
|
|||||||
/** Index of SOUTH-WEST quadrant. */
|
/** Index of SOUTH-WEST quadrant. */
|
||||||
const val SW = 3
|
const val SW = 3
|
||||||
|
|
||||||
|
interface ColumnBlockKey {
|
||||||
|
val axis: Axis?
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sealed class hierarchy for all possible render outcomes
|
* Sealed class hierarchy for all possible render outcomes
|
||||||
*/
|
*/
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
sealed class ColumnLayerData {
|
sealed class ColumnLayerData {
|
||||||
/**
|
/**
|
||||||
* Data structure to cache texture and world neighborhood data relevant to column rendering
|
* Data structure to cache texture and world neighborhood data relevant to column rendering
|
||||||
*/
|
*/
|
||||||
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
|
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
data class SpecialRender(
|
data class SpecialRender(
|
||||||
val column: ColumnTextureInfo,
|
val column: ColumnBlockKey,
|
||||||
val upType: BlockType,
|
val upType: BlockType,
|
||||||
val downType: BlockType,
|
val downType: BlockType,
|
||||||
val quadrants: Array<QuadrantType>,
|
val quadrants: Array<QuadrantType>,
|
||||||
@@ -48,49 +45,48 @@ sealed class ColumnLayerData {
|
|||||||
val quadrantsBottom: Array<QuadrantType>
|
val quadrantsBottom: Array<QuadrantType>
|
||||||
) : ColumnLayerData() {
|
) : ColumnLayerData() {
|
||||||
enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR }
|
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 */
|
/** Column block should not be rendered at all */
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object SkipRender : ColumnLayerData()
|
object SkipRender : ColumnLayerData()
|
||||||
|
|
||||||
/** Column block must be rendered normally */
|
/** Column block must be rendered normally */
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object NormalRender : ColumnLayerData()
|
object NormalRender : ColumnLayerData()
|
||||||
|
|
||||||
/** Error while resolving render data, column block must be rendered normally */
|
/** Error while resolving render data, column block must be rendered normally */
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object ResolveError : ColumnLayerData()
|
object ResolveError : ColumnLayerData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
||||||
|
|
||||||
abstract val registry: ModelRenderRegistry<ColumnTextureInfo>
|
|
||||||
abstract val blockPredicate: (IBlockState)->Boolean
|
|
||||||
abstract val surroundPredicate: (IBlockState) -> Boolean
|
|
||||||
abstract val connectSolids: Boolean
|
abstract val connectSolids: Boolean
|
||||||
abstract val lenientConnect: Boolean
|
abstract val lenientConnect: Boolean
|
||||||
abstract val defaultToY: 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) }}}
|
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
|
||||||
|
|
||||||
override fun onBlockUpdate(world: IBlockAccess, pos: BlockPos) {
|
override fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos) {
|
||||||
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(this, pos + offset) }
|
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun calculate(world: IBlockAccess, pos: BlockPos) = calculate(BlockContext(world, pos))
|
override fun calculate(ctx: BlockCtx): ColumnLayerData {
|
||||||
|
if (allDirections.all { ctx.offset(it).isNormalCube }) return ColumnLayerData.SkipRender
|
||||||
fun calculate(ctx: BlockContext): ColumnLayerData {
|
// val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
|
||||||
if (ctx.isSurroundedBy(surroundPredicate)) return ColumnLayerData.SkipRender
|
val columnTextures = getColumnKey(ctx.state) ?: return ColumnLayerData.ResolveError
|
||||||
val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
|
|
||||||
|
|
||||||
// if log axis is not defined and "Default to vertical" config option is not set, render normally
|
// if log axis is not defined and "Default to vertical" config option is not set, render normally
|
||||||
val logAxis = columnTextures.axis ?: if (defaultToY) EnumFacing.Axis.Y else return ColumnLayerData.NormalRender
|
val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender
|
||||||
|
|
||||||
// check log neighborhood
|
// check log neighborhood
|
||||||
val baseRotation = rotationFromUp[(logAxis to EnumFacing.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 upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
|
||||||
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
|
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
|
||||||
@@ -109,7 +105,7 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */
|
/** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */
|
||||||
fun Array<QuadrantType>.checkNeighbors(ctx: BlockContext, rotation: Rotation, logAxis: EnumFacing.Axis, yOff: Int): Array<QuadrantType> {
|
fun Array<QuadrantType>.checkNeighbors(ctx: BlockCtx, rotation: Rotation, logAxis: Axis, yOff: Int): Array<QuadrantType> {
|
||||||
val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1))
|
val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1))
|
||||||
val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0))
|
val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0))
|
||||||
val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1))
|
val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1))
|
||||||
@@ -176,13 +172,13 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
|||||||
/**
|
/**
|
||||||
* Get the type of the block at the given offset in a rotated reference frame.
|
* Get the type of the block at the given offset in a rotated reference frame.
|
||||||
*/
|
*/
|
||||||
fun BlockContext.blockType(rotation: Rotation, axis: EnumFacing.Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
|
fun BlockCtx.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
|
||||||
val offsetRot = offset.rotate(rotation)
|
val offsetRot = offset.rotate(rotation)
|
||||||
val state = blockState(offsetRot)
|
val key = getColumnKey(state(offsetRot))
|
||||||
return if (!blockPredicate(state)) {
|
return if (key == null) {
|
||||||
if (state.isOpaqueCube) SOLID else NONSOLID
|
if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
|
||||||
} else {
|
} else {
|
||||||
(registry[state, world!!, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) EnumFacing.Axis.Y else null)?.let {
|
(key.axis ?: if (BetterFoliage.config.roundLogs.defaultY) Axis.Y else null)?.let {
|
||||||
if (it == axis) PARALLEL else PERPENDICULAR
|
if (it == axis) PARALLEL else PERPENDICULAR
|
||||||
} ?: SOLID
|
} ?: SOLID
|
||||||
}
|
}
|
||||||
@@ -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<Direction, Direction>? {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/main/kotlin/mods/betterfoliage/render/lighting/Indigo.kt
Normal file
53
src/main/kotlin/mods/betterfoliage/render/lighting/Indigo.kt
Normal file
@@ -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<ModifiedTerrainMeshConsumer>()
|
||||||
|
|
||||||
|
fun TerrainMeshConsumer.modified() = MODIFIED_CONSUMER_POOL.get() ?: let {
|
||||||
|
val blockInfo = reflectField<TerrainBlockRenderInfo>("blockInfo")
|
||||||
|
val chunkInfo = reflectField<ChunkRenderInfo>("chunkInfo")
|
||||||
|
val aoCalc = reflectField<AoCalculator>("aoCalc")
|
||||||
|
val transform = reflectField<RenderContext.QuadTransform>("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<Random>, context: RenderContext) = when(this) {
|
||||||
|
is TerrainRenderContext -> {
|
||||||
|
val blockInfo = reflectField<TerrainBlockRenderInfo>("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<Mesh>)->Unit) = when(this) {
|
||||||
|
is TerrainRenderContext -> {
|
||||||
|
val consumer = (meshConsumer() as TerrainMeshConsumer).modified()
|
||||||
|
consumer.clearLighting()
|
||||||
|
consumer.lighter = lighter
|
||||||
|
func(consumer)
|
||||||
|
consumer.lighter = null
|
||||||
|
}
|
||||||
|
else -> func(meshConsumer())
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<String, List<Identifier>>()
|
||||||
|
val spriteSets = mutableMapOf<String, SpriteSet>()
|
||||||
|
|
||||||
|
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<Mapping> = 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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Double3> = LinkedList<Double3>()
|
||||||
|
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") }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
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<Map<BlockState, BakedModel>>("net.minecraft.class_773", "field_4162", "Ljava/util/Map;")
|
||||||
|
|
||||||
|
class BakedModelReplacer : ModelLoadingCallback, ClientSpriteRegistryCallback, BlockModelsReloadCallback, Invalidator, HasLogger {
|
||||||
|
override val logger get() = BetterFoliage.logDetail
|
||||||
|
|
||||||
|
val discoverers = mutableListOf<ModelDiscovery>()
|
||||||
|
override val callbacks = mutableListOf<WeakReference<()->Unit>>()
|
||||||
|
|
||||||
|
protected var keys = emptyMap<BlockState, BlockRenderKey>()
|
||||||
|
|
||||||
|
operator fun get(state: BlockState) = keys[state]
|
||||||
|
inline fun <reified T> getTyped(state: BlockState) = get(state) as? T
|
||||||
|
|
||||||
|
var currentLoader: ModelLoader? = null
|
||||||
|
|
||||||
|
override fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) {
|
||||||
|
// Step 1: get a hold of the ModelLoader instance when model reloading starts
|
||||||
|
currentLoader = loader
|
||||||
|
log("reloading block discovery configuration")
|
||||||
|
BetterFoliage.blockConfig.reloadConfig(manager)
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
|
||||||
|
// Step 2: ModelLoader is finished with the unbaked models by now, we can inspect them
|
||||||
|
log("discovering blocks")
|
||||||
|
val idSet = Collections.synchronizedSet(mutableSetOf<Identifier>())
|
||||||
|
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<BlockState, BlockRenderKey>()
|
||||||
|
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) {
|
||||||
|
// Step 3: replace the baked models with our own
|
||||||
|
log("block model baking finished")
|
||||||
|
val modelMap = blockModels[BlockModels_models] as MutableMap<BlockState, BakedModel>
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<JsonUnbakedModel>("net.minecraft.class_793", "field_4253", "Lnet/minecraft/class_793;")
|
||||||
|
// net.minecraft.client.render.model.json.JsonUnbakedModel.parentId
|
||||||
|
val JsonUnbakedModel_parentId = YarnHelper.requiredField<Identifier>("net.minecraft.class_793", "field_4247", "Lnet/minecraft/class_2960;")
|
||||||
|
|
||||||
|
fun Pair<JsonUnbakedModel, Identifier>.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<ModelTextureList>
|
||||||
|
|
||||||
|
abstract fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): BlockRenderKey?
|
||||||
|
|
||||||
|
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>): 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<Pair<JsonUnbakedModel, Identifier>>).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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package mods.betterfoliage.resource.discovery
|
||||||
|
|
||||||
|
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.resource.ResourceManager
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import org.apache.logging.log4j.Logger
|
||||||
|
|
||||||
|
interface IBlockMatcher {
|
||||||
|
fun matchesClass(block: Block): Boolean
|
||||||
|
fun matchingClass(block: Block): Class<*>?
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleBlockMatcher(vararg val classes: Class<*>) : IBlockMatcher {
|
||||||
|
override fun matchesClass(block: Block) = matchingClass(block) != null
|
||||||
|
|
||||||
|
override fun matchingClass(block: Block): Class<*>? {
|
||||||
|
val blockClass = block.javaClass
|
||||||
|
classes.forEach { if (it.isAssignableFrom(blockClass)) return it }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigurableBlockMatcher(val logger: Logger, val location: Identifier) : IBlockMatcher {
|
||||||
|
|
||||||
|
val blackList = mutableListOf<Class<*>>()
|
||||||
|
val whiteList = mutableListOf<Class<*>>()
|
||||||
|
|
||||||
|
override fun matchesClass(block: Block): Boolean {
|
||||||
|
val blockClass = block.javaClass
|
||||||
|
blackList.forEach { if (it.isAssignableFrom(blockClass)) return false }
|
||||||
|
whiteList.forEach { if (it.isAssignableFrom(blockClass)) return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun matchingClass(block: Block): Class<*>? {
|
||||||
|
val blockClass = block.javaClass
|
||||||
|
blackList.forEach { if (it.isAssignableFrom(blockClass)) return null }
|
||||||
|
whiteList.forEach { if (it.isAssignableFrom(blockClass)) return it }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readDefaults(manager: ResourceManager) {
|
||||||
|
blackList.clear()
|
||||||
|
whiteList.clear()
|
||||||
|
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("-")) 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: Identifier, val textureNames: List<String>) {
|
||||||
|
constructor(vararg args: String) : this(Identifier(args[0]), listOf(*args).drop(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModelTextureListConfiguration(val logger: Logger, val location: Identifier) {
|
||||||
|
val modelList = mutableListOf<ModelTextureList>()
|
||||||
|
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(Identifier(elements.first()), elements.drop(1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<UnbakedModel, Identifier>): List<Pair<UnbakedModel, Identifier>> = when(val model = modelAndLoc.first) {
|
||||||
|
is WeightedUnbakedModel -> (model.variants as List<ModelVariant>).flatMap {
|
||||||
|
variant -> unwrapVariants(getOrLoadModel(variant.location) to variant.location)
|
||||||
|
}
|
||||||
|
is JsonUnbakedModel -> listOf(modelAndLoc)
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModelDiscovery {
|
||||||
|
fun discover(loader: ModelLoader, atlas: Consumer<Identifier>): Map<BlockState, BlockRenderKey>
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ModelDiscoveryBase : ModelDiscovery, HasLogger {
|
||||||
|
override fun discover(loader: ModelLoader, atlas: Consumer<Identifier>): Map<BlockState, BlockRenderKey> {
|
||||||
|
val keys = mutableMapOf<BlockState, BlockRenderKey>()
|
||||||
|
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<Identifier>): BlockRenderKey?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package mods.betterfoliage.resource.generated
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCKS, val aspectHeight: Int = 1, val aspectWidth: Int = 1) {
|
||||||
|
|
||||||
|
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
|
||||||
|
|
||||||
|
fun draw(resourceManager: ResourceManager): ByteArray {
|
||||||
|
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
|
||||||
|
|
||||||
|
val frameWidth = baseTexture.width
|
||||||
|
val frameHeight = baseTexture.width * aspectHeight / aspectWidth
|
||||||
|
val frames = baseTexture.height / frameHeight
|
||||||
|
val size = max(frameWidth, frameHeight)
|
||||||
|
|
||||||
|
val result = BufferedImage(size, size * frames, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
val graphics = result.createGraphics()
|
||||||
|
|
||||||
|
// iterate all frames
|
||||||
|
for (frame in 0 until frames) {
|
||||||
|
val baseFrame = baseTexture.getSubimage(0, size * frame, frameWidth, frameHeight)
|
||||||
|
val resultFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
|
||||||
|
resultFrame.createGraphics().apply {
|
||||||
|
drawImage(baseFrame, (size - frameWidth) / 2, (size - frameHeight) / 2, null)
|
||||||
|
}
|
||||||
|
graphics.drawImage(resultFrame, 0, size * frame, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <T : Any?> parseMetadata(deserializer: ResourceMetadataReader<T>) = null
|
||||||
|
override fun openRoot(id: String) = null
|
||||||
|
override fun findResources(type: ResourceType, path: String, maxDepth: Int, filter: Predicate<String>) = emptyList<Identifier>()
|
||||||
|
override fun close() {}
|
||||||
|
|
||||||
|
protected var manager: ResourceManager? = null
|
||||||
|
val identifiers: MutableMap<Any, Identifier> = Collections.synchronizedMap(mutableMapOf<Any, Identifier>())
|
||||||
|
val resources: MutableMap<Identifier, ByteArray> = Collections.synchronizedMap(mutableMapOf<Identifier, ByteArray>())
|
||||||
|
|
||||||
|
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<Void> {
|
||||||
|
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 <T : ResourcePackContainer> registerContainer(nameToPackMap: MutableMap<String, T>, packInfoFactory: ResourcePackContainer.Factory<T>) {
|
||||||
|
(nameToPackMap as MutableMap<String, ResourcePackContainer>)[reloadId.toString()] = packInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package mods.betterfoliage.client.texture
|
package mods.betterfoliage.resource.generated
|
||||||
|
|
||||||
import mods.octarinecore.client.resource.*
|
import mods.betterfoliage.util.*
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.resource.ResourceManager
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,13 +11,13 @@ import java.awt.image.BufferedImage
|
|||||||
*
|
*
|
||||||
* @param[domain] Resource domain of generator
|
* @param[domain] Resource domain of generator
|
||||||
*/
|
*/
|
||||||
class GrassGenerator(domain: String) : TextureGenerator(domain) {
|
data class GeneratedGrassSprite(val sprite: Identifier, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) {
|
||||||
|
constructor(sprite: String, isSnowed: Boolean) : this(Identifier(sprite), isSnowed)
|
||||||
|
|
||||||
override fun generate(params: ParameterList): BufferedImage? {
|
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
|
||||||
val target = targetResource(params)!!
|
|
||||||
val isSnowed = params["snowed"]?.toBoolean() ?: false
|
|
||||||
|
|
||||||
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
|
fun draw(resourceManager: ResourceManager): ByteArray {
|
||||||
|
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
|
||||||
|
|
||||||
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
|
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
val graphics = result.createGraphics()
|
val graphics = result.createGraphics()
|
||||||
@@ -25,7 +26,7 @@ class GrassGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
val frames = baseTexture.height / size
|
val frames = baseTexture.height / size
|
||||||
|
|
||||||
// iterate all frames
|
// iterate all frames
|
||||||
for (frame in 0 .. frames - 1) {
|
for (frame in 0 until frames) {
|
||||||
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
|
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
|
||||||
val grassFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
|
val grassFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
|
||||||
@@ -39,12 +40,11 @@ class GrassGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// blend with white if snowed
|
// blend with white if snowed
|
||||||
if (isSnowed && target.first == ResourceType.COLOR) {
|
if (isSnowed) {
|
||||||
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
|
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
|
||||||
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
|
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result.bytes
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,42 +1,38 @@
|
|||||||
package mods.betterfoliage.client.texture
|
package mods.betterfoliage.resource.generated
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.octarinecore.client.resource.*
|
import mods.betterfoliage.util.*
|
||||||
import mods.octarinecore.stripStart
|
import net.minecraft.resource.Resource
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.resource.ResourceManager
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate round leaf textures from leaf block textures.
|
* Generate round leaf textures from leaf block textures.
|
||||||
* The base texture is tiled 2x2, then parts of it are made transparent by applying a mask to the alpha channel.
|
* The base texture is tiled 2x2, then parts of it are made transparent by applying a mask to the alpha channel.
|
||||||
*
|
*
|
||||||
* Generator parameter _type_: Leaf type (configurable by user). Different leaf types may have their own alpha mask.
|
* Different leaf types may have their own alpha mask.
|
||||||
*
|
*
|
||||||
* @param[domain] Resource domain of generator
|
* @param[domain] Resource domain of generator
|
||||||
*/
|
*/
|
||||||
class LeafGenerator(domain: String) : TextureGenerator(domain) {
|
data class GeneratedLeafSprite(val sprite: Identifier, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) {
|
||||||
|
|
||||||
override fun generate(params: ParameterList): BufferedImage? {
|
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
|
||||||
val target = targetResource(params)!!
|
|
||||||
val leafType = params["type"] ?: "default"
|
|
||||||
|
|
||||||
val handDrawnLoc = target.second.stripStart("textures/").stripStart("blocks/").let {
|
fun draw(resourceManager: ResourceManager): ByteArray {
|
||||||
ResourceLocation(BetterFoliageMod.DOMAIN, "${it.namespace}/textures/blocks/${it.path}")
|
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
|
||||||
}
|
|
||||||
resourceManager[handDrawnLoc]?.loadImage()?.let { return it }
|
|
||||||
|
|
||||||
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
|
|
||||||
val size = baseTexture.width
|
val size = baseTexture.width
|
||||||
val frames = baseTexture.height / size
|
val frames = baseTexture.height / size
|
||||||
|
|
||||||
val maskTexture = (getLeafMask(leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage()
|
val maskTexture = (getLeafMask(leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage()
|
||||||
fun scale(i: Int) = i * maskTexture!!.width / (size * 2)
|
fun scale(i: Int) = i * maskTexture!!.width / (size * 2)
|
||||||
|
|
||||||
val leafTexture = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR)
|
val result = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
val graphics = leafTexture.createGraphics()
|
val graphics = result.createGraphics()
|
||||||
|
|
||||||
// iterate all frames
|
// iterate all frames
|
||||||
for (frame in 0 .. frames - 1) {
|
for (frame in 0 until frames) {
|
||||||
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
|
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
|
||||||
val leafFrame = BufferedImage(size * 2, size * 2, BufferedImage.TYPE_4BYTE_ABGR)
|
val leafFrame = BufferedImage(size * 2, size * 2, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
|
||||||
@@ -49,8 +45,8 @@ class LeafGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// overlay alpha mask
|
// overlay alpha mask
|
||||||
if (target.first == ResourceType.COLOR && maskTexture != null) {
|
if (maskTexture != null) {
|
||||||
for (x in 0 .. size * 2 - 1) for (y in 0 .. size * 2 - 1) {
|
for (x in 0 until size * 2) for (y in 0 until size * 2) {
|
||||||
val basePixel = leafFrame[x, y].toLong() and 0xFFFFFFFFL
|
val basePixel = leafFrame[x, y].toLong() and 0xFFFFFFFFL
|
||||||
val maskPixel = maskTexture[scale(x), scale(y)].toLong() and 0xFF000000L or 0xFFFFFFL
|
val maskPixel = maskTexture[scale(x), scale(y)].toLong() and 0xFF000000L or 0xFFFFFFL
|
||||||
leafFrame[x, y] = (basePixel and maskPixel).toInt()
|
leafFrame[x, y] = (basePixel and maskPixel).toInt()
|
||||||
@@ -61,7 +57,7 @@ class LeafGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
graphics.drawImage(leafFrame, 0, size * frame * 2, null)
|
graphics.drawImage(leafFrame, 0, size * frame * 2, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
return leafTexture
|
return result.bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +67,20 @@ class LeafGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
* @param[maxSize] Preferred mask size.
|
* @param[maxSize] Preferred mask size.
|
||||||
*/
|
*/
|
||||||
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
|
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
|
||||||
ResourceLocation(BetterFoliageMod.DOMAIN, "textures/blocks/leafmask_${size}_${type}.png")
|
Atlas.BLOCKS.wrap(Identifier(BetterFoliage.MOD_ID, "blocks/leafmask_${size}_${type}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a texture resource when multiple sizes may exist.
|
||||||
|
*
|
||||||
|
* @param[maxSize] Maximum size to consider. This value is progressively halved when searching for smaller versions.
|
||||||
|
* @param[maskPath] Location of the texture of the given size
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->Identifier): Resource? {
|
||||||
|
var size = maxSize
|
||||||
|
val sizes = mutableListOf<Int>()
|
||||||
|
while(size > 2) { sizes.add(size); size /= 2 }
|
||||||
|
return sizes.findFirst { resourceManager[maskPath(it)] }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
114
src/main/kotlin/mods/betterfoliage/resource/model/Meshify.kt
Normal file
114
src/main/kotlin/mods/betterfoliage/resource/model/Meshify.kt
Normal file
@@ -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<BakedModelConverter>.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<Quad> {
|
||||||
|
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 }
|
||||||
230
src/main/kotlin/mods/betterfoliage/resource/model/Quads.kt
Normal file
230
src/main/kotlin/mods/betterfoliage/resource/model/Quads.kt
Normal file
@@ -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<Double, Direction>) = 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<Quad>.transform(trans: Quad.()-> Quad) = map { it.trans() }
|
||||||
|
fun Array<List<Quad>>.transform(trans: Quad.(Int)-> Quad) = mapIndexed { idx, qList -> qList.map { it.trans(idx) } }.toTypedArray()
|
||||||
|
|
||||||
|
fun List<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) }
|
||||||
|
fun Array<List<Quad>>.withOpposites() = map { it.withOpposites() }.toTypedArray()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pour quad data into a fabric-renderer-api Mesh
|
||||||
|
*/
|
||||||
|
fun List<Quad>.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<List<Quad>>.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)) }
|
||||||
@@ -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<Sprite>) : SpriteSet {
|
||||||
|
override val num = sprites.size
|
||||||
|
override fun get(idx: Int) = sprites[idx % num]
|
||||||
|
|
||||||
|
constructor(atlas: Atlas, ids: List<Identifier>) : 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<Any, Sprite>, 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<Any, SpriteSet>, ClientSpriteRegistryCallback {
|
||||||
|
private var idList: List<Identifier> = 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!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<TuftShapeKey> {
|
||||||
|
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<TuftShapeKey>, 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<List<Quad>> {
|
||||||
|
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<List<Quad>>, 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<List<Quad>>.buildTufts() = withOpposites().build(CUTOUT_MIPPED)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user