Compare commits
215 Commits
0.9.6b
...
1.14.4-Fab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d741338d46 | ||
|
|
01f697d2d3 | ||
|
|
4c439dccd2 | ||
|
|
df50f61b0d | ||
|
|
2252fb3b42 | ||
|
|
4efa831296 | ||
|
|
a3d99c3076 | ||
|
|
c4ee768025 | ||
|
|
b4f18c1e1d | ||
|
|
2a06c18884 | ||
|
|
2ba99f40e7 | ||
|
|
46cbe64328 | ||
|
|
7b739c172f | ||
|
|
8319d721c7 | ||
|
|
1b0e93b050 | ||
|
|
1ea2b6b946 | ||
|
|
558c9a2c34 | ||
|
|
f102bf4bda | ||
|
|
f5cbf48dfa | ||
|
|
d9cc03511a | ||
|
|
d0265483d2 | ||
|
|
20da2a27bd | ||
|
|
02509fa44d | ||
|
|
6801304bd1 | ||
|
|
d96ac1c94c | ||
|
|
ac015b12df | ||
|
|
71f0be0c93 | ||
|
|
bcc1b04e6b | ||
|
|
cbee4916ed | ||
|
|
418d955f54 | ||
|
|
3121542c07 | ||
|
|
ab08457ae7 | ||
|
|
5b1b35a891 | ||
|
|
787b3993b5 | ||
|
|
c593ff9bcb | ||
|
|
afa619f838 | ||
|
|
e704a0af94 | ||
|
|
0997b83367 | ||
|
|
fe3030ef77 | ||
|
|
7aa510189a | ||
|
|
36a3a38ff1 | ||
|
|
7f4ee4b0a3 | ||
|
|
a4c6d1eecd | ||
|
|
73223c15c3 | ||
|
|
b55d90802f | ||
|
|
c5261216b2 | ||
|
|
50399052b0 | ||
|
|
e29d224df4 | ||
|
|
3b7c44b062 | ||
|
|
32bf60492d | ||
|
|
9685971fd4 | ||
|
|
dacc63b11a | ||
|
|
e20a3fbc54 | ||
|
|
39df1951df | ||
|
|
4761eb266f | ||
|
|
25c302ecb6 | ||
|
|
dec1f6ff03 | ||
|
|
f145ff221e | ||
|
|
c865c8e5ad | ||
|
|
47781cad91 | ||
|
|
e329ce0270 | ||
|
|
7cae04d7b4 | ||
|
|
0bbf206569 | ||
|
|
55095e7252 | ||
|
|
8eebb98c9d | ||
|
|
2a8a9c2703 | ||
|
|
7da89e24f1 | ||
|
|
fb078ab365 | ||
|
|
b5d87bb148 | ||
|
|
6c98940d3e | ||
|
|
813719c7f2 | ||
|
|
800fb4db9f | ||
|
|
38b35c910b | ||
|
|
61076789db | ||
|
|
fde6c47ed3 | ||
|
|
a9fba1a18e | ||
|
|
81ef954524 | ||
|
|
674d22fdbb | ||
|
|
381b154413 | ||
|
|
568549e260 | ||
|
|
6d62cb9ac0 | ||
|
|
5bea5cde99 | ||
|
|
28cead464e | ||
|
|
8ffca417fb | ||
|
|
8f9a35f40e | ||
|
|
ef90adf577 | ||
|
|
370e2bb38c | ||
|
|
fefd5e5633 | ||
|
|
bae81e8085 | ||
|
|
479e4cadfa | ||
|
|
821d618395 | ||
|
|
eb6058c4ee | ||
|
|
625a3bd543 | ||
|
|
37d091daed | ||
|
|
d852faad96 | ||
|
|
da8d7ec237 | ||
|
|
31f64749b1 | ||
|
|
66558932a9 | ||
|
|
ceb3e5b116 | ||
|
|
1a1aa81c0f | ||
|
|
522fc1de33 | ||
|
|
90c13a3a8e | ||
|
|
66b4df2850 | ||
|
|
7df142cf50 | ||
|
|
2b7582c5af | ||
|
|
56e3dc5d24 | ||
|
|
f7044e5225 | ||
|
|
59e4d0c602 | ||
|
|
931dca6f3f | ||
|
|
c356c3ef57 | ||
|
|
468d0f34b6 | ||
|
|
6f42152cde | ||
|
|
44bfc93e1b | ||
|
|
70591a484e | ||
|
|
fdc14595db | ||
|
|
aa8226b46b | ||
|
|
ad78529d2a | ||
|
|
8c922fd2e8 | ||
|
|
9e9666f69f | ||
|
|
2c0e95ba5b | ||
|
|
85a4707494 | ||
|
|
f0a447bbbb | ||
|
|
4a4d39b523 | ||
|
|
e00ccd5919 | ||
|
|
f032814d99 | ||
|
|
62294bb2bb | ||
|
|
dec1ffd71c | ||
|
|
488078b50f | ||
|
|
1bd353577f | ||
|
|
913496473d | ||
|
|
a8e6c6c470 | ||
|
|
8bdb5ca8fd | ||
|
|
5efc974133 | ||
|
|
7f3617ef59 | ||
|
|
400b965e02 | ||
|
|
f96409f9a1 | ||
|
|
2ca330fd29 | ||
|
|
acf477d709 | ||
|
|
97d5d6320c | ||
|
|
6831050c77 | ||
|
|
70ec7e5289 | ||
|
|
f66aabea67 | ||
|
|
0635f1e19e | ||
|
|
44a8abeb4b | ||
|
|
b5af0fe1c5 | ||
|
|
69db3d6608 | ||
|
|
6903bd2de2 | ||
|
|
9a544b1b53 | ||
|
|
bbd4df418c | ||
|
|
626bc69dad | ||
|
|
66ed1c098f | ||
|
|
f37cb273f1 | ||
|
|
a6cc354965 | ||
|
|
8fe4346922 | ||
|
|
2f45abcd7c | ||
|
|
57cae957f8 | ||
|
|
befb64b8fc | ||
|
|
a0aad5d608 | ||
|
|
c0685d829b | ||
|
|
4209d1eea3 | ||
|
|
ac2001694b | ||
|
|
50c4882855 | ||
|
|
07dc8888ef | ||
|
|
efebf29c64 | ||
|
|
983703133a | ||
|
|
ee19120651 | ||
|
|
cbaebfa26a | ||
|
|
1ff5d45840 | ||
|
|
7e667d483a | ||
|
|
6d5c03ba6a | ||
|
|
f47aedf84d | ||
|
|
6ee27c2a99 | ||
|
|
abf037d8a9 | ||
|
|
c0be72bb37 | ||
|
|
42c14790af | ||
|
|
087e8d5685 | ||
|
|
aaca43fe2c | ||
|
|
8e251dc038 | ||
|
|
1a6ffb251b | ||
|
|
efb49125b8 | ||
|
|
6032a120d8 | ||
|
|
f68f0e5edd | ||
|
|
7393fed5fd | ||
|
|
41b080646a | ||
|
|
111f1b3907 | ||
|
|
662b4f50f0 | ||
|
|
d9a042b356 | ||
|
|
ef574a13ac | ||
|
|
b246c7acd0 | ||
|
|
35ce80c602 | ||
|
|
4fdabbaf69 | ||
|
|
0a6c433530 | ||
|
|
3e6f98885f | ||
|
|
4720667f53 | ||
|
|
5616a68f5a | ||
|
|
d1480ed3be | ||
|
|
023e286fd4 | ||
|
|
6c8f40f4e2 | ||
|
|
6306a5b03b | ||
|
|
b0193bb108 | ||
|
|
eafd36b4b1 | ||
|
|
dac7033e18 | ||
|
|
36c6b775db | ||
|
|
0f985e996b | ||
|
|
e926f018e7 | ||
|
|
3716804ffb | ||
|
|
2d70de00e7 | ||
|
|
9b717b549b | ||
|
|
81abad82e9 | ||
|
|
601679f070 | ||
|
|
f0073b0f2a | ||
|
|
5bdbb366eb | ||
|
|
2e09f1f10a | ||
|
|
8460103030 | ||
|
|
f44043bb0b |
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<classpath>
|
|
||||||
<classpathentry kind="src" path="src/main/java"/>
|
|
||||||
<classpathentry kind="src" path="src/main/resources"/>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
|
||||||
<classpathentry kind="con" path="org.springsource.ide.eclipse.gradle.classpathcontainer"/>
|
|
||||||
<classpathentry kind="output" path="bin"/>
|
|
||||||
</classpath>
|
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,5 +1,9 @@
|
|||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
run/
|
||||||
.gradle/
|
.gradle/
|
||||||
.settings/
|
|
||||||
bin/
|
|
||||||
build/
|
build/
|
||||||
libs/
|
classes/
|
||||||
|
temp/
|
||||||
|
|||||||
18
.project
18
.project
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<projectDescription>
|
|
||||||
<name>BetterFoliage</name>
|
|
||||||
<comment></comment>
|
|
||||||
<projects>
|
|
||||||
</projects>
|
|
||||||
<buildSpec>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
|
||||||
<natures>
|
|
||||||
<nature>org.springsource.ide.eclipse.gradle.core.nature</nature>
|
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
||||||
</natures>
|
|
||||||
</projectDescription>
|
|
||||||
@@ -1,11 +1,4 @@
|
|||||||
BetterFoliage
|
BetterFoliage
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Minecraft mod that alters the appearance of leaves & grass
|
Minecraft mod that alters the appearance of leaves & grass
|
||||||
|
|
||||||
More info: http://www.minecraftforum.net/topic/2776217-better-foliage/
|
|
||||||
|
|
||||||
Download
|
|
||||||
========
|
|
||||||
[BetterFoliage 0.9.4-beta] (http://goo.gl/pNBE23) (MC 1.7.2)
|
|
||||||
|
|
||||||
[BetterFoliage 0.9.4-beta] (http://goo.gl/ywT6Xg) (MC 1.7.10)
|
|
||||||
|
|||||||
45
build.gradle
45
build.gradle
@@ -1,45 +0,0 @@
|
|||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
name = "forge"
|
|
||||||
url = "http://files.minecraftforge.net/maven"
|
|
||||||
}
|
|
||||||
maven {
|
|
||||||
name = "sonatype"
|
|
||||||
url = "https://oss.sonatype.org/content/repositories/snapshots/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'net.minecraftforge.gradle:ForgeGradle:1.2-SNAPSHOT'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
apply plugin: 'forge'
|
|
||||||
|
|
||||||
minecraft {
|
|
||||||
version = '1.7.2-10.12.2.1147'
|
|
||||||
}
|
|
||||||
|
|
||||||
jar.baseName = 'BetterFoliage'
|
|
||||||
group = 'com.github.octarine-noise'
|
|
||||||
version='0.9.6b'
|
|
||||||
|
|
||||||
processResources {
|
|
||||||
inputs.property "version", project.version
|
|
||||||
inputs.property "mcversion", project.minecraft.version
|
|
||||||
|
|
||||||
from(sourceSets.main.resources.srcDirs) {
|
|
||||||
include 'mcmod.info'
|
|
||||||
expand 'version':project.version, 'mcversion':project.minecraft.version
|
|
||||||
}
|
|
||||||
|
|
||||||
from(sourceSets.main.resources.srcDirs) {
|
|
||||||
exclude 'mcmod.info'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
manifest {
|
|
||||||
attributes("FMLCorePlugin": "mods.betterfoliage.loader.BetterFoliageLoader", "FMLCorePluginContainsFMLMod": "mods.betterfoliage.BetterFoliage")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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"
|
||||||
|
}
|
||||||
21
gradle.properties
Normal file
21
gradle.properties
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx2G
|
||||||
|
org.gradle.daemon=false
|
||||||
|
|
||||||
|
group = com.github.octarine-noise
|
||||||
|
name = betterfoliage
|
||||||
|
jarName = BetterFoliage-Forge
|
||||||
|
|
||||||
|
version = 2.5.0
|
||||||
|
|
||||||
|
mcVersion = 1.14.4
|
||||||
|
yarnMappings=1.14.4+build.15
|
||||||
|
loaderVersion=0.7.3+build.176
|
||||||
|
fabricVersion=0.4.2+build.246-1.14
|
||||||
|
loomVersion=0.2.6-SNAPSHOT
|
||||||
|
|
||||||
|
kotlinVersion=1.3.60
|
||||||
|
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
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
183
gradlew
vendored
Normal file
183
gradlew
vendored
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
#!/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
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, 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"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
100
gradlew.bat
vendored
Normal file
100
gradlew.bat
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
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,48 +0,0 @@
|
|||||||
package mods.betterfoliage;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import mods.betterfoliage.client.BetterFoliageClient;
|
|
||||||
import mods.betterfoliage.common.config.BetterFoliageConfig;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import cpw.mods.fml.common.Mod;
|
|
||||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
|
||||||
import cpw.mods.fml.common.network.NetworkCheckHandler;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
|
|
||||||
@Mod(name=BetterFoliage.MOD_NAME, modid=BetterFoliage.MOD_ID, acceptedMinecraftVersions=BetterFoliage.MC_VERSIONS, guiFactory=BetterFoliage.GUI_FACTORY)
|
|
||||||
public class BetterFoliage {
|
|
||||||
|
|
||||||
public static final String MOD_ID = "BetterFoliage";
|
|
||||||
public static final String MOD_NAME = "Better Foliage";
|
|
||||||
public static final String MC_VERSIONS = "[1.7.2,1.7.10]";
|
|
||||||
public static final String GUI_FACTORY = "mods.betterfoliage.client.gui.ConfigGuiFactory";
|
|
||||||
|
|
||||||
@Mod.Instance
|
|
||||||
public static BetterFoliage instance;
|
|
||||||
|
|
||||||
public static BetterFoliageConfig config = new BetterFoliageConfig();
|
|
||||||
|
|
||||||
public static Logger log;
|
|
||||||
|
|
||||||
public static File configDir;
|
|
||||||
|
|
||||||
@Mod.EventHandler
|
|
||||||
public void preInit(FMLPreInitializationEvent event) {
|
|
||||||
log = event.getModLog();
|
|
||||||
if (event.getSide() == Side.CLIENT) {
|
|
||||||
configDir = new File(event.getModConfigurationDirectory(), MOD_ID);
|
|
||||||
configDir.mkdir();
|
|
||||||
config.load(new File(configDir, "betterfoliage.cfg"));
|
|
||||||
BetterFoliageClient.preInit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NetworkCheckHandler
|
|
||||||
public boolean checkVersion(Map<String, String> mods, Side side) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package mods.betterfoliage.client;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.render.IRenderBlockDecorator;
|
|
||||||
import mods.betterfoliage.client.render.impl.RenderBlockBetterAlgae;
|
|
||||||
import mods.betterfoliage.client.render.impl.RenderBlockBetterCactus;
|
|
||||||
import mods.betterfoliage.client.render.impl.RenderBlockBetterGrass;
|
|
||||||
import mods.betterfoliage.client.render.impl.RenderBlockBetterLeaves;
|
|
||||||
import mods.betterfoliage.client.render.impl.RenderBlockBetterLilypad;
|
|
||||||
import mods.betterfoliage.client.render.impl.RenderBlockBetterReed;
|
|
||||||
import mods.betterfoliage.client.resource.BlockTextureGenerator;
|
|
||||||
import mods.betterfoliage.client.resource.HalfTextureResource;
|
|
||||||
import mods.betterfoliage.client.resource.LeafTextureGenerator;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
|
||||||
import net.minecraft.client.resources.IResource;
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
|
||||||
import net.minecraft.world.IBlockAccess;
|
|
||||||
import net.minecraftforge.common.MinecraftForge;
|
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
|
|
||||||
import cpw.mods.fml.client.registry.RenderingRegistry;
|
|
||||||
import cpw.mods.fml.common.FMLCommonHandler;
|
|
||||||
|
|
||||||
public class BetterFoliageClient {
|
|
||||||
|
|
||||||
public static Map<Integer, IRenderBlockDecorator> decorators = Maps.newHashMap();
|
|
||||||
public static LeafTextureGenerator leafGenerator;
|
|
||||||
|
|
||||||
public static BlockMatcher leaves = new BlockMatcher();
|
|
||||||
public static BlockMatcher crops = new BlockMatcher();
|
|
||||||
|
|
||||||
public static void preInit() {
|
|
||||||
FMLCommonHandler.instance().bus().register(new KeyHandler());
|
|
||||||
|
|
||||||
BetterFoliage.log.info("Registering renderers");
|
|
||||||
registerRenderer(new RenderBlockBetterLeaves());
|
|
||||||
registerRenderer(new RenderBlockBetterGrass());
|
|
||||||
registerRenderer(new RenderBlockBetterCactus());
|
|
||||||
registerRenderer(new RenderBlockBetterLilypad());
|
|
||||||
registerRenderer(new RenderBlockBetterReed());
|
|
||||||
registerRenderer(new RenderBlockBetterAlgae());
|
|
||||||
|
|
||||||
leaves.load(new File(BetterFoliage.configDir, "classesLeaves.cfg"), new ResourceLocation("betterfoliage:classesLeavesDefault.cfg"));
|
|
||||||
MinecraftForge.EVENT_BUS.register(leaves);
|
|
||||||
|
|
||||||
crops.load(new File(BetterFoliage.configDir, "classesCrops.cfg"), new ResourceLocation("betterfoliage:classesCropsDefault.cfg"));
|
|
||||||
MinecraftForge.EVENT_BUS.register(crops);
|
|
||||||
|
|
||||||
BetterFoliage.log.info("Registering leaf texture generator");
|
|
||||||
leafGenerator = new LeafTextureGenerator();
|
|
||||||
MinecraftForge.EVENT_BUS.register(leafGenerator);
|
|
||||||
|
|
||||||
MinecraftForge.EVENT_BUS.register(new BlockTextureGenerator("bf_reed_bottom", new ResourceLocation("betterfoliage", "textures/blocks/missing_leaf.png")) {
|
|
||||||
@Override
|
|
||||||
public IResource getResource(ResourceLocation var1) throws IOException {
|
|
||||||
return new HalfTextureResource(unwrapResource(var1), true, getMissingResource());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
MinecraftForge.EVENT_BUS.register(new BlockTextureGenerator("bf_reed_top", new ResourceLocation("betterfoliage", "textures/blocks/missing_leaf.png")) {
|
|
||||||
@Override
|
|
||||||
public IResource getResource(ResourceLocation var1) throws IOException {
|
|
||||||
return new HalfTextureResource(unwrapResource(var1), false, getMissingResource());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
MinecraftForge.EVENT_BUS.register(new BetterFoliageClient());
|
|
||||||
|
|
||||||
ShadersModIntegration.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isLeafTexture(TextureAtlasSprite icon) {
|
|
||||||
String resourceLocation = icon.getIconName();
|
|
||||||
if (resourceLocation.startsWith("forestry:leaves/")) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getRenderTypeOverride(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
|
|
||||||
// universal sign for DON'T RENDER ME!
|
|
||||||
if (original == -1) return original;
|
|
||||||
|
|
||||||
for (Map.Entry<Integer, IRenderBlockDecorator> entry : decorators.entrySet())
|
|
||||||
if (entry.getValue().isBlockAccepted(blockAccess, x, y, z, block, original))
|
|
||||||
return entry.getKey();
|
|
||||||
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void registerRenderer(IRenderBlockDecorator decorator) {
|
|
||||||
int renderId = RenderingRegistry.getNextAvailableRenderId();
|
|
||||||
decorators.put(renderId, decorator);
|
|
||||||
RenderingRegistry.registerBlockHandler(renderId, decorator);
|
|
||||||
MinecraftForge.EVENT_BUS.register(decorator);
|
|
||||||
decorator.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
package mods.betterfoliage.client;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.common.util.Utils;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
|
||||||
import net.minecraftforge.event.world.WorldEvent;
|
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
|
|
||||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
|
||||||
|
|
||||||
public class BlockMatcher {
|
|
||||||
|
|
||||||
public Set<String> whiteList = Sets.newHashSet();
|
|
||||||
public Set<String> blackList = Sets.newHashSet();
|
|
||||||
public Set<Integer> blockIDs = Sets.newHashSet();
|
|
||||||
|
|
||||||
public void addClass(String className) {
|
|
||||||
if (className.startsWith("-"))
|
|
||||||
blackList.add(className.substring(1));
|
|
||||||
else
|
|
||||||
whiteList.add(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean matchesClass(Block block) {
|
|
||||||
for (String className : blackList) {
|
|
||||||
try {
|
|
||||||
Class<?> clazz = Class.forName(className);
|
|
||||||
if (clazz.isAssignableFrom(block.getClass())) return false;
|
|
||||||
} catch(ClassNotFoundException e) {}
|
|
||||||
}
|
|
||||||
for (String className : whiteList) {
|
|
||||||
try {
|
|
||||||
Class<?> clazz = Class.forName(className);
|
|
||||||
if (clazz.isAssignableFrom(block.getClass())) return true;
|
|
||||||
} catch(ClassNotFoundException e) {}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean matchesID(int blockId) {
|
|
||||||
return blockIDs.contains(blockId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean matchesID(Block block) {
|
|
||||||
return blockIDs.contains(Block.blockRegistry.getIDForObject(block));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load(File file, ResourceLocation defaults) {
|
|
||||||
if (!file.exists()) Utils.copyFromTextResource(defaults, file);
|
|
||||||
|
|
||||||
BufferedReader reader = null;
|
|
||||||
try {
|
|
||||||
reader = new BufferedReader(new FileReader(file));
|
|
||||||
whiteList.clear();
|
|
||||||
blackList.clear();
|
|
||||||
String line = reader.readLine();
|
|
||||||
while(line != null) {
|
|
||||||
addClass(line.trim());
|
|
||||||
line = reader.readLine();
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
saveDefaults(file);
|
|
||||||
} catch (IOException e) {
|
|
||||||
BetterFoliage.log.warn(String.format("Error reading configuration: %s", file.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveDefaults(File file) {
|
|
||||||
FileWriter writer = null;
|
|
||||||
try {
|
|
||||||
writer = new FileWriter(file);
|
|
||||||
for (String className : whiteList) {
|
|
||||||
writer.write(className);
|
|
||||||
writer.write("\n");
|
|
||||||
}
|
|
||||||
for (String className : blackList) {
|
|
||||||
writer.write("-");
|
|
||||||
writer.write(className);
|
|
||||||
writer.write("\n");
|
|
||||||
}
|
|
||||||
writer.close();
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
saveDefaults(file);
|
|
||||||
} catch (IOException e) {
|
|
||||||
BetterFoliage.log.warn(String.format("Error writing default configuration: %s", file.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Caches block IDs on world load for fast lookup
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@SubscribeEvent
|
|
||||||
public void handleWorldLoad(WorldEvent.Load event) {
|
|
||||||
blockIDs.clear();
|
|
||||||
Iterator<Block> iter = Block.blockRegistry.iterator();
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
Block block = iter.next();
|
|
||||||
if (matchesClass(block)) blockIDs.add(Block.blockRegistry.getIDForObject(block));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package mods.betterfoliage.client;
|
|
||||||
|
|
||||||
import cpw.mods.fml.client.FMLClientHandler;
|
|
||||||
import cpw.mods.fml.client.registry.ClientRegistry;
|
|
||||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
|
||||||
import cpw.mods.fml.common.gameevent.InputEvent;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.gui.ConfigGuiMain;
|
|
||||||
import net.minecraft.client.settings.KeyBinding;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class KeyHandler {
|
|
||||||
|
|
||||||
public static KeyBinding guiBinding;
|
|
||||||
|
|
||||||
public KeyHandler() {
|
|
||||||
guiBinding = new KeyBinding("key.betterfoliage.gui", 66, BetterFoliage.MOD_NAME);
|
|
||||||
ClientRegistry.registerKeyBinding(guiBinding);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public void handleKeyPress(InputEvent.KeyInputEvent event) {
|
|
||||||
if (guiBinding.isPressed()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiMain(null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package mods.betterfoliage.client;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.init.Blocks;
|
|
||||||
|
|
||||||
public class ShadersModIntegration {
|
|
||||||
|
|
||||||
private static boolean hasShadersMod = false;
|
|
||||||
private static int tallGrassEntityData;
|
|
||||||
private static int leavesEntityData;
|
|
||||||
private static Field shadersEntityData;
|
|
||||||
private static Field shadersEntityDataIndex;
|
|
||||||
|
|
||||||
private ShadersModIntegration() {}
|
|
||||||
|
|
||||||
public static void init() {
|
|
||||||
tallGrassEntityData = Block.blockRegistry.getIDForObject(Blocks.tallgrass) & 0xFFFF | Blocks.tallgrass.getRenderType() << 16;
|
|
||||||
leavesEntityData = Block.blockRegistry.getIDForObject(Blocks.leaves) & 0xFFFF | Blocks.leaves.getRenderType() << 16;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Class<?> classShaders = Class.forName("shadersmodcore.client.Shaders");
|
|
||||||
shadersEntityData = classShaders.getDeclaredField("entityData");
|
|
||||||
shadersEntityDataIndex = classShaders.getDeclaredField("entityDataIndex");
|
|
||||||
hasShadersMod = true;
|
|
||||||
} catch(Exception e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void startGrassQuads() {
|
|
||||||
if (!hasShadersMod) return;
|
|
||||||
setShadersEntityData(tallGrassEntityData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void startLeavesQuads() {
|
|
||||||
if (!hasShadersMod) return;
|
|
||||||
setShadersEntityData(leavesEntityData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setShadersEntityData(int data) {
|
|
||||||
try {
|
|
||||||
int[] entityData = (int[]) shadersEntityData.get(null);
|
|
||||||
int entityDataIndex = shadersEntityDataIndex.getInt(null);
|
|
||||||
entityData[(entityDataIndex * 2)] = data;
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getBlockIdOverride(int original, Block block) {
|
|
||||||
if (BetterFoliageClient.leaves.matchesID(original & 0xFFFF)) return tallGrassEntityData;
|
|
||||||
if (BetterFoliageClient.crops.matchesID(original & 0xFFFF)) return leavesEntityData;
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.gui.widget.OptionDoubleWidget;
|
|
||||||
import mods.betterfoliage.client.gui.widget.OptionIntegerWidget;
|
|
||||||
import net.minecraft.client.gui.GuiButton;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
import net.minecraft.client.resources.I18n;
|
|
||||||
import cpw.mods.fml.client.FMLClientHandler;
|
|
||||||
|
|
||||||
public class ConfigGuiAlgae extends ConfigGuiScreenBase {
|
|
||||||
|
|
||||||
public ConfigGuiAlgae(GuiScreen parent) {
|
|
||||||
super(parent);
|
|
||||||
int id = 10;
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.algaeHOffset, -100, -70, 200, 50, id++, id++, "message.betterfoliage.hOffset", "%.3f"));
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.algaeSize, -100, -40, 200, 50, id++, id++, "message.betterfoliage.size", "%.2f"));
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.algaeHeightMin, -100, -10, 200, 50, id++, id++, "message.betterfoliage.minHeight", "%.2f"));
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.algaeHeightMax, -100, 20, 200, 50, id++, id++, "message.betterfoliage.maxHeight", "%.2f"));
|
|
||||||
widgets.add(new OptionIntegerWidget(BetterFoliage.config.algaeChance, -100, 50, 200, 50, id++, id++, "message.betterfoliage.algaeChance"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void addButtons(int x, int y) {
|
|
||||||
buttonList.add(new GuiButton(0, x - 50, y + 100, 100, 20, I18n.format("message.betterfoliage.back")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onButtonPress(int id) {
|
|
||||||
if (id == 0) FMLClientHandler.instance().showGuiScreen(parent);
|
|
||||||
|
|
||||||
if (BetterFoliage.config.algaeHeightMin.value > BetterFoliage.config.algaeHeightMax.value) BetterFoliage.config.algaeHeightMin.value = BetterFoliage.config.algaeHeightMax.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
import cpw.mods.fml.client.IModGuiFactory;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class ConfigGuiFactory implements IModGuiFactory {
|
|
||||||
|
|
||||||
public void initialize(Minecraft minecraftInstance) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<? extends GuiScreen> mainConfigGuiClass() {
|
|
||||||
return ConfigGuiMain.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<RuntimeOptionCategoryElement> runtimeGuiCategories() {
|
|
||||||
return ImmutableSet.<RuntimeOptionCategoryElement>of();
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.gui.widget.OptionDoubleWidget;
|
|
||||||
import net.minecraft.client.gui.GuiButton;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
import net.minecraft.client.resources.I18n;
|
|
||||||
import cpw.mods.fml.client.FMLClientHandler;
|
|
||||||
|
|
||||||
public class ConfigGuiGrass extends ConfigGuiScreenBase {
|
|
||||||
|
|
||||||
public ConfigGuiGrass(GuiScreen parent) {
|
|
||||||
super(parent);
|
|
||||||
int id = 10;
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.grassSize, -100, -70, 200, 50, id++, id++, "message.betterfoliage.size", "%.2f"));
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.grassHOffset, -100, -40, 200, 50, id++, id++, "message.betterfoliage.hOffset", "%.3f"));
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.grassHeightMin, -100, -10, 200, 50, id++, id++, "message.betterfoliage.minHeight", "%.2f"));
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.grassHeightMax, -100, 20, 200, 50, id++, id++, "message.betterfoliage.maxHeight", "%.2f"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void addButtons(int x, int y) {
|
|
||||||
buttonList.add(new GuiButton(0, x - 50, y + 50, 100, 20, I18n.format("message.betterfoliage.back")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onButtonPress(int id) {
|
|
||||||
if (id == 0) FMLClientHandler.instance().showGuiScreen(parent);
|
|
||||||
|
|
||||||
if (BetterFoliage.config.grassHeightMin.value > BetterFoliage.config.grassHeightMax.value) BetterFoliage.config.grassHeightMin.value = BetterFoliage.config.grassHeightMax.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.gui.widget.OptionDoubleWidget;
|
|
||||||
import net.minecraft.client.gui.GuiButton;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
import net.minecraft.client.resources.I18n;
|
|
||||||
import cpw.mods.fml.client.FMLClientHandler;
|
|
||||||
|
|
||||||
public class ConfigGuiLeaves extends ConfigGuiScreenBase {
|
|
||||||
|
|
||||||
public enum Button {CLOSE, LEAVES_OFFSET_MODE}
|
|
||||||
|
|
||||||
public ConfigGuiLeaves(GuiScreen parent) {
|
|
||||||
super(parent);
|
|
||||||
int id = 10;
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.leavesSize, -100, -70, 200, 50, id++, id++, "message.betterfoliage.size", "%.2f"));
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.leavesHOffset, -100, -10, 200, 50, id++, id++, "message.betterfoliage.hOffset", "%.3f"));
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.leavesVOffset, -100, 20, 200, 50, id++, id++, "message.betterfoliage.vOffset", "%.3f"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void addButtons(int x, int y) {
|
|
||||||
buttonList.add(new GuiButton(Button.CLOSE.ordinal(), x - 50, y + 50, 100, 20, I18n.format("message.betterfoliage.back")));
|
|
||||||
buttonList.add(new GuiButton(Button.LEAVES_OFFSET_MODE.ordinal(), x - 100, y - 40, 200, 20, ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateButtons() {
|
|
||||||
setButtonOptionBoolean(Button.LEAVES_OFFSET_MODE.ordinal(), "message.betterfoliage.leavesMode", BetterFoliage.config.leavesSkew ? "message.betterfoliage.leavesSkew" : "message.betterfoliage.leavesTranslate");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onButtonPress(int id) {
|
|
||||||
if (id == Button.CLOSE.ordinal()) FMLClientHandler.instance().showGuiScreen(parent);
|
|
||||||
if (id == Button.LEAVES_OFFSET_MODE.ordinal()) BetterFoliage.config.leavesSkew = !BetterFoliage.config.leavesSkew;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.gui.widget.OptionDoubleWidget;
|
|
||||||
import mods.betterfoliage.client.gui.widget.OptionIntegerWidget;
|
|
||||||
import net.minecraft.client.gui.GuiButton;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
import net.minecraft.client.resources.I18n;
|
|
||||||
import cpw.mods.fml.client.FMLClientHandler;
|
|
||||||
|
|
||||||
public class ConfigGuiLilypad extends ConfigGuiScreenBase {
|
|
||||||
|
|
||||||
public ConfigGuiLilypad(GuiScreen parent) {
|
|
||||||
super(parent);
|
|
||||||
int id = 10;
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.lilypadHOffset, -100, -40, 200, 50, id++, id++, "message.betterfoliage.hOffset", "%.3f"));
|
|
||||||
widgets.add(new OptionIntegerWidget(BetterFoliage.config.lilypadChance, -100, -10, 200, 50, id++, id++, "message.betterfoliage.flowerChance"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void addButtons(int x, int y) {
|
|
||||||
buttonList.add(new GuiButton(0, x - 50, y + 50, 100, 20, I18n.format("message.betterfoliage.back")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onButtonPress(int id) {
|
|
||||||
if (id == 0) FMLClientHandler.instance().showGuiScreen(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.gui.GuiButton;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
import net.minecraft.client.resources.I18n;
|
|
||||||
import cpw.mods.fml.client.FMLClientHandler;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class ConfigGuiMain extends ConfigGuiScreenBase {
|
|
||||||
|
|
||||||
public enum Button {CLOSE,
|
|
||||||
TOGGLE_LEAVES, CONFIG_LEAVES,
|
|
||||||
TOGGLE_GRASS, CONFIG_GRASS,
|
|
||||||
TOGGLE_CACTUS, CONFIG_CACTUS,
|
|
||||||
TOGGLE_LILYPAD, CONFIG_LILYPAD,
|
|
||||||
TOGGLE_REED, CONFIG_REED,
|
|
||||||
TOGGLE_ALGAE, CONFIG_ALGAE}
|
|
||||||
|
|
||||||
public ConfigGuiMain(GuiScreen parent) {
|
|
||||||
super(parent);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
protected void addButtons(int x, int y) {
|
|
||||||
buttonList.add(new GuiButton(Button.CLOSE.ordinal(), x - 50, y + 80, 100, 20, I18n.format("message.betterfoliage.close")));
|
|
||||||
|
|
||||||
buttonList.add(new GuiButton(Button.TOGGLE_LEAVES.ordinal(), x - 100, y - 100, 150, 20, ""));
|
|
||||||
buttonList.add(new GuiButton(Button.CONFIG_LEAVES.ordinal(), x + 60, y - 100, 40, 20, I18n.format("message.betterfoliage.config")));
|
|
||||||
|
|
||||||
buttonList.add(new GuiButton(Button.TOGGLE_GRASS.ordinal(), x - 100, y - 70, 150, 20, ""));
|
|
||||||
buttonList.add(new GuiButton(Button.CONFIG_GRASS.ordinal(), x + 60, y - 70, 40, 20, I18n.format("message.betterfoliage.config")));
|
|
||||||
|
|
||||||
buttonList.add(new GuiButton(Button.TOGGLE_CACTUS.ordinal(), x - 100, y - 40, 150, 20, ""));
|
|
||||||
buttonList.add(new GuiButton(Button.CONFIG_CACTUS.ordinal(), x + 60, y - 40, 40, 20, I18n.format("message.betterfoliage.config")));
|
|
||||||
|
|
||||||
buttonList.add(new GuiButton(Button.TOGGLE_LILYPAD.ordinal(), x - 100, y - 10, 150, 20, ""));
|
|
||||||
buttonList.add(new GuiButton(Button.CONFIG_LILYPAD.ordinal(), x + 60, y - 10, 40, 20, I18n.format("message.betterfoliage.config")));
|
|
||||||
|
|
||||||
buttonList.add(new GuiButton(Button.TOGGLE_REED.ordinal(), x - 100, y + 20, 150, 20, ""));
|
|
||||||
buttonList.add(new GuiButton(Button.CONFIG_REED.ordinal(), x + 60, y + 20, 40, 20, I18n.format("message.betterfoliage.config")));
|
|
||||||
|
|
||||||
buttonList.add(new GuiButton(Button.TOGGLE_ALGAE.ordinal(), x - 100, y + 50, 150, 20, ""));
|
|
||||||
buttonList.add(new GuiButton(Button.CONFIG_ALGAE.ordinal(), x + 60, y + 50, 40, 20, I18n.format("message.betterfoliage.config")));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateButtons() {
|
|
||||||
setButtonOptionBoolean(Button.TOGGLE_LEAVES.ordinal(), "message.betterfoliage.betterLeaves", BetterFoliage.config.leavesEnabled);
|
|
||||||
setButtonOptionBoolean(Button.TOGGLE_GRASS.ordinal(), "message.betterfoliage.betterGrass", BetterFoliage.config.grassEnabled);
|
|
||||||
setButtonOptionBoolean(Button.TOGGLE_CACTUS.ordinal(), "message.betterfoliage.betterCactus", BetterFoliage.config.cactusEnabled);
|
|
||||||
setButtonOptionBoolean(Button.TOGGLE_LILYPAD.ordinal(), "message.betterfoliage.betterLilypad", BetterFoliage.config.lilypadEnabled);
|
|
||||||
setButtonOptionBoolean(Button.TOGGLE_REED.ordinal(), "message.betterfoliage.betterReed", BetterFoliage.config.reedEnabled);
|
|
||||||
setButtonOptionBoolean(Button.TOGGLE_ALGAE.ordinal(), "message.betterfoliage.betterAlgae", BetterFoliage.config.algaeEnabled);
|
|
||||||
((GuiButton) buttonList.get(Button.CONFIG_CACTUS.ordinal())).enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onButtonPress(int id) {
|
|
||||||
if (id == Button.CLOSE.ordinal()) {
|
|
||||||
BetterFoliage.config.save();
|
|
||||||
Minecraft.getMinecraft().renderGlobal.loadRenderers();
|
|
||||||
FMLClientHandler.instance().showGuiScreen(parent);
|
|
||||||
}
|
|
||||||
if (id == Button.TOGGLE_LEAVES.ordinal()) BetterFoliage.config.leavesEnabled = !BetterFoliage.config.leavesEnabled;
|
|
||||||
if (id == Button.TOGGLE_GRASS.ordinal()) BetterFoliage.config.grassEnabled = !BetterFoliage.config.grassEnabled;
|
|
||||||
if (id == Button.TOGGLE_CACTUS.ordinal()) BetterFoliage.config.cactusEnabled = !BetterFoliage.config.cactusEnabled;
|
|
||||||
if (id == Button.TOGGLE_LILYPAD.ordinal()) BetterFoliage.config.lilypadEnabled = !BetterFoliage.config.lilypadEnabled;
|
|
||||||
if (id == Button.TOGGLE_REED.ordinal()) BetterFoliage.config.reedEnabled = !BetterFoliage.config.reedEnabled;
|
|
||||||
if (id == Button.TOGGLE_ALGAE.ordinal()) BetterFoliage.config.algaeEnabled = !BetterFoliage.config.algaeEnabled;
|
|
||||||
|
|
||||||
if (id== Button.CONFIG_LEAVES.ordinal()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiLeaves(this));
|
|
||||||
if (id== Button.CONFIG_GRASS.ordinal()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiGrass(this));
|
|
||||||
if (id== Button.CONFIG_LILYPAD.ordinal()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiLilypad(this));
|
|
||||||
if (id== Button.CONFIG_REED.ordinal()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiReed(this));
|
|
||||||
if (id== Button.CONFIG_ALGAE.ordinal()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiAlgae(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.gui.widget.OptionDoubleWidget;
|
|
||||||
import mods.betterfoliage.client.gui.widget.OptionIntegerWidget;
|
|
||||||
import net.minecraft.client.gui.GuiButton;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
import net.minecraft.client.resources.I18n;
|
|
||||||
import cpw.mods.fml.client.FMLClientHandler;
|
|
||||||
|
|
||||||
public class ConfigGuiReed extends ConfigGuiScreenBase {
|
|
||||||
|
|
||||||
public ConfigGuiReed(GuiScreen parent) {
|
|
||||||
super(parent);
|
|
||||||
int id = 10;
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.reedHOffset, -100, -70, 200, 50, id++, id++, "message.betterfoliage.hOffset", "%.3f"));
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.reedHeightMin, -100, -40, 200, 50, id++, id++, "message.betterfoliage.minHeight", "%.2f"));
|
|
||||||
widgets.add(new OptionDoubleWidget(BetterFoliage.config.reedHeightMax, -100, -10, 200, 50, id++, id++, "message.betterfoliage.maxHeight", "%.2f"));
|
|
||||||
widgets.add(new OptionIntegerWidget(BetterFoliage.config.reedChance, -100, 20, 200, 50, id++, id++, "message.betterfoliage.reedChance"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void addButtons(int x, int y) {
|
|
||||||
buttonList.add(new GuiButton(0, x - 50, y + 50, 100, 20, I18n.format("message.betterfoliage.back")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onButtonPress(int id) {
|
|
||||||
if (id == 0) FMLClientHandler.instance().showGuiScreen(parent);
|
|
||||||
|
|
||||||
if (BetterFoliage.config.reedHeightMin.value > BetterFoliage.config.reedHeightMax.value) BetterFoliage.config.reedHeightMin.value = BetterFoliage.config.reedHeightMax.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import mods.betterfoliage.client.gui.widget.IOptionWidget;
|
|
||||||
import net.minecraft.client.gui.GuiButton;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
import net.minecraft.client.resources.I18n;
|
|
||||||
import net.minecraft.util.EnumChatFormatting;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
|
|
||||||
public class ConfigGuiScreenBase extends GuiScreen {
|
|
||||||
|
|
||||||
protected GuiScreen parent;
|
|
||||||
protected List<IOptionWidget> widgets = Lists.newLinkedList();
|
|
||||||
|
|
||||||
public ConfigGuiScreenBase(GuiScreen parent) {
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void drawScreen(int par1, int par2, float par3) {
|
|
||||||
this.drawDefaultBackground();
|
|
||||||
int x = width / 2;
|
|
||||||
int y = height / 2;
|
|
||||||
for (IOptionWidget widget : widgets) widget.drawStrings(this, fontRendererObj, x, y, 14737632, 16777120);
|
|
||||||
super.drawScreen(par1, par2, par3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void initGui() {
|
|
||||||
int x = width / 2;
|
|
||||||
int y = height / 2;
|
|
||||||
for (IOptionWidget widget : widgets) widget.addButtons(buttonList, x, y);
|
|
||||||
addButtons(x, y);
|
|
||||||
updateButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addButtons(int x, int y) {}
|
|
||||||
|
|
||||||
protected void updateButtons() {}
|
|
||||||
|
|
||||||
protected void onButtonPress(int id) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void actionPerformed(GuiButton button) {
|
|
||||||
super.actionPerformed(button);
|
|
||||||
for (IOptionWidget widget : widgets) widget.onAction(button.id);
|
|
||||||
onButtonPress(button.id);
|
|
||||||
updateButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected void setButtonOptionBoolean(int id, String msgKey, boolean option) {
|
|
||||||
for (GuiButton button : (List<GuiButton>) buttonList) {
|
|
||||||
if (button.id == id) {
|
|
||||||
String optionText = option ? (EnumChatFormatting.GREEN + I18n.format("message.betterfoliage.optionOn")) : (EnumChatFormatting.RED + I18n.format("message.betterfoliage.optionOff"));
|
|
||||||
button.displayString = I18n.format(msgKey, optionText);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected void setButtonOptionBoolean(int id, String msgKey, String optionKey) {
|
|
||||||
for (GuiButton button : (List<GuiButton>) buttonList) {
|
|
||||||
if (button.id == id) {
|
|
||||||
button.displayString = I18n.format(msgKey, I18n.format(optionKey));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui.widget;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import net.minecraft.client.gui.FontRenderer;
|
|
||||||
import net.minecraft.client.gui.GuiButton;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
|
|
||||||
public interface IOptionWidget {
|
|
||||||
|
|
||||||
public void addButtons(List<GuiButton> buttonList, int xOffset, int yOffset);
|
|
||||||
public void drawStrings(GuiScreen screen, FontRenderer fontRenderer, int xOffset, int yOffset, int labelColor, int numColor);
|
|
||||||
public void onAction(int buttonId);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui.widget;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
import mods.betterfoliage.common.config.OptionDouble;
|
|
||||||
import net.minecraft.client.gui.FontRenderer;
|
|
||||||
import net.minecraft.client.gui.GuiButton;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
import net.minecraft.client.resources.I18n;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class OptionDoubleWidget implements IOptionWidget {
|
|
||||||
|
|
||||||
public OptionDouble option;
|
|
||||||
public int x;
|
|
||||||
public int y;
|
|
||||||
public int width;
|
|
||||||
public int numWidth;
|
|
||||||
public int idDecrement;
|
|
||||||
public int idIncrement;
|
|
||||||
public String keyLabel;
|
|
||||||
public String formatString;
|
|
||||||
|
|
||||||
public OptionDoubleWidget(OptionDouble option, int x, int y, int width, int numWidth, int idDecrement, int idIncrement, String keyLabel, String formatString) {
|
|
||||||
this.option = option;
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.width = width;
|
|
||||||
this.numWidth = numWidth;
|
|
||||||
this.idDecrement = idDecrement;
|
|
||||||
this.idIncrement = idIncrement;
|
|
||||||
this.keyLabel = keyLabel;
|
|
||||||
this.formatString = formatString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addButtons(List<GuiButton> buttonList, int xOffset, int yOffset) {
|
|
||||||
buttonList.add(new GuiButton(idDecrement, xOffset + x + width - numWidth - 40, yOffset + y, 20, 20, "-"));
|
|
||||||
buttonList.add(new GuiButton(idIncrement, xOffset + x + width - 20, yOffset + y, 20, 20, "+"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawStrings(GuiScreen screen, FontRenderer fontRenderer, int xOffset, int yOffset, int labelColor, int numColor) {
|
|
||||||
screen.drawString(fontRenderer, I18n.format(keyLabel), xOffset + x, yOffset + y + 5, labelColor);
|
|
||||||
screen.drawCenteredString(fontRenderer, String.format(formatString, option.value), xOffset + x + width - 20 - numWidth / 2, yOffset + y + 5, numColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onAction(int buttonId) {
|
|
||||||
if (buttonId == idDecrement) option.decrement();
|
|
||||||
if (buttonId == idIncrement) option.increment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui.widget;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import mods.betterfoliage.common.config.OptionInteger;
|
|
||||||
import net.minecraft.client.gui.FontRenderer;
|
|
||||||
import net.minecraft.client.gui.GuiButton;
|
|
||||||
import net.minecraft.client.gui.GuiScreen;
|
|
||||||
import net.minecraft.client.resources.I18n;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class OptionIntegerWidget implements IOptionWidget {
|
|
||||||
|
|
||||||
public OptionInteger option;
|
|
||||||
public int x;
|
|
||||||
public int y;
|
|
||||||
public int width;
|
|
||||||
public int numWidth;
|
|
||||||
public int idDecrement;
|
|
||||||
public int idIncrement;
|
|
||||||
public String keyLabel;
|
|
||||||
|
|
||||||
public OptionIntegerWidget(OptionInteger option, int x, int y, int width, int numWidth, int idDecrement, int idIncrement, String keyLabel) {
|
|
||||||
this.option = option;
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.width = width;
|
|
||||||
this.numWidth = numWidth;
|
|
||||||
this.idDecrement = idDecrement;
|
|
||||||
this.idIncrement = idIncrement;
|
|
||||||
this.keyLabel = keyLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addButtons(List<GuiButton> buttonList, int xOffset, int yOffset) {
|
|
||||||
buttonList.add(new GuiButton(idDecrement, xOffset + x + width - numWidth - 40, yOffset + y, 20, 20, "-"));
|
|
||||||
buttonList.add(new GuiButton(idIncrement, xOffset + x + width - 20, yOffset + y, 20, 20, "+"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawStrings(GuiScreen screen, FontRenderer fontRenderer, int xOffset, int yOffset, int labelColor, int numColor) {
|
|
||||||
screen.drawString(fontRenderer, I18n.format(keyLabel), xOffset + x, yOffset + y + 5, labelColor);
|
|
||||||
screen.drawCenteredString(fontRenderer, Integer.toString(option.value), xOffset + x + width - 20 - numWidth / 2, yOffset + y + 5, numColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onAction(int buttonId) {
|
|
||||||
if (buttonId == idDecrement) option.decrement();
|
|
||||||
if (buttonId == idIncrement) option.increment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render;
|
|
||||||
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.util.IIcon;
|
|
||||||
|
|
||||||
/** Same as {@link RenderBlockAOBase}, but does not actually render anything.
|
|
||||||
* @author octarine-noise
|
|
||||||
*/
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class FakeRenderBlockAOBase extends RenderBlockAOBase {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
saveShadingTopLeft(aoZNXYPP);
|
|
||||||
saveShadingTopRight(aoZNXYNP);
|
|
||||||
saveShadingBottomLeft(aoZNXYPN);
|
|
||||||
saveShadingBottomRight(aoZNXYNN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
saveShadingTopLeft(aoZPXYNP);
|
|
||||||
saveShadingTopRight(aoZPXYPP);
|
|
||||||
saveShadingBottomLeft(aoZPXYNN);
|
|
||||||
saveShadingBottomRight(aoZPXYPN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
saveShadingTopLeft(aoXNYZPN);
|
|
||||||
saveShadingTopRight(aoXNYZPP);
|
|
||||||
saveShadingBottomLeft(aoXNYZNN);
|
|
||||||
saveShadingBottomRight(aoXNYZNP);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
saveShadingTopLeft(aoXPYZPP);
|
|
||||||
saveShadingTopRight(aoXPYZPN);
|
|
||||||
saveShadingBottomLeft(aoXPYZNP);
|
|
||||||
saveShadingBottomRight(aoXPYZNN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
saveShadingTopLeft(aoYNXZNP);
|
|
||||||
saveShadingTopRight(aoYNXZPP);
|
|
||||||
saveShadingBottomLeft(aoYNXZNN);
|
|
||||||
saveShadingBottomRight(aoYNXZPN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceYPos(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
saveShadingTopLeft(aoYPXZPP);
|
|
||||||
saveShadingTopRight(aoYPXZNP);
|
|
||||||
saveShadingBottomLeft(aoYPXZPN);
|
|
||||||
saveShadingBottomRight(aoYPXZNN);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render;
|
|
||||||
|
|
||||||
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.world.IBlockAccess;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public interface IRenderBlockDecorator extends ISimpleBlockRenderingHandler {
|
|
||||||
|
|
||||||
public void init();
|
|
||||||
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render;
|
|
||||||
|
|
||||||
import mods.betterfoliage.common.util.Utils;
|
|
||||||
import net.minecraft.client.renderer.texture.IIconRegister;
|
|
||||||
import net.minecraft.util.IIcon;
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
/** Loads an indexed set of textures
|
|
||||||
* @author octarine-noise
|
|
||||||
*/
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class IconSet {
|
|
||||||
|
|
||||||
/** Icon array */
|
|
||||||
public IIcon[] icons = new IIcon[16];
|
|
||||||
|
|
||||||
/** Number of successfully loaded icons*/
|
|
||||||
public int numLoaded = 0;
|
|
||||||
|
|
||||||
/** Resource domain of icons */
|
|
||||||
String domain;
|
|
||||||
|
|
||||||
/** Format string of icon paths */
|
|
||||||
String path;
|
|
||||||
|
|
||||||
public IconSet(String domain, String path) {
|
|
||||||
this.domain = domain;
|
|
||||||
this.path = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerIcons(IIconRegister register) {
|
|
||||||
numLoaded = 0;
|
|
||||||
for (int idx = 0; idx < 16; idx++) {
|
|
||||||
icons[idx] = null;
|
|
||||||
// if the path contains a domain, use that to check if the resource exists
|
|
||||||
String resolvedDomain = path.contains(":") ? new ResourceLocation(path).getResourceDomain() : domain;
|
|
||||||
String resolvedPath = String.format("textures/blocks/" + (path.contains(":") ? new ResourceLocation(path).getResourcePath() : path) + ".png", idx);
|
|
||||||
if (Utils.resourceExists(new ResourceLocation(resolvedDomain, resolvedPath)))
|
|
||||||
icons[numLoaded++] = register.registerIcon(domain + ":" + String.format(path, idx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IIcon get(int variation) {
|
|
||||||
return numLoaded == 0 ? null : icons[variation % numLoaded];
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasIcons() {
|
|
||||||
return numLoaded > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,437 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import mods.betterfoliage.common.util.Double3;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.renderer.RenderBlocks;
|
|
||||||
import net.minecraft.client.renderer.Tessellator;
|
|
||||||
import net.minecraft.init.Blocks;
|
|
||||||
import net.minecraft.util.IIcon;
|
|
||||||
import net.minecraft.util.MathHelper;
|
|
||||||
import net.minecraftforge.common.util.ForgeDirection;
|
|
||||||
|
|
||||||
import org.lwjgl.opengl.GL11;
|
|
||||||
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
/** Block renderer base class. Stores calculated ambient occlusion light and color values when rendering
|
|
||||||
* block sides for later use.
|
|
||||||
* @author octarine-noise
|
|
||||||
*/
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class RenderBlockAOBase extends RenderBlocks {
|
|
||||||
|
|
||||||
/** AO light and color values
|
|
||||||
* @author octarine-noise
|
|
||||||
*/
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public static class ShadingValues {
|
|
||||||
public int passCounter = 0;
|
|
||||||
public int brightness;
|
|
||||||
public float red;
|
|
||||||
public float green;
|
|
||||||
public float blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected double[] uValues = new double[] {0.0, 16.0, 16.0, 0.0};
|
|
||||||
protected double[] vValues = new double[] {0.0, 0.0, 16.0, 16.0};
|
|
||||||
|
|
||||||
protected ForgeDirection[] faceDir1 = new ForgeDirection[] {ForgeDirection.WEST, ForgeDirection.WEST, ForgeDirection.WEST, ForgeDirection.EAST, ForgeDirection.SOUTH, ForgeDirection.NORTH};
|
|
||||||
protected ForgeDirection[] faceDir2 = new ForgeDirection[] {ForgeDirection.NORTH, ForgeDirection.SOUTH, ForgeDirection.UP, ForgeDirection.UP, ForgeDirection.UP, ForgeDirection.UP};
|
|
||||||
|
|
||||||
/** Random vector pool. Unit rotation vectors in the XZ plane, Y coord goes between [-1.0, 1.0].
|
|
||||||
* Filled at init time */
|
|
||||||
public Double3[] pRot = new Double3[64];
|
|
||||||
|
|
||||||
/** Pool of random double values. Filled at init time. */
|
|
||||||
public double[] pRand = new double[64];
|
|
||||||
|
|
||||||
public ShadingValues aoXPYZPP = new ShadingValues();
|
|
||||||
public ShadingValues aoXPYZPN = new ShadingValues();
|
|
||||||
public ShadingValues aoXPYZNP = new ShadingValues();
|
|
||||||
public ShadingValues aoXPYZNN = new ShadingValues();
|
|
||||||
public ShadingValues aoXNYZPP = new ShadingValues();
|
|
||||||
public ShadingValues aoXNYZPN = new ShadingValues();
|
|
||||||
public ShadingValues aoXNYZNP = new ShadingValues();
|
|
||||||
public ShadingValues aoXNYZNN = new ShadingValues();
|
|
||||||
public ShadingValues aoYPXZPP = new ShadingValues();
|
|
||||||
public ShadingValues aoYPXZPN = new ShadingValues();
|
|
||||||
public ShadingValues aoYPXZNP = new ShadingValues();
|
|
||||||
public ShadingValues aoYPXZNN = new ShadingValues();
|
|
||||||
public ShadingValues aoYNXZPP = new ShadingValues();
|
|
||||||
public ShadingValues aoYNXZPN = new ShadingValues();
|
|
||||||
public ShadingValues aoYNXZNP = new ShadingValues();
|
|
||||||
public ShadingValues aoYNXZNN = new ShadingValues();
|
|
||||||
public ShadingValues aoZPXYPP = new ShadingValues();
|
|
||||||
public ShadingValues aoZPXYPN = new ShadingValues();
|
|
||||||
public ShadingValues aoZPXYNP = new ShadingValues();
|
|
||||||
public ShadingValues aoZPXYNN = new ShadingValues();
|
|
||||||
public ShadingValues aoZNXYPP = new ShadingValues();
|
|
||||||
public ShadingValues aoZNXYPN = new ShadingValues();
|
|
||||||
public ShadingValues aoZNXYNP = new ShadingValues();
|
|
||||||
public ShadingValues aoZNXYNN = new ShadingValues();
|
|
||||||
|
|
||||||
// temporary shading values for a single face
|
|
||||||
public ShadingValues faceAOPP, faceAOPN, faceAONN, faceAONP;
|
|
||||||
|
|
||||||
/** Initialize random values */
|
|
||||||
public void init() {
|
|
||||||
List<Double3> perturbs = new ArrayList<Double3>(64);
|
|
||||||
for (int idx = 0; idx < 64; idx++) {
|
|
||||||
double angle = (double) idx * Math.PI * 2.0 / 64.0;
|
|
||||||
perturbs.add(new Double3(Math.cos(angle), Math.random() * 2.0 - 1.0, Math.sin(angle)));
|
|
||||||
pRand[idx] = Math.random();
|
|
||||||
}
|
|
||||||
Collections.shuffle(perturbs);
|
|
||||||
Iterator<Double3> iter = perturbs.iterator();
|
|
||||||
for (int idx = 0; idx < 64; idx++) pRot[idx] = iter.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get a semi-random value depending on block position.
|
|
||||||
* @param x block X coord
|
|
||||||
* @param y block Y coord
|
|
||||||
* @param z block Z coord
|
|
||||||
* @param seed additional seed
|
|
||||||
* @return semirandom value
|
|
||||||
*/
|
|
||||||
protected int getSemiRandomFromPos(double x, double y, double z, int seed) {
|
|
||||||
long lx = MathHelper.floor_double(x);
|
|
||||||
long ly = MathHelper.floor_double(y);
|
|
||||||
long lz = MathHelper.floor_double(z);
|
|
||||||
long value = (lx * lx + ly * ly + lz * lz + lx * ly + ly * lz + lz * lx + seed * seed) & 63;
|
|
||||||
value = (3 * lx * value + 5 * ly * value + 7 * lz * value + 11 * seed) & 63;
|
|
||||||
return (int) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderInventoryBlock(Block block, int metadata, int modelId, RenderBlocks renderer) {
|
|
||||||
renderStandardBlockAsItem(renderer, block, metadata, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean shouldRender3DInInventory(int modelId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRenderId() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void renderStandardBlockAsItem(RenderBlocks renderer, Block p_147800_1_, int p_147800_2_, float p_147800_3_) {
|
|
||||||
Tessellator tessellator = Tessellator.instance;
|
|
||||||
boolean flag = p_147800_1_ == Blocks.grass;
|
|
||||||
|
|
||||||
float f2;
|
|
||||||
float f3;
|
|
||||||
int k;
|
|
||||||
|
|
||||||
p_147800_1_.setBlockBoundsForItemRender();
|
|
||||||
renderer.setRenderBoundsFromBlock(p_147800_1_);
|
|
||||||
GL11.glRotatef(90.0F, 0.0F, 1.0F, 0.0F);
|
|
||||||
GL11.glTranslatef(-0.5F, -0.5F, -0.5F);
|
|
||||||
tessellator.startDrawingQuads();
|
|
||||||
tessellator.setNormal(0.0F, -1.0F, 0.0F);
|
|
||||||
renderer.renderFaceYNeg(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 0, p_147800_2_));
|
|
||||||
tessellator.draw();
|
|
||||||
|
|
||||||
if (flag && renderer.useInventoryTint)
|
|
||||||
{
|
|
||||||
k = p_147800_1_.getRenderColor(p_147800_2_);
|
|
||||||
f2 = (float)(k >> 16 & 255) / 255.0F;
|
|
||||||
f3 = (float)(k >> 8 & 255) / 255.0F;
|
|
||||||
float f4 = (float)(k & 255) / 255.0F;
|
|
||||||
GL11.glColor4f(f2 * p_147800_3_, f3 * p_147800_3_, f4 * p_147800_3_, 1.0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
tessellator.startDrawingQuads();
|
|
||||||
tessellator.setNormal(0.0F, 1.0F, 0.0F);
|
|
||||||
renderer.renderFaceYPos(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 1, p_147800_2_));
|
|
||||||
tessellator.draw();
|
|
||||||
|
|
||||||
if (flag && renderer.useInventoryTint)
|
|
||||||
{
|
|
||||||
GL11.glColor4f(p_147800_3_, p_147800_3_, p_147800_3_, 1.0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
tessellator.startDrawingQuads();
|
|
||||||
tessellator.setNormal(0.0F, 0.0F, -1.0F);
|
|
||||||
renderer.renderFaceZNeg(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 2, p_147800_2_));
|
|
||||||
tessellator.draw();
|
|
||||||
tessellator.startDrawingQuads();
|
|
||||||
tessellator.setNormal(0.0F, 0.0F, 1.0F);
|
|
||||||
renderer.renderFaceZPos(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 3, p_147800_2_));
|
|
||||||
tessellator.draw();
|
|
||||||
tessellator.startDrawingQuads();
|
|
||||||
tessellator.setNormal(-1.0F, 0.0F, 0.0F);
|
|
||||||
renderer.renderFaceXNeg(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 4, p_147800_2_));
|
|
||||||
tessellator.draw();
|
|
||||||
tessellator.startDrawingQuads();
|
|
||||||
tessellator.setNormal(1.0F, 0.0F, 0.0F);
|
|
||||||
renderer.renderFaceXPos(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 5, p_147800_2_));
|
|
||||||
tessellator.draw();
|
|
||||||
GL11.glTranslatef(0.5F, 0.5F, 0.5F);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setShadingForFace(ForgeDirection dir) {
|
|
||||||
if (dir == ForgeDirection.DOWN) {
|
|
||||||
// dir1 WEST, dir2 NORTH
|
|
||||||
faceAOPP = aoYNXZPP; faceAOPN = aoYNXZPN; faceAONN = aoYNXZNN; faceAONP = aoYNXZNP;
|
|
||||||
} else if (dir == ForgeDirection.UP) {
|
|
||||||
// dir1 WEST, dir2 SOUTH
|
|
||||||
faceAOPP = aoYPXZPP; faceAOPN = aoYPXZPN; faceAONN = aoYPXZNN; faceAONP = aoYPXZNP;
|
|
||||||
} else if (dir == ForgeDirection.NORTH) {
|
|
||||||
// dir1 WEST, dir2 UP
|
|
||||||
faceAOPP = aoZNXYNP; faceAOPN = aoZNXYNN; faceAONN = aoZNXYPN; faceAONP = aoZNXYPP;
|
|
||||||
} else if (dir == ForgeDirection.SOUTH) {
|
|
||||||
// dir1 EAST, dir2 UP
|
|
||||||
faceAOPP = aoZPXYPP; faceAOPN = aoZPXYPN; faceAONN = aoZPXYNN; faceAONP = aoZPXYNP;
|
|
||||||
} else if (dir == ForgeDirection.WEST) {
|
|
||||||
// dir1 SOUTH, dir2 UP
|
|
||||||
faceAOPP = aoXNYZPP; faceAOPN = aoXNYZNP; faceAONN = aoXNYZNN; faceAONP = aoXNYZPN;
|
|
||||||
} else if (dir == ForgeDirection.EAST) {
|
|
||||||
// dir1 NORTH, dir2 UP
|
|
||||||
faceAOPP = aoXPYZPN; faceAOPN = aoXPYZNN; faceAONN = aoXPYZNP; faceAONP = aoXPYZPP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void renderCrossedSideQuads(Double3 drawBase, ForgeDirection dir, double scale, double halfHeight, Double3 rendomVec, double offset, IIcon renderIcon, int uvRot, boolean noShading) {
|
|
||||||
Double3 facePP, faceNP, faceNormal, drawCenter;
|
|
||||||
|
|
||||||
if (dir == ForgeDirection.UP) {
|
|
||||||
// special case for block top, we'll be rendering a LOT of those
|
|
||||||
facePP = new Double3(-scale, 0.0, scale);
|
|
||||||
faceNP = new Double3(scale, 0.0, scale);
|
|
||||||
faceNormal = new Double3(0.0, halfHeight, 0.0);
|
|
||||||
drawCenter = drawBase.add(faceNormal);
|
|
||||||
if (rendomVec != null) {
|
|
||||||
drawCenter = drawBase.add(faceNormal).add(rendomVec.scaleAxes(-offset, 0.0, offset));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
facePP = new Double3(faceDir1[dir.ordinal()]).add(new Double3(faceDir2[dir.ordinal()])).scale(scale);
|
|
||||||
faceNP = new Double3(faceDir1[dir.ordinal()]).inverse().add(new Double3(faceDir2[dir.ordinal()])).scale(scale);
|
|
||||||
faceNormal = new Double3(dir).scale(halfHeight);
|
|
||||||
drawCenter = drawBase.add(faceNormal);
|
|
||||||
if (rendomVec != null) {
|
|
||||||
drawCenter = drawCenter.add(new Double3(faceDir1[dir.ordinal()]).scale(rendomVec.x).scale(offset))
|
|
||||||
.add(new Double3(faceDir2[dir.ordinal()]).scale(rendomVec.z).scale(offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Minecraft.isAmbientOcclusionEnabled() && !noShading) {
|
|
||||||
setShadingForFace(dir);
|
|
||||||
renderQuadWithShading(renderIcon, drawCenter, facePP, faceNormal, uvRot, faceAOPP, faceAONN, faceAONN, faceAOPP);
|
|
||||||
renderQuadWithShading(renderIcon, drawCenter, facePP.inverse(), faceNormal, uvRot, faceAONN, faceAOPP, faceAOPP, faceAONN);
|
|
||||||
renderQuadWithShading(renderIcon, drawCenter, faceNP, faceNormal, uvRot, faceAONP, faceAOPN, faceAOPN, faceAONP);
|
|
||||||
renderQuadWithShading(renderIcon, drawCenter, faceNP.inverse(), faceNormal, uvRot, faceAOPN, faceAONP, faceAONP, faceAOPN);
|
|
||||||
} else {
|
|
||||||
renderQuad(renderIcon, drawCenter, facePP, faceNormal, uvRot);
|
|
||||||
renderQuad(renderIcon, drawCenter, facePP.inverse(), faceNormal, uvRot);
|
|
||||||
renderQuad(renderIcon, drawCenter, faceNP, faceNormal, uvRot);
|
|
||||||
renderQuad(renderIcon, drawCenter, faceNP.inverse(), faceNormal, uvRot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void renderCrossedBlockQuadsTranslate(Double3 blockCenter, double halfSize, Double3 offsetVec, IIcon crossLeafIcon, int uvRot, boolean isAirTop, boolean isAirBottom) {
|
|
||||||
Double3 drawCenter = blockCenter;
|
|
||||||
if (offsetVec != null) drawCenter = drawCenter.add(offsetVec);
|
|
||||||
Double3 horz1 = new Double3(halfSize, 0.0, halfSize);
|
|
||||||
Double3 horz2 = new Double3(halfSize, 0.0, -halfSize);
|
|
||||||
Double3 vert1 = new Double3(0.0, halfSize * 1.41, 0.0);
|
|
||||||
|
|
||||||
renderCrossedBlockQuadsInternal(drawCenter, horz1, horz2, vert1, crossLeafIcon, uvRot, isAirTop, isAirBottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void renderCrossedBlockQuadsSkew(Double3 blockCenter, double halfSize, Double3 offsetVec1, Double3 offsetVec2, IIcon crossLeafIcon, int uvRot, boolean isAirTop, boolean isAirBottom) {
|
|
||||||
Double3 horz1 = new Double3(halfSize, 0.0, halfSize).add(offsetVec1);
|
|
||||||
Double3 horz2 = new Double3(halfSize, 0.0, -halfSize).add(offsetVec2);
|
|
||||||
Double3 vert1 = new Double3(0.0, halfSize * 1.41, 0.0);
|
|
||||||
|
|
||||||
renderCrossedBlockQuadsInternal(blockCenter, horz1, horz2, vert1, crossLeafIcon, uvRot, isAirTop, isAirBottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderCrossedBlockQuadsInternal(Double3 drawCenter, Double3 horz1, Double3 horz2, Double3 vert1, IIcon crossLeafIcon, int uvRot, boolean isAirTop, boolean isAirBottom) {
|
|
||||||
if (Minecraft.isAmbientOcclusionEnabled()) {
|
|
||||||
renderQuadWithShading(crossLeafIcon, drawCenter, horz1, vert1, uvRot,
|
|
||||||
isAirTop ? aoYPXZPP : aoZPXYPP, isAirTop ? aoYPXZNN : aoXNYZPN, isAirBottom ? aoYNXZNN : aoXNYZNN, isAirBottom ? aoYNXZPP : aoZPXYPN);
|
|
||||||
renderQuadWithShading(crossLeafIcon, drawCenter, horz1.inverse(), vert1, uvRot,
|
|
||||||
isAirTop ? aoYPXZNN : aoZNXYNP, isAirTop ? aoYPXZPP : aoXPYZPP, isAirBottom ? aoYNXZPP : aoXPYZNP, isAirBottom ? aoYNXZNN : aoZNXYNN);
|
|
||||||
renderQuadWithShading(crossLeafIcon, drawCenter, horz2, vert1, uvRot,
|
|
||||||
isAirTop ? aoYPXZPN : aoXPYZPN, isAirTop ? aoYPXZNP : aoZPXYNP, isAirBottom ? aoYNXZNP : aoZPXYNN, isAirBottom ? aoYNXZPN : aoXPYZNN);
|
|
||||||
renderQuadWithShading(crossLeafIcon, drawCenter, horz2.inverse(), vert1, uvRot,
|
|
||||||
isAirTop ? aoYPXZNP : aoXNYZPP, isAirTop ? aoYPXZPN : aoZNXYPP, isAirBottom ? aoYNXZPN : aoZNXYPN, isAirBottom ? aoYNXZNP : aoXNYZNP);
|
|
||||||
} else {
|
|
||||||
renderQuad(crossLeafIcon, drawCenter, horz1, vert1, uvRot);
|
|
||||||
renderQuad(crossLeafIcon, drawCenter, horz1.inverse(), vert1, uvRot);
|
|
||||||
renderQuad(crossLeafIcon, drawCenter, horz2, vert1, uvRot);
|
|
||||||
renderQuad(crossLeafIcon, drawCenter, horz2.inverse(), vert1, uvRot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
super.renderFaceZNeg(block, x, y, z, icon);
|
|
||||||
saveShadingTopLeft(aoZNXYPP);
|
|
||||||
saveShadingTopRight(aoZNXYNP);
|
|
||||||
saveShadingBottomLeft(aoZNXYPN);
|
|
||||||
saveShadingBottomRight(aoZNXYNN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
super.renderFaceZPos(block, x, y, z, icon);
|
|
||||||
saveShadingTopLeft(aoZPXYNP);
|
|
||||||
saveShadingTopRight(aoZPXYPP);
|
|
||||||
saveShadingBottomLeft(aoZPXYNN);
|
|
||||||
saveShadingBottomRight(aoZPXYPN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
super.renderFaceXNeg(block, x, y, z, icon);
|
|
||||||
saveShadingTopLeft(aoXNYZPN);
|
|
||||||
saveShadingTopRight(aoXNYZPP);
|
|
||||||
saveShadingBottomLeft(aoXNYZNN);
|
|
||||||
saveShadingBottomRight(aoXNYZNP);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
super.renderFaceXPos(block, x, y, z, icon);
|
|
||||||
saveShadingTopLeft(aoXPYZPP);
|
|
||||||
saveShadingTopRight(aoXPYZPN);
|
|
||||||
saveShadingBottomLeft(aoXPYZNP);
|
|
||||||
saveShadingBottomRight(aoXPYZNN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
super.renderFaceYNeg(block, x, y, z, icon);
|
|
||||||
saveShadingTopLeft(aoYNXZNP);
|
|
||||||
saveShadingTopRight(aoYNXZPP);
|
|
||||||
saveShadingBottomLeft(aoYNXZNN);
|
|
||||||
saveShadingBottomRight(aoYNXZPN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderFaceYPos(Block block, double x, double y, double z, IIcon icon) {
|
|
||||||
super.renderFaceYPos(block, x, y, z, icon);
|
|
||||||
saveShadingTopLeft(aoYPXZPP);
|
|
||||||
saveShadingTopRight(aoYPXZNP);
|
|
||||||
saveShadingBottomLeft(aoYPXZPN);
|
|
||||||
saveShadingBottomRight(aoYPXZNN);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void saveShadingTopLeft(ShadingValues values) {
|
|
||||||
if (--values.passCounter != 0) return;
|
|
||||||
values.brightness = brightnessTopLeft;
|
|
||||||
values.red = colorRedTopLeft;
|
|
||||||
values.green = colorGreenTopLeft;
|
|
||||||
values.blue = colorBlueTopLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void saveShadingTopRight(ShadingValues values) {
|
|
||||||
if (--values.passCounter != 0) return;
|
|
||||||
values.brightness = brightnessTopRight;
|
|
||||||
values.red = colorRedTopRight;
|
|
||||||
values.green = colorGreenTopRight;
|
|
||||||
values.blue = colorBlueTopRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void saveShadingBottomLeft(ShadingValues values) {
|
|
||||||
if (--values.passCounter != 0) return;
|
|
||||||
values.brightness = brightnessBottomLeft;
|
|
||||||
values.red = colorRedBottomLeft;
|
|
||||||
values.green = colorGreenBottomLeft;
|
|
||||||
values.blue = colorBlueBottomLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void saveShadingBottomRight(ShadingValues values) {
|
|
||||||
if (--values.passCounter != 0) return;
|
|
||||||
values.brightness = brightnessBottomRight;
|
|
||||||
values.red = colorRedBottomRight;
|
|
||||||
values.green = colorGreenBottomRight;
|
|
||||||
values.blue = colorBlueBottomRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set pass counter on all shading value objects.
|
|
||||||
* Used to collect AO values from a specific draw pass
|
|
||||||
* if the underlying renderer draws overlays
|
|
||||||
* @param value pass counter
|
|
||||||
*/
|
|
||||||
protected void setPassCounters(int value) {
|
|
||||||
aoXPYZPP.passCounter = value;
|
|
||||||
aoXPYZPN.passCounter = value;
|
|
||||||
aoXPYZNP.passCounter = value;
|
|
||||||
aoXPYZNN.passCounter = value;
|
|
||||||
aoXNYZPP.passCounter = value;
|
|
||||||
aoXNYZPN.passCounter = value;
|
|
||||||
aoXNYZNP.passCounter = value;
|
|
||||||
aoXNYZNN.passCounter = value;
|
|
||||||
aoYPXZPP.passCounter = value;
|
|
||||||
aoYPXZPN.passCounter = value;
|
|
||||||
aoYPXZNP.passCounter = value;
|
|
||||||
aoYPXZNN.passCounter = value;
|
|
||||||
aoYNXZPP.passCounter = value;
|
|
||||||
aoYNXZPN.passCounter = value;
|
|
||||||
aoYNXZNP.passCounter = value;
|
|
||||||
aoYNXZNN.passCounter = value;
|
|
||||||
aoZPXYPP.passCounter = value;
|
|
||||||
aoZPXYPN.passCounter = value;
|
|
||||||
aoZPXYNP.passCounter = value;
|
|
||||||
aoZPXYNN.passCounter = value;
|
|
||||||
aoZNXYPP.passCounter = value;
|
|
||||||
aoZNXYPN.passCounter = value;
|
|
||||||
aoZNXYNP.passCounter = value;
|
|
||||||
aoZNXYNN.passCounter = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Render textured quad
|
|
||||||
* @param icon texture to use
|
|
||||||
* @param center center of quad
|
|
||||||
* @param vec1 vector to the half-point of one of the sides
|
|
||||||
* @param vec2 vector to half-point of side next to vec1
|
|
||||||
* @param uvRot number of increments to rotate UV coordinates by
|
|
||||||
*/
|
|
||||||
protected void renderQuad(IIcon icon, Double3 center, Double3 vec1, Double3 vec2, int uvRot) {
|
|
||||||
Tessellator tessellator = Tessellator.instance;
|
|
||||||
tessellator.addVertexWithUV(center.x + vec1.x + vec2.x, center.y + vec1.y + vec2.y, center.z + vec1.z + vec2.z, icon.getInterpolatedU(uValues[uvRot & 3]), icon.getInterpolatedV(vValues[uvRot & 3]));
|
|
||||||
tessellator.addVertexWithUV(center.x - vec1.x + vec2.x, center.y - vec1.y + vec2.y, center.z - vec1.z + vec2.z, icon.getInterpolatedU(uValues[(uvRot + 1) & 3]), icon.getInterpolatedV(vValues[(uvRot + 1) & 3]));
|
|
||||||
tessellator.addVertexWithUV(center.x - vec1.x - vec2.x, center.y - vec1.y - vec2.y, center.z - vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 2) & 3]), icon.getInterpolatedV(vValues[(uvRot + 2) & 3]));
|
|
||||||
tessellator.addVertexWithUV(center.x + vec1.x - vec2.x, center.y + vec1.y - vec2.y, center.z + vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 3) & 3]), icon.getInterpolatedV(vValues[(uvRot + 3) & 3]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Render textured quad using AO information
|
|
||||||
* @param icon texture to use
|
|
||||||
* @param center center of quad
|
|
||||||
* @param vec1 vector to the half-point of one of the sides
|
|
||||||
* @param vec2 vector to half-point of side next to vec1
|
|
||||||
* @param uvRot number of increments to rotate UV coordinates by
|
|
||||||
* @param aoPP AO values for vertex at (+vec1, +vec2)
|
|
||||||
* @param aoNP AO values for vertex at (-vec1, +vec2)
|
|
||||||
* @param aoNN AO values for vertex at (-vec1, -vec2)
|
|
||||||
* @param aoPN AO values for vertex at (+vec1, -vec2)
|
|
||||||
*/
|
|
||||||
protected void renderQuadWithShading(IIcon icon, Double3 center, Double3 vec1, Double3 vec2, int uvRot, ShadingValues aoPP, ShadingValues aoNP, ShadingValues aoNN, ShadingValues aoPN) {
|
|
||||||
Tessellator tessellator = Tessellator.instance;
|
|
||||||
tessellator.setBrightness(aoPP.brightness);
|
|
||||||
tessellator.setColorOpaque_F(aoPP.red, aoPP.green, aoPP.blue);
|
|
||||||
tessellator.addVertexWithUV(center.x + vec1.x + vec2.x, center.y + vec1.y + vec2.y, center.z + vec1.z + vec2.z, icon.getInterpolatedU(uValues[uvRot & 3]), icon.getInterpolatedV(vValues[uvRot & 3]));
|
|
||||||
tessellator.setBrightness(aoNP.brightness);
|
|
||||||
tessellator.setColorOpaque_F(aoNP.red, aoNP.green, aoNP.blue);
|
|
||||||
tessellator.addVertexWithUV(center.x - vec1.x + vec2.x, center.y - vec1.y + vec2.y, center.z - vec1.z + vec2.z, icon.getInterpolatedU(uValues[(uvRot + 1) & 3]), icon.getInterpolatedV(vValues[(uvRot + 1) & 3]));
|
|
||||||
tessellator.setBrightness(aoNN.brightness);
|
|
||||||
tessellator.setColorOpaque_F(aoNN.red, aoNN.green, aoNN.blue);
|
|
||||||
tessellator.addVertexWithUV(center.x - vec1.x - vec2.x, center.y - vec1.y - vec2.y, center.z - vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 2) & 3]), icon.getInterpolatedV(vValues[(uvRot + 2) & 3]));
|
|
||||||
tessellator.setBrightness(aoPN.brightness);
|
|
||||||
tessellator.setColorOpaque_F(aoPN.red, aoPN.green, aoPN.blue);
|
|
||||||
tessellator.addVertexWithUV(center.x + vec1.x - vec2.x, center.y + vec1.y - vec2.y, center.z + vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 3) & 3]), icon.getInterpolatedV(vValues[(uvRot + 3) & 3]));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int getBrightness(Block block, int x, int y, int z) {
|
|
||||||
return block.getMixedBrightnessForBlock(blockAccess, x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render.impl;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.render.IRenderBlockDecorator;
|
|
||||||
import mods.betterfoliage.client.render.IconSet;
|
|
||||||
import mods.betterfoliage.client.render.RenderBlockAOBase;
|
|
||||||
import mods.betterfoliage.common.util.Double3;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.block.BlockDirt;
|
|
||||||
import net.minecraft.block.material.Material;
|
|
||||||
import net.minecraft.client.renderer.RenderBlocks;
|
|
||||||
import net.minecraft.client.renderer.Tessellator;
|
|
||||||
import net.minecraft.util.IIcon;
|
|
||||||
import net.minecraft.util.MathHelper;
|
|
||||||
import net.minecraft.world.IBlockAccess;
|
|
||||||
import net.minecraft.world.gen.NoiseGeneratorSimplex;
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
|
||||||
import net.minecraftforge.common.util.ForgeDirection;
|
|
||||||
import net.minecraftforge.event.world.WorldEvent;
|
|
||||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class RenderBlockBetterAlgae extends RenderBlockAOBase implements IRenderBlockDecorator {
|
|
||||||
|
|
||||||
public IconSet algaeIcons = new IconSet("bettergrassandleaves", "better_algae_%d");
|
|
||||||
public NoiseGeneratorSimplex noise;
|
|
||||||
|
|
||||||
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
|
|
||||||
if (!BetterFoliage.config.algaeEnabled) return false;
|
|
||||||
if (y >= 254 || !(block instanceof BlockDirt)) return false;
|
|
||||||
if (blockAccess.getBlock(x, y + 1, z).getMaterial() != Material.water) return false;
|
|
||||||
if (blockAccess.getBlock(x, y + 2, z).getMaterial() != Material.water) return false;
|
|
||||||
if (blockAccess.getBiomeGenForCoords(x, z).temperature < 0.4f) return false;
|
|
||||||
int terrainVariation = MathHelper.floor_double((noise.func_151605_a(x, z) + 1.0) * 32.0);
|
|
||||||
return terrainVariation < BetterFoliage.config.algaeChance.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
|
|
||||||
// store world for later use
|
|
||||||
blockAccess = world;
|
|
||||||
|
|
||||||
// render dirt block
|
|
||||||
setPassCounters(1);
|
|
||||||
setRenderBoundsFromBlock(block);
|
|
||||||
renderStandardBlock(block, x, y, z);
|
|
||||||
|
|
||||||
int variation = getSemiRandomFromPos(x, y, z, 0);
|
|
||||||
int heightVariation = getSemiRandomFromPos(x, y, z, 1);
|
|
||||||
|
|
||||||
IIcon renderIcon = algaeIcons.get(variation);
|
|
||||||
if (renderIcon == null) return true;
|
|
||||||
|
|
||||||
double scale = BetterFoliage.config.algaeSize.value * 0.5;
|
|
||||||
double halfHeight = 0.5 * (BetterFoliage.config.algaeHeightMin.value + pRand[heightVariation] * (BetterFoliage.config.algaeHeightMax.value - BetterFoliage.config.algaeHeightMin.value));
|
|
||||||
Tessellator.instance.setBrightness(getBrightness(block, x, y + 1, z));
|
|
||||||
renderCrossedSideQuads(new Double3(x + 0.5, y + 1.0 - 0.125 * halfHeight, z + 0.5), ForgeDirection.UP, scale, halfHeight, pRot[variation], BetterFoliage.config.algaeHOffset.value, renderIcon, 0, false);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public void handleTextureReload(TextureStitchEvent.Pre event) {
|
|
||||||
if (event.map.getTextureType() != 0) return;
|
|
||||||
|
|
||||||
algaeIcons.registerIcons(event.map);
|
|
||||||
BetterFoliage.log.info(String.format("Found %d algae textures", algaeIcons.numLoaded));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public void handleWorldLoad(WorldEvent.Load event) {
|
|
||||||
noise = new NoiseGeneratorSimplex(new Random(event.world.getWorldInfo().getSeed() + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render.impl;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.render.FakeRenderBlockAOBase;
|
|
||||||
import mods.betterfoliage.client.render.IRenderBlockDecorator;
|
|
||||||
import mods.betterfoliage.client.render.IconSet;
|
|
||||||
import mods.betterfoliage.common.util.Double3;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.renderer.RenderBlocks;
|
|
||||||
import net.minecraft.client.renderer.Tessellator;
|
|
||||||
import net.minecraft.init.Blocks;
|
|
||||||
import net.minecraft.util.IIcon;
|
|
||||||
import net.minecraft.world.IBlockAccess;
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
|
||||||
import net.minecraftforge.common.util.ForgeDirection;
|
|
||||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class RenderBlockBetterCactus extends FakeRenderBlockAOBase implements IRenderBlockDecorator {
|
|
||||||
|
|
||||||
public IIcon cactusRoundIcon;
|
|
||||||
public IconSet cactusSideIcons = new IconSet("bettergrassandleaves", "better_cactus_arm_%d");
|
|
||||||
|
|
||||||
public static ForgeDirection[] cactusDirections = new ForgeDirection[] { ForgeDirection.NORTH, ForgeDirection.SOUTH, ForgeDirection.EAST, ForgeDirection.WEST};
|
|
||||||
public static double cactusRadius = 0.4375;
|
|
||||||
|
|
||||||
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
|
|
||||||
return BetterFoliage.config.cactusEnabled && block == Blocks.cactus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
|
|
||||||
// store world for later use
|
|
||||||
blockAccess = world;
|
|
||||||
|
|
||||||
// render cactus center
|
|
||||||
setPassCounters(1);
|
|
||||||
setRenderBoundsFromBlock(block);
|
|
||||||
|
|
||||||
Double3 blockCenter = new Double3(x + 0.5, y + 0.5, z + 0.5);
|
|
||||||
renderStandardBlock(block, x, y, z);
|
|
||||||
Tessellator.instance.setBrightness(getBrightness(block,x, y, z));
|
|
||||||
renderCactusCore(block.getBlockTextureFromSide(ForgeDirection.UP.ordinal()),
|
|
||||||
block.getBlockTextureFromSide(ForgeDirection.NORTH.ordinal()),
|
|
||||||
blockCenter, 0);
|
|
||||||
|
|
||||||
// render side growth
|
|
||||||
ForgeDirection drawDirection = cactusDirections[getSemiRandomFromPos(x, y, z, 0) % 4];
|
|
||||||
int iconVariation = getSemiRandomFromPos(x, y, z, 1);
|
|
||||||
Double3 drawBase = blockCenter.add(new Double3(drawDirection).scale(cactusRadius));
|
|
||||||
|
|
||||||
Tessellator.instance.setBrightness(getBrightness(block, x, y, z));
|
|
||||||
if (cactusSideIcons.hasIcons()) renderCrossedSideQuads(drawBase, drawDirection, 0.5, 0.5, pRot[iconVariation], 0.2, cactusSideIcons.get(iconVariation), 0, false);
|
|
||||||
renderCrossedBlockQuadsSkew(blockCenter, 0.65,
|
|
||||||
pRot[iconVariation].scaleAxes(0.1, 0.0, 0.1),
|
|
||||||
pRot[(iconVariation + 1) & 63].scaleAxes(0.1, 0.0, 0.1),
|
|
||||||
cactusRoundIcon, iconVariation, false, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void renderCactusCore(IIcon topIcon, IIcon sideIcon, Double3 blockCenter, int sideUvRot) {
|
|
||||||
if (Minecraft.isAmbientOcclusionEnabled()) {
|
|
||||||
renderQuadWithShading(sideIcon, blockCenter.add(cactusRadius, 0.0, 0.0), new Double3(0.0, 0.0, -0.5), new Double3(0.0, 0.5, 0.0), sideUvRot, aoXPYZPN, aoXPYZPP, aoXPYZNP, aoXPYZNN);
|
|
||||||
renderQuadWithShading(sideIcon, blockCenter.add(-cactusRadius, 0.0, 0.0), new Double3(0.0, 0.0, 0.5), new Double3(0.0, 0.5, 0.0), sideUvRot, aoXNYZPP, aoXNYZPN, aoXNYZNN, aoXNYZNP);
|
|
||||||
renderQuadWithShading(sideIcon, blockCenter.add(0.0, 0.0, cactusRadius), new Double3(0.5, 0.0, 0.0), new Double3(0.0, 0.5, 0.0), sideUvRot, aoZPXYPP, aoZPXYNP, aoZPXYNN, aoZPXYPN);
|
|
||||||
renderQuadWithShading(sideIcon, blockCenter.add(0.0, 0.0, -cactusRadius), new Double3(-0.5, 0.0, 0.0), new Double3(0.0, 0.5, 0.0), sideUvRot, aoZNXYNP, aoZNXYPP, aoZNXYPN, aoZNXYNN);
|
|
||||||
renderQuadWithShading(topIcon, blockCenter.add(0.0, 0.5, 0.0), new Double3(-0.5, 0.0, 0.0), new Double3(0.0, 0.0, 0.5), 0, aoYPXZNP, aoYPXZPP, aoYPXZPN, aoYPXZNN);
|
|
||||||
} else {
|
|
||||||
renderQuad(sideIcon, blockCenter.add(cactusRadius, 0.0, 0.0), new Double3(0.0, 0.0, -0.5), new Double3(0.0, 0.5, 0.0), sideUvRot);
|
|
||||||
renderQuad(sideIcon, blockCenter.add(-cactusRadius, 0.0, 0.0), new Double3(0.0, 0.0, 0.5), new Double3(0.0, 0.5, 0.0), sideUvRot);
|
|
||||||
renderQuad(sideIcon, blockCenter.add(0.0, 0.0, cactusRadius), new Double3(0.5, 0.0, 0.0), new Double3(0.0, 0.5, 0.0), sideUvRot);
|
|
||||||
renderQuad(sideIcon, blockCenter.add(0.0, 0.0, -cactusRadius), new Double3(-0.5, 0.0, 0.0), new Double3(0.0, 0.5, 0.0), sideUvRot);
|
|
||||||
renderQuad(topIcon, blockCenter.add(0.0, 0.5, 0.0), new Double3(-0.5, 0.0, 0.0), new Double3(0.0, 0.0, 0.5), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public void handleTextureReload(TextureStitchEvent.Pre event) {
|
|
||||||
if (event.map.getTextureType() != 0) return;
|
|
||||||
|
|
||||||
cactusRoundIcon = event.map.registerIcon("bettergrassandleaves:better_cactus");
|
|
||||||
cactusSideIcons.registerIcons(event.map);
|
|
||||||
BetterFoliage.log.info(String.format("Found %d cactus arm textures", cactusSideIcons.numLoaded));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render.impl;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.ShadersModIntegration;
|
|
||||||
import mods.betterfoliage.client.render.IRenderBlockDecorator;
|
|
||||||
import mods.betterfoliage.client.render.IconSet;
|
|
||||||
import mods.betterfoliage.client.render.RenderBlockAOBase;
|
|
||||||
import mods.betterfoliage.common.util.Double3;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.block.BlockGrass;
|
|
||||||
import net.minecraft.client.renderer.RenderBlocks;
|
|
||||||
import net.minecraft.client.renderer.Tessellator;
|
|
||||||
import net.minecraft.init.Blocks;
|
|
||||||
import net.minecraft.util.IIcon;
|
|
||||||
import net.minecraft.world.IBlockAccess;
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
|
||||||
import net.minecraftforge.common.util.ForgeDirection;
|
|
||||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class RenderBlockBetterGrass extends RenderBlockAOBase implements IRenderBlockDecorator {
|
|
||||||
|
|
||||||
public IconSet grassIcons = new IconSet("bettergrassandleaves", "better_grass_long_%d");
|
|
||||||
public IconSet myceliumIcons = new IconSet("bettergrassandleaves", "better_mycel_%d");
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
|
|
||||||
if (!BetterFoliage.config.grassEnabled) return false;
|
|
||||||
if (!((block instanceof BlockGrass || block == Blocks.mycelium))) return false;
|
|
||||||
if (y == 255 || !blockAccess.isAirBlock(x, y + 1, z)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
|
|
||||||
// store world for later use
|
|
||||||
blockAccess = world;
|
|
||||||
|
|
||||||
// render grass block
|
|
||||||
setPassCounters(1);
|
|
||||||
setRenderBoundsFromBlock(block);
|
|
||||||
renderStandardBlock(block, x, y, z);
|
|
||||||
|
|
||||||
int variation = getSemiRandomFromPos(x, y, z, 0);
|
|
||||||
int heightVariation = getSemiRandomFromPos(x, y, z, 1);
|
|
||||||
|
|
||||||
IIcon renderIcon = (block == Blocks.mycelium) ? myceliumIcons.get(variation) : grassIcons.get(variation);
|
|
||||||
if (renderIcon == null) return true;
|
|
||||||
|
|
||||||
double scale = BetterFoliage.config.grassSize.value * 0.5;
|
|
||||||
double halfHeight = 0.5 * (BetterFoliage.config.grassHeightMin.value + pRand[heightVariation] * (BetterFoliage.config.grassHeightMax.value - BetterFoliage.config.grassHeightMin.value));
|
|
||||||
|
|
||||||
// render short grass
|
|
||||||
ShadersModIntegration.startGrassQuads();
|
|
||||||
Tessellator.instance.setBrightness(getBrightness(block, x, y + 1, z));
|
|
||||||
renderCrossedSideQuads(new Double3(x + 0.5, y + 1.0 - 0.125 * halfHeight, z + 0.5), ForgeDirection.UP, scale, halfHeight, pRot[variation], BetterFoliage.config.grassHOffset.value, renderIcon, 0, false);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public void handleTextureReload(TextureStitchEvent.Pre event) {
|
|
||||||
if (event.map.getTextureType() != 0) return;
|
|
||||||
|
|
||||||
grassIcons.registerIcons(event.map);
|
|
||||||
myceliumIcons.registerIcons(event.map);
|
|
||||||
BetterFoliage.log.info(String.format("Found %d short grass textures", grassIcons.numLoaded));
|
|
||||||
BetterFoliage.log.info(String.format("Found %d mycelium textures", myceliumIcons.numLoaded));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render.impl;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.BetterFoliageClient;
|
|
||||||
import mods.betterfoliage.client.render.IRenderBlockDecorator;
|
|
||||||
import mods.betterfoliage.client.render.RenderBlockAOBase;
|
|
||||||
import mods.betterfoliage.common.util.Double3;
|
|
||||||
import mods.betterfoliage.common.util.Utils;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.renderer.RenderBlocks;
|
|
||||||
import net.minecraft.client.renderer.Tessellator;
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
|
||||||
import net.minecraft.util.IIcon;
|
|
||||||
import net.minecraft.world.IBlockAccess;
|
|
||||||
import net.minecraftforge.common.util.ForgeDirection;
|
|
||||||
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class RenderBlockBetterLeaves extends RenderBlockAOBase implements IRenderBlockDecorator {
|
|
||||||
|
|
||||||
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
|
|
||||||
if (!BetterFoliage.config.leavesEnabled) return false;
|
|
||||||
if (original > 0 && original < 42) return false;
|
|
||||||
return BetterFoliageClient.leaves.matchesID(block) && !isBlockSurrounded(blockAccess, x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
|
|
||||||
// store world for later use
|
|
||||||
blockAccess = world;
|
|
||||||
|
|
||||||
// render leaves center
|
|
||||||
setPassCounters(1);
|
|
||||||
setRenderBoundsFromBlock(block);
|
|
||||||
if (block.getRenderType() == 0) {
|
|
||||||
renderStandardBlock(block, x, y, z);
|
|
||||||
} else {
|
|
||||||
ISimpleBlockRenderingHandler handler = Utils.getRenderingHandler(block.getRenderType());
|
|
||||||
handler.renderWorldBlock(world, x, y, z, block, block.getRenderType(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// find generated texture to render with, assume the
|
|
||||||
// "true" texture of the block is the one on the north size
|
|
||||||
TextureAtlasSprite blockLeafIcon = (TextureAtlasSprite) block.getIcon(world, x, y, z, ForgeDirection.NORTH.ordinal());
|
|
||||||
if (blockLeafIcon == null) {
|
|
||||||
BetterFoliage.log.debug(String.format("null leaf texture, x:%d, y:%d, z:%d, meta:%d, block:%s", x, y, z, blockAccess.getBlockMetadata(x, y, z), block.getClass().getName()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
IIcon crossLeafIcon = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(BetterFoliageClient.leafGenerator.domainName + ":" + blockLeafIcon.getIconName());
|
|
||||||
if (crossLeafIcon == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int offsetVariation = getSemiRandomFromPos(x, y, z, 0);
|
|
||||||
int uvVariation = getSemiRandomFromPos(x, y, z, 1);
|
|
||||||
double halfSize = 0.5 * BetterFoliage.config.leavesSize.value;
|
|
||||||
boolean isAirTop = y == 255 || blockAccess.isAirBlock(x, y + 1, z);
|
|
||||||
boolean isAirBottom = y == 0 || blockAccess.isAirBlock(x, y - 1, z);
|
|
||||||
|
|
||||||
Tessellator.instance.setBrightness(isAirTop ? getBrightness(block, x, y + 1, z) : (isAirBottom ? getBrightness(block, x, y - 1, z) : getBrightness(block, x, y, z)));
|
|
||||||
Tessellator.instance.setColorOpaque_I(block.colorMultiplier(blockAccess, x, y, z));
|
|
||||||
|
|
||||||
if (BetterFoliage.config.leavesSkew) {
|
|
||||||
renderCrossedBlockQuadsSkew(new Double3(x + 0.5, y + 0.5, z + 0.5), halfSize,
|
|
||||||
pRot[offsetVariation].scaleAxes(BetterFoliage.config.leavesHOffset.value, BetterFoliage.config.leavesVOffset.value, BetterFoliage.config.leavesHOffset.value),
|
|
||||||
pRot[(offsetVariation + 1) & 63].scaleAxes(BetterFoliage.config.leavesHOffset.value, BetterFoliage.config.leavesVOffset.value, BetterFoliage.config.leavesHOffset.value),
|
|
||||||
crossLeafIcon, uvVariation, isAirTop, isAirBottom);
|
|
||||||
} else {
|
|
||||||
renderCrossedBlockQuadsTranslate(new Double3(x + 0.5, y + 0.5, z + 0.5), halfSize,
|
|
||||||
pRot[offsetVariation].scaleAxes(BetterFoliage.config.leavesHOffset.value, BetterFoliage.config.leavesVOffset.value, BetterFoliage.config.leavesHOffset.value),
|
|
||||||
crossLeafIcon, uvVariation, isAirTop, isAirBottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isBlockSurrounded(IBlockAccess blockAccess, int x, int y, int z) {
|
|
||||||
if (blockAccess.isAirBlock(x + 1, y, z)) return false;
|
|
||||||
if (blockAccess.isAirBlock(x - 1, y, z)) return false;
|
|
||||||
if (blockAccess.isAirBlock(x, y, z + 1)) return false;
|
|
||||||
if (blockAccess.isAirBlock(x, y, z - 1)) return false;
|
|
||||||
if (y == 255 || blockAccess.isAirBlock(x, y + 1, z)) return false;
|
|
||||||
if (y == 0 || blockAccess.isAirBlock(x, y - 1, z)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render.impl;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.render.FakeRenderBlockAOBase;
|
|
||||||
import mods.betterfoliage.client.render.IRenderBlockDecorator;
|
|
||||||
import mods.betterfoliage.client.render.IconSet;
|
|
||||||
import mods.betterfoliage.common.util.Double3;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.client.renderer.RenderBlocks;
|
|
||||||
import net.minecraft.client.renderer.Tessellator;
|
|
||||||
import net.minecraft.init.Blocks;
|
|
||||||
import net.minecraft.world.IBlockAccess;
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
|
||||||
import net.minecraftforge.common.util.ForgeDirection;
|
|
||||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class RenderBlockBetterLilypad extends FakeRenderBlockAOBase implements IRenderBlockDecorator {
|
|
||||||
|
|
||||||
public IconSet lilypadFlowers = new IconSet("bettergrassandleaves", "better_lilypad_flower_%d");
|
|
||||||
public IconSet lilypadRoots = new IconSet("bettergrassandleaves", "better_lilypad_roots_%d");
|
|
||||||
|
|
||||||
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
|
|
||||||
return BetterFoliage.config.lilypadEnabled && block == Blocks.waterlily;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
|
|
||||||
// store world for later use
|
|
||||||
blockAccess = world;
|
|
||||||
|
|
||||||
// render lilypad block
|
|
||||||
renderBlockLilyPad(block, x, y, z);
|
|
||||||
|
|
||||||
int chanceVariation = getSemiRandomFromPos(x, y, z, 0);
|
|
||||||
int iconVariation = getSemiRandomFromPos(x, y, z, 1);
|
|
||||||
int offsetVariation = getSemiRandomFromPos(x, y, z, 2);
|
|
||||||
|
|
||||||
Tessellator.instance.setBrightness(getBrightness(block, x, y, z));
|
|
||||||
Tessellator.instance.setColorOpaque(255, 255, 255);
|
|
||||||
if (lilypadRoots.hasIcons()) renderCrossedSideQuads(new Double3(x + 0.5, y + 0.015, z + 0.5), ForgeDirection.DOWN,
|
|
||||||
0.2, 0.3,
|
|
||||||
null, 0.0,
|
|
||||||
lilypadRoots.get(iconVariation), 2,
|
|
||||||
true);
|
|
||||||
if (chanceVariation < BetterFoliage.config.lilypadChance.value && lilypadFlowers.hasIcons())
|
|
||||||
renderCrossedSideQuads(new Double3(x + 0.5, y + 0.02, z + 0.5), ForgeDirection.UP,
|
|
||||||
0.2, 0.3,
|
|
||||||
pRot[offsetVariation], BetterFoliage.config.lilypadHOffset.value,
|
|
||||||
lilypadFlowers.get(iconVariation), 0,
|
|
||||||
true);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public void handleTextureReload(TextureStitchEvent.Pre event) {
|
|
||||||
if (event.map.getTextureType() != 0) return;
|
|
||||||
|
|
||||||
lilypadFlowers.registerIcons(event.map);
|
|
||||||
lilypadRoots.registerIcons(event.map);
|
|
||||||
BetterFoliage.log.info(String.format("Found %d lilypad flower textures", lilypadFlowers.numLoaded));
|
|
||||||
BetterFoliage.log.info(String.format("Found %d lilypad root textures", lilypadRoots.numLoaded));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render.impl;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.ShadersModIntegration;
|
|
||||||
import mods.betterfoliage.client.render.IRenderBlockDecorator;
|
|
||||||
import mods.betterfoliage.client.render.IconSet;
|
|
||||||
import mods.betterfoliage.client.render.RenderBlockAOBase;
|
|
||||||
import mods.betterfoliage.common.util.Double3;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.block.BlockDirt;
|
|
||||||
import net.minecraft.block.material.Material;
|
|
||||||
import net.minecraft.client.renderer.RenderBlocks;
|
|
||||||
import net.minecraft.client.renderer.Tessellator;
|
|
||||||
import net.minecraft.util.IIcon;
|
|
||||||
import net.minecraft.util.MathHelper;
|
|
||||||
import net.minecraft.world.IBlockAccess;
|
|
||||||
import net.minecraft.world.gen.NoiseGeneratorSimplex;
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
|
||||||
import net.minecraftforge.common.util.ForgeDirection;
|
|
||||||
import net.minecraftforge.event.world.WorldEvent;
|
|
||||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class RenderBlockBetterReed extends RenderBlockAOBase implements IRenderBlockDecorator {
|
|
||||||
|
|
||||||
public IconSet reedBottomIcons = new IconSet("bf_reed_bottom", "bettergrassandleaves:better_reed_%d");
|
|
||||||
public IconSet reedTopIcons = new IconSet("bf_reed_top", "bettergrassandleaves:better_reed_%d");
|
|
||||||
public NoiseGeneratorSimplex noise;
|
|
||||||
|
|
||||||
public boolean isBlockAccepted(IBlockAccess blockAccess, int x, int y, int z, Block block, int original) {
|
|
||||||
if (!BetterFoliage.config.reedEnabled) return false;
|
|
||||||
if (y >= 254 || !(block instanceof BlockDirt)) return false;
|
|
||||||
if (blockAccess.getBlock(x, y + 1, z).getMaterial() != Material.water) return false;
|
|
||||||
if (!blockAccess.isAirBlock(x, y + 2, z)) return false;
|
|
||||||
if (blockAccess.getBiomeGenForCoords(x, z).temperature < 0.4f || blockAccess.getBiomeGenForCoords(x, z).rainfall < 0.4f) return false;
|
|
||||||
int terrainVariation = MathHelper.floor_double((noise.func_151605_a(x, z) + 1.0) * 32.0);
|
|
||||||
return terrainVariation < BetterFoliage.config.reedChance.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) {
|
|
||||||
// store world for later use
|
|
||||||
blockAccess = world;
|
|
||||||
|
|
||||||
// render dirt block
|
|
||||||
setPassCounters(1);
|
|
||||||
setRenderBoundsFromBlock(block);
|
|
||||||
renderStandardBlock(block, x, y, z);
|
|
||||||
|
|
||||||
int iconVariation = getSemiRandomFromPos(x, y, z, 0);
|
|
||||||
int heightVariation = getSemiRandomFromPos(x, y, z, 1);
|
|
||||||
|
|
||||||
IIcon bottomIcon = reedBottomIcons.get(iconVariation);
|
|
||||||
IIcon topIcon = reedTopIcons.get(iconVariation);
|
|
||||||
if (bottomIcon == null || topIcon == null) return true;
|
|
||||||
|
|
||||||
double quarterHeight = 0.25 * (BetterFoliage.config.reedHeightMin.value + pRand[heightVariation] * (BetterFoliage.config.reedHeightMax.value - BetterFoliage.config.reedHeightMin.value));
|
|
||||||
Tessellator.instance.setBrightness(getBrightness(block, x, y + 2, z));
|
|
||||||
Tessellator.instance.setColorOpaque(255, 255, 255);
|
|
||||||
|
|
||||||
// render reeds
|
|
||||||
ShadersModIntegration.startGrassQuads();
|
|
||||||
renderCrossedSideQuads(new Double3(x + 0.5, y + 1.0, z + 0.5), ForgeDirection.UP, 0.5, quarterHeight, pRot[iconVariation], BetterFoliage.config.reedHOffset.value, bottomIcon, 0, true);
|
|
||||||
renderCrossedSideQuads(new Double3(x + 0.5, y + 1.0 + 2.0 * quarterHeight, z + 0.5), ForgeDirection.UP, 0.5, quarterHeight, pRot[iconVariation], BetterFoliage.config.reedHOffset.value, topIcon, 0, true);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public void handleTextureReload(TextureStitchEvent.Pre event) {
|
|
||||||
if (event.map.getTextureType() != 0) return;
|
|
||||||
|
|
||||||
reedBottomIcons.registerIcons(event.map);
|
|
||||||
reedTopIcons.registerIcons(event.map);
|
|
||||||
BetterFoliage.log.info(String.format("Found %d reed textures", reedBottomIcons.numLoaded));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public void handleWorldLoad(WorldEvent.Load event) {
|
|
||||||
noise = new NoiseGeneratorSimplex(new Random(event.world.getWorldInfo().getSeed()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package mods.betterfoliage.client.resource;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.common.util.Utils;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.renderer.texture.TextureMap;
|
|
||||||
import net.minecraft.client.resources.IResource;
|
|
||||||
import net.minecraft.client.resources.IResourceManager;
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
|
|
||||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public abstract class BlockTextureGenerator implements IResourceManager {
|
|
||||||
|
|
||||||
/** Resource domain name of generated textures */
|
|
||||||
public String domainName;
|
|
||||||
|
|
||||||
/** Resource location for fallback texture (if the generation process fails) */
|
|
||||||
public ResourceLocation missingResource;
|
|
||||||
|
|
||||||
/** Texture atlas for block textures used in the current run */
|
|
||||||
public TextureMap blockTextures;
|
|
||||||
|
|
||||||
/** Number of textures generated in the current run */
|
|
||||||
int counter = 0;
|
|
||||||
|
|
||||||
public BlockTextureGenerator(String domainName, ResourceLocation missingResource) {
|
|
||||||
this.domainName = domainName;
|
|
||||||
this.missingResource = missingResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onStitchStart(TextureStitchEvent.Pre event) {}
|
|
||||||
|
|
||||||
public void onStitchEnd(TextureStitchEvent.Post event) {}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public void handleTextureReload(TextureStitchEvent.Pre event) {
|
|
||||||
if (event.map.getTextureType() != 0) return;
|
|
||||||
|
|
||||||
blockTextures = event.map;
|
|
||||||
counter = 0;
|
|
||||||
Map<String, IResourceManager> domainManagers = Utils.getDomainResourceManagers();
|
|
||||||
if (domainManagers == null) {
|
|
||||||
BetterFoliage.log.warn("Failed to inject texture generator");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
domainManagers.put(domainName, this);
|
|
||||||
|
|
||||||
onStitchStart(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public void endTextureReload(TextureStitchEvent.Post event) {
|
|
||||||
blockTextures = null;
|
|
||||||
if (event.map.getTextureType() != 0) return;
|
|
||||||
|
|
||||||
// don't leave a mess
|
|
||||||
Map<String, IResourceManager> domainManagers = Utils.getDomainResourceManagers();
|
|
||||||
if (domainManagers != null) domainManagers.remove(domainName);
|
|
||||||
|
|
||||||
onStitchEnd(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getResourceDomains() {
|
|
||||||
return ImmutableSet.<String>of(domainName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IResource> getAllResources(ResourceLocation resource) throws IOException {
|
|
||||||
return ImmutableList.<IResource>of(getResource(resource));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResource getMissingResource() throws IOException {
|
|
||||||
return Minecraft.getMinecraft().getResourceManager().getResource(missingResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResourceLocation unwrapResource(ResourceLocation wrapped) {
|
|
||||||
return new ResourceLocation(wrapped.getResourcePath().substring(16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package mods.betterfoliage.client.resource;
|
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.resources.IResource;
|
|
||||||
import net.minecraft.client.resources.IResourceManager;
|
|
||||||
import net.minecraft.client.resources.data.IMetadataSection;
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
|
||||||
|
|
||||||
/** {@link IResource} of PNG containing one half (top or bottom) of a given texture resource
|
|
||||||
* @author octarine-noise
|
|
||||||
*/
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class HalfTextureResource implements IResource {
|
|
||||||
|
|
||||||
/** Raw PNG data*/
|
|
||||||
public byte[] data = null;
|
|
||||||
|
|
||||||
/** Resource to return if generation fails */
|
|
||||||
public IResource fallbackResource;
|
|
||||||
|
|
||||||
public HalfTextureResource(ResourceLocation resource, boolean bottom, IResource fallbackResource) {
|
|
||||||
this.fallbackResource = fallbackResource;
|
|
||||||
|
|
||||||
IResourceManager resourceManager = Minecraft.getMinecraft().getResourceManager();
|
|
||||||
try {
|
|
||||||
// load full texture
|
|
||||||
ResourceLocation origResource = new ResourceLocation(resource.getResourceDomain(), "textures/blocks/" + resource.getResourcePath());
|
|
||||||
BufferedImage origImage = ImageIO.read(resourceManager.getResource(origResource).getInputStream());
|
|
||||||
|
|
||||||
// draw half texture
|
|
||||||
BufferedImage result = new BufferedImage(origImage.getWidth(), origImage.getHeight() / 2, BufferedImage.TYPE_4BYTE_ABGR);
|
|
||||||
Graphics2D graphics = result.createGraphics();
|
|
||||||
graphics.drawImage(origImage, 0, bottom ? -origImage.getHeight() / 2 : 0, null);
|
|
||||||
|
|
||||||
// create PNG image
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
ImageIO.write(result, "PNG", baos);
|
|
||||||
data = baos.toByteArray();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// stop log spam with GLSL installed
|
|
||||||
if (e instanceof FileNotFoundException) return;
|
|
||||||
BetterFoliage.log.info(String.format("Could not load texture: %s, exception: %s", resource.toString(), e.getClass().getSimpleName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getInputStream() {
|
|
||||||
return data != null ? new ByteArrayInputStream(data) : fallbackResource.getInputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasMetadata() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IMetadataSection getMetadata(String var1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package mods.betterfoliage.client.resource;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import mods.betterfoliage.client.BetterFoliageClient;
|
|
||||||
import mods.betterfoliage.common.util.Utils;
|
|
||||||
import mods.betterfoliage.loader.DeobfHelper;
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.renderer.texture.IIconRegister;
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
|
||||||
import net.minecraft.client.resources.IResource;
|
|
||||||
import net.minecraft.util.IIcon;
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent.Post;
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent.Pre;
|
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
/** Generates rounded crossleaf textures for all registered normal leaf textures at stitch time.
|
|
||||||
* @author octarine-noise
|
|
||||||
*/
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class LeafTextureGenerator extends BlockTextureGenerator implements IIconRegister {
|
|
||||||
|
|
||||||
public String nonGeneratedDomain = "betterfoliage";
|
|
||||||
|
|
||||||
public int nonGeneratedCounter = 0;
|
|
||||||
|
|
||||||
public LeafTextureGenerator() {
|
|
||||||
super("bf_leaves_autogen", new ResourceLocation("betterfoliage", "textures/blocks/missing_leaf.png"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IResource getResource(ResourceLocation resourceLocation) throws IOException {
|
|
||||||
ResourceLocation original = unwrapResource(resourceLocation);
|
|
||||||
|
|
||||||
// check for provided texture
|
|
||||||
ResourceLocation handDrawnLocation = new ResourceLocation(nonGeneratedDomain, String.format("textures/blocks/%s/%s", original.getResourceDomain(), original.getResourcePath()));
|
|
||||||
if (Utils.resourceExists(handDrawnLocation)) {
|
|
||||||
nonGeneratedCounter++;
|
|
||||||
return Minecraft.getMinecraft().getResourceManager().getResource(handDrawnLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate our own
|
|
||||||
LeafTextureResource result = new LeafTextureResource(original, getMissingResource());
|
|
||||||
if (result.data != null) counter++;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Leaf blocks register their textures here. An extra texture will be registered in the atlas
|
|
||||||
* for each, with the resource domain of this generator.
|
|
||||||
* @return the originally registered {@link IIcon} already in the atlas
|
|
||||||
*/
|
|
||||||
public IIcon registerIcon(String resourceLocation) {
|
|
||||||
IIcon original = blockTextures.getTextureExtry(resourceLocation);
|
|
||||||
blockTextures.registerIcon(new ResourceLocation(domainName, resourceLocation).toString());
|
|
||||||
BetterFoliage.log.debug(String.format("Found leaf texture: %s", resourceLocation));
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Iterates through all leaf blocks in the registry and makes them register
|
|
||||||
* their textures to "sniff out" all leaf textures.
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void onStitchStart(Pre event) {
|
|
||||||
nonGeneratedCounter = 0;
|
|
||||||
BetterFoliage.log.info("Reloading leaf textures");
|
|
||||||
|
|
||||||
// register simple block textures
|
|
||||||
Iterator<Block> iter = Block.blockRegistry.iterator();
|
|
||||||
while(iter.hasNext()) {
|
|
||||||
Block block = iter.next();
|
|
||||||
if (BetterFoliageClient.leaves.matchesClass(block)) {
|
|
||||||
BetterFoliage.log.debug(String.format("Inspecting leaf block: %s", block.getClass().getName()));
|
|
||||||
block.registerBlockIcons(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// enumerate all registered textures, find leaf textures among them
|
|
||||||
Map<String, TextureAtlasSprite> mapAtlas = null;
|
|
||||||
mapAtlas = Utils.getField(blockTextures, DeobfHelper.transformElementSearge("mapRegisteredSprites"), Map.class);
|
|
||||||
if (mapAtlas == null) mapAtlas = Utils.getField(blockTextures, "mapRegisteredSprites", Map.class);
|
|
||||||
if (mapAtlas == null) {
|
|
||||||
BetterFoliage.log.warn("Failed to reflect texture atlas, textures may be missing");
|
|
||||||
} else {
|
|
||||||
Set<String> foundLeafTextures = Sets.newHashSet();
|
|
||||||
for (TextureAtlasSprite icon : mapAtlas.values())
|
|
||||||
if (BetterFoliageClient.isLeafTexture(icon))
|
|
||||||
foundLeafTextures.add(icon.getIconName());
|
|
||||||
for (String resourceLocation : foundLeafTextures) {
|
|
||||||
BetterFoliage.log.debug(String.format("Found non-block-registered leaf texture: %s", resourceLocation));
|
|
||||||
blockTextures.registerIcon(new ResourceLocation(domainName, resourceLocation).toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStitchEnd(Post event) {
|
|
||||||
BetterFoliage.log.info(String.format("Found %d pre-drawn leaf textures", nonGeneratedCounter));
|
|
||||||
BetterFoliage.log.info(String.format("Generated %d leaf textures", counter));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package mods.betterfoliage.client.resource;
|
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliage;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.resources.IResource;
|
|
||||||
import net.minecraft.client.resources.IResourceManager;
|
|
||||||
import net.minecraft.client.resources.data.IMetadataSection;
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
|
||||||
import cpw.mods.fml.relauncher.Side;
|
|
||||||
import cpw.mods.fml.relauncher.SideOnly;
|
|
||||||
|
|
||||||
/** {@link IResource} containing an autogenerated round crossleaf texture
|
|
||||||
* @author octarine-noise
|
|
||||||
*/
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
public class LeafTextureResource implements IResource {
|
|
||||||
|
|
||||||
/** Raw PNG data*/
|
|
||||||
protected byte[] data = null;
|
|
||||||
|
|
||||||
/** Name of the default alpha mask to use */
|
|
||||||
public static String defaultMask = "rough";
|
|
||||||
|
|
||||||
/** Resource to return if generation fails */
|
|
||||||
public IResource fallbackResource;
|
|
||||||
|
|
||||||
public LeafTextureResource(ResourceLocation resLeaf, IResource fallbackResource) {
|
|
||||||
this.fallbackResource = fallbackResource;
|
|
||||||
|
|
||||||
IResourceManager resourceManager = Minecraft.getMinecraft().getResourceManager();
|
|
||||||
try {
|
|
||||||
// load normal leaf texture
|
|
||||||
ResourceLocation origResource = new ResourceLocation(resLeaf.getResourceDomain(), "textures/blocks/" + resLeaf.getResourcePath());
|
|
||||||
BufferedImage origImage = ImageIO.read(resourceManager.getResource(origResource).getInputStream());
|
|
||||||
if (origImage.getWidth() != origImage.getHeight()) return;
|
|
||||||
int size = origImage.getWidth();
|
|
||||||
|
|
||||||
// load alpha mask of appropriate size
|
|
||||||
BufferedImage maskImage = loadLeafMaskImage(defaultMask, size * 2);
|
|
||||||
int scale = size * 2 / maskImage.getWidth();
|
|
||||||
|
|
||||||
// tile leaf texture 2x2
|
|
||||||
BufferedImage overlayIcon = new BufferedImage(size * 2, size * 2, BufferedImage.TYPE_4BYTE_ABGR);
|
|
||||||
Graphics2D graphics = overlayIcon.createGraphics();
|
|
||||||
graphics.drawImage(origImage, 0, 0, null);
|
|
||||||
graphics.drawImage(origImage, 0, size, null);
|
|
||||||
graphics.drawImage(origImage, size, 0, null);
|
|
||||||
graphics.drawImage(origImage, size, size, null);
|
|
||||||
|
|
||||||
// overlay mask alpha on texture
|
|
||||||
for (int x = 0; x < overlayIcon.getWidth(); x++) {
|
|
||||||
for (int y = 0; y < overlayIcon.getHeight(); y++) {
|
|
||||||
long origPixel = overlayIcon.getRGB(x, y) & 0xFFFFFFFFl;
|
|
||||||
long maskPixel = maskImage.getRGB(x / scale, y / scale) & 0xFF000000l | 0x00FFFFFF;
|
|
||||||
overlayIcon.setRGB(x, y, (int) (origPixel & maskPixel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create PNG image
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
ImageIO.write(overlayIcon, "PNG", baos);
|
|
||||||
data = baos.toByteArray();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// stop log spam with GLSL installed
|
|
||||||
if (e instanceof FileNotFoundException) return;
|
|
||||||
BetterFoliage.log.info(String.format("Could not create leaf texture: %s, exception: %s", resLeaf.toString(), e.getClass().getSimpleName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Loads the alpha mask of the given type and size. If a mask of the exact size can not be found,
|
|
||||||
* will try to load progressively smaller masks down to 16x16
|
|
||||||
* @param type mask type
|
|
||||||
* @param size texture size
|
|
||||||
* @return alpha mask
|
|
||||||
*/
|
|
||||||
protected BufferedImage loadLeafMaskImage(String type, int size) {
|
|
||||||
IResourceManager resourceManager = Minecraft.getMinecraft().getResourceManager();
|
|
||||||
IResource maskResource = null;
|
|
||||||
|
|
||||||
while (maskResource == null && size >= 16) {
|
|
||||||
try {
|
|
||||||
maskResource = resourceManager.getResource(new ResourceLocation(String.format("betterfoliage:textures/blocks/leafmask_%d_%s.png", size, type)));
|
|
||||||
} catch (Exception e) {}
|
|
||||||
size /= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return maskResource == null ? null : ImageIO.read(maskResource.getInputStream());
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputStream getInputStream() {
|
|
||||||
return data != null ? new ByteArrayInputStream(data) : fallbackResource.getInputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasMetadata() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IMetadataSection getMetadata(String var1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package mods.betterfoliage.common.config;
|
|
||||||
|
|
||||||
public class BetterFoliageConfig extends ConfigBase {
|
|
||||||
|
|
||||||
@CfgElement(category="leaves", key="enabled")
|
|
||||||
public boolean leavesEnabled = true;
|
|
||||||
|
|
||||||
@CfgElement(category="leaves", key="skewMode")
|
|
||||||
public boolean leavesSkew = false;
|
|
||||||
|
|
||||||
@CfgElement(category="grass", key="enabled")
|
|
||||||
public boolean grassEnabled = true;
|
|
||||||
|
|
||||||
@CfgElement(category="cactus", key="enabled")
|
|
||||||
public boolean cactusEnabled = true;
|
|
||||||
|
|
||||||
@CfgElement(category="lilypad", key="enabled")
|
|
||||||
public boolean lilypadEnabled = true;
|
|
||||||
|
|
||||||
@CfgElement(category="reed", key="enabled")
|
|
||||||
public boolean reedEnabled = true;
|
|
||||||
|
|
||||||
@CfgElement(category="algae", key="enabled")
|
|
||||||
public boolean algaeEnabled = true;
|
|
||||||
|
|
||||||
@CfgElement(category="leaves", key="horizontalOffset")
|
|
||||||
public OptionDouble leavesHOffset = new OptionDouble(0.0, 0.4, 0.025, 0.2);
|
|
||||||
|
|
||||||
@CfgElement(category="leaves", key="verticalOffset")
|
|
||||||
public OptionDouble leavesVOffset = new OptionDouble(0.0, 0.4, 0.025, 0.1);
|
|
||||||
|
|
||||||
@CfgElement(category="leaves", key="size")
|
|
||||||
public OptionDouble leavesSize = new OptionDouble(0.75, 1.8, 0.05, 1.4);
|
|
||||||
|
|
||||||
@CfgElement(category="grass", key="horizontalOffset")
|
|
||||||
public OptionDouble grassHOffset = new OptionDouble(0.0, 0.4, 0.025, 0.2);
|
|
||||||
|
|
||||||
@CfgElement(category="grass", key="heightMin")
|
|
||||||
@Limit(max="grassHeightMax")
|
|
||||||
public OptionDouble grassHeightMin = new OptionDouble(0.1, 1.5, 0.05, 0.5);
|
|
||||||
|
|
||||||
@CfgElement(category="grass", key="heightMax")
|
|
||||||
public OptionDouble grassHeightMax = new OptionDouble(0.1, 1.5, 0.05, 1.0);
|
|
||||||
|
|
||||||
@CfgElement(category="grass", key="size")
|
|
||||||
public OptionDouble grassSize = new OptionDouble(0.5, 1.5, 0.05, 1.0);
|
|
||||||
|
|
||||||
@CfgElement(category="lilypad", key="horizontalOffset")
|
|
||||||
public OptionDouble lilypadHOffset = new OptionDouble(0.0, 0.25, 0.025, 0.1);
|
|
||||||
|
|
||||||
@CfgElement(category="lilypad", key="chance")
|
|
||||||
public OptionInteger lilypadChance = new OptionInteger(0, 64, 1, 16);
|
|
||||||
|
|
||||||
@CfgElement(category="reed", key="horizontalOffset")
|
|
||||||
public OptionDouble reedHOffset = new OptionDouble(0.0, 0.25, 0.025, 0.1);
|
|
||||||
|
|
||||||
@CfgElement(category="reed", key="heightMin")
|
|
||||||
@Limit(max="reedHeightMax")
|
|
||||||
public OptionDouble reedHeightMin = new OptionDouble(1.5, 3.5, 0.1, 2.0);
|
|
||||||
|
|
||||||
@CfgElement(category="reed", key="heightMax")
|
|
||||||
public OptionDouble reedHeightMax = new OptionDouble(1.5, 3.5, 0.1, 2.5);
|
|
||||||
|
|
||||||
@CfgElement(category="reed", key="chance")
|
|
||||||
public OptionInteger reedChance = new OptionInteger(0, 64, 1, 32);
|
|
||||||
|
|
||||||
@CfgElement(category="algae", key="horizontalOffset")
|
|
||||||
public OptionDouble algaeHOffset = new OptionDouble(0.0, 0.25, 0.025, 0.1);
|
|
||||||
|
|
||||||
@CfgElement(category="algae", key="size")
|
|
||||||
public OptionDouble algaeSize = new OptionDouble(0.5, 1.5, 0.05, 1.0);
|
|
||||||
|
|
||||||
@CfgElement(category="algae", key="heightMin")
|
|
||||||
@Limit(max="algaeHeightMax")
|
|
||||||
public OptionDouble algaeHeightMin = new OptionDouble(0.1, 1.5, 0.05, 0.5);
|
|
||||||
|
|
||||||
@CfgElement(category="algae", key="heightMax")
|
|
||||||
public OptionDouble algaeHeightMax = new OptionDouble(0.1, 1.5, 0.05, 1.0);
|
|
||||||
|
|
||||||
@CfgElement(category="algae", key="chance")
|
|
||||||
public OptionInteger algaeChance = new OptionInteger(0, 64, 1, 48);
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
package mods.betterfoliage.common.config;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
|
|
||||||
import net.minecraftforge.common.config.Configuration;
|
|
||||||
import net.minecraftforge.common.config.Property;
|
|
||||||
|
|
||||||
public class ConfigBase {
|
|
||||||
|
|
||||||
@Target(ElementType.FIELD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public static @interface CfgElement {
|
|
||||||
String category();
|
|
||||||
String key();
|
|
||||||
String comment() default "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Target(ElementType.FIELD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public static @interface Limit {
|
|
||||||
String min() default "";
|
|
||||||
String max() default "";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Configuration config;
|
|
||||||
|
|
||||||
public void load(File configFile) {
|
|
||||||
config = new Configuration(configFile);
|
|
||||||
config.load();
|
|
||||||
|
|
||||||
for (Field field : getClass().getDeclaredFields()) {
|
|
||||||
CfgElement annot = field.getAnnotation(CfgElement.class);
|
|
||||||
if (annot == null) continue;
|
|
||||||
|
|
||||||
field.setAccessible(true);
|
|
||||||
if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) {
|
|
||||||
try {
|
|
||||||
Property prop = config.get(annot.category(), annot.key(), field.getBoolean(this));
|
|
||||||
field.setBoolean(this, prop.getBoolean(field.getBoolean(this)));
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
} else if (field.getType().equals(OptionInteger.class)) {
|
|
||||||
try {
|
|
||||||
OptionInteger option = (OptionInteger) field.get(this);
|
|
||||||
Property prop = config.get(annot.category(), annot.key(), option.value);
|
|
||||||
option.value = prop.getInt(option.value);
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
} else if (field.getType().equals(OptionDouble.class)) {
|
|
||||||
try {
|
|
||||||
OptionDouble option = (OptionDouble) field.get(this);
|
|
||||||
Property prop = config.get(annot.category(), annot.key(), option.value);
|
|
||||||
option.value = prop.getDouble(option.value);
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validateLimits();
|
|
||||||
if (config.hasChanged()) config.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void validateLimits() {
|
|
||||||
for (Field fieldThis : getClass().getDeclaredFields()) {
|
|
||||||
Limit annot = fieldThis.getAnnotation(Limit.class);
|
|
||||||
if (annot == null) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Field fieldMin = annot.min().isEmpty() ? null : getClass().getDeclaredField(annot.min());
|
|
||||||
Field fieldMax = annot.max().isEmpty() ? null : getClass().getDeclaredField(annot.max());
|
|
||||||
fieldThis.setAccessible(true);
|
|
||||||
fieldMin.setAccessible(true);
|
|
||||||
fieldMax.setAccessible(true);
|
|
||||||
|
|
||||||
if (fieldThis.getType().equals(OptionInteger.class)) {
|
|
||||||
OptionInteger optionThis = (OptionInteger) fieldThis.get(this);
|
|
||||||
OptionInteger optionMin = fieldMin == null ? null : (OptionInteger) fieldMin.get(this);
|
|
||||||
OptionInteger optionMax = fieldMax == null ? null : (OptionInteger) fieldMax.get(this);
|
|
||||||
if (optionMin != null) optionThis.value = Math.max(optionThis.value, optionMin.value);
|
|
||||||
if (optionMax != null) optionThis.value = Math.min(optionThis.value, optionMax.value);
|
|
||||||
} else if (fieldThis.getType().equals(OptionDouble.class)) {
|
|
||||||
OptionDouble optionThis = (OptionDouble) fieldThis.get(this);
|
|
||||||
OptionDouble optionMin = fieldMin == null ? null : (OptionDouble) fieldMin.get(this);
|
|
||||||
OptionDouble optionMax = fieldMax == null ? null : (OptionDouble) fieldMax.get(this);
|
|
||||||
if (optionMin != null) optionThis.value = Math.max(optionThis.value, optionMin.value);
|
|
||||||
if (optionMax != null) optionThis.value = Math.min(optionThis.value, optionMax.value);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
|
||||||
for (Field field : getClass().getDeclaredFields()) {
|
|
||||||
CfgElement annot = field.getAnnotation(CfgElement.class);
|
|
||||||
if (annot == null) continue;
|
|
||||||
|
|
||||||
field.setAccessible(true);
|
|
||||||
if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) {
|
|
||||||
try {
|
|
||||||
Property prop = config.get(annot.category(), annot.key(), field.getBoolean(this));
|
|
||||||
prop.set(field.getBoolean(this));
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
} else if (field.getType().equals(OptionInteger.class)) {
|
|
||||||
try {
|
|
||||||
OptionInteger option = (OptionInteger) field.get(this);
|
|
||||||
Property prop = config.get(annot.category(), annot.key(), option.value);
|
|
||||||
prop.set(option.value);
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
} else if (field.getType().equals(OptionDouble.class)) {
|
|
||||||
try {
|
|
||||||
OptionDouble option = (OptionDouble) field.get(this);
|
|
||||||
Property prop = config.get(annot.category(), annot.key(), option.value);
|
|
||||||
prop.set(option.value);
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package mods.betterfoliage.common.config;
|
|
||||||
|
|
||||||
public class OptionDouble {
|
|
||||||
|
|
||||||
public double min;
|
|
||||||
public double max;
|
|
||||||
public double step;
|
|
||||||
public double value;
|
|
||||||
|
|
||||||
public OptionDouble(double min, double max, double step, double value) {
|
|
||||||
this.min = min;
|
|
||||||
this.max = max;
|
|
||||||
this.step = step;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void increment() {
|
|
||||||
value += step;
|
|
||||||
if (value > max) value = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decrement() {
|
|
||||||
value -= step;
|
|
||||||
if (value < min) value = min;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package mods.betterfoliage.common.config;
|
|
||||||
|
|
||||||
public class OptionInteger {
|
|
||||||
|
|
||||||
public int min;
|
|
||||||
public int max;
|
|
||||||
public int step;
|
|
||||||
public int value;
|
|
||||||
|
|
||||||
public OptionInteger(int min, int max, int step, int value) {
|
|
||||||
this.min = min;
|
|
||||||
this.max = max;
|
|
||||||
this.step = step;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void increment() {
|
|
||||||
value += step;
|
|
||||||
if (value > max) value = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decrement() {
|
|
||||||
value -= step;
|
|
||||||
if (value < min) value = min;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package mods.betterfoliage.common.util;
|
|
||||||
|
|
||||||
import net.minecraftforge.common.util.ForgeDirection;
|
|
||||||
|
|
||||||
public class Double3 {
|
|
||||||
|
|
||||||
public final double x;
|
|
||||||
public final double y;
|
|
||||||
public final double z;
|
|
||||||
|
|
||||||
public Double3(double x, double y, double z) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double3(ForgeDirection dir) {
|
|
||||||
this.x = dir.offsetX;
|
|
||||||
this.y = dir.offsetY;
|
|
||||||
this.z = dir.offsetZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double3 add(Double3 other) {
|
|
||||||
return new Double3(x + other.x, y + other.y, z + other.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double3 add(double x, double y, double z) {
|
|
||||||
return new Double3(this.x + x, this.y + y, this.z + z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double3 scaleAxes(double sx, double sy, double sz) {
|
|
||||||
return new Double3(x * sx, y * sy, z * sz);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double3 scale(double s) {
|
|
||||||
return new Double3(x * s, y * s, z * s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double3 inverse() {
|
|
||||||
return new Double3(-x, -y, -z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package mods.betterfoliage.common.util;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
|
|
||||||
import mods.betterfoliage.loader.DeobfHelper;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.resources.IResource;
|
|
||||||
import net.minecraft.client.resources.IResourceManager;
|
|
||||||
import net.minecraft.util.ResourceLocation;
|
|
||||||
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
|
|
||||||
import cpw.mods.fml.client.registry.RenderingRegistry;
|
|
||||||
|
|
||||||
public class Utils {
|
|
||||||
|
|
||||||
private Utils() {}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static Map<String, IResourceManager> getDomainResourceManagers() {
|
|
||||||
IResourceManager manager = Minecraft.getMinecraft().getResourceManager();
|
|
||||||
Map<String, IResourceManager> result = getField(manager, "domainResourceManagers", Map.class);
|
|
||||||
if (result == null) result = getField(manager, DeobfHelper.transformElementSearge("domainResourceManagers"), Map.class);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T> T getField(Object target, String fieldName, Class<T> resultClass) {
|
|
||||||
try {
|
|
||||||
Field field = target.getClass().getDeclaredField(fieldName);
|
|
||||||
field.setAccessible(true);
|
|
||||||
return (T) field.get(target);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T> T getStaticField(Class<?> clazz, String fieldName, Class<T> resultClass) {
|
|
||||||
try {
|
|
||||||
Field field = clazz.getDeclaredField(fieldName);
|
|
||||||
field.setAccessible(true);
|
|
||||||
return (T) field.get(null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static ISimpleBlockRenderingHandler getRenderingHandler(int renderType) {
|
|
||||||
try {
|
|
||||||
Field field = RenderingRegistry.class.getDeclaredField("INSTANCE");
|
|
||||||
field.setAccessible(true);
|
|
||||||
RenderingRegistry inst = (RenderingRegistry) field.get(null);
|
|
||||||
field = RenderingRegistry.class.getDeclaredField("blockRenderers");
|
|
||||||
field.setAccessible(true);
|
|
||||||
return ((Map<Integer, ISimpleBlockRenderingHandler>) field.get(inst)).get(renderType);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean resourceExists(ResourceLocation resourceLocation) {
|
|
||||||
try {
|
|
||||||
IResource resource = Minecraft.getMinecraft().getResourceManager().getResource(resourceLocation);
|
|
||||||
if (resource != null) return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void copyFromTextResource(ResourceLocation resourceLocation, File target) {
|
|
||||||
try {
|
|
||||||
IResource defaults = Minecraft.getMinecraft().getResourceManager().getResource(resourceLocation);
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(defaults.getInputStream(), Charsets.UTF_8));
|
|
||||||
FileWriter writer = new FileWriter(target);
|
|
||||||
|
|
||||||
String line = reader.readLine();
|
|
||||||
while(line != null) {
|
|
||||||
writer.write(line + System.lineSeparator());
|
|
||||||
line = reader.readLine();
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
writer.close();
|
|
||||||
} catch(IOException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package mods.betterfoliage.loader;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
|
||||||
|
|
||||||
@IFMLLoadingPlugin.TransformerExclusions({"mods.betterfoliage.loader"})
|
|
||||||
public class BetterFoliageLoader implements IFMLLoadingPlugin {
|
|
||||||
|
|
||||||
public String[] getASMTransformerClass() {
|
|
||||||
return new String[] {"mods.betterfoliage.loader.BetterFoliageTransformer"};
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getModContainerClass() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSetupClass() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void injectData(Map<String, Object> data) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAccessTransformerClass() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package mods.betterfoliage.loader;
|
|
||||||
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.FieldInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
|
||||||
import org.objectweb.asm.tree.VarInsnNode;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
|
|
||||||
import cpw.mods.fml.relauncher.FMLInjectionData;
|
|
||||||
|
|
||||||
public class BetterFoliageTransformer extends EZTransformerBase {
|
|
||||||
|
|
||||||
public BetterFoliageTransformer() {
|
|
||||||
String mcVersion = FMLInjectionData.data()[4].toString();
|
|
||||||
if (!ImmutableList.<String>of("1.7.2", "1.7.10").contains(mcVersion))
|
|
||||||
logger.warn(String.format("Unsupported Minecraft version %s", mcVersion));
|
|
||||||
|
|
||||||
DeobfHelper.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MethodTransform(className="net.minecraft.client.renderer.RenderBlocks",
|
|
||||||
methodName="renderBlockByRenderType",
|
|
||||||
signature="(Lnet/minecraft/block/Block;III)Z",
|
|
||||||
log="Applying RenderBlocks.renderBlockByRenderType() render type ovverride")
|
|
||||||
public void handleRenderBlockOverride(MethodNode method) {
|
|
||||||
AbstractInsnNode invokeGetRenderType = findNext(method.instructions.getFirst(), matchInvokeAny());
|
|
||||||
AbstractInsnNode storeRenderType = findNext(invokeGetRenderType, matchOpcode(Opcodes.ISTORE));
|
|
||||||
insertAfter(method.instructions, storeRenderType,
|
|
||||||
new VarInsnNode(Opcodes.ALOAD, 0),
|
|
||||||
new FieldInsnNode(Opcodes.GETFIELD, className("net/minecraft/client/renderer/RenderBlocks"), element("blockAccess"), signature("Lnet/minecraft/world/IBlockAccess;")),
|
|
||||||
new VarInsnNode(Opcodes.ILOAD, 2),
|
|
||||||
new VarInsnNode(Opcodes.ILOAD, 3),
|
|
||||||
new VarInsnNode(Opcodes.ILOAD, 4),
|
|
||||||
new VarInsnNode(Opcodes.ALOAD, 1),
|
|
||||||
new VarInsnNode(Opcodes.ILOAD, 5),
|
|
||||||
new MethodInsnNode(Opcodes.INVOKESTATIC, "mods/betterfoliage/client/BetterFoliageClient", "getRenderTypeOverride", signature("(Lnet/minecraft/world/IBlockAccess;IIILnet/minecraft/block/Block;I)I")),
|
|
||||||
new VarInsnNode(Opcodes.ISTORE, 5)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MethodTransform(className="shadersmodcore.client.Shaders",
|
|
||||||
methodName="pushEntity",
|
|
||||||
signature="(Lnet/minecraft/client/renderer/RenderBlocks;Lnet/minecraft/block/Block;III)V",
|
|
||||||
log="Applying Shaders.pushEntity() block id ovverride")
|
|
||||||
public void handleGLSLBlockIDOverride(MethodNode method) {
|
|
||||||
AbstractInsnNode arrayStore = findNext(method.instructions.getFirst(), matchOpcode(Opcodes.IASTORE));
|
|
||||||
insertAfter(method.instructions, arrayStore.getPrevious(),
|
|
||||||
new VarInsnNode(Opcodes.ALOAD, 1),
|
|
||||||
new MethodInsnNode(Opcodes.INVOKESTATIC, "mods/betterfoliage/client/ShadersModIntegration", "getBlockIdOverride", signature("(ILnet/minecraft/block/Block;)I"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package mods.betterfoliage.loader;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
|
|
||||||
import cpw.mods.fml.relauncher.FMLInjectionData;
|
|
||||||
|
|
||||||
public class DeobfHelper {
|
|
||||||
|
|
||||||
private static Map<String, String> obfClasses = Maps.newHashMap();
|
|
||||||
private static Map<String, String> obfElements = Maps.newHashMap();
|
|
||||||
private static Map<String, String> srgElements = Maps.newHashMap();
|
|
||||||
|
|
||||||
public static void init() {
|
|
||||||
String mcVersion = FMLInjectionData.data()[4].toString();
|
|
||||||
srgElements.put("domainResourceManagers", "field_110548_a");
|
|
||||||
srgElements.put("mapRegisteredSprites", "field_110574_e");
|
|
||||||
if ("1.7.2".equals(mcVersion)) {
|
|
||||||
obfClasses.put("net/minecraft/client/renderer/RenderBlocks", "ble");
|
|
||||||
obfClasses.put("net/minecraft/world/IBlockAccess", "afx");
|
|
||||||
obfClasses.put("net/minecraft/block/Block", "ahu");
|
|
||||||
|
|
||||||
obfElements.put("blockAccess", "a");
|
|
||||||
obfElements.put("renderBlockByRenderType", "b");
|
|
||||||
obfElements.put("mapRegisteredSprites", "bpr");
|
|
||||||
} else if ("1.7.10".equals(mcVersion)) {
|
|
||||||
obfClasses.put("net/minecraft/client/renderer/RenderBlocks", "blm");
|
|
||||||
obfClasses.put("net/minecraft/world/IBlockAccess", "ahl");
|
|
||||||
obfClasses.put("net/minecraft/block/Block", "aji");
|
|
||||||
|
|
||||||
obfElements.put("blockAccess", "a");
|
|
||||||
obfElements.put("renderBlockByRenderType", "b");
|
|
||||||
obfElements.put("mapRegisteredSprites", "bpr");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String transformClassName(String className) {
|
|
||||||
return obfClasses.containsKey(className) ? obfClasses.get(className) : className;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String transformElementName(String elementName) {
|
|
||||||
return obfElements.containsKey(elementName) ? obfElements.get(elementName) : elementName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String transformElementSearge(String elementName) {
|
|
||||||
return srgElements.containsKey(elementName) ? srgElements.get(elementName) : elementName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String transformSignature(String signature) {
|
|
||||||
String result = signature;
|
|
||||||
boolean hasChanged = false;
|
|
||||||
do {
|
|
||||||
hasChanged = false;
|
|
||||||
for (Map.Entry<String, String> entry : obfClasses.entrySet()) if (result.contains("L" + entry.getKey() + ";")) {
|
|
||||||
result = result.replace("L" + entry.getKey() + ";", "L" + entry.getValue() + ";");
|
|
||||||
hasChanged = true;
|
|
||||||
}
|
|
||||||
} while(hasChanged);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
package mods.betterfoliage.loader;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
import net.minecraft.launchwrapper.IClassTransformer;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.objectweb.asm.ClassReader;
|
|
||||||
import org.objectweb.asm.ClassWriter;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.ClassNode;
|
|
||||||
import org.objectweb.asm.tree.InsnList;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
|
||||||
|
|
||||||
public class EZTransformerBase implements IClassTransformer {
|
|
||||||
|
|
||||||
public static interface IInstructionMatch {
|
|
||||||
public boolean matches(AbstractInsnNode node);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public static @interface MethodTransform {
|
|
||||||
public String className();
|
|
||||||
public String methodName();
|
|
||||||
public String signature();
|
|
||||||
public String log();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Logger logger = LogManager.getLogger(getClass().getSimpleName());
|
|
||||||
|
|
||||||
protected Boolean isObfuscated;
|
|
||||||
|
|
||||||
public byte[] transform(String name, String transformedName, byte[] basicClass) {
|
|
||||||
// ???
|
|
||||||
if (basicClass == null) return null;
|
|
||||||
|
|
||||||
// read class
|
|
||||||
ClassNode classNode = new ClassNode();
|
|
||||||
ClassReader classReader = new ClassReader(basicClass);
|
|
||||||
classReader.accept(classNode, 0);
|
|
||||||
boolean hasTransformed = false;
|
|
||||||
|
|
||||||
for (Method classMethod : getClass().getMethods()) {
|
|
||||||
// check for annotated method with correct signature
|
|
||||||
MethodTransform annot = classMethod.getAnnotation(MethodTransform.class);
|
|
||||||
if (annot == null) continue;
|
|
||||||
if (classMethod.getParameterTypes().length != 1) continue;
|
|
||||||
if (!classMethod.getParameterTypes()[0].equals(MethodNode.class)) continue;
|
|
||||||
|
|
||||||
// try to find specified method in class
|
|
||||||
if (!transformedName.equals(annot.className())) continue;
|
|
||||||
logger.debug(String.format("Found class: %s -> %s", name, transformedName));
|
|
||||||
for (MethodNode methodNode : classNode.methods) {
|
|
||||||
logger.trace(String.format("Checking method: %s, sig: %s", methodNode.name, methodNode.desc));
|
|
||||||
isObfuscated = null;
|
|
||||||
if (methodNode.name.equals(DeobfHelper.transformElementName(annot.methodName())) && methodNode.desc.equals(DeobfHelper.transformSignature(annot.signature()))) {
|
|
||||||
isObfuscated = true;
|
|
||||||
} else if (methodNode.name.equals(annot.methodName()) && methodNode.desc.equals(annot.signature())) {
|
|
||||||
isObfuscated = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isObfuscated != null) {
|
|
||||||
// transform
|
|
||||||
hasTransformed = true;
|
|
||||||
try {
|
|
||||||
classMethod.invoke(this, new Object[] {methodNode});
|
|
||||||
logger.info(String.format("%s: SUCCESS", annot.log()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.info(String.format("%s: FAILURE", annot.log()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return result
|
|
||||||
ClassWriter writer = new ClassWriter(0);
|
|
||||||
if (hasTransformed) classNode.accept(writer);
|
|
||||||
return !hasTransformed ? basicClass : writer.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String className(String className) {
|
|
||||||
return isObfuscated ? DeobfHelper.transformClassName(className) : className;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String element(String fieldName) {
|
|
||||||
return isObfuscated ? DeobfHelper.transformElementName(fieldName) : fieldName;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String signature(String signature) {
|
|
||||||
return isObfuscated ? DeobfHelper.transformSignature(signature) : signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AbstractInsnNode findNext(AbstractInsnNode start, IInstructionMatch match) {
|
|
||||||
AbstractInsnNode current = start;
|
|
||||||
while(current != null) {
|
|
||||||
if (match.matches(current)) break;
|
|
||||||
current = current.getNext();
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AbstractInsnNode findPrevious(AbstractInsnNode start, IInstructionMatch match) {
|
|
||||||
AbstractInsnNode current = start;
|
|
||||||
while(current != null) {
|
|
||||||
if (match.matches(current)) break;
|
|
||||||
current = current.getPrevious();
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static IInstructionMatch matchInvokeAny() {
|
|
||||||
return new IInstructionMatch() {
|
|
||||||
public boolean matches(AbstractInsnNode node) {
|
|
||||||
return node instanceof MethodInsnNode;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static IInstructionMatch matchOpcode(final int opcode) {
|
|
||||||
return new IInstructionMatch() {
|
|
||||||
public boolean matches(AbstractInsnNode node) {
|
|
||||||
return node.getOpcode() == opcode;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void insertAfter(InsnList insnList, AbstractInsnNode node, AbstractInsnNode... added) {
|
|
||||||
InsnList listAdd = new InsnList();
|
|
||||||
for (AbstractInsnNode inst : added) listAdd.add(inst);
|
|
||||||
insnList.insert(node, listAdd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
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) }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package mods.betterfoliage.render.column
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
|
import mods.betterfoliage.chunk.ChunkOverlayLayer
|
||||||
|
import mods.betterfoliage.chunk.ChunkOverlayManager
|
||||||
|
import mods.betterfoliage.chunk.dimType
|
||||||
|
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
|
||||||
|
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
|
||||||
|
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
|
||||||
|
import mods.betterfoliage.chunk.BlockCtx
|
||||||
|
import mods.betterfoliage.util.*
|
||||||
|
import net.minecraft.block.BlockState
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.minecraft.util.math.Direction.Axis
|
||||||
|
import net.minecraft.util.math.Direction.AxisDirection
|
||||||
|
import net.minecraft.world.ExtendedBlockView
|
||||||
|
|
||||||
|
/** Index of SOUTH-EAST quadrant. */
|
||||||
|
const val SE = 0
|
||||||
|
/** Index of NORTH-EAST quadrant. */
|
||||||
|
const val NE = 1
|
||||||
|
/** Index of NORTH-WEST quadrant. */
|
||||||
|
const val NW = 2
|
||||||
|
/** Index of SOUTH-WEST quadrant. */
|
||||||
|
const val SW = 3
|
||||||
|
|
||||||
|
interface ColumnBlockKey {
|
||||||
|
val axis: Axis?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sealed class hierarchy for all possible render outcomes
|
||||||
|
*/
|
||||||
|
sealed class ColumnLayerData {
|
||||||
|
/**
|
||||||
|
* Data structure to cache texture and world neighborhood data relevant to column rendering
|
||||||
|
*/
|
||||||
|
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
|
||||||
|
data class SpecialRender(
|
||||||
|
val column: ColumnBlockKey,
|
||||||
|
val upType: BlockType,
|
||||||
|
val downType: BlockType,
|
||||||
|
val quadrants: Array<QuadrantType>,
|
||||||
|
val quadrantsTop: Array<QuadrantType>,
|
||||||
|
val quadrantsBottom: Array<QuadrantType>
|
||||||
|
) : ColumnLayerData() {
|
||||||
|
enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR }
|
||||||
|
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 */
|
||||||
|
object SkipRender : ColumnLayerData()
|
||||||
|
|
||||||
|
/** Column block must be rendered normally */
|
||||||
|
object NormalRender : ColumnLayerData()
|
||||||
|
|
||||||
|
/** Error while resolving render data, column block must be rendered normally */
|
||||||
|
object ResolveError : ColumnLayerData()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
||||||
|
|
||||||
|
abstract val connectSolids: Boolean
|
||||||
|
abstract val lenientConnect: Boolean
|
||||||
|
abstract val defaultToY: Boolean
|
||||||
|
|
||||||
|
abstract fun getColumnKey(state: BlockState): ColumnBlockKey?
|
||||||
|
|
||||||
|
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
|
||||||
|
|
||||||
|
override fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos) {
|
||||||
|
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun calculate(ctx: BlockCtx): ColumnLayerData {
|
||||||
|
if (allDirections.all { ctx.offset(it).isNormalCube }) return ColumnLayerData.SkipRender
|
||||||
|
// val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
|
||||||
|
val columnTextures = getColumnKey(ctx.state) ?: return ColumnLayerData.ResolveError
|
||||||
|
|
||||||
|
// if log axis is not defined and "Default to vertical" config option is not set, render normally
|
||||||
|
val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender
|
||||||
|
|
||||||
|
// check log neighborhood
|
||||||
|
val baseRotation = Rotation.fromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal]
|
||||||
|
|
||||||
|
val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
|
||||||
|
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
|
||||||
|
|
||||||
|
val quadrants = Array(4) { SMALL_RADIUS }.checkNeighbors(ctx, baseRotation, logAxis, 0)
|
||||||
|
val quadrantsTop = Array(4) { SMALL_RADIUS }
|
||||||
|
if (upType == PARALLEL) quadrantsTop.checkNeighbors(ctx, baseRotation, logAxis, 1)
|
||||||
|
val quadrantsBottom = Array(4) { SMALL_RADIUS }
|
||||||
|
if (downType == PARALLEL) quadrantsBottom.checkNeighbors(ctx, baseRotation, logAxis, -1)
|
||||||
|
return ColumnLayerData.SpecialRender(columnTextures, upType, downType, quadrants, quadrantsTop, quadrantsBottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the type of the given quadrant only if the new value is "stronger" (larger ordinal). */
|
||||||
|
inline fun Array<QuadrantType>.upgrade(idx: Int, value: QuadrantType) {
|
||||||
|
if (this[idx].ordinal < value.ordinal) this[idx] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */
|
||||||
|
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 blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0))
|
||||||
|
val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1))
|
||||||
|
val blkW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 0))
|
||||||
|
|
||||||
|
// a solid block on one side will make the 2 neighboring quadrants SQUARE
|
||||||
|
// if there are solid blocks to both sides of a quadrant, it is INVISIBLE
|
||||||
|
if (connectSolids) {
|
||||||
|
if (blkS == SOLID) {
|
||||||
|
upgrade(SW, SQUARE); upgrade(SE, SQUARE)
|
||||||
|
}
|
||||||
|
if (blkE == SOLID) {
|
||||||
|
upgrade(SE, SQUARE); upgrade(NE, SQUARE)
|
||||||
|
}
|
||||||
|
if (blkN == SOLID) {
|
||||||
|
upgrade(NE, SQUARE); upgrade(NW, SQUARE)
|
||||||
|
}
|
||||||
|
if (blkW == SOLID) {
|
||||||
|
upgrade(NW, SQUARE); upgrade(SW, SQUARE)
|
||||||
|
}
|
||||||
|
if (blkS == SOLID && blkE == SOLID) upgrade(SE, INVISIBLE)
|
||||||
|
if (blkN == SOLID && blkE == SOLID) upgrade(NE, INVISIBLE)
|
||||||
|
if (blkN == SOLID && blkW == SOLID) upgrade(NW, INVISIBLE)
|
||||||
|
if (blkS == SOLID && blkW == SOLID) upgrade(SW, INVISIBLE)
|
||||||
|
}
|
||||||
|
val blkSE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 1))
|
||||||
|
val blkNE = ctx.blockType(rotation, logAxis, Int3(1, yOff, -1))
|
||||||
|
val blkNW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, -1))
|
||||||
|
val blkSW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 1))
|
||||||
|
|
||||||
|
if (lenientConnect) {
|
||||||
|
// if the block forms the tip of an L-shape, connect to its neighbor with SQUARE quadrants
|
||||||
|
if (blkE == PARALLEL && (blkSE == PARALLEL || blkNE == PARALLEL)) {
|
||||||
|
upgrade(SE, SQUARE); upgrade(NE, SQUARE)
|
||||||
|
}
|
||||||
|
if (blkN == PARALLEL && (blkNE == PARALLEL || blkNW == PARALLEL)) {
|
||||||
|
upgrade(NE, SQUARE); upgrade(NW, SQUARE)
|
||||||
|
}
|
||||||
|
if (blkW == PARALLEL && (blkNW == PARALLEL || blkSW == PARALLEL)) {
|
||||||
|
upgrade(NW, SQUARE); upgrade(SW, SQUARE)
|
||||||
|
}
|
||||||
|
if (blkS == PARALLEL && (blkSE == PARALLEL || blkSW == PARALLEL)) {
|
||||||
|
upgrade(SW, SQUARE); upgrade(SE, SQUARE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the block forms the middle of an L-shape, or is part of a 2x2 configuration,
|
||||||
|
// connect to its neighbors with SQUARE quadrants, INVISIBLE on the inner corner, and LARGE_RADIUS on the outer corner
|
||||||
|
if (blkN == PARALLEL && blkW == PARALLEL && (lenientConnect || blkNW == PARALLEL)) {
|
||||||
|
upgrade(SE, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(NW, INVISIBLE)
|
||||||
|
}
|
||||||
|
if (blkS == PARALLEL && blkW == PARALLEL && (lenientConnect || blkSW == PARALLEL)) {
|
||||||
|
upgrade(NE, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(SW, INVISIBLE)
|
||||||
|
}
|
||||||
|
if (blkS == PARALLEL && blkE == PARALLEL && (lenientConnect || blkSE == PARALLEL)) {
|
||||||
|
upgrade(NW, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(SE, INVISIBLE)
|
||||||
|
}
|
||||||
|
if (blkN == PARALLEL && blkE == PARALLEL && (lenientConnect || blkNE == PARALLEL)) {
|
||||||
|
upgrade(SW, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(NE, INVISIBLE)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of the block at the given offset in a rotated reference frame.
|
||||||
|
*/
|
||||||
|
fun BlockCtx.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
|
||||||
|
val offsetRot = offset.rotate(rotation)
|
||||||
|
val key = getColumnKey(state(offsetRot))
|
||||||
|
return if (key == null) {
|
||||||
|
if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
|
||||||
|
} else {
|
||||||
|
(key.axis ?: if (BetterFoliage.config.roundLogs.defaultY) Axis.Y else null)?.let {
|
||||||
|
if (it == axis) PARALLEL else PERPENDICULAR
|
||||||
|
} ?: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package mods.betterfoliage.resource.generated
|
||||||
|
|
||||||
|
import mods.betterfoliage.util.*
|
||||||
|
import net.minecraft.resource.ResourceManager
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Short Grass textures from [Blocks.tallgrass] block textures.
|
||||||
|
* The bottom 3/8 of the base texture is chopped off.
|
||||||
|
*
|
||||||
|
* @param[domain] Resource domain of generator
|
||||||
|
*/
|
||||||
|
data class GeneratedGrassSprite(val sprite: Identifier, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) {
|
||||||
|
constructor(sprite: String, isSnowed: Boolean) : this(Identifier(sprite), isSnowed)
|
||||||
|
|
||||||
|
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
|
||||||
|
|
||||||
|
fun draw(resourceManager: ResourceManager): ByteArray {
|
||||||
|
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
|
||||||
|
|
||||||
|
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
val graphics = result.createGraphics()
|
||||||
|
|
||||||
|
val size = baseTexture.width
|
||||||
|
val frames = baseTexture.height / size
|
||||||
|
|
||||||
|
// iterate all frames
|
||||||
|
for (frame in 0 until frames) {
|
||||||
|
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
|
||||||
|
val grassFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
|
||||||
|
// draw bottom half of texture
|
||||||
|
grassFrame.createGraphics().apply {
|
||||||
|
drawImage(baseFrame, 0, 3 * size / 8, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to animated png
|
||||||
|
graphics.drawImage(grassFrame, 0, size * frame, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// blend with white if snowed
|
||||||
|
if (isSnowed) {
|
||||||
|
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
|
||||||
|
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user