Compare commits
16 Commits
kotlin-1.1
...
1.14.4-For
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49d4f8aa31 | ||
|
|
9566ae8341 | ||
|
|
dac7fa0b42 | ||
|
|
c668713051 | ||
|
|
8a82f3772f | ||
|
|
3b728cffcd | ||
|
|
aa91aed58e | ||
|
|
802862f151 | ||
|
|
2252fb3b42 | ||
|
|
4efa831296 | ||
|
|
a3d99c3076 | ||
|
|
c4ee768025 | ||
|
|
b4f18c1e1d | ||
|
|
2a06c18884 | ||
|
|
2ba99f40e7 | ||
|
|
46cbe64328 |
62
build.gradle
62
build.gradle
@@ -1,62 +0,0 @@
|
|||||||
apply plugin: "net.minecraftforge.gradle.forge"
|
|
||||||
apply plugin: 'kotlin'
|
|
||||||
|
|
||||||
archivesBaseName = jarName
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
name = "forge"
|
|
||||||
url = "http://files.minecraftforge.net/maven"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath "net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT"
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
jcenter()
|
|
||||||
maven {
|
|
||||||
name = "shadowfacts"
|
|
||||||
url = "https://maven.shadowfacts.net/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile "net.shadowfacts:Forgelin:$forgelin_version"
|
|
||||||
}
|
|
||||||
|
|
||||||
minecraft {
|
|
||||||
version = mc_version + "-" + forge_version
|
|
||||||
mappings = mcp_mappings
|
|
||||||
runDir = 'run'
|
|
||||||
}
|
|
||||||
processResources {
|
|
||||||
from(sourceSets.main.resources) { exclude 'mcmod.info' }
|
|
||||||
from(sourceSets.main.resources) { include 'mcmod.info' expand 'version':version, 'mcversion':minecraft.version }
|
|
||||||
|
|
||||||
into "${buildDir}/classes/main"
|
|
||||||
}
|
|
||||||
|
|
||||||
def manifestCfg = {
|
|
||||||
attributes "FMLCorePlugin": "mods.betterfoliage.loader.BetterFoliageLoader"
|
|
||||||
attributes "FMLCorePluginContainsFMLMod": "mods.betterfoliage.BetterFoliageMod"
|
|
||||||
attributes "FMLAT": "BetterFoliage_at.cfg"
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
manifest manifestCfg
|
|
||||||
exclude "optifine"
|
|
||||||
}
|
|
||||||
|
|
||||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
|
||||||
classifier = 'sources'
|
|
||||||
manifest manifestCfg
|
|
||||||
from(sourceSets.main.kotlin)
|
|
||||||
from(sourceSets.main.resources) { exclude 'mcmod.info' }
|
|
||||||
from(sourceSets.main.resources) { include 'mcmod.info' expand 'version':version, 'mcversion':minecraft.version }
|
|
||||||
}
|
|
||||||
60
build.gradle.kts
Normal file
60
build.gradle.kts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm").version("1.3.61")
|
||||||
|
id("net.minecraftforge.gradle").version("3.0.157")
|
||||||
|
id("org.spongepowered.mixin").version("0.7-SNAPSHOT")
|
||||||
|
}
|
||||||
|
apply(plugin = "org.spongepowered.mixin")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven("http://files.minecraftforge.net/maven")
|
||||||
|
maven("https://repo.spongepowered.org/maven")
|
||||||
|
maven("https://minecraft.curseforge.com/api/maven")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
"minecraft"("net.minecraftforge:forge:${properties["mcVersion"]}-${properties["forgeVersion"]}")
|
||||||
|
|
||||||
|
"implementation"("kottle:Kottle:${properties["kottleVersion"]}")
|
||||||
|
"implementation"("org.spongepowered:mixin:0.8-SNAPSHOT")
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations["annotationProcessor"].extendsFrom(configurations["implementation"])
|
||||||
|
sourceSets {
|
||||||
|
get("main").ext["refMap"] = "betterfoliage.refmap.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
minecraft {
|
||||||
|
mappings(properties["mappingsChannel"] as String, properties["mappingsVersion"] as String)
|
||||||
|
accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
|
||||||
|
|
||||||
|
runs.create("client") {
|
||||||
|
workingDirectory(file("run"))
|
||||||
|
properties["forge.logging.markers"] = "CORE"
|
||||||
|
properties["forge.logging.console.level"] = "debug"
|
||||||
|
mods.create("betterfoliage") {
|
||||||
|
source(sourceSets["main"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
target.compilations.configureEach {
|
||||||
|
kotlinOptions.freeCompilerArgs += listOf("-Xno-param-assertions", "-Xno-call-assertions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.getByName<Jar>("jar") {
|
||||||
|
archiveName = "BetterFoliage-${project.version}-Forge-${properties["mcVersion"]}.jar"
|
||||||
|
manifest {
|
||||||
|
from(file("src/main/resources/META-INF/MANIFEST.MF"))
|
||||||
|
attributes["Implementation-Version"] = project.version
|
||||||
|
}
|
||||||
|
exclude("net")
|
||||||
|
filesMatching("META-INF/mods.toml") { expand(project.properties) }
|
||||||
|
filesMatching("mcmod.info") { expand(project.properties) }
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx2G
|
||||||
|
org.gradle.daemon=false
|
||||||
|
|
||||||
group = com.github.octarine-noise
|
group = com.github.octarine-noise
|
||||||
jarName = BetterFoliage-MC1.12
|
jarName = BetterFoliage-Forge
|
||||||
|
|
||||||
version = 2.3.1
|
version = 2.5.1
|
||||||
|
|
||||||
mc_version = 1.12.2
|
mcVersion = 1.14.4
|
||||||
forge_version = 14.23.5.2847
|
forgeVersion = 28.2.0
|
||||||
mcp_mappings = stable_39
|
mappingsChannel = snapshot
|
||||||
|
mappingsVersion = 20190719-1.14.3
|
||||||
|
|
||||||
kotlin_version = 1.3.40
|
kottleVersion = 1.4.0
|
||||||
forgelin_version = 1.8.4
|
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
rootProject.name = 'BetterFoliage'
|
|
||||||
|
|
||||||
13
settings.gradle.kts
Normal file
13
settings.gradle.kts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
maven("http://files.minecraftforge.net/maven")
|
||||||
|
maven("https://repo.spongepowered.org/maven")
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
resolutionStrategy {
|
||||||
|
eachPlugin {
|
||||||
|
if (requested.id.let { it.namespace == "net.minecraftforge" && it.name == "gradle"} ) useModule("net.minecraftforge.gradle:ForgeGradle:${requested.version}")
|
||||||
|
if (requested.id.let { it.namespace == "org.spongepowered" && it.name == "mixin"} ) useModule("org.spongepowered:mixingradle:${requested.version}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/main/java/mods/betterfoliage/MixinConnector.java
Normal file
19
src/main/java/mods/betterfoliage/MixinConnector.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package mods.betterfoliage;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixins;
|
||||||
|
import org.spongepowered.asm.mixin.connect.IMixinConnector;
|
||||||
|
|
||||||
|
public class MixinConnector implements IMixinConnector {
|
||||||
|
@Override
|
||||||
|
public void connect() {
|
||||||
|
Mixins.addConfiguration("betterfoliage.common.mixins.json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class.forName("optifine.OptiFineTransformationService");
|
||||||
|
Mixins.addConfiguration("betterfoliage.optifine.mixins.json");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
Mixins.addConfiguration("betterfoliage.vanilla.mixins.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package mods.betterfoliage.loader;
|
|
||||||
|
|
||||||
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@IFMLLoadingPlugin.TransformerExclusions({
|
|
||||||
"mods.betterfoliage.loader",
|
|
||||||
"mods.octarinecore.metaprog",
|
|
||||||
"kotlin"
|
|
||||||
})
|
|
||||||
@IFMLLoadingPlugin.MCVersion("1.12.2")
|
|
||||||
@IFMLLoadingPlugin.SortingIndex(1400)
|
|
||||||
public class BetterFoliageLoader implements IFMLLoadingPlugin {
|
|
||||||
@Override
|
|
||||||
public String[] getASMTransformerClass() {
|
|
||||||
return new String[] { "mods.betterfoliage.loader.BetterFoliageTransformer" };
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getModContainerClass() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSetupClass() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectData(Map<String, Object> data) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAccessTransformerClass() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
29
src/main/java/mods/betterfoliage/mixin/MixinBlock.java
Normal file
29
src/main/java/mods/betterfoliage/mixin/MixinBlock.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.Hooks;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.util.Direction;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.shapes.VoxelShape;
|
||||||
|
import net.minecraft.world.IBlockReader;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixin overriding the {@link VoxelShape} used for the neighbor block in {@link Block}.shouldSideBeRendered().
|
||||||
|
*
|
||||||
|
* This way log blocks can be made to not block rendering, without altering any {@link Block} or
|
||||||
|
* {@link BlockState} properties with potential gameplay ramifications.
|
||||||
|
*/
|
||||||
|
@Mixin(Block.class)
|
||||||
|
public class MixinBlock {
|
||||||
|
private static final String shouldSideBeRendered = "Lnet/minecraft/block/Block;shouldSideBeRendered(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Z";
|
||||||
|
private static final String getVoxelShape = "Lnet/minecraft/block/BlockState;func_215702_a(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
|
||||||
|
|
||||||
|
@Redirect(method = shouldSideBeRendered, at = @At(value = "INVOKE", target = getVoxelShape, ordinal = 1))
|
||||||
|
private static VoxelShape getVoxelShapeOverride(BlockState state, IBlockReader reader, BlockPos pos, Direction dir) {
|
||||||
|
return Hooks.getVoxelShapeOverride(state, reader, pos, dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
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.client.Hooks;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.IBlockReader;
|
||||||
|
import org.spongepowered.asm.mixin.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;func_215703_d(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";
|
||||||
|
private static final String callTo = "Lnet/minecraft/block/Block;func_220080_a(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";
|
||||||
|
|
||||||
|
@Redirect(method = callFrom, at = @At(value = "INVOKE", target = callTo))
|
||||||
|
float getAmbientOcclusionValue(Block block, BlockState state, IBlockReader reader, BlockPos pos) {
|
||||||
|
return Hooks.getAmbientOcclusionLightValueOverride(block.func_220080_a(state, reader, pos), state);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/java/mods/betterfoliage/mixin/MixinChunkRender.java
Normal file
28
src/main/java/mods/betterfoliage/mixin/MixinChunkRender.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.Hooks;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.renderer.BlockRendererDispatcher;
|
||||||
|
import net.minecraft.client.renderer.BufferBuilder;
|
||||||
|
import net.minecraft.client.renderer.chunk.ChunkRender;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.IEnviromentBlockReader;
|
||||||
|
import net.minecraftforge.client.MinecraftForgeClient;
|
||||||
|
import net.minecraftforge.client.model.data.IModelData;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@Mixin(ChunkRender.class)
|
||||||
|
public class MixinChunkRender {
|
||||||
|
|
||||||
|
private static final String rebuildChunk = "rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V";
|
||||||
|
private static final String renderBlock = "Lnet/minecraft/client/renderer/BlockRendererDispatcher;renderBlock(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/IEnviromentBlockReader;Lnet/minecraft/client/renderer/BufferBuilder;Ljava/util/Random;Lnet/minecraftforge/client/model/data/IModelData;)Z";
|
||||||
|
|
||||||
|
@Redirect(method = rebuildChunk, at = @At(value = "INVOKE", target = renderBlock))
|
||||||
|
public boolean renderBlock(BlockRendererDispatcher dispatcher, BlockState state, BlockPos pos, IEnviromentBlockReader reader, BufferBuilder buffer, Random random, IModelData modelData) {
|
||||||
|
return Hooks.renderWorldBlock(dispatcher, state, pos, reader, buffer, random, modelData, MinecraftForgeClient.getRenderLayer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.Hooks;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.renderer.chunk.ChunkRender;
|
||||||
|
import net.minecraft.util.BlockRenderLayer;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
@Mixin(ChunkRender.class)
|
||||||
|
public class MixinChunkRenderVanilla {
|
||||||
|
|
||||||
|
private static final String rebuildChunk = "rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V";
|
||||||
|
private static final String canRenderInLayer = "Lnet/minecraft/block/BlockState;canRenderInLayer(Lnet/minecraft/util/BlockRenderLayer;)Z";
|
||||||
|
|
||||||
|
@Redirect(method = rebuildChunk, at = @At(value = "INVOKE", target = canRenderInLayer))
|
||||||
|
boolean canRenderInLayer(BlockState state, BlockRenderLayer layer) {
|
||||||
|
return Hooks.canRenderInLayerOverride(state, layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/main/java/mods/betterfoliage/mixin/MixinClientWorld.java
Normal file
44
src/main/java/mods/betterfoliage/mixin/MixinClientWorld.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.Hooks;
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.renderer.WorldRenderer;
|
||||||
|
import net.minecraft.client.world.ClientWorld;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.IBlockReader;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@Mixin(ClientWorld.class)
|
||||||
|
public class MixinClientWorld {
|
||||||
|
|
||||||
|
private static final String worldAnimateTick = "Lnet/minecraft/client/world/ClientWorld;animateTick(IIIILjava/util/Random;ZLnet/minecraft/util/math/BlockPos$MutableBlockPos;)V";
|
||||||
|
private static final String blockAnimateTick = "Lnet/minecraft/block/Block;animateTick(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/Random;)V";
|
||||||
|
|
||||||
|
private static final String worldNotify = "Lnet/minecraft/client/world/ClientWorld;notifyBlockUpdate(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V";
|
||||||
|
private static final String rendererNotify = "Lnet/minecraft/client/renderer/WorldRenderer;notifyBlockUpdate(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/BlockState;I)V";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject a callback to call for every random display tick. Used for adding custom particle effects to blocks.
|
||||||
|
*/
|
||||||
|
@Redirect(method = worldAnimateTick, at = @At(value = "INVOKE", target = blockAnimateTick))
|
||||||
|
void onAnimateTick(Block block, BlockState state, World world, BlockPos pos, Random random) {
|
||||||
|
Hooks.onRandomDisplayTick(block, state, world, pos, random);
|
||||||
|
block.animateTick(state, world, pos, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject callback to get notified of client-side blockstate changes.
|
||||||
|
* Used to invalidate caches in the {@link mods.betterfoliage.client.chunk.ChunkOverlayManager}
|
||||||
|
*/
|
||||||
|
@Redirect(method = worldNotify, at = @At(value = "INVOKE", target = rendererNotify))
|
||||||
|
void onClientBlockChanged(WorldRenderer renderer, IBlockReader world, BlockPos pos, BlockState oldState, BlockState newState, int flags) {
|
||||||
|
Hooks.onClientBlockChanged((ClientWorld) world, pos, oldState, newState, flags);
|
||||||
|
renderer.notifyBlockUpdate(world, pos, oldState, newState, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java
Normal file
29
src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage;
|
||||||
|
import net.minecraft.client.renderer.model.ModelBakery;
|
||||||
|
import net.minecraft.client.renderer.texture.AtlasTexture;
|
||||||
|
import net.minecraft.profiler.IProfiler;
|
||||||
|
import net.minecraft.resources.IResourceManager;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
@Mixin(ModelBakery.class)
|
||||||
|
abstract public class MixinModelBakery {
|
||||||
|
|
||||||
|
private static final String processLoading = "processLoading(Lnet/minecraft/profiler/IProfiler;)V";
|
||||||
|
private static final String stitch = "Lnet/minecraft/client/renderer/texture/AtlasTexture;stitch(Lnet/minecraft/resources/IResourceManager;Ljava/lang/Iterable;Lnet/minecraft/profiler/IProfiler;)Lnet/minecraft/client/renderer/texture/AtlasTexture$SheetData;";
|
||||||
|
|
||||||
|
@Redirect(method = processLoading, at = @At(value = "INVOKE", target = stitch))
|
||||||
|
AtlasTexture.SheetData onStitchModelTextures(AtlasTexture atlas, IResourceManager manager, Iterable<ResourceLocation> idList, IProfiler profiler) {
|
||||||
|
return BetterFoliage.INSTANCE.getBlockSprites().finish(
|
||||||
|
atlas.stitch(
|
||||||
|
manager,
|
||||||
|
BetterFoliage.INSTANCE.getBlockSprites().prepare(this, manager, idList, profiler),
|
||||||
|
profiler
|
||||||
|
), profiler
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.Hooks;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.util.Direction;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.shapes.VoxelShape;
|
||||||
|
import net.minecraft.world.IBlockReader;
|
||||||
|
import net.optifine.util.BlockUtils;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
@Mixin(BlockUtils.class)
|
||||||
|
public class MixinOptifineBlockUtils {
|
||||||
|
private static final String shouldSideBeRenderedCached = "shouldSideBeRenderedCached(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;Lnet/optifine/render/RenderEnv;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;)Z";
|
||||||
|
private static final String getVoxelShape = "Lnet/minecraft/block/BlockState;func_215702_a(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
|
||||||
|
|
||||||
|
@SuppressWarnings("UnresolvedMixinReference")
|
||||||
|
@Redirect(method = shouldSideBeRenderedCached, at = @At(value = "INVOKE", target = getVoxelShape, ordinal = 1))
|
||||||
|
private static VoxelShape getVoxelShapeOverride(BlockState state, IBlockReader reader, BlockPos pos, Direction dir) {
|
||||||
|
return Hooks.getVoxelShapeOverride(state, reader, pos, dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.Hooks;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.renderer.chunk.ChunkRender;
|
||||||
|
import net.minecraft.util.BlockRenderLayer;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Coerce;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Slice;
|
||||||
|
|
||||||
|
@Mixin(ChunkRender.class)
|
||||||
|
public class MixinOptifineChunkRender {
|
||||||
|
|
||||||
|
private static final String rebuildChunk = "rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V";
|
||||||
|
private static final String invokeReflector = "Lnet/optifine/reflect/Reflector;callBoolean(Ljava/lang/Object;Lnet/optifine/reflect/ReflectorMethod;[Ljava/lang/Object;)Z";
|
||||||
|
private static final String forgeBlockCanRender = "Lnet/minecraft/client/renderer/chunk/ChunkRender;FORGE_BLOCK_CAN_RENDER_IN_LAYER:Z";
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = rebuildChunk,
|
||||||
|
at = @At(value = "INVOKE", target = invokeReflector),
|
||||||
|
slice = @Slice(
|
||||||
|
from = @At(value = "FIELD", target = forgeBlockCanRender)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@SuppressWarnings("UnresolvedMixinReference")
|
||||||
|
boolean canRenderInLayer(Object state, @Coerce Object reflector, Object[] layer) {
|
||||||
|
return Hooks.canRenderInLayerOverride((BlockState) state, (BlockRenderLayer) layer[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage;
|
||||||
|
import mods.octarinecore.client.resource.AsnycSpriteProviderManager;
|
||||||
|
import net.minecraft.client.particle.ParticleManager;
|
||||||
|
import net.minecraft.client.renderer.texture.AtlasTexture;
|
||||||
|
import net.minecraft.profiler.IProfiler;
|
||||||
|
import net.minecraft.resources.IResourceManager;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
@Mixin(ParticleManager.class)
|
||||||
|
public class MixinParticleManager {
|
||||||
|
|
||||||
|
private static final String reload = "reload(Lnet/minecraft/resources/IFutureReloadListener$IStage;Lnet/minecraft/resources/IResourceManager;Lnet/minecraft/profiler/IProfiler;Lnet/minecraft/profiler/IProfiler;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;";
|
||||||
|
private static final String stitch = "Lnet/minecraft/client/renderer/texture/AtlasTexture;stitch(Lnet/minecraft/resources/IResourceManager;Ljava/lang/Iterable;Lnet/minecraft/profiler/IProfiler;)Lnet/minecraft/client/renderer/texture/AtlasTexture$SheetData;";
|
||||||
|
|
||||||
|
// ewww :S
|
||||||
|
@SuppressWarnings("UnresolvedMixinReference")
|
||||||
|
@Redirect(method = "*", at = @At(value = "INVOKE", target = stitch))
|
||||||
|
AtlasTexture.SheetData onStitchModelTextures(AtlasTexture atlas, IResourceManager manager, Iterable<ResourceLocation> idList, IProfiler profiler) {
|
||||||
|
return BetterFoliage.INSTANCE.getParticleSprites().finish(
|
||||||
|
atlas.stitch(
|
||||||
|
manager,
|
||||||
|
BetterFoliage.INSTANCE.getParticleSprites().prepare(this, manager, idList, profiler),
|
||||||
|
profiler
|
||||||
|
), profiler
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package mods.betterfoliage.mixin;
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.integration.ShadersModIntegration;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.renderer.BlockModelRenderer;
|
||||||
|
import net.minecraft.client.renderer.BufferBuilder;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.IEnviromentBlockReader;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||||
|
|
||||||
|
@Mixin(BlockModelRenderer.class)
|
||||||
|
public class MixinShadersBlockModelRenderer {
|
||||||
|
|
||||||
|
private static final String renderModel = "renderModel(Lnet/minecraft/world/IEnviromentBlockReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/client/renderer/BufferBuilder;ZLjava/util/Random;JLnet/minecraftforge/client/model/data/IModelData;)Z";
|
||||||
|
private static final String pushEntity = "Lnet/optifine/shaders/SVertexBuilder;pushEntity(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/IEnviromentBlockReader;Lnet/minecraft/client/renderer/BufferBuilder;)V";
|
||||||
|
|
||||||
|
@SuppressWarnings("UnresolvedMixinReference")
|
||||||
|
@ModifyArg(method = renderModel, at = @At(value = "INVOKE", target = pushEntity), remap = false)
|
||||||
|
BlockState overrideBlockState(BlockState state, BlockPos pos, IEnviromentBlockReader world, BufferBuilder buffer) {
|
||||||
|
return ShadersModIntegration.getBlockStateOverride(state, world, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/main/java/net/optifine/util/BlockUtils.java
Normal file
5
src/main/java/net/optifine/util/BlockUtils.java
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package net.optifine.util;
|
||||||
|
|
||||||
|
public class BlockUtils {
|
||||||
|
// whyyyy?
|
||||||
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package optifine;
|
|
||||||
|
|
||||||
import net.minecraft.launchwrapper.IClassTransformer;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLClassLoader;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipFile;
|
|
||||||
|
|
||||||
public class OptifineTransformerDevWrapper implements IClassTransformer {
|
|
||||||
|
|
||||||
public static String OPTIFINE_CLASSNAME = "optifine/OptiFineClassTransformer.class";
|
|
||||||
private ZipFile ofZip = null;
|
|
||||||
|
|
||||||
public OptifineTransformerDevWrapper() {
|
|
||||||
Stream<URL> loaderSources = Arrays.stream(((URLClassLoader) getClass().getClassLoader()).getURLs());
|
|
||||||
Optional<URL> optifineURL = loaderSources.filter(this::isOptifineJar).findFirst();
|
|
||||||
optifineURL.ifPresent(url -> ofZip = getZip(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ZipFile getZip(URL url) {
|
|
||||||
try {
|
|
||||||
return new ZipFile(new File(url.toURI()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isOptifineJar(URL url) {
|
|
||||||
ZipFile zip = getZip(url);
|
|
||||||
return zip != null && zip.getEntry(OPTIFINE_CLASSNAME) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] transform(String name, String transformedName, byte[] basicClass) {
|
|
||||||
if (ofZip == null) return basicClass;
|
|
||||||
ZipEntry replacement = ofZip.getEntry(name.replace(".", "/") + ".class");
|
|
||||||
if (replacement == null) return basicClass;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return readAll(ofZip.getInputStream(replacement));
|
|
||||||
} catch (IOException e) {
|
|
||||||
return basicClass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] readAll(InputStream is) throws IOException {
|
|
||||||
byte[] buf = new byte[4096];
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
int len;
|
|
||||||
do {
|
|
||||||
len = is.read(buf, 0, 4096);
|
|
||||||
if (len > 0) bos.write(buf, 0, len);
|
|
||||||
} while (len > -1);
|
|
||||||
is.close();
|
|
||||||
return bos.toByteArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package optifine;
|
|
||||||
|
|
||||||
import net.minecraft.launchwrapper.ITweaker;
|
|
||||||
import net.minecraft.launchwrapper.LaunchClassLoader;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class OptifineTweakerDevWrapper implements ITweaker {
|
|
||||||
@Override
|
|
||||||
public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectIntoClassLoader(LaunchClassLoader classLoader) {
|
|
||||||
classLoader.registerTransformer("optifine.OptifineTransformerDevWrapper");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getLaunchTarget() {
|
|
||||||
return "net.minecraft.client.main.Main";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getLaunchArguments() {
|
|
||||||
return new String[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
76
src/main/kotlin/mods/betterfoliage/BetterFoliage.kt
Normal file
76
src/main/kotlin/mods/betterfoliage/BetterFoliage.kt
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package mods.betterfoliage
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.Client
|
||||||
|
import mods.betterfoliage.client.render.RisingSoulTextures
|
||||||
|
import mods.octarinecore.client.gui.textComponent
|
||||||
|
import mods.octarinecore.client.resource.AsnycSpriteProviderManager
|
||||||
|
import mods.octarinecore.client.resource.AsyncSpriteProvider
|
||||||
|
import mods.octarinecore.client.resource.Atlas
|
||||||
|
import mods.octarinecore.client.resource.GeneratedBlockTexturePack
|
||||||
|
import net.minecraft.block.BlockState
|
||||||
|
import net.minecraft.client.Minecraft
|
||||||
|
import net.minecraft.client.particle.ParticleManager
|
||||||
|
import net.minecraft.client.renderer.model.ModelBakery
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.minecraft.util.text.TextFormatting
|
||||||
|
import net.minecraft.util.text.TranslationTextComponent
|
||||||
|
import net.minecraftforge.registries.ForgeRegistries
|
||||||
|
import 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 {
|
||||||
|
var log = LogManager.getLogger("BetterFoliage")
|
||||||
|
var logDetail = SimpleLogger(
|
||||||
|
"BetterFoliage",
|
||||||
|
Level.DEBUG,
|
||||||
|
false, false, true, false,
|
||||||
|
"yyyy-MM-dd HH:mm:ss",
|
||||||
|
null,
|
||||||
|
PropertiesUtil(Properties()),
|
||||||
|
PrintStream(File("logs/betterfoliage.log").apply {
|
||||||
|
parentFile.mkdirs()
|
||||||
|
if (!exists()) createNewFile()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
val blockSprites = AsnycSpriteProviderManager<ModelBakery>("bf-blocks-extra")
|
||||||
|
val particleSprites = AsnycSpriteProviderManager<ParticleManager>("bf-particles-extra")
|
||||||
|
val asyncPack = GeneratedBlockTexturePack("bf_gen", "Better Foliage generated assets", logDetail)
|
||||||
|
|
||||||
|
fun getSpriteManager(atlas: Atlas) = when(atlas) {
|
||||||
|
Atlas.BLOCKS -> blockSprites
|
||||||
|
Atlas.PARTICLES -> particleSprites
|
||||||
|
} as AsnycSpriteProviderManager<Any>
|
||||||
|
|
||||||
|
init {
|
||||||
|
blockSprites.providers.add(asyncPack)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun log(level: Level, msg: String) {
|
||||||
|
log.log(level, "[BetterFoliage] $msg")
|
||||||
|
logDetail.log(level, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logDetail(msg: String) {
|
||||||
|
logDetail.log(Level.DEBUG, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logRenderError(state: BlockState, location: BlockPos) {
|
||||||
|
if (state in Client.suppressRenderErrors) return
|
||||||
|
Client.suppressRenderErrors.add(state)
|
||||||
|
|
||||||
|
val blockName = ForgeRegistries.BLOCKS.getKey(state.block).toString()
|
||||||
|
val blockLoc = "${location.x},${location.y},${location.z}"
|
||||||
|
Minecraft.getInstance().ingameGUI.chatGUI.printChatMessage(TranslationTextComponent(
|
||||||
|
"betterfoliage.rendererror",
|
||||||
|
textComponent(blockName, TextFormatting.GOLD),
|
||||||
|
textComponent(blockLoc, TextFormatting.GOLD)
|
||||||
|
))
|
||||||
|
logDetail("Error rendering block $state at $blockLoc")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,78 +1,35 @@
|
|||||||
package mods.betterfoliage
|
package mods.betterfoliage
|
||||||
|
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.client.Client
|
||||||
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.betterfoliage.client.isAfterPostInit
|
import mods.octarinecore.client.resource.AsnycSpriteProviderManager
|
||||||
import net.minecraftforge.common.config.Configuration
|
import mods.octarinecore.client.resource.GeneratedBlockTexturePack
|
||||||
import net.minecraftforge.fml.common.FMLCommonHandler
|
import net.alexwells.kottle.FMLKotlinModLoadingContext
|
||||||
|
import net.minecraft.client.Minecraft
|
||||||
|
import net.minecraft.client.particle.ParticleManager
|
||||||
|
import net.minecraft.client.renderer.model.ModelBakery
|
||||||
|
import net.minecraftforge.fml.ModLoadingContext
|
||||||
import net.minecraftforge.fml.common.Mod
|
import net.minecraftforge.fml.common.Mod
|
||||||
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent
|
import net.minecraftforge.fml.config.ModConfig
|
||||||
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent
|
|
||||||
import net.minecraftforge.fml.common.network.NetworkCheckHandler
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import org.apache.logging.log4j.Level.DEBUG
|
import org.apache.logging.log4j.Level.DEBUG
|
||||||
import org.apache.logging.log4j.Level.INFO
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
import org.apache.logging.log4j.simple.SimpleLogger
|
import org.apache.logging.log4j.simple.SimpleLogger
|
||||||
import org.apache.logging.log4j.util.PropertiesUtil
|
import org.apache.logging.log4j.util.PropertiesUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Mod(
|
@Mod(BetterFoliageMod.MOD_ID)
|
||||||
modid = BetterFoliageMod.MOD_ID,
|
|
||||||
name = BetterFoliageMod.MOD_NAME,
|
|
||||||
acceptedMinecraftVersions = BetterFoliageMod.MC_VERSIONS,
|
|
||||||
guiFactory = BetterFoliageMod.GUI_FACTORY,
|
|
||||||
dependencies = "after:forgelin",
|
|
||||||
modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter",
|
|
||||||
clientSideOnly = true
|
|
||||||
)
|
|
||||||
object BetterFoliageMod {
|
object BetterFoliageMod {
|
||||||
|
|
||||||
const val MOD_ID = "betterfoliage"
|
const val MOD_ID = "betterfoliage"
|
||||||
const val MOD_NAME = "Better Foliage"
|
|
||||||
const val DOMAIN = "betterfoliage"
|
|
||||||
const val LEGACY_DOMAIN = "bettergrassandleaves"
|
|
||||||
const val MC_VERSIONS = "[1.12]"
|
|
||||||
const val GUI_FACTORY = "mods.betterfoliage.client.gui.ConfigGuiFactory"
|
|
||||||
|
|
||||||
lateinit var log: Logger
|
val bus = FMLKotlinModLoadingContext.get().modEventBus
|
||||||
lateinit var logDetail: Logger
|
|
||||||
|
|
||||||
var config: Configuration? = null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// inject pack into default list at construction time to get domains enumerated
|
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.build())
|
||||||
// there's no 2nd resource reload pass anymore
|
Minecraft.getInstance().resourcePackList.addPackFinder(BetterFoliage.asyncPack.finder)
|
||||||
Client.generatorPack.inject()
|
bus.register(BlockConfig)
|
||||||
}
|
|
||||||
|
|
||||||
@Mod.EventHandler
|
|
||||||
fun preInit(event: FMLPreInitializationEvent) {
|
|
||||||
log = event.modLog
|
|
||||||
val logDetailFile = File(event.modConfigurationDirectory.parentFile, "logs/betterfoliage.log").apply {
|
|
||||||
parentFile.mkdirs()
|
|
||||||
if (!exists()) createNewFile()
|
|
||||||
}
|
|
||||||
logDetail = SimpleLogger(
|
|
||||||
"BetterFoliage",
|
|
||||||
DEBUG,
|
|
||||||
false, false, true, false,
|
|
||||||
"yyyy-MM-dd HH:mm:ss",
|
|
||||||
null,
|
|
||||||
PropertiesUtil(Properties()),
|
|
||||||
PrintStream(logDetailFile)
|
|
||||||
)
|
|
||||||
config = Configuration(event.suggestedConfigurationFile, null, true)
|
|
||||||
|
|
||||||
Config.attach(config!!)
|
|
||||||
Client.init()
|
Client.init()
|
||||||
Client.log(INFO, "BetterFoliage initialized")
|
|
||||||
isAfterPostInit = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Mod is cosmetic only, always allow connection. */
|
|
||||||
@NetworkCheckHandler
|
|
||||||
fun checkVersion(mods: Map<String, String>, side: Side) = true
|
|
||||||
}
|
}
|
||||||
@@ -2,50 +2,32 @@ package mods.betterfoliage.client
|
|||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
||||||
import mods.betterfoliage.client.gui.ConfigGuiFactory
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.client.integration.*
|
import mods.betterfoliage.client.integration.*
|
||||||
import mods.betterfoliage.client.render.*
|
import mods.betterfoliage.client.render.*
|
||||||
import mods.betterfoliage.client.texture.*
|
import mods.betterfoliage.client.texture.AsyncGrassDiscovery
|
||||||
import mods.octarinecore.client.KeyHandler
|
import mods.betterfoliage.client.texture.AsyncLeafDiscovery
|
||||||
|
import mods.betterfoliage.client.texture.LeafParticleRegistry
|
||||||
import mods.octarinecore.client.gui.textComponent
|
import mods.octarinecore.client.gui.textComponent
|
||||||
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
|
import mods.octarinecore.client.render.RenderDecorator
|
||||||
import mods.octarinecore.client.resource.CenteringTextureGenerator
|
import mods.octarinecore.client.resource.IConfigChangeListener
|
||||||
import mods.octarinecore.client.resource.GeneratorPack
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.block.Block
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.Minecraft
|
import net.minecraft.client.Minecraft
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.util.text.TextComponentTranslation
|
|
||||||
import net.minecraft.util.text.TextFormatting
|
import net.minecraft.util.text.TextFormatting
|
||||||
import net.minecraftforge.fml.client.FMLClientHandler
|
import net.minecraft.util.text.TranslationTextComponent
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
import net.minecraftforge.registries.ForgeRegistries
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
import org.apache.logging.log4j.Level
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object responsible for initializing (and holding a reference to) all the infrastructure of the mod
|
* Object responsible for initializing (and holding a reference to) all the infrastructure of the mod
|
||||||
* except for the call hooks.
|
* except for the call hooks.
|
||||||
*
|
|
||||||
* This and all other singletons are annotated [SideOnly] to avoid someone accidentally partially
|
|
||||||
* initializing the mod on a server environment.
|
|
||||||
*/
|
*/
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object Client {
|
object Client {
|
||||||
|
var renderers= emptyList<RenderDecorator>()
|
||||||
|
var configListeners = emptyList<IConfigChangeListener>()
|
||||||
|
|
||||||
lateinit var renderers: List<AbstractBlockRenderingHandler>
|
val suppressRenderErrors = mutableSetOf<BlockState>()
|
||||||
val suppressRenderErrors = mutableSetOf<IBlockState>()
|
|
||||||
|
|
||||||
// texture generation stuff
|
|
||||||
val genGrass = GrassGenerator("bf_gen_grass")
|
|
||||||
val genLeaves = LeafGenerator("bf_gen_leaves")
|
|
||||||
val genReeds = CenteringTextureGenerator("bf_gen_reeds", 1, 2)
|
|
||||||
|
|
||||||
val generatorPack = GeneratorPack(
|
|
||||||
"Better Foliage generated",
|
|
||||||
genGrass,
|
|
||||||
genLeaves,
|
|
||||||
genReeds
|
|
||||||
)
|
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
// init renderers
|
// init renderers
|
||||||
@@ -66,8 +48,7 @@ object Client {
|
|||||||
|
|
||||||
// init other singletons
|
// init other singletons
|
||||||
val singletons = listOf(
|
val singletons = listOf(
|
||||||
StandardCactusRegistry,
|
BlockConfig,
|
||||||
LeafParticleRegistry,
|
|
||||||
ChunkOverlayManager,
|
ChunkOverlayManager,
|
||||||
LeafWindTracker,
|
LeafWindTracker,
|
||||||
RisingSoulTextures
|
RisingSoulTextures
|
||||||
@@ -76,46 +57,22 @@ object Client {
|
|||||||
// init mod integrations
|
// init mod integrations
|
||||||
val integrations = listOf(
|
val integrations = listOf(
|
||||||
ShadersModIntegration,
|
ShadersModIntegration,
|
||||||
OptifineCustomColors,
|
OptifineCustomColors
|
||||||
ForestryIntegration,
|
// ForestryIntegration,
|
||||||
IC2RubberIntegration,
|
// IC2RubberIntegration,
|
||||||
TechRebornRubberIntegration
|
// TechRebornRubberIntegration
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LeafParticleRegistry.init()
|
||||||
|
|
||||||
// add basic block support instances as last
|
// add basic block support instances as last
|
||||||
GrassRegistry.addRegistry(StandardGrassRegistry)
|
AsyncLeafDiscovery.init()
|
||||||
LeafRegistry.addRegistry(StandardLeafRegistry)
|
AsyncGrassDiscovery.init()
|
||||||
LogRegistry.addRegistry(StandardLogRegistry)
|
AsyncLogDiscovery.init()
|
||||||
|
AsyncCactusDiscovery.init()
|
||||||
|
|
||||||
// init config hotkey
|
configListeners = listOf(renderers, singletons, integrations).flatten().filterIsInstance<IConfigChangeListener>()
|
||||||
val configKey = KeyHandler(BetterFoliageMod.MOD_NAME, 66, "key.betterfoliage.gui") {
|
configListeners.forEach { it.onConfigChange() }
|
||||||
FMLClientHandler.instance().showGuiScreen(
|
|
||||||
ConfigGuiFactory.createBFConfigGui(Minecraft.getMinecraft().currentScreen)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun log(level: Level, msg: String) {
|
|
||||||
BetterFoliageMod.log.log(level, "[BetterFoliage] $msg")
|
|
||||||
BetterFoliageMod.logDetail.log(level, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun logDetail(msg: String) {
|
|
||||||
BetterFoliageMod.logDetail.log(Level.DEBUG, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun logRenderError(state: IBlockState, location: BlockPos) {
|
|
||||||
if (state in suppressRenderErrors) return
|
|
||||||
suppressRenderErrors.add(state)
|
|
||||||
|
|
||||||
val blockName = Block.REGISTRY.getNameForObject(state.block).toString()
|
|
||||||
val blockLoc = "${location.x},${location.y},${location.z}"
|
|
||||||
Minecraft.getMinecraft().ingameGUI.chatGUI.printChatMessage(TextComponentTranslation(
|
|
||||||
"betterfoliage.rendererror",
|
|
||||||
textComponent(blockName, TextFormatting.GOLD),
|
|
||||||
textComponent(blockLoc, TextFormatting.GOLD)
|
|
||||||
))
|
|
||||||
logDetail("Error rendering block $state at $blockLoc")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,48 @@
|
|||||||
@file:JvmName("Hooks")
|
@file:JvmName("Hooks")
|
||||||
@file:SideOnly(Side.CLIENT)
|
|
||||||
package mods.betterfoliage.client
|
package mods.betterfoliage.client
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
||||||
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.betterfoliage.client.render.*
|
import mods.betterfoliage.client.render.*
|
||||||
import mods.betterfoliage.loader.Refs
|
import mods.octarinecore.ThreadLocalDelegate
|
||||||
import mods.octarinecore.client.render.blockContext
|
import mods.octarinecore.client.render.*
|
||||||
import mods.octarinecore.client.resource.LoadModelDataEvent
|
import mods.octarinecore.client.render.lighting.DefaultLightingCtx
|
||||||
import mods.octarinecore.common.plus
|
import mods.octarinecore.common.plus
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
|
||||||
import net.minecraft.block.Block
|
import net.minecraft.block.Block
|
||||||
import net.minecraft.block.state.IBlockState
|
import net.minecraft.block.BlockState
|
||||||
|
import net.minecraft.block.Blocks
|
||||||
import net.minecraft.client.Minecraft
|
import net.minecraft.client.Minecraft
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.client.renderer.BlockRendererDispatcher
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.client.renderer.BufferBuilder
|
||||||
import net.minecraft.init.Blocks
|
import net.minecraft.client.world.ClientWorld
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import net.minecraft.util.BlockRenderLayer
|
||||||
import net.minecraft.util.BlockRenderLayer.CUTOUT
|
import net.minecraft.util.BlockRenderLayer.CUTOUT
|
||||||
import net.minecraft.util.BlockRenderLayer.CUTOUT_MIPPED
|
import net.minecraft.util.BlockRenderLayer.CUTOUT_MIPPED
|
||||||
import net.minecraft.util.EnumFacing
|
import net.minecraft.util.Direction
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.world.IBlockAccess
|
import net.minecraft.util.math.shapes.VoxelShape
|
||||||
|
import net.minecraft.util.math.shapes.VoxelShapes
|
||||||
|
import net.minecraft.world.IBlockReader
|
||||||
|
import net.minecraft.world.IEnviromentBlockReader
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
import net.minecraftforge.client.model.ModelLoader
|
import net.minecraftforge.client.model.data.IModelData
|
||||||
import net.minecraftforge.common.MinecraftForge
|
import java.util.*
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
|
|
||||||
var isAfterPostInit = false
|
fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float {
|
||||||
val isOptifinePresent = allAvailable(Refs.OptifineClassTransformer)
|
if (Config.enabled && Config.roundLogs.enabled && BlockConfig.logBlocks.matchesClass(state.block)) return Config.roundLogs.dimming.toFloat();
|
||||||
|
return original
|
||||||
fun doesSideBlockRenderingOverride(original: Boolean, blockAccess: IBlockAccess, pos: BlockPos, side: EnumFacing): Boolean {
|
|
||||||
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(blockAccess.getBlockState(pos).block));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isOpaqueCubeOverride(original: Boolean, state: IBlockState): Boolean {
|
fun getUseNeighborBrightnessOverride(original: Boolean, state: BlockState): Boolean {
|
||||||
// caution: blocks are initialized and the method called during startup
|
return original || (Config.enabled && Config.roundLogs.enabled && BlockConfig.logBlocks.matchesClass(state.block));
|
||||||
if (!isAfterPostInit) return original
|
|
||||||
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAmbientOcclusionLightValueOverride(original: Float, state: IBlockState): Float {
|
fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: BlockState, newState: BlockState, flags: Int) {
|
||||||
if (Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block)) return Config.roundLogs.dimming;
|
ChunkOverlayManager.onBlockChange(worldClient, pos)
|
||||||
return original;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUseNeighborBrightnessOverride(original: Boolean, state: IBlockState): Boolean {
|
fun onRandomDisplayTick(block: Block, state: BlockState, world: World, pos: BlockPos, random: Random) {
|
||||||
return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block));
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) {
|
|
||||||
if (Config.enabled &&
|
if (Config.enabled &&
|
||||||
Config.risingSoul.enabled &&
|
Config.risingSoul.enabled &&
|
||||||
state.block == Blocks.SOUL_SAND &&
|
state.block == Blocks.SOUL_SAND &&
|
||||||
@@ -60,42 +53,60 @@ fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) {
|
|||||||
|
|
||||||
if (Config.enabled &&
|
if (Config.enabled &&
|
||||||
Config.fallingLeaves.enabled &&
|
Config.fallingLeaves.enabled &&
|
||||||
Config.blocks.leavesClasses.matchesClass(state.block) &&
|
BlockConfig.leafBlocks.matchesClass(state.block) &&
|
||||||
world.isAirBlock(pos + down1) &&
|
world.isAirBlock(pos + down1) &&
|
||||||
Math.random() < Config.fallingLeaves.chance) {
|
Math.random() < Config.fallingLeaves.chance) {
|
||||||
EntityFallingLeavesFX(world, pos).addIfValid()
|
EntityFallingLeavesFX(world, pos).addIfValid()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAfterLoadModelDefinitions(loader: ModelLoader) {
|
fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos, dir: Direction): VoxelShape {
|
||||||
MinecraftForge.EVENT_BUS.post(LoadModelDataEvent(loader))
|
if (LogRegistry[state, reader, pos] != null) return VoxelShapes.empty()
|
||||||
|
return state.func_215702_a(reader, pos, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val lightingCtx by ThreadLocalDelegate { DefaultLightingCtx(BasicBlockCtx(NonNullWorld, BlockPos.ZERO)) }
|
||||||
fun renderWorldBlock(dispatcher: BlockRendererDispatcher,
|
fun renderWorldBlock(dispatcher: BlockRendererDispatcher,
|
||||||
state: IBlockState,
|
state: BlockState,
|
||||||
pos: BlockPos,
|
pos: BlockPos,
|
||||||
blockAccess: IBlockAccess,
|
reader: IEnviromentBlockReader,
|
||||||
worldRenderer: BufferBuilder,
|
buffer: BufferBuilder,
|
||||||
|
random: Random,
|
||||||
|
modelData: IModelData,
|
||||||
layer: BlockRenderLayer
|
layer: BlockRenderLayer
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
// build context
|
||||||
|
val blockCtx = CachedBlockCtx(reader, pos)
|
||||||
|
val renderCtx = RenderCtx(dispatcher, buffer, layer, random, modelData)
|
||||||
|
lightingCtx.reset(blockCtx)
|
||||||
|
val combinedCtx = CombinedContext(blockCtx, renderCtx, lightingCtx)
|
||||||
|
|
||||||
|
// loop render decorators
|
||||||
val doBaseRender = state.canRenderInLayer(layer) || (layer == targetCutoutLayer && state.canRenderInLayer(otherCutoutLayer))
|
val doBaseRender = state.canRenderInLayer(layer) || (layer == targetCutoutLayer && state.canRenderInLayer(otherCutoutLayer))
|
||||||
blockContext.let { ctx ->
|
Client.renderers.forEach { renderer ->
|
||||||
ctx.set(blockAccess, pos)
|
if (renderer.isEligible(combinedCtx)) {
|
||||||
Client.renderers.forEach { renderer ->
|
// render on the block's default layer
|
||||||
if (renderer.isEligible(ctx)) {
|
// also render on the cutout layer if the renderer requires it
|
||||||
// render on the block's default layer
|
|
||||||
// also render on the cutout layer if the renderer requires it
|
val doCutoutRender = renderer.renderOnCutout && layer == targetCutoutLayer
|
||||||
if (doBaseRender || (renderer.addToCutout && layer == targetCutoutLayer)) {
|
val stopRender = renderer.onlyOnCutout && !layer.isCutout
|
||||||
return renderer.render(ctx, dispatcher, worldRenderer, layer)
|
|
||||||
}
|
if ((doBaseRender || doCutoutRender) && !stopRender) {
|
||||||
|
renderer.render(combinedCtx)
|
||||||
|
return combinedCtx.hasRendered
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (doBaseRender) dispatcher.renderBlock(state, pos, blockAccess, worldRenderer) else false
|
// no render decorators have taken on this block, proceed to normal rendering
|
||||||
|
combinedCtx.render()
|
||||||
|
return combinedCtx.hasRendered
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canRenderBlockInLayer(block: Block, state: IBlockState, layer: BlockRenderLayer) = block.canRenderInLayer(state, layer) || layer == targetCutoutLayer
|
fun canRenderInLayerOverride(state: BlockState, layer: BlockRenderLayer) = state.canRenderInLayer(layer) || layer == targetCutoutLayer
|
||||||
|
|
||||||
val targetCutoutLayer: BlockRenderLayer get() = if (Minecraft.getMinecraft().gameSettings.mipmapLevels > 0) CUTOUT_MIPPED else CUTOUT
|
fun canRenderInLayerOverrideOptifine(state: BlockState, optifineReflector: Any?, layerArray: Array<Any>) =
|
||||||
val otherCutoutLayer: BlockRenderLayer get() = if (Minecraft.getMinecraft().gameSettings.mipmapLevels > 0) CUTOUT else CUTOUT_MIPPED
|
canRenderInLayerOverride(state, layerArray[0] as BlockRenderLayer)
|
||||||
|
|
||||||
|
val targetCutoutLayer: BlockRenderLayer get() = if (Minecraft.getInstance().gameSettings.mipmapLevels > 0) CUTOUT_MIPPED else CUTOUT
|
||||||
|
val otherCutoutLayer: BlockRenderLayer get() = if (Minecraft.getInstance().gameSettings.mipmapLevels > 0) CUTOUT else CUTOUT_MIPPED
|
||||||
|
|||||||
@@ -1,57 +1,71 @@
|
|||||||
package mods.betterfoliage.client.chunk
|
package mods.betterfoliage.client.chunk
|
||||||
|
|
||||||
import mods.betterfoliage.client.Client
|
import mods.octarinecore.ChunkCacheOF
|
||||||
import net.minecraft.block.state.IBlockState
|
import mods.octarinecore.client.render.BlockCtx
|
||||||
import net.minecraft.client.multiplayer.WorldClient
|
import mods.octarinecore.metaprog.get
|
||||||
import net.minecraft.entity.Entity
|
import mods.octarinecore.metaprog.isInstance
|
||||||
import net.minecraft.entity.player.EntityPlayer
|
import net.minecraft.client.renderer.chunk.ChunkRenderCache
|
||||||
import net.minecraft.util.SoundCategory
|
import net.minecraft.client.world.ClientWorld
|
||||||
import net.minecraft.util.SoundEvent
|
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.util.math.ChunkPos
|
import net.minecraft.util.math.ChunkPos
|
||||||
import net.minecraft.world.IBlockAccess
|
import net.minecraft.world.IEnviromentBlockReader
|
||||||
import net.minecraft.world.IWorldEventListener
|
import net.minecraft.world.IWorldReader
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.dimension.DimensionType
|
||||||
import net.minecraft.world.chunk.EmptyChunk
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
import net.minecraftforge.common.MinecraftForge
|
||||||
import net.minecraftforge.event.world.ChunkEvent
|
import net.minecraftforge.event.world.ChunkEvent
|
||||||
import net.minecraftforge.event.world.WorldEvent
|
import net.minecraftforge.event.world.WorldEvent
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||||
import org.apache.logging.log4j.Level
|
import java.util.*
|
||||||
|
import kotlin.collections.List
|
||||||
|
import kotlin.collections.MutableMap
|
||||||
|
import kotlin.collections.associateWith
|
||||||
|
import kotlin.collections.forEach
|
||||||
|
import kotlin.collections.mutableListOf
|
||||||
|
import kotlin.collections.mutableMapOf
|
||||||
|
import kotlin.collections.set
|
||||||
|
|
||||||
|
val IEnviromentBlockReader.dimType: DimensionType get() = when {
|
||||||
|
this is IWorldReader -> dimension.type
|
||||||
|
this is ChunkRenderCache -> world.dimension.type
|
||||||
|
this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache].world.dimension.type
|
||||||
|
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
|
* Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position
|
||||||
*/
|
*/
|
||||||
interface ChunkOverlayLayer<T> {
|
interface ChunkOverlayLayer<T> {
|
||||||
abstract fun calculate(world: IBlockAccess, pos: BlockPos): T
|
fun calculate(ctx: BlockCtx): T
|
||||||
abstract fun onBlockUpdate(world: IBlockAccess, pos: BlockPos)
|
fun onBlockUpdate(world: IEnviromentBlockReader, pos: BlockPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
|
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
|
||||||
*/
|
*/
|
||||||
object ChunkOverlayManager : IBlockUpdateListener {
|
object ChunkOverlayManager {
|
||||||
|
|
||||||
|
var tempCounter = 0
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Client.log(Level.INFO, "Initializing client overlay manager")
|
|
||||||
MinecraftForge.EVENT_BUS.register(this)
|
MinecraftForge.EVENT_BUS.register(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
val chunkData = mutableMapOf<ChunkPos, ChunkOverlayData>()
|
val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>()
|
||||||
val layers = mutableListOf<ChunkOverlayLayer<*>>()
|
val layers = mutableListOf<ChunkOverlayLayer<*>>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the overlay data for a given layer and position
|
* Get the overlay data for a given layer and position
|
||||||
*
|
*
|
||||||
* @param layer Overlay layer to query
|
* @param layer Overlay layer to query
|
||||||
* @param world World to use if calculation of overlay value is necessary
|
* @param reader World to use if calculation of overlay value is necessary
|
||||||
* @param pos Block position
|
* @param pos Block position
|
||||||
*/
|
*/
|
||||||
fun <T> get(layer: ChunkOverlayLayer<T>, world: IBlockAccess, pos: BlockPos): T? {
|
fun <T> get(layer: ChunkOverlayLayer<T>, ctx: BlockCtx): T? {
|
||||||
val data = chunkData[ChunkPos(pos)] ?: return null
|
val data = chunkData[ctx.world.dimType]?.get(ChunkPos(ctx.pos)) ?: return null
|
||||||
data.get(layer, pos).let { value ->
|
data.get(layer, ctx.pos).let { value ->
|
||||||
if (value !== ChunkOverlayData.UNCALCULATED) return value
|
if (value !== ChunkOverlayData.UNCALCULATED) return value
|
||||||
val newValue = layer.calculate(world, pos)
|
val newValue = layer.calculate(ctx)
|
||||||
data.set(layer, pos, newValue)
|
data.set(layer, ctx.pos, newValue)
|
||||||
return newValue
|
return newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,32 +76,37 @@ object ChunkOverlayManager : IBlockUpdateListener {
|
|||||||
* @param layer Overlay layer to clear
|
* @param layer Overlay layer to clear
|
||||||
* @param pos Block position
|
* @param pos Block position
|
||||||
*/
|
*/
|
||||||
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) {
|
fun <T> clear(dimension: DimensionType, layer: ChunkOverlayLayer<T>, pos: BlockPos) {
|
||||||
chunkData[ChunkPos(pos)]?.clear(layer, pos)
|
chunkData[dimension]?.get(ChunkPos(pos))?.clear(layer, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyBlockUpdate(world: World, pos: BlockPos, oldState: IBlockState, newState: IBlockState, flags: Int) {
|
fun onBlockChange(world: ClientWorld, pos: BlockPos) {
|
||||||
if (chunkData.containsKey(ChunkPos(pos))) layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
|
if (chunkData[world.dimType]?.containsKey(ChunkPos(pos)) == true) {
|
||||||
}
|
layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handleLoadWorld(event: WorldEvent.Load) {
|
|
||||||
if (event.world is WorldClient) {
|
|
||||||
event.world.addEventListener(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
fun handleLoadChunk(event: ChunkEvent.Load) {
|
fun handleLoadWorld(event: WorldEvent.Load) = (event.world as? ClientWorld)?.let { world ->
|
||||||
if (event.world is WorldClient && event.chunk !is EmptyChunk) {
|
chunkData[world.dimType] = mutableMapOf()
|
||||||
chunkData[event.chunk.pos] = ChunkOverlayData(layers)
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
fun handleUnloadWorld(event: WorldEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
|
||||||
|
chunkData.remove(world.dimType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
fun handleLoadChunk(event: ChunkEvent.Load) = (event.world as? ClientWorld)?.let { world ->
|
||||||
|
chunkData[world.dimType]?.let { chunks ->
|
||||||
|
// check for existence first because Optifine fires a TON of these
|
||||||
|
if (event.chunk.pos !in chunks.keys) chunks[event.chunk.pos] = ChunkOverlayData(layers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
fun handleUnloadChunk(event: ChunkEvent.Unload) {
|
fun handleUnloadChunk(event: ChunkEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
|
||||||
if (event.world is WorldClient) {
|
chunkData[world.dimType]?.remove(event.chunk.pos)
|
||||||
chunkData.remove(event.chunk.pos)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,21 +123,3 @@ class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
|
|||||||
val validYRange = 0 until 256
|
val validYRange = 0 until 256
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* IWorldEventListener helper subclass
|
|
||||||
* No-op for everything except notifyBlockUpdate()
|
|
||||||
*/
|
|
||||||
interface IBlockUpdateListener : IWorldEventListener {
|
|
||||||
override fun playSoundToAllNearExcept(player: EntityPlayer?, soundIn: SoundEvent, category: SoundCategory, x: Double, y: Double, z: Double, volume: Float, pitch: Float) {}
|
|
||||||
override fun onEntityAdded(entityIn: Entity) {}
|
|
||||||
override fun broadcastSound(soundID: Int, pos: BlockPos, data: Int) {}
|
|
||||||
override fun playEvent(player: EntityPlayer?, type: Int, blockPosIn: BlockPos, data: Int) {}
|
|
||||||
override fun onEntityRemoved(entityIn: Entity) {}
|
|
||||||
override fun notifyLightSet(pos: BlockPos) {}
|
|
||||||
override fun spawnParticle(particleID: Int, ignoreRange: Boolean, xCoord: Double, yCoord: Double, zCoord: Double, xSpeed: Double, ySpeed: Double, zSpeed: Double, vararg parameters: Int) {}
|
|
||||||
override fun spawnParticle(id: Int, ignoreRange: Boolean, minimiseParticleLevel: Boolean, x: Double, y: Double, z: Double, xSpeed: Double, ySpeed: Double, zSpeed: Double, vararg parameters: Int) {}
|
|
||||||
override fun playRecord(soundIn: SoundEvent, pos: BlockPos) {}
|
|
||||||
override fun sendBlockBreakProgress(breakerId: Int, pos: BlockPos, progress: Int) {}
|
|
||||||
override fun markBlockRangeForRenderUpdate(x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int) {}
|
|
||||||
}
|
|
||||||
@@ -1,67 +1,22 @@
|
|||||||
package mods.betterfoliage.client.config
|
package mods.betterfoliage.client.config
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.gui.BiomeListConfigEntry
|
|
||||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||||
import mods.octarinecore.common.config.*
|
import mods.octarinecore.common.config.*
|
||||||
import net.minecraft.client.Minecraft
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.world.biome.Biome
|
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||||
import net.minecraftforge.fml.client.event.ConfigChangedEvent
|
import net.minecraftforge.fml.config.ModConfig
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.lwjgl.opengl.GL11
|
|
||||||
|
|
||||||
// BetterFoliage-specific property delegates
|
|
||||||
private val OBSOLETE = ObsoleteConfigProperty()
|
|
||||||
private fun featureEnable() = boolean(true).lang("enabled")
|
private fun featureEnable() = boolean(true).lang("enabled")
|
||||||
fun biomeList(defaults: (Biome) -> Boolean) = intList {
|
|
||||||
Biome.REGISTRY
|
|
||||||
.filter { it != null && defaults(it) }
|
|
||||||
.map { Biome.REGISTRY.getIDForObject(it) }
|
|
||||||
.toTypedArray()
|
|
||||||
}.apply { guiClass = BiomeListConfigEntry::class.java }
|
|
||||||
|
|
||||||
// Biome filter methods
|
|
||||||
private fun Biome.filterTemp(min: Float?, max: Float?) = (min == null || min <= defaultTemperature) && (max == null || max >= defaultTemperature)
|
|
||||||
private fun Biome.filterRain(min: Float?, max: Float?) = (min == null || min <= rainfall) && (max == null || max >= rainfall)
|
|
||||||
private fun Biome.filterClass(vararg name: String) = name.any { it in this.javaClass.name.toLowerCase() }
|
|
||||||
|
|
||||||
// Config singleton
|
// Config singleton
|
||||||
@SideOnly(Side.CLIENT)
|
object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.MOD_ID) {
|
||||||
object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAIN) {
|
|
||||||
|
|
||||||
var enabled by boolean(true)
|
val enabled by boolean(true)
|
||||||
var nVidia by boolean(GL11.glGetString(GL11.GL_VENDOR).toLowerCase().contains("nvidia"))
|
val nVidia by boolean(false)
|
||||||
|
|
||||||
object shaders {
|
object leaves : ConfigCategory() {
|
||||||
val leavesId by long(min = 1, max = 65535, default = ShadersModIntegration.leavesDefaultBlockId.toInt())
|
|
||||||
val grassId by long(min = 1, max = 65535, default = ShadersModIntegration.grassDefaultBlockId.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
object blocks {
|
|
||||||
val leavesClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "leaves_blocks_default.cfg")
|
|
||||||
val leavesModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "leaves_models_default.cfg", 1)
|
|
||||||
val grassClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "grass_blocks_default.cfg")
|
|
||||||
val grassModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "grass_models_default.cfg", 1)
|
|
||||||
val mycelium = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "mycelium_blocks_default.cfg")
|
|
||||||
val dirt = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "dirt_default.cfg")
|
|
||||||
val crops = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "crop_default.cfg")
|
|
||||||
val logClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "log_blocks_default.cfg")
|
|
||||||
val logModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "log_models_default.cfg", 3)
|
|
||||||
val sand = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "sand_default.cfg")
|
|
||||||
val lilypad = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "lilypad_default.cfg")
|
|
||||||
val cactus = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "cactus_default.cfg")
|
|
||||||
val netherrack = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "netherrack_blocks_default.cfg")
|
|
||||||
|
|
||||||
val leavesWhitelist = OBSOLETE
|
|
||||||
val leavesBlacklist = OBSOLETE
|
|
||||||
val grassWhitelist = OBSOLETE
|
|
||||||
val grassBlacklist = OBSOLETE
|
|
||||||
val logsWhitelist = OBSOLETE
|
|
||||||
val logsBlacklist = OBSOLETE
|
|
||||||
}
|
|
||||||
|
|
||||||
object leaves {
|
|
||||||
val enabled by featureEnable()
|
val enabled by featureEnable()
|
||||||
val snowEnabled by boolean(true)
|
val snowEnabled by boolean(true)
|
||||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||||
@@ -71,7 +26,7 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
|
|||||||
val hideInternal by boolean(true)
|
val hideInternal by boolean(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
object shortGrass {
|
object shortGrass : ConfigCategory(){
|
||||||
val grassEnabled by boolean(true)
|
val grassEnabled by boolean(true)
|
||||||
val myceliumEnabled by boolean(true)
|
val myceliumEnabled by boolean(true)
|
||||||
val snowEnabled by boolean(true)
|
val snowEnabled by boolean(true)
|
||||||
@@ -85,23 +40,16 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
|
|||||||
val saturationThreshold by double(default=0.1)
|
val saturationThreshold by double(default=0.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// object hangingGrass {
|
object connectedGrass : ConfigCategory(){
|
||||||
// var enabled by featureEnable()
|
|
||||||
// var distance by distanceLimit()
|
|
||||||
// var size by double(min=0.25, max=1.5, default=0.75).lang("size")
|
|
||||||
// var separation by double(max=0.5, default=0.25)
|
|
||||||
// }
|
|
||||||
|
|
||||||
object connectedGrass {
|
|
||||||
val enabled by boolean(true)
|
val enabled by boolean(true)
|
||||||
val snowEnabled by boolean(false)
|
val snowEnabled by boolean(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
object roundLogs {
|
object roundLogs : ConfigCategory(){
|
||||||
val enabled by featureEnable()
|
val enabled by featureEnable()
|
||||||
val radiusSmall by double(max=0.5, default=0.25)
|
val radiusSmall by double(max=0.5, default=0.25)
|
||||||
val radiusLarge by double(max=0.5, default=0.44)
|
val radiusLarge by double(max=0.5, default=0.44)
|
||||||
val dimming by float(default = 0.7)
|
val dimming by double(default = 0.7)
|
||||||
val connectSolids by boolean(false)
|
val connectSolids by boolean(false)
|
||||||
val lenientConnect by boolean(true)
|
val lenientConnect by boolean(true)
|
||||||
val connectPerpendicular by boolean(true)
|
val connectPerpendicular by boolean(true)
|
||||||
@@ -110,41 +58,43 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
|
|||||||
val zProtection by double(min = 0.9, default = 0.99)
|
val zProtection by double(min = 0.9, default = 0.99)
|
||||||
}
|
}
|
||||||
|
|
||||||
object cactus {
|
object cactus : ConfigCategory(){
|
||||||
val enabled by featureEnable()
|
val enabled by featureEnable()
|
||||||
val size by double(min=0.5, max=1.5, default=0.8).lang("size")
|
val size by double(min=0.5, max=1.5, default=0.8).lang("size")
|
||||||
val sizeVariation by double(max=0.5, default=0.1)
|
val sizeVariation by double(max=0.5, default=0.1)
|
||||||
val hOffset by double(max=0.5, default=0.1).lang("hOffset")
|
val hOffset by double(max=0.5, default=0.1).lang("hOffset")
|
||||||
}
|
}
|
||||||
|
|
||||||
object lilypad {
|
object lilypad : ConfigCategory(){
|
||||||
val enabled by featureEnable()
|
val enabled by featureEnable()
|
||||||
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
|
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
|
||||||
val flowerChance by int(max=64, default=16, min=0)
|
val flowerChance by int(max=64, default=16, min=0)
|
||||||
}
|
}
|
||||||
|
|
||||||
object reed {
|
object reed : ConfigCategory(){
|
||||||
val enabled by featureEnable()
|
val enabled by featureEnable()
|
||||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||||
val heightMin by double(min=1.5, max=3.5, default=1.7).lang("heightMin")
|
val heightMin by double(min=1.5, max=3.5, default=1.7).lang("heightMin")
|
||||||
val heightMax by double(min=1.5, max=3.5, default=2.2).lang("heightMax")
|
val heightMax by double(min=1.5, max=3.5, default=2.2).lang("heightMax")
|
||||||
val population by int(max=64, default=32).lang("population")
|
val population by int(max=64, default=32).lang("population")
|
||||||
val biomes by biomeList { it.filterTemp(0.4f, null) && it.filterRain(0.4f, null) }
|
val minBiomeTemp by double(default=0.4)
|
||||||
|
val minBiomeRainfall by double(default=0.4)
|
||||||
|
// val biomes by biomeList { it.filterTemp(0.4f, null) && it.filterRain(0.4f, null) }
|
||||||
val shaderWind by boolean(true).lang("shaderWind")
|
val shaderWind by boolean(true).lang("shaderWind")
|
||||||
}
|
}
|
||||||
|
|
||||||
object algae {
|
object algae : ConfigCategory(){
|
||||||
val enabled by featureEnable()
|
val enabled by featureEnable()
|
||||||
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
|
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
|
||||||
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
|
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
|
||||||
val heightMin by double(min=0.1, max=1.5, default=0.5).lang("heightMin")
|
val heightMin by double(min=0.1, max=1.5, default=0.5).lang("heightMin")
|
||||||
val heightMax by double(min=0.1, max=1.5, default=1.0).lang("heightMax")
|
val heightMax by double(min=0.1, max=1.5, default=1.0).lang("heightMax")
|
||||||
val population by int(max=64, default=48).lang("population")
|
val population by int(max=64, default=48).lang("population")
|
||||||
val biomes by biomeList { it.filterClass("river", "ocean") }
|
// val biomes by biomeList { it.filterClass("river", "ocean") }
|
||||||
val shaderWind by boolean(true).lang("shaderWind")
|
val shaderWind by boolean(true).lang("shaderWind")
|
||||||
}
|
}
|
||||||
|
|
||||||
object coral {
|
object coral : ConfigCategory(){
|
||||||
val enabled by featureEnable()
|
val enabled by featureEnable()
|
||||||
val shallowWater by boolean(false)
|
val shallowWater by boolean(false)
|
||||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||||
@@ -153,10 +103,10 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
|
|||||||
val crustSize by double(min=0.5, max=1.5, default=1.4)
|
val crustSize by double(min=0.5, max=1.5, default=1.4)
|
||||||
val chance by int(max=64, default=32)
|
val chance by int(max=64, default=32)
|
||||||
val population by int(max=64, default=48).lang("population")
|
val population by int(max=64, default=48).lang("population")
|
||||||
val biomes by biomeList { it.filterClass("river", "ocean", "beach") }
|
// val biomes by biomeList { it.filterClass("river", "ocean", "beach") }
|
||||||
}
|
}
|
||||||
|
|
||||||
object netherrack {
|
object netherrack : ConfigCategory(){
|
||||||
val enabled by featureEnable()
|
val enabled by featureEnable()
|
||||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||||
val heightMin by double(min=0.1, max=1.5, default=0.6).lang("heightMin")
|
val heightMin by double(min=0.1, max=1.5, default=0.6).lang("heightMin")
|
||||||
@@ -164,44 +114,56 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
|
|||||||
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
|
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
|
||||||
}
|
}
|
||||||
|
|
||||||
object fallingLeaves {
|
object fallingLeaves : ConfigCategory(){
|
||||||
val enabled by featureEnable()
|
val enabled by featureEnable()
|
||||||
val speed by double(min=0.01, max=0.15, default=0.05)
|
val speed by double(min=0.01, max=0.15, default=0.05)
|
||||||
val windStrength by double(min=0.1, max=2.0, default=0.5)
|
val windStrength by double(min=0.1, max=2.0, default=0.5)
|
||||||
val stormStrength by double(min=0.1, max=2.0, default=0.8)
|
val stormStrength by double(min=0.1, max=2.0, default=0.8)
|
||||||
val size by double(min=0.25, max=1.5, default=0.75).lang("size")
|
val size by double(min=0.25, max=1.5, default=0.75).lang("size")
|
||||||
val chance by double(min=0.001, max=1.0, default=0.05)
|
val chance by double(min=0.001, max=1.0, default=0.02)
|
||||||
val perturb by double(min=0.01, max=1.0, default=0.25)
|
val perturb by double(min=0.01, max=1.0, default=0.25)
|
||||||
val lifetime by double(min=1.0, max=15.0, default=5.0)
|
val lifetime by double(min=1.0, max=15.0, default=5.0)
|
||||||
val opacityHack by boolean(true)
|
val opacityHack by boolean(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
object risingSoul {
|
object risingSoul : ConfigCategory(){
|
||||||
val enabled by featureEnable()
|
val enabled by featureEnable()
|
||||||
val chance by double(min=0.001, max=1.0, default=0.02)
|
val chance by double(min=0.001, max=1.0, default=0.02)
|
||||||
val perturb by double(min=0.01, max=0.25, default=0.05)
|
val perturb by double(min=0.01, max=0.25, default=0.05)
|
||||||
val headSize by double(min=0.25, max=1.5, default=1.0)
|
val headSize by double(min=0.25, max=1.5, default=1.0)
|
||||||
val trailSize by double(min=0.25, max=1.5, default=0.75)
|
val trailSize by double(min=0.25, max=1.5, default=0.75)
|
||||||
val opacity by float(min=0.05, max=1.0, default=0.5)
|
val opacity by double(min=0.05, max=1.0, default=0.5)
|
||||||
val sizeDecay by double(min=0.5, max=1.0, default=0.97)
|
val sizeDecay by double(min=0.5, max=1.0, default=0.97)
|
||||||
val opacityDecay by float(min=0.5, max=1.0, default=0.97)
|
val opacityDecay by double(min=0.5, max=1.0, default=0.97)
|
||||||
val lifetime by double(min=1.0, max=15.0, default=4.0)
|
val lifetime by double(min=1.0, max=15.0, default=4.0)
|
||||||
val trailLength by int(min=2, max=128, default=48)
|
val trailLength by int(min=2, max=128, default=48)
|
||||||
val trailDensity by int(min=1, max=16, default=3)
|
val trailDensity by int(min=1, max=16, default=3)
|
||||||
}
|
}
|
||||||
|
|
||||||
val forceReloadOptions = listOf(
|
|
||||||
blocks.leavesClasses,
|
|
||||||
blocks.leavesModels,
|
|
||||||
blocks.grassClasses,
|
|
||||||
blocks.grassModels,
|
|
||||||
shortGrass["saturationThreshold"]!!
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun onChange(event: ConfigChangedEvent.PostConfigChangedEvent) {
|
|
||||||
if (hasChanged(forceReloadOptions))
|
|
||||||
Minecraft.getMinecraft().refreshResources()
|
|
||||||
else
|
|
||||||
Minecraft.getMinecraft().renderGlobal.loadRenderers()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object BlockConfig {
|
||||||
|
private val list = mutableListOf<Any>()
|
||||||
|
|
||||||
|
val leafBlocks = blocks("leaves_blocks_default.cfg")
|
||||||
|
val leafModels = models("leaves_models_default.cfg")
|
||||||
|
val grassBlocks = blocks("grass_blocks_default.cfg")
|
||||||
|
val grassModels = models("grass_models_default.cfg")
|
||||||
|
val mycelium = blocks("mycelium_blocks_default.cfg")
|
||||||
|
// val dirt = blocks("dirt_default.cfg")
|
||||||
|
val crops = blocks("crop_default.cfg")
|
||||||
|
val logBlocks = blocks("log_blocks_default.cfg")
|
||||||
|
val logModels = models("log_models_default.cfg")
|
||||||
|
val lilypad = blocks("lilypad_default.cfg")
|
||||||
|
|
||||||
|
init { BetterFoliageMod.bus.register(this) }
|
||||||
|
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(BetterFoliage.logDetail, ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
|
||||||
|
private fun models(cfgName: String) = ModelTextureListConfiguration(BetterFoliage.logDetail, ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
fun onConfig(event: ModConfig.ModConfigEvent) {
|
||||||
|
list.forEach { when(it) {
|
||||||
|
is ConfigurableBlockMatcher -> it.readDefaults()
|
||||||
|
is ModelTextureListConfiguration -> it.readDefaults()
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui
|
|
||||||
|
|
||||||
import mods.octarinecore.client.gui.IdListConfigEntry
|
|
||||||
import net.minecraft.world.biome.Biome
|
|
||||||
import net.minecraftforge.fml.client.config.GuiConfig
|
|
||||||
import net.minecraftforge.fml.client.config.GuiConfigEntries
|
|
||||||
import net.minecraftforge.fml.client.config.IConfigElement
|
|
||||||
|
|
||||||
/** Toggleable list of all defined biomes. */
|
|
||||||
class BiomeListConfigEntry(
|
|
||||||
owningScreen: GuiConfig,
|
|
||||||
owningEntryList: GuiConfigEntries,
|
|
||||||
configElement: IConfigElement)
|
|
||||||
: IdListConfigEntry<Biome>(owningScreen, owningEntryList, configElement) {
|
|
||||||
|
|
||||||
override val baseSet: List<Biome> get() = Biome.REGISTRY.filterNotNull()
|
|
||||||
override val Biome.itemId: Int get() = Biome.REGISTRY.getIDForObject(this)
|
|
||||||
override val Biome.itemName: String get() = this.biomeName
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package mods.betterfoliage.client.gui
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import net.minecraft.client.Minecraft
|
|
||||||
import net.minecraft.client.gui.GuiScreen
|
|
||||||
import net.minecraftforge.fml.client.IModGuiFactory
|
|
||||||
import net.minecraftforge.fml.client.config.GuiConfig
|
|
||||||
|
|
||||||
class ConfigGuiFactory : IModGuiFactory {
|
|
||||||
|
|
||||||
override fun initialize(minecraftInstance: Minecraft?) { }
|
|
||||||
override fun hasConfigGui() = true
|
|
||||||
override fun runtimeGuiCategories() = hashSetOf<IModGuiFactory.RuntimeOptionCategoryElement>()
|
|
||||||
override fun createConfigGui(parentScreen: GuiScreen?) = createBFConfigGui(parentScreen)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun createBFConfigGui(parentScreen: GuiScreen?) = GuiConfig(
|
|
||||||
parentScreen,
|
|
||||||
Config.rootGuiElements,
|
|
||||||
BetterFoliageMod.MOD_ID,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
BetterFoliageMod.MOD_NAME
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +1,135 @@
|
|||||||
package mods.betterfoliage.client.integration
|
package mods.betterfoliage.client.integration
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.render.AsyncLogDiscovery
|
||||||
import mods.betterfoliage.client.render.LogRegistry
|
|
||||||
import mods.betterfoliage.client.render.StandardLogRegistry
|
|
||||||
import mods.betterfoliage.client.render.column.ColumnTextureInfo
|
import mods.betterfoliage.client.render.column.ColumnTextureInfo
|
||||||
import mods.betterfoliage.client.render.column.SimpleColumnInfo
|
import mods.betterfoliage.client.render.column.SimpleColumnInfo
|
||||||
import mods.betterfoliage.client.texture.LeafInfo
|
import mods.betterfoliage.client.texture.LeafInfo
|
||||||
import mods.betterfoliage.client.texture.LeafRegistry
|
import mods.betterfoliage.client.texture.defaultRegisterLeaf
|
||||||
import mods.betterfoliage.client.texture.StandardLeafKey
|
import mods.octarinecore.HasLogger
|
||||||
import mods.betterfoliage.loader.Refs
|
import mods.octarinecore.Map
|
||||||
import mods.octarinecore.client.resource.ModelRenderKey
|
import mods.octarinecore.ResourceLocation
|
||||||
import mods.octarinecore.client.resource.ModelRenderRegistry
|
import mods.octarinecore.String
|
||||||
import mods.octarinecore.client.resource.ModelRenderRegistryBase
|
import mods.octarinecore.client.resource.*
|
||||||
import mods.octarinecore.getTileEntitySafe
|
import mods.octarinecore.metaprog.*
|
||||||
import mods.octarinecore.metaprog.ClassRef
|
import mods.octarinecore.metaprog.ClassRef.Companion.boolean
|
||||||
import mods.octarinecore.metaprog.FieldRef
|
import net.minecraft.block.BlockState
|
||||||
import mods.octarinecore.metaprog.MethodRef
|
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.Minecraft
|
import net.minecraft.client.Minecraft
|
||||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation
|
import net.minecraft.client.renderer.model.ModelBakery
|
||||||
|
import net.minecraft.resources.IResourceManager
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.world.IBlockAccess
|
import net.minecraft.world.IBlockReader
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent
|
import net.minecraftforge.fml.ModList
|
||||||
import net.minecraftforge.client.model.IModel
|
|
||||||
import net.minecraftforge.fml.common.Loader
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.EventPriority
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
import org.apache.logging.log4j.Level
|
||||||
import kotlin.collections.Map
|
import java.util.concurrent.CompletableFuture
|
||||||
import kotlin.collections.component1
|
import kotlin.collections.component1
|
||||||
import kotlin.collections.component2
|
import kotlin.collections.component2
|
||||||
import kotlin.collections.emptyMap
|
|
||||||
import kotlin.collections.find
|
|
||||||
import kotlin.collections.forEach
|
|
||||||
import kotlin.collections.get
|
|
||||||
import kotlin.collections.listOf
|
|
||||||
import kotlin.collections.mapValues
|
|
||||||
import kotlin.collections.mutableMapOf
|
|
||||||
import kotlin.collections.set
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
val TextureLeaves = ClassRef<Any>("forestry.arboriculture.models.TextureLeaves")
|
||||||
|
val TextureLeaves_leafTextures = FieldRef(TextureLeaves, "leafTextures", Map)
|
||||||
|
val TextureLeaves_plain = FieldRef(TextureLeaves, "plain", ResourceLocation)
|
||||||
|
val TextureLeaves_fancy = FieldRef(TextureLeaves, "fancy", ResourceLocation)
|
||||||
|
val TextureLeaves_pollinatedPlain = FieldRef(TextureLeaves, "pollinatedPlain", ResourceLocation)
|
||||||
|
val TextureLeaves_pollinatedFancy = FieldRef(TextureLeaves, "pollinatedFancy", ResourceLocation)
|
||||||
|
|
||||||
|
|
||||||
|
val TileLeaves = ClassRef<Any>("forestry.arboriculture.tiles.TileLeaves")
|
||||||
|
val TileLeaves_getLeaveSprite = MethodRef(TileLeaves, "getLeaveSprite", ResourceLocation, boolean)
|
||||||
|
val PropertyWoodType = ClassRef<Any>("forestry.arboriculture.blocks.PropertyWoodType")
|
||||||
|
val IWoodType = ClassRef<Any>("forestry.api.arboriculture.IWoodType")
|
||||||
|
val IWoodType_barkTex = MethodRef(IWoodType, "getBarkTexture", String)
|
||||||
|
val IWoodType_heartTex = MethodRef(IWoodType, "getHeartTexture", String)
|
||||||
|
|
||||||
|
val PropertyTreeType = ClassRef<Any>("forestry.arboriculture.blocks.PropertyTreeType")
|
||||||
|
val IAlleleTreeSpecies = ClassRef<Any>("forestry.api.arboriculture.IAlleleTreeSpecies")
|
||||||
|
val ILeafSpriteProvider = ClassRef<Any>("forestry.api.arboriculture.ILeafSpriteProvider")
|
||||||
|
val TreeDefinition = ClassRef<Any>("forestry.arboriculture.genetics.TreeDefinition")
|
||||||
|
|
||||||
|
val IAlleleTreeSpecies_getLeafSpriteProvider = MethodRef(IAlleleTreeSpecies, "getLeafSpriteProvider", ILeafSpriteProvider)
|
||||||
|
val TreeDefinition_species = FieldRef(TreeDefinition, "species", IAlleleTreeSpecies)
|
||||||
|
val ILeafSpriteProvider_getSprite = MethodRef(ILeafSpriteProvider, "getSprite", ResourceLocation, boolean, boolean)
|
||||||
|
|
||||||
object ForestryIntegration {
|
object ForestryIntegration {
|
||||||
|
|
||||||
val TextureLeaves = ClassRef("forestry.arboriculture.models.TextureLeaves")
|
|
||||||
val TeLleafTextures = FieldRef(TextureLeaves, "leafTextures", Refs.Map)
|
|
||||||
val TeLplain = FieldRef(TextureLeaves, "plain", Refs.ResourceLocation)
|
|
||||||
val TeLfancy = FieldRef(TextureLeaves, "fancy", Refs.ResourceLocation)
|
|
||||||
val TeLpollplain = FieldRef(TextureLeaves, "pollinatedPlain", Refs.ResourceLocation)
|
|
||||||
val TeLpollfancy = FieldRef(TextureLeaves, "pollinatedFancy", Refs.ResourceLocation)
|
|
||||||
val TileLeaves = ClassRef("forestry.arboriculture.tiles.TileLeaves")
|
|
||||||
val TiLgetLeaveSprite = MethodRef(TileLeaves, "getLeaveSprite", Refs.ResourceLocation, ClassRef.boolean)
|
|
||||||
|
|
||||||
val PropertyWoodType = ClassRef("forestry.arboriculture.blocks.PropertyWoodType")
|
|
||||||
val IWoodType = ClassRef("forestry.api.arboriculture.IWoodType")
|
|
||||||
val barkTex = MethodRef(IWoodType, "getBarkTexture", Refs.String)
|
|
||||||
val heartTex = MethodRef(IWoodType, "getHeartTexture", Refs.String)
|
|
||||||
|
|
||||||
val PropertyTreeType = ClassRef("forestry.arboriculture.blocks.PropertyTreeType")
|
|
||||||
val TreeDefinition = ClassRef("forestry.arboriculture.genetics.TreeDefinition")
|
|
||||||
val IAlleleTreeSpecies = ClassRef("forestry.api.arboriculture.IAlleleTreeSpecies")
|
|
||||||
val ILeafSpriteProvider = ClassRef("forestry.api.arboriculture.ILeafSpriteProvider")
|
|
||||||
val TdSpecies = FieldRef(TreeDefinition, "species", IAlleleTreeSpecies)
|
|
||||||
val getLeafSpriteProvider = MethodRef(IAlleleTreeSpecies, "getLeafSpriteProvider", ILeafSpriteProvider)
|
|
||||||
val getSprite = MethodRef(ILeafSpriteProvider, "getSprite", Refs.ResourceLocation, ClassRef.boolean, ClassRef.boolean)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (Loader.isModLoaded("forestry") && allAvailable(TiLgetLeaveSprite, getLeafSpriteProvider, getSprite)) {
|
if (ModList.get().isLoaded("forestry") && allAvailable(TileLeaves_getLeaveSprite, IAlleleTreeSpecies_getLeafSpriteProvider, ILeafSpriteProvider_getSprite)) {
|
||||||
Client.log(Level.INFO, "Forestry support initialized")
|
// Just keep it inactive for now until Forestry updates
|
||||||
LeafRegistry.addRegistry(ForestryLeafRegistry)
|
|
||||||
LogRegistry.addRegistry(ForestryLogRegistry)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ForestryLeafRegistry : ModelRenderRegistry<LeafInfo> {
|
object ForestryLeafDiscovery : HasLogger, AsyncSpriteProvider<ModelBakery>, ModelRenderRegistry<LeafInfo> {
|
||||||
val logger = BetterFoliageMod.logDetail
|
override val logger = BetterFoliage.logDetail
|
||||||
val textureToKey = mutableMapOf<ResourceLocation, ModelRenderKey<LeafInfo>>()
|
var idToValue = emptyMap<ResourceLocation, LeafInfo>()
|
||||||
var textureToValue = emptyMap<ResourceLocation, LeafInfo>()
|
|
||||||
|
|
||||||
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos): LeafInfo? {
|
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos): LeafInfo? {
|
||||||
// check variant property (used in decorative leaves)
|
// check variant property (used in decorative leaves)
|
||||||
state.properties.entries.find {
|
state.values.entries.find {
|
||||||
ForestryIntegration.PropertyTreeType.isInstance(it.key) && ForestryIntegration.TreeDefinition.isInstance(it.value)
|
PropertyTreeType.isInstance(it.key) && TreeDefinition.isInstance(it.value)
|
||||||
} ?.let {
|
} ?.let {
|
||||||
val species = ForestryIntegration.TdSpecies.get(it.value)
|
val species = it.value[TreeDefinition_species]
|
||||||
val spriteProvider = ForestryIntegration.getLeafSpriteProvider.invoke(species!!)
|
val spriteProvider = species[IAlleleTreeSpecies_getLeafSpriteProvider]()
|
||||||
val textureLoc = ForestryIntegration.getSprite.invoke(spriteProvider!!, false, Minecraft.isFancyGraphicsEnabled())
|
val textureLoc = spriteProvider[ILeafSpriteProvider_getSprite](false, Minecraft.isFancyGraphicsEnabled())
|
||||||
return textureToValue[textureLoc]
|
return idToValue[textureLoc]
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract leaf texture information from TileEntity
|
// extract leaf texture information from TileEntity
|
||||||
val tile = world.getTileEntitySafe(pos) ?: return null
|
val tile = world.getTileEntity(pos) ?: return null
|
||||||
if (!ForestryIntegration.TileLeaves.isInstance(tile)) return null
|
if (!TileLeaves.isInstance(tile)) return null
|
||||||
val textureLoc = ForestryIntegration.TiLgetLeaveSprite.invoke(tile, Minecraft.isFancyGraphicsEnabled()) ?: return null
|
val textureLoc = tile[TileLeaves_getLeaveSprite](Minecraft.isFancyGraphicsEnabled())
|
||||||
return textureToValue[textureLoc]
|
return idToValue[textureLoc]
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
override fun setup(manager: IResourceManager, bakeryF: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases {
|
||||||
fun handlePreStitch(event: TextureStitchEvent.Pre) {
|
val futures = mutableMapOf<ResourceLocation, CompletableFuture<LeafInfo>>()
|
||||||
textureToValue = emptyMap()
|
|
||||||
|
|
||||||
val allLeaves = ForestryIntegration.TeLleafTextures.getStatic() as Map<*, *>
|
return StitchPhases(
|
||||||
allLeaves.entries.forEach {
|
discovery = bakeryF.thenRunAsync {
|
||||||
logger.log(Level.DEBUG, "ForestryLeavesSupport: base leaf type ${it.key.toString()}")
|
val allLeaves = TextureLeaves_leafTextures.getStatic()
|
||||||
listOf(
|
allLeaves.entries.forEach { (type, leaves) ->
|
||||||
ForestryIntegration.TeLplain.get(it.value) as ResourceLocation,
|
log("base leaf type $type")
|
||||||
ForestryIntegration.TeLfancy.get(it.value) as ResourceLocation,
|
leaves!!
|
||||||
ForestryIntegration.TeLpollplain.get(it.value) as ResourceLocation,
|
listOf(
|
||||||
ForestryIntegration.TeLpollfancy.get(it.value) as ResourceLocation
|
leaves[TextureLeaves_plain], leaves[TextureLeaves_pollinatedPlain],
|
||||||
).forEach { textureLocation ->
|
leaves[TextureLeaves_fancy], leaves[TextureLeaves_pollinatedFancy]
|
||||||
val key = StandardLeafKey(logger, textureLocation.toString()).apply { onPreStitch(event.map) }
|
).forEach { textureLocation ->
|
||||||
textureToKey[textureLocation] = key
|
futures[textureLocation] = defaultRegisterLeaf(textureLocation, atlasFuture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cleanup = atlasFuture.runAfter {
|
||||||
|
idToValue = futures.mapValues { it.value.get() }
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent(priority = EventPriority.LOW)
|
|
||||||
fun handlePostStitch(event: TextureStitchEvent.Post) {
|
|
||||||
textureToValue = textureToKey.mapValues { (_, key) -> key.resolveSprites(event.map) }
|
|
||||||
textureToKey.clear()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ForestryLogRegistry : ModelRenderRegistryBase<ColumnTextureInfo>() {
|
object ForestryLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
|
||||||
override val logger = BetterFoliageMod.logDetail
|
override val logger = BetterFoliage.logDetail
|
||||||
|
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
|
||||||
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
|
|
||||||
// respect class list to avoid triggering on fences, stairs, etc.
|
// respect class list to avoid triggering on fences, stairs, etc.
|
||||||
if (!Config.blocks.logClasses.matchesClass(state.block)) return null
|
if (!BlockConfig.logBlocks.matchesClass(ctx.state.block)) return null
|
||||||
|
|
||||||
// find wood type property
|
// find wood type property
|
||||||
val woodType = state.properties.entries.find {
|
val woodType = ctx.state.values.entries.find {
|
||||||
ForestryIntegration.PropertyWoodType.isInstance(it.key) && ForestryIntegration.IWoodType.isInstance(it.value)
|
PropertyWoodType.isInstance(it.key) && IWoodType.isInstance(it.value)
|
||||||
} ?: return null
|
}
|
||||||
|
if (woodType != null) {
|
||||||
|
logger.log(Level.DEBUG, "ForestryLogRegistry: block state ${ctx.state}")
|
||||||
|
logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}")
|
||||||
|
|
||||||
logger.log(Level.DEBUG, "ForestryLogRegistry: block state $state")
|
// get texture names for wood type
|
||||||
logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}")
|
val bark = woodType.value[IWoodType_barkTex]()
|
||||||
|
val heart = woodType.value[IWoodType_heartTex]()
|
||||||
|
logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]")
|
||||||
|
|
||||||
// get texture names for wood type
|
val heartSprite = atlas.sprite(heart)
|
||||||
val bark = ForestryIntegration.barkTex.invoke(woodType.value) as String?
|
val barkSprite = atlas.sprite(bark)
|
||||||
val heart = ForestryIntegration.heartTex.invoke(woodType.value) as String?
|
return atlas.mapAfter {
|
||||||
|
SimpleColumnInfo(AsyncLogDiscovery.getAxis(ctx.state), heartSprite.get(), heartSprite.get(), listOf(barkSprite.get()))
|
||||||
logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]")
|
}
|
||||||
if (bark != null && heart != null) return SimpleColumnInfo.Key(logger, StandardLogRegistry.getAxis(state), listOf(heart, heart, bark))
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,60 +1,49 @@
|
|||||||
package mods.betterfoliage.client.integration
|
package mods.betterfoliage.client.integration
|
||||||
|
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.loader.Refs
|
import mods.octarinecore.*
|
||||||
import mods.octarinecore.ThreadLocalDelegate
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.client.render.BlockContext
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
import mods.octarinecore.metaprog.allAvailable
|
||||||
import mods.octarinecore.metaprog.reflectField
|
import mods.octarinecore.metaprog.reflectField
|
||||||
import net.minecraft.block.state.IBlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.client.Minecraft
|
import net.minecraft.client.Minecraft
|
||||||
import net.minecraft.client.renderer.block.model.BakedQuad
|
import net.minecraft.client.renderer.model.BakedQuad
|
||||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
|
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
|
||||||
import net.minecraft.util.EnumFacing
|
import net.minecraft.util.Direction.UP
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
import org.apache.logging.log4j.Level
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration for OptiFine custom block colors.
|
* Integration for OptiFine custom block colors.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object OptifineCustomColors {
|
object OptifineCustomColors {
|
||||||
|
|
||||||
val isColorAvailable = allAvailable(
|
val isColorAvailable = allAvailable(CustomColors, CustomColors.getColorMultiplier)
|
||||||
Refs.CustomColors, Refs.getColorMultiplier
|
|
||||||
)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Client.log(Level.INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
|
BetterFoliage.log(Level.INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
|
||||||
}
|
}
|
||||||
|
|
||||||
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }
|
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }
|
||||||
val fakeQuad = BakedQuad(IntArray(0), 1, EnumFacing.UP, null, true, DefaultVertexFormats.BLOCK)
|
val fakeQuad = BakedQuad(IntArray(0), 1, UP, null, true, DefaultVertexFormats.BLOCK)
|
||||||
|
|
||||||
fun getBlockColor(ctx: BlockContext): Int {
|
fun getBlockColor(ctx: CombinedContext): Int {
|
||||||
val ofColor = if (isColorAvailable && Minecraft.getMinecraft().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
|
val ofColor = if (isColorAvailable && Minecraft.getInstance().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
|
||||||
renderEnv.reset(ctx.blockState(Int3.zero), ctx.pos)
|
renderEnv.reset(ctx.state, ctx.pos)
|
||||||
Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, renderEnv.wrapped) as? Int
|
CustomColors.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int
|
||||||
} else null
|
} else null
|
||||||
return if (ofColor == null || ofColor == -1) ctx.blockData(Int3.zero).color else ofColor
|
return if (ofColor == null || ofColor == -1) ctx.lightingCtx.color else ofColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class OptifineRenderEnv {
|
class OptifineRenderEnv {
|
||||||
val wrapped: Any = Refs.RenderEnv.element!!.getDeclaredConstructor(
|
val wrapped: Any = RenderEnv.element!!.getDeclaredConstructor(BlockState.element, BlockPos.element).let {
|
||||||
Refs.IBlockState.element, Refs.BlockPos.element
|
|
||||||
).let {
|
|
||||||
it.isAccessible = true
|
it.isAccessible = true
|
||||||
it.newInstance(null, null)
|
it.newInstance(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset(state: IBlockState, pos: BlockPos) {
|
fun reset(state: BlockState, pos: BlockPos) {
|
||||||
Refs.RenderEnv_reset.invoke(wrapped, state, pos)
|
RenderEnv.reset.invoke(wrapped, state, pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,147 +1,160 @@
|
|||||||
package mods.betterfoliage.client.integration
|
package mods.betterfoliage.client.integration
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.render.LogRegistry
|
|
||||||
import mods.betterfoliage.client.render.column.ColumnTextureInfo
|
import mods.betterfoliage.client.render.column.ColumnTextureInfo
|
||||||
import mods.betterfoliage.client.render.column.SimpleColumnInfo
|
import mods.betterfoliage.client.render.column.SimpleColumnInfo
|
||||||
|
import mods.octarinecore.Sprite
|
||||||
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.client.render.Quad
|
import mods.octarinecore.client.render.Quad
|
||||||
import mods.octarinecore.client.render.QuadIconResolver
|
import mods.octarinecore.client.render.lighting.QuadIconResolver
|
||||||
import mods.octarinecore.client.render.ShadingContext
|
|
||||||
import mods.octarinecore.client.render.blockContext
|
|
||||||
import mods.octarinecore.client.resource.*
|
import mods.octarinecore.client.resource.*
|
||||||
import mods.octarinecore.common.rotate
|
import mods.octarinecore.common.rotate
|
||||||
import mods.octarinecore.metaprog.ClassRef
|
import mods.octarinecore.metaprog.ClassRef
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
import mods.octarinecore.metaprog.allAvailable
|
||||||
import net.minecraft.block.state.IBlockState
|
import net.minecraft.client.renderer.model.BlockModel
|
||||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation
|
import net.minecraft.util.Direction
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
import net.minecraft.util.Direction.*
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraftforge.client.model.IModel
|
import net.minecraftforge.fml.ModList
|
||||||
import net.minecraftforge.fml.common.Loader
|
import java.util.concurrent.CompletableFuture
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object IC2RubberIntegration {
|
object IC2RubberIntegration {
|
||||||
|
|
||||||
val BlockRubWood = ClassRef("ic2.core.block.BlockRubWood")
|
val BlockRubWood = ClassRef<Any>("ic2.core.block.BlockRubWood")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (Loader.isModLoaded("ic2") && allAvailable(BlockRubWood)) {
|
if (ModList.get().isLoaded("ic2") && allAvailable(BlockRubWood)) {
|
||||||
Client.log(Level.INFO, "IC2 rubber support initialized")
|
// keep it inactive for now until IC2 updates
|
||||||
LogRegistry.addRegistry(IC2LogSupport)
|
// BetterFoliage.log(Level.INFO, "IC2 rubber support initialized")
|
||||||
|
// LogRegistry.registries.add(IC2LogDiscovery)
|
||||||
|
// BetterFoliage.blockSprites.providers.add(IC2LogDiscovery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
// Probably unneeded, as TechReborn went Fabric-only
|
||||||
|
/*
|
||||||
object TechRebornRubberIntegration {
|
object TechRebornRubberIntegration {
|
||||||
|
|
||||||
val BlockRubberLog = ClassRef("techreborn.blocks.BlockRubberLog")
|
val BlockRubberLog = ClassRef<Any>("techreborn.blocks.BlockRubberLog")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (Loader.isModLoaded("techreborn") && allAvailable(BlockRubberLog)) {
|
if (ModList.get().isLoaded("techreborn") && allAvailable(BlockRubberLog)) {
|
||||||
Client.log(Level.INFO, "TechReborn rubber support initialized")
|
BetterFoliage.log(Level.INFO, "TechReborn rubber support initialized")
|
||||||
LogRegistry.addRegistry(TechRebornLogSupport)
|
LogRegistry.registries.add(TechRebornLogDiscovery)
|
||||||
|
BetterFoliage.blockSprites.providers.add(TechRebornLogDiscovery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
class RubberLogInfo(
|
class RubberLogInfo(
|
||||||
axis: EnumFacing.Axis?,
|
axis: Axis?,
|
||||||
val spotDir: EnumFacing,
|
val spotDir: Direction,
|
||||||
topTexture: TextureAtlasSprite,
|
topTexture: Sprite,
|
||||||
bottomTexture: TextureAtlasSprite,
|
bottomTexture: Sprite,
|
||||||
val spotTexture: TextureAtlasSprite,
|
val spotTexture: Sprite,
|
||||||
sideTextures: List<TextureAtlasSprite>
|
sideTextures: List<Sprite>
|
||||||
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
|
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
|
||||||
|
|
||||||
override val side: QuadIconResolver = { ctx: ShadingContext, idx: Int, quad: Quad ->
|
override val side: QuadIconResolver = { ctx: CombinedContext, idx: Int, quad: Quad ->
|
||||||
val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
|
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation)
|
||||||
if (worldFace == spotDir) spotTexture else {
|
if (worldFace == spotDir) spotTexture else {
|
||||||
val sideIdx = if (this.sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
|
val sideIdx = if (this.sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
|
||||||
this.sideTextures[sideIdx]
|
this.sideTextures[sideIdx]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Key(override val logger: Logger, val axis: EnumFacing.Axis?, val spotDir: EnumFacing, val textures: List<String>): ModelRenderKey<ColumnTextureInfo> {
|
|
||||||
override fun resolveSprites(atlas: TextureMap) = RubberLogInfo(
|
|
||||||
axis,
|
|
||||||
spotDir,
|
|
||||||
atlas[textures[0]] ?: atlas.missingSprite,
|
|
||||||
atlas[textures[1]] ?: atlas.missingSprite,
|
|
||||||
atlas[textures[2]] ?: atlas.missingSprite,
|
|
||||||
textures.drop(3).map { atlas[it] ?: atlas.missingSprite }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object IC2LogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
|
object IC2LogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
|
||||||
override val logger = BetterFoliageMod.logDetail
|
override val logger = BetterFoliage.logDetail
|
||||||
|
|
||||||
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
|
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
|
||||||
// check for proper block class, existence of ModelBlock, and "state" blockstate property
|
// check for proper block class, existence of ModelBlock, and "state" blockstate property
|
||||||
if (!IC2RubberIntegration.BlockRubWood.isInstance(state.block)) return null
|
if (!IC2RubberIntegration.BlockRubWood.isInstance(ctx.state.block)) return null
|
||||||
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
|
val blockLoc = ctx.models.firstOrNull() as Pair<BlockModel, ResourceLocation> ?: return null
|
||||||
val type = state.properties.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
|
val type = ctx.state.values.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
|
||||||
|
|
||||||
// logs with no rubber spot
|
// logs with no rubber spot
|
||||||
if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) {
|
if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) {
|
||||||
val axis = when(type) {
|
val axis = when(type) {
|
||||||
"plain_y" -> EnumFacing.Axis.Y
|
"plain_y" -> Axis.Y
|
||||||
"plain_x" -> EnumFacing.Axis.X
|
"plain_x" -> Axis.X
|
||||||
"plain_z" -> EnumFacing.Axis.Z
|
"plain_z" -> Axis.Z
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
|
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTextureName(it) }
|
||||||
if (textureNames.any { it == "missingno" }) return null
|
if (textureNames.any { it == "missingno" }) return null
|
||||||
logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}")
|
log("IC2LogSupport: block state ${ctx.state.toString()}")
|
||||||
logger.log(Level.DEBUG, "IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[2]}")
|
log("IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[1]}")
|
||||||
return SimpleColumnInfo.Key(logger, axis, textureNames)
|
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
|
// logs with rubber spot
|
||||||
val spotDir = when(type) {
|
val spotDir = when(type) {
|
||||||
"dry_north", "wet_north" -> EnumFacing.NORTH
|
"dry_north", "wet_north" -> NORTH
|
||||||
"dry_south", "wet_south" -> EnumFacing.SOUTH
|
"dry_south", "wet_south" -> SOUTH
|
||||||
"dry_west", "wet_west" -> EnumFacing.WEST
|
"dry_west", "wet_west" -> WEST
|
||||||
"dry_east", "wet_east" -> EnumFacing.EAST
|
"dry_east", "wet_east" -> EAST
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTextureName(it) }
|
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTextureName(it) }
|
||||||
if (textureNames.any { it == "missingno" }) return null
|
if (textureNames.any { it == "missingno" }) return null
|
||||||
logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}")
|
log("IC2LogSupport: block state ${ctx.state.toString()}")
|
||||||
logger.log(Level.DEBUG, "IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
|
log("IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
|
||||||
return if (spotDir != null) RubberLogInfo.Key(logger, EnumFacing.Axis.Y, spotDir, textureNames) else SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
|
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 TechRebornLogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
|
/*
|
||||||
override val logger = BetterFoliageMod.logDetail
|
object TechRebornLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
|
||||||
|
override val logger = BetterFoliage.logDetail
|
||||||
|
|
||||||
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
|
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
|
||||||
// check for proper block class, existence of ModelBlock
|
// check for proper block class, existence of ModelBlock
|
||||||
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(state.block)) return null
|
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(ctx.state.block)) return null
|
||||||
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
|
val blockLoc = ctx.models.map { it as? Pair<BlockModel, ResourceLocation> }.firstOrNull() ?: return null
|
||||||
|
|
||||||
val hasSap = state.properties.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
|
val hasSap = ctx.state.values.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
|
||||||
val sapSide = state.properties.entries.find { it.key.getName() == "sapside" }?.value as? EnumFacing ?: return null
|
val sapSide = ctx.state.values.entries.find { it.key.getName() == "sapside" }?.value as? Direction ?: return null
|
||||||
|
|
||||||
logger.log(Level.DEBUG, "$logName: block state $state")
|
log("$logName: block state ${ctx.state}")
|
||||||
if (hasSap) {
|
if (hasSap) {
|
||||||
val textureNames = listOf("end", "end", "sapside", "side").map { blockLoc.first.resolveTextureName(it) }
|
val textureNames = listOf("end", "side", "sapside").map { blockLoc.first.resolveTextureName(it) }
|
||||||
logger.log(Level.DEBUG, "$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
|
log("$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
|
||||||
if (textureNames.all { it != "missingno" }) return RubberLogInfo.Key(logger, EnumFacing.Axis.Y, sapSide, textureNames)
|
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 {
|
} else {
|
||||||
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
|
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTextureName(it) }
|
||||||
logger.log(Level.DEBUG, "$logName: end=${textureNames[0]}, side=${textureNames[2]}")
|
log("$logName: end=${textureNames[0]}, side=${textureNames[1]}")
|
||||||
if (textureNames.all { it != "missingno" })return SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
|
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
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,81 +1,66 @@
|
|||||||
package mods.betterfoliage.client.integration
|
package mods.betterfoliage.client.integration
|
||||||
|
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.loader.Refs
|
import mods.betterfoliage.client.texture.LeafRegistry
|
||||||
|
import mods.octarinecore.*
|
||||||
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
import mods.octarinecore.metaprog.allAvailable
|
||||||
import net.minecraft.block.Block
|
import mods.octarinecore.metaprog.get
|
||||||
import net.minecraft.block.BlockTallGrass
|
import net.minecraft.block.BlockRenderType
|
||||||
import net.minecraft.block.state.IBlockState
|
import net.minecraft.block.BlockRenderType.MODEL
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.init.Blocks
|
import net.minecraft.block.Blocks
|
||||||
import net.minecraft.util.EnumBlockRenderType
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.util.EnumBlockRenderType.MODEL
|
import net.minecraft.world.IEnviromentBlockReader
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
import org.apache.logging.log4j.Level.INFO
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration for ShadersMod.
|
* Integration for ShadersMod.
|
||||||
*/
|
*/
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object ShadersModIntegration {
|
object ShadersModIntegration {
|
||||||
|
|
||||||
@JvmStatic val isAvailable = allAvailable(Refs.sVertexBuilder, Refs.pushEntity_state, Refs.pushEntity_num, Refs.popEntity)
|
@JvmStatic val isAvailable = allAvailable(SVertexBuilder, SVertexBuilder.pushState, SVertexBuilder.popState, BlockAliases.getAliasBlockId)
|
||||||
|
|
||||||
val grassDefaultBlockId = blockIdFor(Blocks.TALLGRASS.defaultState.withProperty(BlockTallGrass.TYPE, BlockTallGrass.EnumType.GRASS))
|
val defaultLeaves = Blocks.OAK_LEAVES.defaultState
|
||||||
val leavesDefaultBlockId = blockIdFor(Blocks.LEAVES.defaultState)
|
val defaultGrass = Blocks.GRASS.defaultState
|
||||||
fun blockIdFor(blockState: IBlockState) = Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535
|
|
||||||
|
|
||||||
// fun entityDataFor(blockState: IBlockState) =
|
|
||||||
// (Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535) //or
|
|
||||||
// ((blockState.renderType.ordinal.toLong() and 65535) shl 16) or
|
|
||||||
// (blockState.block.getMetaFromState(blockState).toLong() shl 32)
|
|
||||||
|
|
||||||
fun logEntityData(name: String, blockState: IBlockState) {
|
|
||||||
val blockId = Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535
|
|
||||||
val meta = blockState.renderType.ordinal.toLong() and 65535
|
|
||||||
val renderType = blockState.renderType.ordinal.toLong() and 65535
|
|
||||||
Client.log(INFO, "ShadersMod integration for $name")
|
|
||||||
Client.log(INFO, " blockState=$blockState")
|
|
||||||
Client.log(INFO, " blockId=$blockId, meta=$meta, type=$renderType")
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Called from transformed ShadersMod code.
|
* Called from transformed ShadersMod code.
|
||||||
* @see mods.betterfoliage.loader.BetterFoliageTransformer
|
* @see mods.betterfoliage.loader.BetterFoliageTransformer
|
||||||
*/
|
*/
|
||||||
@JvmStatic fun getBlockIdOverride(original: Long, blockState: IBlockState): Long {
|
@JvmStatic fun getBlockStateOverride(state: BlockState, world: IEnviromentBlockReader, pos: BlockPos): BlockState {
|
||||||
if (Config.blocks.leavesClasses.matchesClass(blockState.block)) return Config.shaders.leavesId
|
if (LeafRegistry[state, world, pos] != null) return defaultLeaves
|
||||||
if (Config.blocks.crops.matchesClass(blockState.block)) return Config.shaders.grassId
|
if (BlockConfig.crops.matchesClass(state.block)) return defaultGrass
|
||||||
return original
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Client.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
|
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. */
|
/** Quads rendered inside this block will use the given block entity data in shader programs. */
|
||||||
inline fun renderAs(blockId: Long, renderType: EnumBlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) {
|
inline fun renderAs(ctx: CombinedContext, state: BlockState, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) {
|
||||||
val blockData = blockId or (renderType.ordinal shl 16).toLong()
|
if (isAvailable && enabled) {
|
||||||
if ((isAvailable && enabled)) {
|
val buffer = ctx.renderCtx.renderBuffer
|
||||||
val vertexBuilder = Refs.sVertexBuilder.get(renderer)!!
|
val aliasBlockId = BlockAliases.getAliasBlockId.invokeStatic(state)
|
||||||
Refs.pushEntity_num.invoke(vertexBuilder, blockId)
|
val sVertexBuilder = buffer[BufferBuilder_sVertexBuilder]
|
||||||
|
SVertexBuilder.pushState.invoke(sVertexBuilder, aliasBlockId)
|
||||||
func()
|
func()
|
||||||
Refs.popEntity.invoke(vertexBuilder)
|
SVertexBuilder.popState.invoke(sVertexBuilder)
|
||||||
} else {
|
} else {
|
||||||
func()
|
func()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Quads rendered inside this block will use the given block entity data in shader programs. */
|
|
||||||
inline fun renderAs(state: IBlockState, renderType: EnumBlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
|
||||||
renderAs(blockIdFor(state), renderType, renderer, enabled, func)
|
|
||||||
|
|
||||||
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
|
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
|
||||||
inline fun grass(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
inline fun grass(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
|
||||||
renderAs(Config.shaders.grassId, MODEL, renderer, enabled, func)
|
renderAs(ctx, defaultGrass, MODEL, enabled, func)
|
||||||
|
|
||||||
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
|
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
|
||||||
inline fun leaves(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
inline fun leaves(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
|
||||||
renderAs(Config.shaders.leavesId, MODEL, renderer, enabled, func)
|
renderAs(ctx, defaultLeaves, MODEL, enabled, func)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import mods.betterfoliage.client.texture.LeafParticleRegistry
|
|||||||
import mods.betterfoliage.client.texture.LeafRegistry
|
import mods.betterfoliage.client.texture.LeafRegistry
|
||||||
import mods.octarinecore.PI2
|
import mods.octarinecore.PI2
|
||||||
import mods.octarinecore.client.render.AbstractEntityFX
|
import mods.octarinecore.client.render.AbstractEntityFX
|
||||||
import mods.octarinecore.client.render.HSB
|
import mods.octarinecore.client.render.lighting.HSB
|
||||||
import mods.octarinecore.common.Double3
|
import mods.octarinecore.common.Double3
|
||||||
import mods.octarinecore.minmax
|
import mods.octarinecore.minmax
|
||||||
import mods.octarinecore.random
|
import mods.octarinecore.random
|
||||||
@@ -15,18 +15,22 @@ import net.minecraft.util.math.BlockPos
|
|||||||
import net.minecraft.util.math.MathHelper
|
import net.minecraft.util.math.MathHelper
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
import net.minecraftforge.common.MinecraftForge
|
import net.minecraftforge.common.MinecraftForge
|
||||||
|
import net.minecraftforge.event.TickEvent
|
||||||
import net.minecraftforge.event.world.WorldEvent
|
import net.minecraftforge.event.world.WorldEvent
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||||
import net.minecraftforge.fml.common.gameevent.TickEvent
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.lwjgl.opengl.GL11
|
import org.lwjgl.opengl.GL11
|
||||||
import java.lang.Math.*
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
const val rotationFactor = PI2.toFloat() / 64.0f
|
||||||
class EntityFallingLeavesFX(world: World, pos: BlockPos) :
|
|
||||||
AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5) {
|
class EntityFallingLeavesFX(
|
||||||
|
world: World, pos: BlockPos
|
||||||
|
) : AbstractEntityFX(
|
||||||
|
world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5
|
||||||
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic val biomeBrightnessMultiplier = 0.5f
|
@JvmStatic val biomeBrightnessMultiplier = 0.5f
|
||||||
@@ -38,38 +42,40 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble
|
|||||||
var wasCollided = false
|
var wasCollided = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
particleMaxAge = MathHelper.floor(random(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0)
|
maxAge = MathHelper.floor(random(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0)
|
||||||
motionY = -Config.fallingLeaves.speed
|
motionY = -Config.fallingLeaves.speed
|
||||||
|
|
||||||
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
|
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
|
||||||
|
|
||||||
val state = world.getBlockState(pos)
|
val state = world.getBlockState(pos)
|
||||||
val blockColor = Minecraft.getMinecraft().blockColors.colorMultiplier(state, world, pos, 0)
|
|
||||||
val leafInfo = LeafRegistry[state, world, pos]
|
val leafInfo = LeafRegistry[state, world, pos]
|
||||||
|
val blockColor = Minecraft.getInstance().blockColors.getColor(state, world, pos, 0)
|
||||||
if (leafInfo != null) {
|
if (leafInfo != null) {
|
||||||
particleTexture = leafInfo.particleTextures[rand.nextInt(1024)]
|
sprite = leafInfo.particleTextures[rand.nextInt(1024)]
|
||||||
calculateParticleColor(leafInfo.averageColor, blockColor)
|
calculateParticleColor(leafInfo.averageColor, blockColor)
|
||||||
} else {
|
} else {
|
||||||
particleTexture = LeafParticleRegistry["default"][rand.nextInt(1024)]
|
sprite = LeafParticleRegistry["default"][rand.nextInt(1024)]
|
||||||
setColor(blockColor)
|
setColor(blockColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val isValid: Boolean get() = (particleTexture != null)
|
override val isValid: Boolean get() = (sprite != null)
|
||||||
|
|
||||||
override fun update() {
|
override fun update() {
|
||||||
if (rand.nextFloat() > 0.95f) rotPositive = !rotPositive
|
if (rand.nextFloat() > 0.95f) rotPositive = !rotPositive
|
||||||
if (particleAge > particleMaxAge - 20) particleAlpha = 0.05f * (particleMaxAge - particleAge)
|
if (age > maxAge - 20) particleAlpha = 0.05f * (maxAge - age)
|
||||||
|
|
||||||
if (onGround || wasCollided) {
|
if (onGround || wasCollided) {
|
||||||
velocity.setTo(0.0, 0.0, 0.0)
|
velocity.setTo(0.0, 0.0, 0.0)
|
||||||
if (!wasCollided) {
|
if (!wasCollided) {
|
||||||
particleAge = Math.max(particleAge, particleMaxAge - 20)
|
age = Math.max(age, maxAge - 20)
|
||||||
wasCollided = true
|
wasCollided = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
velocity.setTo(cos[particleRot], 0.0, sin[particleRot]).mul(Config.fallingLeaves.perturb)
|
velocity.setTo(cos[particleRot], 0.0, sin[particleRot]).mul(Config.fallingLeaves.perturb)
|
||||||
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed)
|
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed)
|
||||||
particleRot = (particleRot + (if (rotPositive) 1 else -1)) and 63
|
particleRot = (particleRot + (if (rotPositive) 1 else -1)) and 63
|
||||||
|
particleAngle = rotationFactor * particleRot.toFloat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +102,6 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object LeafWindTracker {
|
object LeafWindTracker {
|
||||||
var random = Random()
|
var random = Random()
|
||||||
val target = Double3.zero
|
val target = Double3.zero
|
||||||
@@ -108,7 +113,7 @@ object LeafWindTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun changeWind(world: World) {
|
fun changeWind(world: World) {
|
||||||
nextChange = world.worldInfo.worldTime + 120 + random.nextInt(80)
|
nextChange = world.worldInfo.gameTime + 120 + random.nextInt(80)
|
||||||
val direction = PI2 * random.nextDouble()
|
val direction = PI2 * random.nextDouble()
|
||||||
val speed = abs(random.nextGaussian()) * Config.fallingLeaves.windStrength +
|
val speed = abs(random.nextGaussian()) * Config.fallingLeaves.windStrength +
|
||||||
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * Config.fallingLeaves.stormStrength)
|
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * Config.fallingLeaves.stormStrength)
|
||||||
@@ -117,9 +122,9 @@ object LeafWindTracker {
|
|||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
fun handleWorldTick(event: TickEvent.ClientTickEvent) {
|
fun handleWorldTick(event: TickEvent.ClientTickEvent) {
|
||||||
if (event.phase == TickEvent.Phase.START) Minecraft.getMinecraft().world?.let { world ->
|
if (event.phase == TickEvent.Phase.START) Minecraft.getInstance().world?.let { world ->
|
||||||
// change target wind speed
|
// change target wind speed
|
||||||
if (world.worldInfo.worldTime >= nextChange) changeWind(world)
|
if (world.worldInfo.dayTime >= nextChange) changeWind(world)
|
||||||
|
|
||||||
// change current wind speed
|
// change current wind speed
|
||||||
val changeRate = if (world.isRaining) 0.015 else 0.005
|
val changeRate = if (world.isRaining) 0.015 else 0.005
|
||||||
@@ -132,5 +137,5 @@ object LeafWindTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isRemote) changeWind(event.world) }
|
fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isRemote) changeWind(event.world.world) }
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,19 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.octarinecore.client.render.AbstractEntityFX
|
import mods.octarinecore.client.render.AbstractEntityFX
|
||||||
|
import mods.octarinecore.client.resource.Atlas
|
||||||
import mods.octarinecore.client.resource.ResourceHandler
|
import mods.octarinecore.client.resource.ResourceHandler
|
||||||
import mods.octarinecore.common.Double3
|
import mods.octarinecore.common.Double3
|
||||||
import mods.octarinecore.forEachPairIndexed
|
import mods.octarinecore.forEachPairIndexed
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.client.renderer.BufferBuilder
|
||||||
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.util.math.MathHelper
|
import net.minecraft.util.math.MathHelper
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class EntityRisingSoulFX(world: World, pos: BlockPos) :
|
class EntityRisingSoulFX(world: World, pos: BlockPos) :
|
||||||
AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5) {
|
AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5) {
|
||||||
|
|
||||||
@@ -26,14 +23,14 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
|
|||||||
init {
|
init {
|
||||||
motionY = 0.1
|
motionY = 0.1
|
||||||
particleGravity = 0.0f
|
particleGravity = 0.0f
|
||||||
particleTexture = RisingSoulTextures.headIcons[rand.nextInt(256)]
|
sprite = RisingSoulTextures.headIcons[rand.nextInt(256)]
|
||||||
particleMaxAge = MathHelper.floor((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
|
maxAge = MathHelper.floor((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val isValid: Boolean get() = true
|
override val isValid: Boolean get() = true
|
||||||
|
|
||||||
override fun update() {
|
override fun update() {
|
||||||
val phase = (initialPhase + particleAge) % 64
|
val phase = (initialPhase + age) % 64
|
||||||
velocity.setTo(cos[phase] * Config.risingSoul.perturb, 0.1, sin[phase] * Config.risingSoul.perturb)
|
velocity.setTo(cos[phase] * Config.risingSoul.perturb, 0.1, sin[phase] * Config.risingSoul.perturb)
|
||||||
|
|
||||||
particleTrail.addFirst(currentPos.copy())
|
particleTrail.addFirst(currentPos.copy())
|
||||||
@@ -43,8 +40,8 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
|
override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
|
||||||
var alpha = Config.risingSoul.opacity
|
var alpha = Config.risingSoul.opacity.toFloat()
|
||||||
if (particleAge > particleMaxAge - 40) alpha *= (particleMaxAge - particleAge) / 40.0f
|
if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f
|
||||||
|
|
||||||
renderParticleQuad(worldRenderer, partialTickTime,
|
renderParticleQuad(worldRenderer, partialTickTime,
|
||||||
size = Config.risingSoul.headSize * 0.25,
|
size = Config.risingSoul.headSize * 0.25,
|
||||||
@@ -54,24 +51,19 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
|
|||||||
var scale = Config.risingSoul.trailSize * 0.25
|
var scale = Config.risingSoul.trailSize * 0.25
|
||||||
particleTrail.forEachPairIndexed { idx, current, previous ->
|
particleTrail.forEachPairIndexed { idx, current, previous ->
|
||||||
scale *= Config.risingSoul.sizeDecay
|
scale *= Config.risingSoul.sizeDecay
|
||||||
alpha *= Config.risingSoul.opacityDecay
|
alpha *= Config.risingSoul.opacityDecay.toFloat()
|
||||||
if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime,
|
if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime,
|
||||||
currentPos = current,
|
currentPos = current,
|
||||||
prevPos = previous,
|
prevPos = previous,
|
||||||
size = scale,
|
size = scale,
|
||||||
alpha = alpha,
|
alpha = alpha,
|
||||||
icon = RisingSoulTextures.trackIcon.icon!!
|
icon = RisingSoulTextures.trackIcon
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus, targetAtlas = Atlas.PARTICLES) {
|
||||||
object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID) {
|
val headIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "rising_soul_$idx") }
|
||||||
val headIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/rising_soul_%d")
|
val trackIcon by sprite(ResourceLocation(BetterFoliageMod.MOD_ID, "soul_track"))
|
||||||
val trackIcon = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/soul_track")
|
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
|
||||||
Client.log(INFO, "Registered ${headIcons.num} soul particle textures")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,10 @@ package mods.betterfoliage.client.render
|
|||||||
|
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.*
|
||||||
|
import mods.octarinecore.client.render.lighting.*
|
||||||
import mods.octarinecore.common.Double3
|
import mods.octarinecore.common.Double3
|
||||||
import mods.octarinecore.exchange
|
import mods.octarinecore.exchange
|
||||||
import net.minecraft.util.EnumFacing.*
|
import net.minecraft.util.Direction.*
|
||||||
import org.lwjgl.opengl.GL11
|
|
||||||
|
|
||||||
/** Weight of the same-side AO values on the outer edges of the 45deg chamfered column faces. */
|
/** Weight of the same-side AO values on the outer edges of the 45deg chamfered column faces. */
|
||||||
const val chamferAffinity = 0.9f
|
const val chamferAffinity = 0.9f
|
||||||
@@ -25,10 +25,10 @@ fun Model.columnSide(radius: Double, yBottom: Double, yTop: Double, transform: (
|
|||||||
verticalRectangle(x1 = 0.5 - radius, z1 = 0.5, x2 = 0.5 - halfRadius, z2 = 0.5 - halfRadius, yBottom = yBottom, yTop = yTop)
|
verticalRectangle(x1 = 0.5 - radius, z1 = 0.5, x2 = 0.5 - halfRadius, z2 = 0.5 - halfRadius, yBottom = yBottom, yTop = yTop)
|
||||||
.clampUV(minU = 0.5 - radius)
|
.clampUV(minU = 0.5 - radius)
|
||||||
.setAoShader(
|
.setAoShader(
|
||||||
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming))
|
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming.toFloat()))
|
||||||
)
|
)
|
||||||
.setAoShader(
|
.setAoShader(
|
||||||
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming)),
|
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming.toFloat())),
|
||||||
predicate = { v, vi -> vi == 1 || vi == 2}
|
predicate = { v, vi -> vi == 1 || vi == 2}
|
||||||
)
|
)
|
||||||
).forEach { transform(it.setFlatShader(FaceFlat(SOUTH))).add() }
|
).forEach { transform(it.setFlatShader(FaceFlat(SOUTH))).add() }
|
||||||
@@ -37,9 +37,9 @@ fun Model.columnSide(radius: Double, yBottom: Double, yTop: Double, transform: (
|
|||||||
verticalRectangle(x1 = 0.5 - halfRadius, z1 = 0.5 - halfRadius, x2 = 0.5, z2 = 0.5 - radius, yBottom = yBottom, yTop = yTop)
|
verticalRectangle(x1 = 0.5 - halfRadius, z1 = 0.5 - halfRadius, x2 = 0.5, z2 = 0.5 - radius, yBottom = yBottom, yTop = yTop)
|
||||||
.clampUV(maxU = radius - 0.5)
|
.clampUV(maxU = radius - 0.5)
|
||||||
.setAoShader(
|
.setAoShader(
|
||||||
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming)))
|
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming.toFloat())))
|
||||||
.setAoShader(
|
.setAoShader(
|
||||||
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming)),
|
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming.toFloat())),
|
||||||
predicate = { v, vi -> vi == 0 || vi == 3}
|
predicate = { v, vi -> vi == 0 || vi == 3}
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +1,42 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.client.Client
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.client.render.RenderDecorator
|
||||||
import mods.octarinecore.common.Rotation
|
|
||||||
import net.minecraft.block.material.Material
|
import net.minecraft.block.material.Material
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.tags.BlockTags
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import net.minecraft.world.biome.Biome
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
import org.apache.logging.log4j.Level.DEBUG
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
class RenderAlgae : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val noise = simplexNoise()
|
val noise = simplexNoise()
|
||||||
|
|
||||||
val algaeIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_algae_%d")
|
val algaeIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_algae_$idx") }
|
||||||
val algaeModels = modelSet(64, RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax))
|
val algaeModels = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax)(idx) }
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
override fun isEligible(ctx: CombinedContext) =
|
||||||
Client.log(INFO, "Registered ${algaeIcons.num} algae textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.algae.enabled &&
|
Config.enabled && Config.algae.enabled &&
|
||||||
ctx.blockState(up2).material == Material.WATER &&
|
ctx.state(up2).material == Material.WATER &&
|
||||||
ctx.blockState(up1).material == Material.WATER &&
|
ctx.state(up1).material == Material.WATER &&
|
||||||
Config.blocks.dirt.matchesClass(ctx.block) &&
|
BlockTags.DIRT_LIKE.contains(ctx.state.block) &&
|
||||||
ctx.biomeId in Config.algae.biomes &&
|
ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH || it == Biome.Category.RIVER } &&
|
||||||
noise[ctx.pos] < Config.algae.population
|
noise[ctx.pos] < Config.algae.population
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
override fun render(ctx: CombinedContext) {
|
||||||
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
ctx.render()
|
||||||
if (!layer.isCutout) return baseRender
|
if (!ctx.isCutout) return
|
||||||
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
val rand = ctx.semiRandomArray(3)
|
val rand = ctx.semiRandomArray(3)
|
||||||
|
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
|
||||||
ShadersModIntegration.grass(renderer, Config.algae.shaderWind) {
|
ctx.render(
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
algaeModels[rand[2]],
|
algaeModels[rand[2]],
|
||||||
Rotation.identity,
|
icon = { _, qi, _ -> algaeIcons[rand[qi and 1]] }
|
||||||
icon = { _, qi, _ -> algaeIcons[rand[qi and 1]]!! },
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,50 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.betterfoliage.client.render.column.ColumnTextureInfo
|
import mods.betterfoliage.client.render.column.ColumnTextureInfo
|
||||||
import mods.betterfoliage.client.render.column.SimpleColumnInfo
|
import mods.betterfoliage.client.render.column.SimpleColumnInfo
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.*
|
||||||
import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
|
import mods.octarinecore.client.render.lighting.*
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.client.resource.*
|
||||||
import mods.octarinecore.common.Rotation
|
import mods.octarinecore.common.Rotation
|
||||||
import mods.octarinecore.common.config.ModelTextureList
|
import mods.octarinecore.common.config.ModelTextureList
|
||||||
import mods.octarinecore.common.config.SimpleBlockMatcher
|
import mods.octarinecore.common.config.SimpleBlockMatcher
|
||||||
import net.minecraft.block.BlockCactus
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.block.state.IBlockState
|
import net.minecraft.block.CactusBlock
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.util.Direction.*
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import java.util.concurrent.CompletableFuture
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
|
|
||||||
object StandardCactusRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
|
object AsyncCactusDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
|
||||||
override val logger = BetterFoliageMod.logDetail
|
override val logger = BetterFoliage.logDetail
|
||||||
override val matchClasses = SimpleBlockMatcher(BlockCactus::class.java)
|
override val matchClasses = SimpleBlockMatcher(CactusBlock::class.java)
|
||||||
override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side"))
|
override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side"))
|
||||||
override fun processModel(state: IBlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, Axis.Y, textures)
|
override fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
|
||||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
val sprites = textures.map { atlas.sprite(ResourceLocation(it)) }
|
||||||
|
return atlas.mapAfter {
|
||||||
|
SimpleColumnInfo(
|
||||||
|
Axis.Y,
|
||||||
|
sprites[0].get(),
|
||||||
|
sprites[1].get(),
|
||||||
|
sprites.drop(2).map { it.get() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
BetterFoliage.blockSprites.providers.add(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
class RenderCactus : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val cactusStemRadius = 0.4375
|
val cactusStemRadius = 0.4375
|
||||||
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
|
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
|
||||||
|
|
||||||
val iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus")
|
val iconCross by sprite(ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus"))
|
||||||
val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus_arm_%d")
|
val iconArm = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx") }
|
||||||
|
|
||||||
val modelStem = model {
|
val modelStem = model {
|
||||||
horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5)
|
horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5)
|
||||||
@@ -67,45 +74,30 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|||||||
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll()
|
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
override fun isEligible(ctx: CombinedContext): Boolean =
|
||||||
Client.log(Level.INFO, "Registered ${iconArm.num} cactus arm textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext): Boolean =
|
|
||||||
Config.enabled && Config.cactus.enabled &&
|
Config.enabled && Config.cactus.enabled &&
|
||||||
Config.blocks.cactus.matchesClass(ctx.block)
|
AsyncCactusDiscovery[ctx] != null
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
override val onlyOnCutout get() = true
|
||||||
// render the whole block on the cutout layer
|
|
||||||
if (!layer.isCutout) return false
|
|
||||||
|
|
||||||
// get AO data
|
override fun render(ctx: CombinedContext) {
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
val icons = AsyncCactusDiscovery[ctx]!!
|
||||||
val icons = StandardCactusRegistry[ctx] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
|
|
||||||
modelRenderer.render(
|
ctx.render(
|
||||||
renderer,
|
|
||||||
modelStem.model,
|
modelStem.model,
|
||||||
Rotation.identity,
|
|
||||||
icon = { ctx, qi, q -> when(qi) {
|
icon = { ctx, qi, q -> when(qi) {
|
||||||
0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q)
|
0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q)
|
||||||
} },
|
} }
|
||||||
postProcess = noPost
|
|
||||||
)
|
)
|
||||||
modelRenderer.render(
|
ctx.render(
|
||||||
renderer,
|
modelCross[ctx.semiRandom(0)],
|
||||||
modelCross[ctx.random(0)],
|
icon = { _, _, _ -> iconCross }
|
||||||
Rotation.identity,
|
|
||||||
icon = { _, _, _ -> iconCross.icon!!},
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
)
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
ctx.render(
|
||||||
modelArm[ctx.random(1)],
|
modelArm[ctx.semiRandom(1)],
|
||||||
cactusArmRotation[ctx.random(2) % 4],
|
cactusArmRotation[ctx.semiRandom(2) % 4],
|
||||||
icon = { _, _, _ -> iconArm[ctx.random(3)]!!},
|
icon = { _, _, _ -> iconArm[ctx.semiRandom(3)] }
|
||||||
postProcess = noPost
|
|
||||||
)
|
)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,35 +2,44 @@ package mods.betterfoliage.client.render
|
|||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
|
import mods.betterfoliage.client.texture.GrassRegistry
|
||||||
import mods.octarinecore.client.render.BlockContext
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.client.render.withOffset
|
import mods.octarinecore.client.render.RenderDecorator
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.common.Int3
|
||||||
import mods.octarinecore.common.forgeDirsHorizontal
|
import mods.octarinecore.common.horizontalDirections
|
||||||
import mods.octarinecore.common.offset
|
import mods.octarinecore.common.offset
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.tags.BlockTags
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
class RenderConnectedGrass : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
override fun isEligible(ctx: CombinedContext) =
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.connectedGrass.enabled &&
|
Config.enabled && Config.connectedGrass.enabled &&
|
||||||
Config.blocks.dirt.matchesClass(ctx.block) &&
|
BlockTags.DIRT_LIKE.contains(ctx.state.block) &&
|
||||||
Config.blocks.grassClasses.matchesClass(ctx.block(up1)) &&
|
GrassRegistry[ctx, up1] != null &&
|
||||||
(Config.connectedGrass.snowEnabled || !ctx.blockState(up2).isSnow)
|
(Config.connectedGrass.snowEnabled || !ctx.state(up2).isSnow)
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
override fun render(ctx: CombinedContext) {
|
||||||
// if the block sides are not visible anyway, render normally
|
// if the block sides are not visible anyway, render normally
|
||||||
if (forgeDirsHorizontal.all { ctx.blockState(it.offset).isOpaqueCube }) return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
if (horizontalDirections.none { ctx.shouldSideBeRendered(it) }) {
|
||||||
|
ctx.render()
|
||||||
|
} else {
|
||||||
|
ctx.exchange(Int3.zero, up1).exchange(up1, up2).render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx.isSurroundedBy { it.isOpaqueCube } ) return false
|
class RenderConnectedGrassLog : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
return ctx.withOffset(Int3.zero, up1) {
|
|
||||||
ctx.withOffset(up1, up2) {
|
override fun isEligible(ctx: CombinedContext) =
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
|
||||||
}
|
BlockTags.DIRT_LIKE.contains(ctx.state.block) &&
|
||||||
|
LogRegistry[ctx, up1] != null
|
||||||
|
|
||||||
|
override fun render(ctx: CombinedContext) {
|
||||||
|
val grassDir = horizontalDirections.find { GrassRegistry[ctx, it.offset] != null }
|
||||||
|
if (grassDir == null) {
|
||||||
|
ctx.render()
|
||||||
|
} else {
|
||||||
|
ctx.exchange(Int3.zero, grassDir.offset).render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package mods.betterfoliage.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
|
|
||||||
import mods.octarinecore.client.render.BlockContext
|
|
||||||
import mods.octarinecore.client.render.withOffset
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.offset
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val grassCheckDirs = listOf(EAST, WEST, NORTH, SOUTH)
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
|
|
||||||
Config.blocks.dirt.matchesClass(ctx.block) &&
|
|
||||||
Config.blocks.logClasses.matchesClass(ctx.block(up1))
|
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
val grassDir = grassCheckDirs.find {
|
|
||||||
Config.blocks.grassClasses.matchesClass(ctx.block(it.offset))
|
|
||||||
} ?: return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
|
|
||||||
return ctx.withOffset(Int3.zero, grassDir.offset) {
|
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,28 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.client.Client
|
||||||
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.*
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.client.render.lighting.*
|
||||||
import mods.octarinecore.common.forgeDirOffsets
|
import mods.octarinecore.common.allDirections
|
||||||
import mods.octarinecore.common.forgeDirs
|
|
||||||
import mods.octarinecore.random
|
import mods.octarinecore.random
|
||||||
import net.minecraft.block.material.Material
|
import net.minecraft.block.material.Material
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.tags.BlockTags
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.util.Direction.Axis
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import net.minecraft.util.Direction.UP
|
||||||
import net.minecraft.util.EnumFacing.Axis
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.EnumFacing.UP
|
import net.minecraft.world.biome.Biome
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
import org.apache.logging.log4j.Level.DEBUG
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
class RenderCoral : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val noise = simplexNoise()
|
val noise = simplexNoise()
|
||||||
|
|
||||||
val coralIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_coral_%d")
|
val coralIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_coral_$idx") }
|
||||||
val crustIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_crust_%d")
|
val crustIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx") }
|
||||||
val coralModels = modelSet(64) { modelIdx ->
|
val coralModels = modelSet(64) { modelIdx ->
|
||||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
|
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
|
||||||
.scale(Config.coral.size).move(0.5 to UP)
|
.scale(Config.coral.size).move(0.5 to UP)
|
||||||
@@ -40,38 +38,27 @@ class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
override fun isEligible(ctx: CombinedContext) =
|
||||||
Client.log(INFO, "Registered ${coralIcons.num} coral textures")
|
|
||||||
Client.log(INFO, "Registered ${crustIcons.num} coral crust textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.coral.enabled &&
|
Config.enabled && Config.coral.enabled &&
|
||||||
(ctx.blockState(up2).material == Material.WATER || Config.coral.shallowWater) &&
|
(ctx.state(up2).material == Material.WATER || Config.coral.shallowWater) &&
|
||||||
ctx.blockState(up1).material == Material.WATER &&
|
ctx.state(up1).material == Material.WATER &&
|
||||||
Config.blocks.sand.matchesClass(ctx.block) &&
|
BlockTags.SAND.contains(ctx.state.block) &&
|
||||||
ctx.biomeId in Config.coral.biomes &&
|
ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH } &&
|
||||||
noise[ctx.pos] < Config.coral.population
|
noise[ctx.pos] < Config.coral.population
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
override fun render(ctx: CombinedContext) {
|
||||||
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
val baseRender = ctx.render()
|
||||||
if (!layer.isCutout) return baseRender
|
if (!ctx.isCutout) return
|
||||||
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
allDirections.forEachIndexed { idx, face ->
|
||||||
|
if (ctx.state(face).material == Material.WATER && ctx.semiRandom(idx) < Config.coral.chance) {
|
||||||
forgeDirs.forEachIndexed { idx, face ->
|
var variation = ctx.semiRandom(6)
|
||||||
if (!ctx.blockState(forgeDirOffsets[idx]).isOpaqueCube && blockContext.random(idx) < Config.coral.chance) {
|
ctx.render(
|
||||||
var variation = blockContext.random(6)
|
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
coralModels[variation++],
|
coralModels[variation++],
|
||||||
rotationFromUp[idx],
|
rotationFromUp[idx],
|
||||||
icon = { _, qi, _ -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!},
|
icon = { _, qi, _ -> if (qi == 4) crustIcons[variation] else coralIcons[variation + (qi and 1)] }
|
||||||
postProcess = noPost
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,29 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.betterfoliage.client.integration.OptifineCustomColors
|
import mods.betterfoliage.client.integration.OptifineCustomColors
|
||||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||||
|
import mods.betterfoliage.client.texture.GeneratedGrass
|
||||||
import mods.betterfoliage.client.texture.GrassRegistry
|
import mods.betterfoliage.client.texture.GrassRegistry
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.common.*
|
import mods.octarinecore.client.render.Model
|
||||||
|
import mods.octarinecore.client.render.RenderDecorator
|
||||||
|
import mods.octarinecore.client.render.fullCube
|
||||||
|
import mods.octarinecore.client.render.lighting.cornerAo
|
||||||
|
import mods.octarinecore.client.render.lighting.cornerFlat
|
||||||
|
import mods.octarinecore.client.render.lighting.faceOrientedAuto
|
||||||
|
import mods.octarinecore.common.Double3
|
||||||
|
import mods.octarinecore.common.allDirections
|
||||||
import mods.octarinecore.random
|
import mods.octarinecore.random
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.tags.BlockTags
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.util.Direction.Axis
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import net.minecraft.util.Direction.DOWN
|
||||||
import net.minecraft.util.EnumBlockRenderType
|
import net.minecraft.util.Direction.UP
|
||||||
import net.minecraft.util.EnumBlockRenderType.MODEL
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.EnumFacing.Axis
|
|
||||||
import net.minecraft.util.EnumFacing.UP
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
class RenderGrass : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx ->
|
@JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx ->
|
||||||
@@ -36,92 +38,65 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|||||||
|
|
||||||
val noise = simplexNoise()
|
val noise = simplexNoise()
|
||||||
|
|
||||||
val normalIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_grass_long_%d")
|
val normalIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx") }
|
||||||
val snowedIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_grass_snowed_%d")
|
val snowedIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_grass_snowed_$idx") }
|
||||||
val normalGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:blocks/tallgrass", "snowed" to false))
|
val normalGenIcon by sprite { GeneratedGrass(sprite = "minecraft:blocks/tall_grass_top", isSnowed = false).register(BetterFoliage.asyncPack) }
|
||||||
val snowedGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:blocks/tallgrass", "snowed" to true))
|
val snowedGenIcon by sprite { GeneratedGrass(sprite = "minecraft:blocks/tall_grass_top", isSnowed = true).register(BetterFoliage.asyncPack) }
|
||||||
|
|
||||||
val grassModels = modelSet(64, grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax))
|
val grassModels = modelSet(64) { idx -> grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) }
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
override fun isEligible(ctx: CombinedContext) =
|
||||||
Client.log(INFO, "Registered ${normalIcons.num} grass textures")
|
|
||||||
Client.log(INFO, "Registered ${snowedIcons.num} snowed grass textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled &&
|
Config.enabled &&
|
||||||
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
|
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
|
||||||
GrassRegistry[ctx] != null
|
GrassRegistry[ctx] != null
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
override val onlyOnCutout get() = true
|
||||||
// render the whole block on the cutout layer
|
|
||||||
if (!layer.isCutout) return false
|
|
||||||
|
|
||||||
val isConnected = ctx.block(down1).let {
|
override fun render(ctx: CombinedContext) {
|
||||||
Config.blocks.dirt.matchesClass(it) ||
|
val isConnected = BlockTags.DIRT_LIKE.contains(ctx.state(DOWN).block) || GrassRegistry[ctx, down1] != null
|
||||||
Config.blocks.grassClasses.matchesClass(it)
|
val isSnowed = ctx.state(UP).isSnow
|
||||||
}
|
|
||||||
val isSnowed = ctx.blockState(up1).isSnow
|
|
||||||
val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
|
val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
|
||||||
|
|
||||||
val grass = GrassRegistry[ctx]
|
val grass = GrassRegistry[ctx]!!
|
||||||
if (grass == null) {
|
|
||||||
// shouldn't happen
|
|
||||||
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
|
|
||||||
return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
}
|
|
||||||
val blockColor = OptifineCustomColors.getBlockColor(ctx)
|
val blockColor = OptifineCustomColors.getBlockColor(ctx)
|
||||||
|
|
||||||
if (connectedGrass) {
|
if (connectedGrass) {
|
||||||
// get full AO data
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
// check occlusion
|
// check occlusion
|
||||||
val isHidden = forgeDirs.map { ctx.blockState(it.offset).isOpaqueCube }
|
val isVisible = allDirections.map { ctx.shouldSideBeRendered(it) }
|
||||||
|
|
||||||
// render full grass block
|
// render full grass block
|
||||||
ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), MODEL, renderer) {
|
ctx.render(
|
||||||
modelRenderer.render(
|
fullCube,
|
||||||
renderer,
|
quadFilter = { qi, _ -> isVisible[qi] },
|
||||||
fullCube,
|
icon = { _, _, _ -> grass.grassTopTexture },
|
||||||
quadFilter = { qi, _ -> !isHidden[qi] },
|
postProcess = { ctx, _, _, _, _ ->
|
||||||
icon = { _, _, _ -> grass.grassTopTexture },
|
rotateUV(2)
|
||||||
postProcess = { ctx, _, _, _, _ ->
|
if (isSnowed) {
|
||||||
rotateUV(2)
|
if (!ctx.aoEnabled) setGrey(1.4f)
|
||||||
if (isSnowed) {
|
} else if (ctx.aoEnabled && grass.overrideColor == null) multiplyColor(blockColor)
|
||||||
if (!ctx.aoEnabled) setGrey(1.4f)
|
}
|
||||||
} else if (ctx.aoEnabled && grass.overrideColor == null) multiplyColor(blockColor)
|
)
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
ctx.render()
|
||||||
|
|
||||||
// get AO data only for block top
|
|
||||||
modelRenderer.updateShading(Int3.zero, topOnly)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Config.shortGrass.grassEnabled) return true
|
if (!Config.shortGrass.grassEnabled) return
|
||||||
if (isSnowed && !Config.shortGrass.snowEnabled) return true
|
if (isSnowed && !Config.shortGrass.snowEnabled) return
|
||||||
if (ctx.blockState(up1).isOpaqueCube) return true
|
if (ctx.offset(UP).isNormalCube) return
|
||||||
if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return true
|
if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return
|
||||||
|
|
||||||
// render grass quads
|
// render grass quads
|
||||||
val iconset = if (isSnowed) snowedIcons else normalIcons
|
val iconset = if (isSnowed) snowedIcons else normalIcons
|
||||||
val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon
|
val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon
|
||||||
val rand = ctx.semiRandomArray(2)
|
val rand = ctx.semiRandomArray(2)
|
||||||
|
|
||||||
ShadersModIntegration.grass(renderer, Config.shortGrass.shaderWind) {
|
ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
|
||||||
modelRenderer.render(
|
ctx.render(
|
||||||
renderer,
|
|
||||||
grassModels[rand[0]],
|
grassModels[rand[0]],
|
||||||
Rotation.identity,
|
translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
|
||||||
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
|
icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen else iconset[rand[qi and 1]] },
|
||||||
icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen.icon!! else iconset[rand[qi and 1]]!! },
|
|
||||||
postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) }
|
postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,27 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.betterfoliage.client.integration.OptifineCustomColors
|
import mods.betterfoliage.client.integration.OptifineCustomColors
|
||||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||||
import mods.betterfoliage.client.texture.LeafRegistry
|
import mods.betterfoliage.client.texture.LeafRegistry
|
||||||
import mods.octarinecore.PI2
|
import mods.octarinecore.PI2
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
|
import mods.octarinecore.client.render.RenderDecorator
|
||||||
|
import mods.octarinecore.client.render.lighting.FlatOffset
|
||||||
|
import mods.octarinecore.client.render.lighting.cornerAoMaxGreen
|
||||||
|
import mods.octarinecore.client.render.lighting.edgeOrientedAuto
|
||||||
import mods.octarinecore.common.Double3
|
import mods.octarinecore.common.Double3
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.common.Int3
|
||||||
import mods.octarinecore.common.Rotation
|
import mods.octarinecore.common.allDirections
|
||||||
import mods.octarinecore.common.vec
|
import mods.octarinecore.common.vec
|
||||||
import mods.octarinecore.random
|
import mods.octarinecore.random
|
||||||
import net.minecraft.block.material.Material
|
import net.minecraft.util.Direction.UP
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing.DOWN
|
|
||||||
import net.minecraft.util.EnumFacing.UP
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import java.lang.Math.cos
|
import java.lang.Math.cos
|
||||||
import java.lang.Math.sin
|
import java.lang.Math.sin
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
class RenderLeaves : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val leavesModel = model {
|
val leavesModel = model {
|
||||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
|
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
|
||||||
@@ -34,7 +30,7 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|||||||
.scale(Config.leaves.size)
|
.scale(Config.leaves.size)
|
||||||
.toCross(UP).addAll()
|
.toCross(UP).addAll()
|
||||||
}
|
}
|
||||||
val snowedIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_leaves_snowed_%d")
|
val snowedIcon = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx") }
|
||||||
|
|
||||||
val perturbs = vectorSet(64) { idx ->
|
val perturbs = vectorSet(64) { idx ->
|
||||||
val angle = PI2 * idx / 64.0
|
val angle = PI2 * idx / 64.0
|
||||||
@@ -42,36 +38,28 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|||||||
UP.vec * random(-1.0, 1.0) * Config.leaves.vOffset
|
UP.vec * random(-1.0, 1.0) * Config.leaves.vOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
override fun isEligible(ctx: CombinedContext) =
|
||||||
Config.enabled &&
|
Config.enabled &&
|
||||||
Config.leaves.enabled &&
|
Config.leaves.enabled &&
|
||||||
LeafRegistry[ctx] != null &&
|
LeafRegistry[ctx] != null &&
|
||||||
!(Config.leaves.hideInternal && ctx.isSurroundedBy { it.isFullCube || it.material == Material.LEAVES } )
|
!(Config.leaves.hideInternal && allDirections.all { ctx.offset(it).isNormalCube } )
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
override val onlyOnCutout get() = true
|
||||||
val isSnowed = ctx.blockState(up1).material.let {
|
|
||||||
it == Material.SNOW || it == Material.CRAFTED_SNOW
|
override fun render(ctx: CombinedContext) {
|
||||||
}
|
val isSnowed = ctx.state(UP).isSnow
|
||||||
val leafInfo = LeafRegistry[ctx]
|
val leafInfo = LeafRegistry[ctx]!!
|
||||||
if (leafInfo == null) {
|
|
||||||
// shouldn't happen
|
|
||||||
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
|
|
||||||
return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
}
|
|
||||||
val blockColor = OptifineCustomColors.getBlockColor(ctx)
|
val blockColor = OptifineCustomColors.getBlockColor(ctx)
|
||||||
|
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
ctx.render(force = true)
|
||||||
if (!layer.isCutout) return true
|
|
||||||
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
ShadersModIntegration.leaves(ctx) {
|
||||||
ShadersModIntegration.leaves(renderer) {
|
|
||||||
val rand = ctx.semiRandomArray(2)
|
val rand = ctx.semiRandomArray(2)
|
||||||
(if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation ->
|
(if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation ->
|
||||||
modelRenderer.render(
|
ctx.render(
|
||||||
renderer,
|
|
||||||
leavesModel.model,
|
leavesModel.model,
|
||||||
rotation,
|
rotation,
|
||||||
ctx.blockCenter + perturbs[rand[0]],
|
translation = ctx.blockCenter + perturbs[rand[0]],
|
||||||
icon = { _, _, _ -> leafInfo.roundLeafTexture },
|
icon = { _, _, _ -> leafInfo.roundLeafTexture },
|
||||||
postProcess = { _, _, _, _, _ ->
|
postProcess = { _, _, _, _, _ ->
|
||||||
rotateUV(rand[1])
|
rotateUV(rand[1])
|
||||||
@@ -79,16 +67,12 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (isSnowed && Config.leaves.snowEnabled) modelRenderer.render(
|
if (isSnowed && Config.leaves.snowEnabled) ctx.render(
|
||||||
renderer,
|
|
||||||
leavesModel.model,
|
leavesModel.model,
|
||||||
Rotation.identity,
|
translation = ctx.blockCenter + perturbs[rand[0]],
|
||||||
ctx.blockCenter + perturbs[rand[0]],
|
icon = { _, _, _ -> snowedIcon[rand[1]] },
|
||||||
icon = { _, _, _ -> snowedIcon[rand[1]]!! },
|
|
||||||
postProcess = whitewash
|
postProcess = whitewash
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,18 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
|
import mods.octarinecore.client.render.RenderDecorator
|
||||||
|
import mods.octarinecore.client.render.lighting.FlatOffsetNoColor
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.common.Int3
|
||||||
import mods.octarinecore.common.Rotation
|
import net.minecraft.util.Direction.DOWN
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.util.Direction.UP
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.EnumFacing.DOWN
|
|
||||||
import net.minecraft.util.EnumFacing.UP
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
class RenderLilypad : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val rootModel = model {
|
val rootModel = model {
|
||||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5)
|
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5)
|
||||||
@@ -30,50 +25,32 @@ class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|||||||
.setFlatShader(FlatOffsetNoColor(Int3.zero))
|
.setFlatShader(FlatOffsetNoColor(Int3.zero))
|
||||||
.toCross(UP).addAll()
|
.toCross(UP).addAll()
|
||||||
}
|
}
|
||||||
val rootIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_roots_%d")
|
val rootIcon = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_roots_$idx") }
|
||||||
val flowerIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_flower_%d")
|
val flowerIcon = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_flower_$idx") }
|
||||||
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset }
|
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset }
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
override fun isEligible(ctx: CombinedContext): Boolean =
|
||||||
Client.log(Level.INFO, "Registered ${rootIcon.num} lilypad root textures")
|
|
||||||
Client.log(Level.INFO, "Registered ${flowerIcon.num} lilypad flower textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext): Boolean =
|
|
||||||
Config.enabled && Config.lilypad.enabled &&
|
Config.enabled && Config.lilypad.enabled &&
|
||||||
Config.blocks.lilypad.matchesClass(ctx.block)
|
BlockConfig.lilypad.matchesClass(ctx.state.block)
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
override fun render(ctx: CombinedContext) {
|
||||||
// render the whole block on the cutout layer
|
ctx.render()
|
||||||
if (!layer.isCutout) return false
|
|
||||||
|
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
val rand = ctx.semiRandomArray(5)
|
val rand = ctx.semiRandomArray(5)
|
||||||
|
ShadersModIntegration.grass(ctx) {
|
||||||
ShadersModIntegration.grass(renderer) {
|
ctx.render(
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
|
||||||
rootModel.model,
|
rootModel.model,
|
||||||
Rotation.identity,
|
translation = ctx.blockCenter.add(perturbs[rand[2]]),
|
||||||
ctx.blockCenter.add(perturbs[rand[2]]),
|
|
||||||
forceFlat = true,
|
forceFlat = true,
|
||||||
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! },
|
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]] }
|
||||||
postProcess = noPost
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rand[3] < Config.lilypad.flowerChance) modelRenderer.render(
|
if (rand[3] < Config.lilypad.flowerChance) ctx.render(
|
||||||
renderer,
|
|
||||||
flowerModel.model,
|
flowerModel.model,
|
||||||
Rotation.identity,
|
translation = ctx.blockCenter.add(perturbs[rand[4]]),
|
||||||
ctx.blockCenter.add(perturbs[rand[4]]),
|
|
||||||
forceFlat = true,
|
forceFlat = true,
|
||||||
icon = { _, _, _ -> flowerIcon[rand[0]]!! },
|
icon = { _, _, _ -> flowerIcon[rand[0]] }
|
||||||
postProcess = noPost
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,33 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
||||||
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.betterfoliage.client.render.column.AbstractRenderColumn
|
import mods.betterfoliage.client.render.column.AbstractRenderColumn
|
||||||
import mods.betterfoliage.client.render.column.ColumnRenderLayer
|
import mods.betterfoliage.client.render.column.ColumnRenderLayer
|
||||||
import mods.betterfoliage.client.render.column.ColumnTextureInfo
|
import mods.betterfoliage.client.render.column.ColumnTextureInfo
|
||||||
import mods.betterfoliage.client.render.column.SimpleColumnInfo
|
import mods.betterfoliage.client.render.column.SimpleColumnInfo
|
||||||
import mods.octarinecore.client.render.BlockContext
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.client.resource.*
|
import mods.octarinecore.client.resource.*
|
||||||
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
||||||
import mods.octarinecore.common.config.ModelTextureList
|
import mods.octarinecore.common.config.ModelTextureList
|
||||||
import mods.octarinecore.tryDefault
|
import mods.octarinecore.tryDefault
|
||||||
import net.minecraft.block.BlockLog
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.block.state.IBlockState
|
import net.minecraft.block.LogBlock
|
||||||
import net.minecraft.util.EnumFacing.Axis
|
import net.minecraft.util.Direction.Axis
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
import org.apache.logging.log4j.Level
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
|
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
|
|
||||||
override val addToCutout: Boolean get() = false
|
override val renderOnCutout: Boolean get() = false
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
override fun isEligible(ctx: CombinedContext) =
|
||||||
Config.enabled && Config.roundLogs.enabled &&
|
Config.enabled && Config.roundLogs.enabled &&
|
||||||
Config.blocks.logClasses.matchesClass(ctx.block)
|
LogRegistry[ctx] != null
|
||||||
|
|
||||||
override val overlayLayer = RoundLogOverlayLayer()
|
override val overlayLayer = RoundLogOverlayLayer()
|
||||||
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
|
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
|
||||||
@@ -37,26 +40,37 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
|
|||||||
|
|
||||||
class RoundLogOverlayLayer : ColumnRenderLayer() {
|
class RoundLogOverlayLayer : ColumnRenderLayer() {
|
||||||
override val registry: ModelRenderRegistry<ColumnTextureInfo> get() = LogRegistry
|
override val registry: ModelRenderRegistry<ColumnTextureInfo> get() = LogRegistry
|
||||||
override val blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) }
|
override val blockPredicate = { state: BlockState -> BlockConfig.logBlocks.matchesClass(state.block) }
|
||||||
override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logClasses.matchesClass(state.block) }
|
|
||||||
|
|
||||||
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
|
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
|
||||||
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
|
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
|
||||||
override val defaultToY: Boolean get() = Config.roundLogs.defaultY
|
override val defaultToY: Boolean get() = Config.roundLogs.defaultY
|
||||||
}
|
}
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object LogRegistry : ModelRenderRegistryRoot<ColumnTextureInfo>()
|
object LogRegistry : ModelRenderRegistryRoot<ColumnTextureInfo>()
|
||||||
|
|
||||||
object StandardLogRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
|
object AsyncLogDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
|
||||||
override val logger = BetterFoliageMod.logDetail
|
override val logger = BetterFoliage.logDetail
|
||||||
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.logClasses
|
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks
|
||||||
override val modelTextures: List<ModelTextureList> get() = Config.blocks.logModels.list
|
override val modelTextures: List<ModelTextureList> get() = BlockConfig.logModels.modelList
|
||||||
override fun processModel(state: IBlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, getAxis(state), textures)
|
|
||||||
|
|
||||||
fun getAxis(state: IBlockState): Axis? {
|
override fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo> {
|
||||||
val axis = tryDefault(null) { state.getValue(BlockLog.LOG_AXIS).toString() } ?:
|
val axis = getAxis(state)
|
||||||
state.properties.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
|
logger.log(Level.DEBUG, "$logName: axis $axis")
|
||||||
|
val spriteList = textures.map { atlas.sprite(ResourceLocation(it)) }
|
||||||
|
return atlas.mapAfter {
|
||||||
|
SimpleColumnInfo(
|
||||||
|
axis,
|
||||||
|
spriteList[0].get(),
|
||||||
|
spriteList[1].get(),
|
||||||
|
spriteList.drop(2).map { it.get() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAxis(state: BlockState): Axis? {
|
||||||
|
val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?:
|
||||||
|
state.values.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
|
||||||
return when (axis) {
|
return when (axis) {
|
||||||
"x" -> Axis.X
|
"x" -> Axis.X
|
||||||
"y" -> Axis.Y
|
"y" -> Axis.Y
|
||||||
@@ -64,4 +78,9 @@ object StandardLogRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
LogRegistry.registries.add(this)
|
||||||
|
BetterFoliage.blockSprites.providers.add(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,57 +1,39 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.client.render.BlockContext
|
import mods.octarinecore.client.render.RenderDecorator
|
||||||
import mods.octarinecore.client.render.modelRenderer
|
|
||||||
import mods.octarinecore.client.render.noPost
|
import mods.octarinecore.client.render.noPost
|
||||||
import mods.octarinecore.common.Double3
|
import mods.octarinecore.common.Double3
|
||||||
import mods.octarinecore.common.Rotation
|
import net.minecraft.util.Direction.UP
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
class RenderMycelium : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_mycel_%d")
|
val myceliumIcon = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx") }
|
||||||
val myceliumModel = modelSet(64, RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax))
|
val myceliumModel = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) }
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
override fun isEligible(ctx: CombinedContext): Boolean {
|
||||||
Client.log(INFO, "Registered ${myceliumIcon.num} mycelium textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext): Boolean {
|
|
||||||
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
|
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
|
||||||
return Config.blocks.mycelium.matchesClass(ctx.block)
|
return BlockConfig.mycelium.matchesClass(ctx.state.block)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
override fun render(ctx: CombinedContext) {
|
||||||
// render the whole block on the cutout layer
|
ctx.render()
|
||||||
if (!layer.isCutout) return false
|
if (!ctx.isCutout) return
|
||||||
|
|
||||||
val isSnowed = ctx.blockState(up1).isSnow
|
|
||||||
|
|
||||||
renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
|
||||||
|
|
||||||
if (isSnowed && !Config.shortGrass.snowEnabled) return true
|
|
||||||
if (ctx.blockState(up1).isOpaqueCube) return true
|
|
||||||
|
|
||||||
|
val isSnowed = ctx.state(UP).isSnow
|
||||||
|
if (isSnowed && !Config.shortGrass.snowEnabled) return
|
||||||
|
if (ctx.offset(UP).isNormalCube) return
|
||||||
val rand = ctx.semiRandomArray(2)
|
val rand = ctx.semiRandomArray(2)
|
||||||
modelRenderer.render(
|
|
||||||
renderer,
|
ctx.render(
|
||||||
myceliumModel[rand[0]],
|
myceliumModel[rand[0]],
|
||||||
Rotation.identity,
|
translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
|
||||||
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
|
icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]] },
|
||||||
icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]]!! },
|
|
||||||
postProcess = if (isSnowed) whitewash else noPost
|
postProcess = if (isSnowed) whitewash else noPost
|
||||||
)
|
)
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,22 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.client.render.RenderDecorator
|
||||||
import mods.octarinecore.common.Rotation
|
import mods.octarinecore.client.render.lighting.cornerAo
|
||||||
|
import mods.octarinecore.client.render.lighting.cornerFlat
|
||||||
|
import mods.octarinecore.client.render.lighting.faceOrientedAuto
|
||||||
import mods.octarinecore.random
|
import mods.octarinecore.random
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.block.Blocks
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.util.Direction.Axis
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import net.minecraft.util.Direction.DOWN
|
||||||
import net.minecraft.util.EnumFacing.*
|
import net.minecraft.util.Direction.UP
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level.INFO
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
class RenderNetherrack : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val netherrackIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_netherrack_%d")
|
val netherrackIcon = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx") }
|
||||||
val netherrackModel = modelSet(64) { modelIdx ->
|
val netherrackModel = modelSet(64) { modelIdx ->
|
||||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yTop = -0.5,
|
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yTop = -0.5,
|
||||||
yBottom = -0.5 - random(Config.netherrack.heightMin, Config.netherrack.heightMax))
|
yBottom = -0.5 - random(Config.netherrack.heightMin, Config.netherrack.heightMax))
|
||||||
@@ -28,32 +26,18 @@ class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
override fun isEligible(ctx: CombinedContext) =
|
||||||
Client.log(INFO, "Registered ${netherrackIcon.num} netherrack textures")
|
Config.enabled && Config.netherrack.enabled && ctx.state.block == Blocks.NETHERRACK
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext): Boolean {
|
override fun render(ctx: CombinedContext) {
|
||||||
if (!Config.enabled || !Config.netherrack.enabled) return false
|
ctx.render()
|
||||||
return Config.blocks.netherrack.matchesClass(ctx.block)
|
if (!ctx.isCutout) return
|
||||||
}
|
if (ctx.offset(DOWN).isNormalCube) return
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
|
||||||
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
if (!layer.isCutout) return baseRender
|
|
||||||
|
|
||||||
if (ctx.blockState(down1).isOpaqueCube) return baseRender
|
|
||||||
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
val rand = ctx.semiRandomArray(2)
|
val rand = ctx.semiRandomArray(2)
|
||||||
modelRenderer.render(
|
ctx.render(
|
||||||
renderer,
|
|
||||||
netherrackModel[rand[0]],
|
netherrackModel[rand[0]],
|
||||||
Rotation.identity,
|
icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]] }
|
||||||
icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]]!! },
|
|
||||||
postProcess = noPost
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.client.render.RenderDecorator
|
||||||
import mods.octarinecore.common.Rotation
|
import mods.octarinecore.client.render.lighting.FlatOffsetNoColor
|
||||||
|
import mods.octarinecore.client.resource.CenteredSprite
|
||||||
import mods.octarinecore.random
|
import mods.octarinecore.random
|
||||||
import net.minecraft.block.material.Material
|
import net.minecraft.block.material.Material
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.tags.BlockTags
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.util.Direction.UP
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.EnumFacing.UP
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
class RenderReeds : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
|
||||||
class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|
||||||
|
|
||||||
val noise = simplexNoise()
|
val noise = simplexNoise()
|
||||||
val reedIcons = iconSet(Client.genReeds.generatedResource("${BetterFoliageMod.LEGACY_DOMAIN}:blocks/better_reed_%d"))
|
val reedIcons = spriteSetTransformed(
|
||||||
|
check = { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_reed_$idx") },
|
||||||
|
register = { CenteredSprite(it).register(BetterFoliage.asyncPack) }
|
||||||
|
)
|
||||||
val reedModels = modelSet(64) { modelIdx ->
|
val reedModels = modelSet(64) { modelIdx ->
|
||||||
val height = random(Config.reed.heightMin, Config.reed.heightMax)
|
val height = random(Config.reed.heightMin, Config.reed.heightMax)
|
||||||
val waterline = 0.875f
|
val waterline = 0.875f
|
||||||
@@ -40,35 +39,27 @@ class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun afterPreStitch() {
|
override fun isEligible(ctx: CombinedContext) =
|
||||||
Client.log(Level.INFO, "Registered ${reedIcons.num} reed textures")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEligible(ctx: BlockContext) =
|
|
||||||
Config.enabled && Config.reed.enabled &&
|
Config.enabled && Config.reed.enabled &&
|
||||||
ctx.blockState(up2).material == Material.AIR &&
|
ctx.state(up2).material == Material.AIR &&
|
||||||
ctx.blockState(up1).material == Material.WATER &&
|
ctx.state(UP).material == Material.WATER &&
|
||||||
Config.blocks.dirt.matchesClass(ctx.block) &&
|
BlockTags.DIRT_LIKE.contains(ctx.state.block) &&
|
||||||
ctx.biomeId in Config.reed.biomes &&
|
ctx.biome.let { it.downfall > Config.reed.minBiomeRainfall && it.defaultTemperature >= Config.reed.minBiomeTemp } &&
|
||||||
noise[ctx.pos] < Config.reed.population
|
noise[ctx.pos] < Config.reed.population
|
||||||
|
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
override val onlyOnCutout get() = false
|
||||||
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
|
|
||||||
if (!layer.isCutout) return baseRender
|
|
||||||
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
override fun render(ctx: CombinedContext) {
|
||||||
|
ctx.render()
|
||||||
|
if (!ctx.isCutout) return
|
||||||
|
|
||||||
val iconVar = ctx.random(1)
|
val iconVar = ctx.semiRandom(1)
|
||||||
ShadersModIntegration.grass(renderer, Config.reed.shaderWind) {
|
ShadersModIntegration.grass(ctx, Config.reed.shaderWind) {
|
||||||
modelRenderer.render(
|
ctx.render(
|
||||||
renderer,
|
reedModels[ctx.semiRandom(0)],
|
||||||
reedModels[ctx.random(0)],
|
|
||||||
Rotation.identity,
|
|
||||||
forceFlat = true,
|
forceFlat = true,
|
||||||
icon = { _, _, _ -> reedIcons[iconVar]!! },
|
icon = { _, _, _ -> reedIcons[iconVar] }
|
||||||
postProcess = noPost
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,20 @@
|
|||||||
package mods.betterfoliage.client.render
|
package mods.betterfoliage.client.render
|
||||||
|
|
||||||
import mods.octarinecore.PI2
|
import mods.octarinecore.PI2
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.Model
|
||||||
|
import mods.octarinecore.client.render.lighting.PostProcessLambda
|
||||||
|
import mods.octarinecore.client.render.Quad
|
||||||
import mods.octarinecore.common.Double3
|
import mods.octarinecore.common.Double3
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.common.Int3
|
||||||
import mods.octarinecore.common.Rotation
|
import mods.octarinecore.common.Rotation
|
||||||
import mods.octarinecore.common.times
|
import mods.octarinecore.common.times
|
||||||
import net.minecraft.block.Block
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.block.material.Material
|
import net.minecraft.block.material.Material
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import net.minecraft.util.BlockRenderLayer
|
||||||
import net.minecraft.util.EnumFacing
|
import net.minecraft.util.Direction
|
||||||
import net.minecraft.util.EnumFacing.*
|
import net.minecraft.util.Direction.*
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
val up1 = Int3(1 to UP)
|
val up1 = Int3(1 to UP)
|
||||||
val up2 = Int3(2 to UP)
|
val up2 = Int3(2 to UP)
|
||||||
@@ -25,15 +28,15 @@ val denseLeavesRot = arrayOf(Rotation.identity, Rotation.rot90[EAST.ordinal], Ro
|
|||||||
val whitewash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.4f) }
|
val whitewash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.4f) }
|
||||||
val greywash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.0f) }
|
val greywash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.0f) }
|
||||||
|
|
||||||
val IBlockState.isSnow: Boolean get() = material.let { it == Material.SNOW || it == Material.CRAFTED_SNOW }
|
val BlockState.isSnow: Boolean get() = material.let { it == Material.SNOW }
|
||||||
|
|
||||||
fun Quad.toCross(rotAxis: EnumFacing, trans: (Quad)->Quad) =
|
fun Quad.toCross(rotAxis: Direction, trans: (Quad)->Quad) =
|
||||||
(0..3).map { rotIdx ->
|
(0..3).map { rotIdx ->
|
||||||
trans(rotate(Rotation.rot90[rotAxis.ordinal] * rotIdx).mirrorUV(rotIdx > 1, false))
|
trans(rotate(Rotation.rot90[rotAxis.ordinal] * rotIdx).mirrorUV(rotIdx > 1, false))
|
||||||
}
|
}
|
||||||
fun Quad.toCross(rotAxis: EnumFacing) = toCross(rotAxis) { it }
|
fun Quad.toCross(rotAxis: Direction) = toCross(rotAxis) { it }
|
||||||
|
|
||||||
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(Math.cos(it), 0.0, Math.sin(it)) }
|
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(cos(it), 0.0, sin(it)) }
|
||||||
|
|
||||||
val rotationFromUp = arrayOf(
|
val rotationFromUp = arrayOf(
|
||||||
Rotation.rot90[EAST.ordinal] * 2,
|
Rotation.rot90[EAST.ordinal] * 2,
|
||||||
@@ -58,5 +61,5 @@ fun Model.mix(first: Model, second: Model, predicate: (Int)->Boolean) {
|
|||||||
|
|
||||||
val BlockRenderLayer.isCutout: Boolean get() = (this == BlockRenderLayer.CUTOUT) || (this == BlockRenderLayer.CUTOUT_MIPPED)
|
val BlockRenderLayer.isCutout: Boolean get() = (this == BlockRenderLayer.CUTOUT) || (this == BlockRenderLayer.CUTOUT_MIPPED)
|
||||||
|
|
||||||
fun IBlockState.canRenderInLayer(layer: BlockRenderLayer) = this.block.canRenderInLayer(this, layer)
|
fun BlockState.canRenderInLayer(layer: BlockRenderLayer) = this.block.canRenderInLayer(this, layer)
|
||||||
fun IBlockState.canRenderInCutout() = this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT) || this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT_MIPPED)
|
fun BlockState.canRenderInCutout() = this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT) || this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT_MIPPED)
|
||||||
@@ -1,33 +1,26 @@
|
|||||||
package mods.betterfoliage.client.render.column
|
package mods.betterfoliage.client.render.column
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.client.Client
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayLayer
|
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
||||||
import mods.betterfoliage.client.config.Config
|
|
||||||
import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs
|
import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs
|
||||||
import mods.betterfoliage.client.render.*
|
import mods.betterfoliage.client.render.*
|
||||||
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
|
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
|
||||||
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
|
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
|
||||||
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
|
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
|
||||||
import mods.octarinecore.client.render.*
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
import mods.octarinecore.client.resource.ModelRenderRegistry
|
import mods.octarinecore.client.render.Model
|
||||||
import mods.octarinecore.common.*
|
import mods.octarinecore.client.render.RenderDecorator
|
||||||
import net.minecraft.block.state.IBlockState
|
import mods.octarinecore.client.render.noPost
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import mods.octarinecore.common.Rotation
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import mods.octarinecore.common.face
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
import mods.octarinecore.common.rot
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import net.minecraft.block.BlockRenderType.MODEL
|
||||||
import net.minecraft.util.EnumBlockRenderType
|
import net.minecraft.util.Direction.*
|
||||||
import net.minecraft.util.EnumBlockRenderType.MODEL
|
import net.minecraftforge.eventbus.api.IEventBus
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) {
|
abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : RenderDecorator(modId, modBus) {
|
||||||
|
|
||||||
/** The rotations necessary to bring the models in position for the 4 quadrants */
|
/** The rotations necessary to bring the models in position for the 4 quadrants */
|
||||||
val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it }
|
val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it }
|
||||||
@@ -98,28 +91,25 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
|
|||||||
q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE))
|
q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE))
|
||||||
|
|
||||||
@Suppress("NON_EXHAUSTIVE_WHEN")
|
@Suppress("NON_EXHAUSTIVE_WHEN")
|
||||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
|
override fun render(ctx: CombinedContext) {
|
||||||
|
|
||||||
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx.world!!, ctx.pos)
|
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
|
||||||
when(roundLog) {
|
when(roundLog) {
|
||||||
ColumnLayerData.SkipRender -> return true
|
ColumnLayerData.SkipRender -> return
|
||||||
ColumnLayerData.NormalRender -> return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
ColumnLayerData.NormalRender -> return ctx.render()
|
||||||
ColumnLayerData.ResolveError, null -> {
|
ColumnLayerData.ResolveError, null -> {
|
||||||
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
|
BetterFoliage.logRenderError(ctx.state, ctx.pos)
|
||||||
return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
return ctx.render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if log axis is not defined and "Default to vertical" config option is not set, render normally
|
// if log axis is not defined and "Default to vertical" config option is not set, render normally
|
||||||
if ((roundLog as ColumnLayerData.SpecialRender).column.axis == null && !overlayLayer.defaultToY) {
|
if ((roundLog as ColumnLayerData.SpecialRender).column.axis == null && !overlayLayer.defaultToY) {
|
||||||
return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
return ctx.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
// get AO data
|
|
||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
|
||||||
|
|
||||||
val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal]
|
val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal]
|
||||||
renderAs(ctx.blockState(Int3.zero), MODEL, renderer) {
|
renderAs(ctx, MODEL) {
|
||||||
quadrantRotations.forEachIndexed { idx, quadrantRotation ->
|
quadrantRotations.forEachIndexed { idx, quadrantRotation ->
|
||||||
// set rotation for the current quadrant
|
// set rotation for the current quadrant
|
||||||
val rotation = baseRotation + quadrantRotation
|
val rotation = baseRotation + quadrantRotation
|
||||||
@@ -141,8 +131,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sideModel != null) modelRenderer.render(
|
if (sideModel != null) ctx.render(
|
||||||
renderer,
|
|
||||||
sideModel,
|
sideModel,
|
||||||
rotation,
|
rotation,
|
||||||
icon = roundLog.column.side,
|
icon = roundLog.column.side,
|
||||||
@@ -195,8 +184,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (upModel != null) modelRenderer.render(
|
if (upModel != null) ctx.render(
|
||||||
renderer,
|
|
||||||
upModel,
|
upModel,
|
||||||
rotation,
|
rotation,
|
||||||
icon = upIcon,
|
icon = upIcon,
|
||||||
@@ -206,8 +194,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (downModel != null) modelRenderer.render(
|
if (downModel != null) ctx.render(
|
||||||
renderer,
|
|
||||||
downModel,
|
downModel,
|
||||||
rotation,
|
rotation,
|
||||||
icon = downIcon,
|
icon = downIcon,
|
||||||
@@ -219,6 +206,5 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
package mods.betterfoliage.client.render.column
|
package mods.betterfoliage.client.render.column
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayLayer
|
import mods.betterfoliage.client.chunk.ChunkOverlayLayer
|
||||||
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
import mods.betterfoliage.client.chunk.ChunkOverlayManager
|
||||||
|
import mods.betterfoliage.client.chunk.dimType
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
|
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
|
||||||
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
|
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
|
||||||
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
|
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
|
||||||
import mods.betterfoliage.client.render.rotationFromUp
|
import mods.betterfoliage.client.render.rotationFromUp
|
||||||
import mods.octarinecore.client.render.BlockContext
|
import mods.octarinecore.client.render.BlockCtx
|
||||||
import mods.octarinecore.client.resource.ModelRenderRegistry
|
import mods.octarinecore.client.resource.ModelRenderRegistry
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.common.*
|
||||||
import mods.octarinecore.common.Rotation
|
import net.minecraft.block.BlockState
|
||||||
import mods.octarinecore.common.face
|
import net.minecraft.util.Direction.Axis
|
||||||
import mods.octarinecore.common.plus
|
import net.minecraft.util.Direction.AxisDirection
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.world.IBlockAccess
|
import net.minecraft.world.IEnviromentBlockReader
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
|
|
||||||
/** Index of SOUTH-EAST quadrant. */
|
/** Index of SOUTH-EAST quadrant. */
|
||||||
const val SE = 0
|
const val SE = 0
|
||||||
@@ -32,13 +30,11 @@ const val SW = 3
|
|||||||
/**
|
/**
|
||||||
* Sealed class hierarchy for all possible render outcomes
|
* Sealed class hierarchy for all possible render outcomes
|
||||||
*/
|
*/
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
sealed class ColumnLayerData {
|
sealed class ColumnLayerData {
|
||||||
/**
|
/**
|
||||||
* Data structure to cache texture and world neighborhood data relevant to column rendering
|
* Data structure to cache texture and world neighborhood data relevant to column rendering
|
||||||
*/
|
*/
|
||||||
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
|
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
data class SpecialRender(
|
data class SpecialRender(
|
||||||
val column: ColumnTextureInfo,
|
val column: ColumnTextureInfo,
|
||||||
val upType: BlockType,
|
val upType: BlockType,
|
||||||
@@ -52,15 +48,12 @@ sealed class ColumnLayerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Column block should not be rendered at all */
|
/** Column block should not be rendered at all */
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object SkipRender : ColumnLayerData()
|
object SkipRender : ColumnLayerData()
|
||||||
|
|
||||||
/** Column block must be rendered normally */
|
/** Column block must be rendered normally */
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object NormalRender : ColumnLayerData()
|
object NormalRender : ColumnLayerData()
|
||||||
|
|
||||||
/** Error while resolving render data, column block must be rendered normally */
|
/** Error while resolving render data, column block must be rendered normally */
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
object ResolveError : ColumnLayerData()
|
object ResolveError : ColumnLayerData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,29 +61,26 @@ sealed class ColumnLayerData {
|
|||||||
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
||||||
|
|
||||||
abstract val registry: ModelRenderRegistry<ColumnTextureInfo>
|
abstract val registry: ModelRenderRegistry<ColumnTextureInfo>
|
||||||
abstract val blockPredicate: (IBlockState)->Boolean
|
abstract val blockPredicate: (BlockState)->Boolean
|
||||||
abstract val surroundPredicate: (IBlockState) -> Boolean
|
|
||||||
abstract val connectSolids: Boolean
|
abstract val connectSolids: Boolean
|
||||||
abstract val lenientConnect: Boolean
|
abstract val lenientConnect: Boolean
|
||||||
abstract val defaultToY: Boolean
|
abstract val defaultToY: Boolean
|
||||||
|
|
||||||
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
|
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
|
||||||
|
|
||||||
override fun onBlockUpdate(world: IBlockAccess, pos: BlockPos) {
|
override fun onBlockUpdate(world: IEnviromentBlockReader, pos: BlockPos) {
|
||||||
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(this, pos + offset) }
|
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun calculate(world: IBlockAccess, pos: BlockPos) = calculate(BlockContext(world, pos))
|
override fun calculate(ctx: BlockCtx): ColumnLayerData {
|
||||||
|
if (allDirections.all { dir -> ctx.offset(dir).let { it.isNormalCube && registry[ctx] == null }}) return ColumnLayerData.SkipRender
|
||||||
fun calculate(ctx: BlockContext): ColumnLayerData {
|
|
||||||
if (ctx.isSurroundedBy(surroundPredicate)) return ColumnLayerData.SkipRender
|
|
||||||
val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
|
val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
|
||||||
|
|
||||||
// if log axis is not defined and "Default to vertical" config option is not set, render normally
|
// if log axis is not defined and "Default to vertical" config option is not set, render normally
|
||||||
val logAxis = columnTextures.axis ?: if (defaultToY) EnumFacing.Axis.Y else return ColumnLayerData.NormalRender
|
val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender
|
||||||
|
|
||||||
// check log neighborhood
|
// check log neighborhood
|
||||||
val baseRotation = rotationFromUp[(logAxis to EnumFacing.AxisDirection.POSITIVE).face.ordinal]
|
val baseRotation = rotationFromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal]
|
||||||
|
|
||||||
val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
|
val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
|
||||||
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
|
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
|
||||||
@@ -109,7 +99,7 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */
|
/** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */
|
||||||
fun Array<QuadrantType>.checkNeighbors(ctx: BlockContext, rotation: Rotation, logAxis: EnumFacing.Axis, yOff: Int): Array<QuadrantType> {
|
fun Array<QuadrantType>.checkNeighbors(ctx: BlockCtx, rotation: Rotation, logAxis: Axis, yOff: Int): Array<QuadrantType> {
|
||||||
val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1))
|
val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1))
|
||||||
val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0))
|
val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0))
|
||||||
val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1))
|
val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1))
|
||||||
@@ -176,13 +166,13 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
|
|||||||
/**
|
/**
|
||||||
* Get the type of the block at the given offset in a rotated reference frame.
|
* Get the type of the block at the given offset in a rotated reference frame.
|
||||||
*/
|
*/
|
||||||
fun BlockContext.blockType(rotation: Rotation, axis: EnumFacing.Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
|
fun BlockCtx.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
|
||||||
val offsetRot = offset.rotate(rotation)
|
val offsetRot = offset.rotate(rotation)
|
||||||
val state = blockState(offsetRot)
|
val state = state(offsetRot)
|
||||||
return if (!blockPredicate(state)) {
|
return if (!blockPredicate(state)) {
|
||||||
if (state.isOpaqueCube) SOLID else NONSOLID
|
if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
|
||||||
} else {
|
} else {
|
||||||
(registry[state, world!!, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) EnumFacing.Axis.Y else null)?.let {
|
(registry[state, world, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let {
|
||||||
if (it == axis) PARALLEL else PERPENDICULAR
|
if (it == axis) PARALLEL else PERPENDICULAR
|
||||||
} ?: SOLID
|
} ?: SOLID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,19 @@
|
|||||||
package mods.betterfoliage.client.render.column
|
package mods.betterfoliage.client.render.column
|
||||||
|
|
||||||
import mods.octarinecore.client.render.QuadIconResolver
|
import mods.octarinecore.client.render.lighting.QuadIconResolver
|
||||||
import mods.octarinecore.client.render.blockContext
|
|
||||||
import mods.octarinecore.client.resource.ModelRenderKey
|
|
||||||
import mods.octarinecore.client.resource.get
|
|
||||||
import mods.octarinecore.common.rotate
|
import mods.octarinecore.common.rotate
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
import net.minecraft.util.Direction.*
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
interface ColumnTextureInfo {
|
interface ColumnTextureInfo {
|
||||||
val axis: EnumFacing.Axis?
|
val axis: Axis?
|
||||||
val top: QuadIconResolver
|
val top: QuadIconResolver
|
||||||
val bottom: QuadIconResolver
|
val bottom: QuadIconResolver
|
||||||
val side: QuadIconResolver
|
val side: QuadIconResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
@SideOnly(Side.CLIENT)
|
|
||||||
open class SimpleColumnInfo(
|
open class SimpleColumnInfo(
|
||||||
override val axis: EnumFacing.Axis?,
|
override val axis: Axis?,
|
||||||
val topTexture: TextureAtlasSprite,
|
val topTexture: TextureAtlasSprite,
|
||||||
val bottomTexture: TextureAtlasSprite,
|
val bottomTexture: TextureAtlasSprite,
|
||||||
val sideTextures: List<TextureAtlasSprite>
|
val sideTextures: List<TextureAtlasSprite>
|
||||||
@@ -34,17 +25,8 @@ open class SimpleColumnInfo(
|
|||||||
override val top: QuadIconResolver = { _, _, _ -> topTexture }
|
override val top: QuadIconResolver = { _, _, _ -> topTexture }
|
||||||
override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture }
|
override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture }
|
||||||
override val side: QuadIconResolver = { ctx, idx, _ ->
|
override val side: QuadIconResolver = { ctx, idx, _ ->
|
||||||
val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
|
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation)
|
||||||
val sideIdx = if (sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0
|
val sideIdx = if (sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0
|
||||||
sideTextures[sideIdx]
|
sideTextures[sideIdx]
|
||||||
}
|
}
|
||||||
|
|
||||||
class Key(override val logger: Logger, val axis: EnumFacing.Axis?, val textures: List<String>) : ModelRenderKey<ColumnTextureInfo> {
|
|
||||||
override fun resolveSprites(atlas: TextureMap) = SimpleColumnInfo(
|
|
||||||
axis,
|
|
||||||
atlas[textures[0]] ?: atlas.missingSprite,
|
|
||||||
atlas[textures[1]] ?: atlas.missingSprite,
|
|
||||||
textures.drop(2).map { atlas[it] ?: atlas.missingSprite }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package mods.betterfoliage.client.texture
|
package mods.betterfoliage.client.texture
|
||||||
|
|
||||||
import mods.octarinecore.client.resource.*
|
import mods.octarinecore.client.resource.*
|
||||||
|
import net.minecraft.resources.IResourceManager
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
@@ -10,13 +11,13 @@ import java.awt.image.BufferedImage
|
|||||||
*
|
*
|
||||||
* @param[domain] Resource domain of generator
|
* @param[domain] Resource domain of generator
|
||||||
*/
|
*/
|
||||||
class GrassGenerator(domain: String) : TextureGenerator(domain) {
|
data class GeneratedGrass(val sprite: ResourceLocation, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) {
|
||||||
|
constructor(sprite: String, isSnowed: Boolean) : this(ResourceLocation(sprite), isSnowed)
|
||||||
|
|
||||||
override fun generate(params: ParameterList): BufferedImage? {
|
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
|
||||||
val target = targetResource(params)!!
|
|
||||||
val isSnowed = params["snowed"]?.toBoolean() ?: false
|
|
||||||
|
|
||||||
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
|
fun draw(resourceManager: IResourceManager): ByteArray {
|
||||||
|
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
|
||||||
|
|
||||||
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
|
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
val graphics = result.createGraphics()
|
val graphics = result.createGraphics()
|
||||||
@@ -25,7 +26,7 @@ class GrassGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
val frames = baseTexture.height / size
|
val frames = baseTexture.height / size
|
||||||
|
|
||||||
// iterate all frames
|
// iterate all frames
|
||||||
for (frame in 0 .. frames - 1) {
|
for (frame in 0 until frames) {
|
||||||
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
|
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
|
||||||
val grassFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
|
val grassFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
|
||||||
@@ -39,12 +40,11 @@ class GrassGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// blend with white if snowed
|
// blend with white if snowed
|
||||||
if (isSnowed && target.first == ResourceType.COLOR) {
|
if (isSnowed) {
|
||||||
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
|
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
|
||||||
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
|
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result.bytes
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,8 @@ package mods.betterfoliage.client.texture
|
|||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.octarinecore.client.resource.*
|
import mods.octarinecore.client.resource.*
|
||||||
import mods.octarinecore.stripStart
|
import net.minecraft.resources.IResource
|
||||||
|
import net.minecraft.resources.IResourceManager
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
@@ -10,22 +11,17 @@ import java.awt.image.BufferedImage
|
|||||||
* Generate round leaf textures from leaf block textures.
|
* Generate round leaf textures from leaf block textures.
|
||||||
* The base texture is tiled 2x2, then parts of it are made transparent by applying a mask to the alpha channel.
|
* The base texture is tiled 2x2, then parts of it are made transparent by applying a mask to the alpha channel.
|
||||||
*
|
*
|
||||||
* Generator parameter _type_: Leaf type (configurable by user). Different leaf types may have their own alpha mask.
|
* Different leaf types may have their own alpha mask.
|
||||||
*
|
*
|
||||||
* @param[domain] Resource domain of generator
|
* @param[domain] Resource domain of generator
|
||||||
*/
|
*/
|
||||||
class LeafGenerator(domain: String) : TextureGenerator(domain) {
|
data class GeneratedLeaf(val sprite: ResourceLocation, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) {
|
||||||
|
|
||||||
override fun generate(params: ParameterList): BufferedImage? {
|
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
|
||||||
val target = targetResource(params)!!
|
|
||||||
val leafType = params["type"] ?: "default"
|
|
||||||
|
|
||||||
val handDrawnLoc = target.second.stripStart("textures/").stripStart("blocks/").let {
|
fun draw(resourceManager: IResourceManager): ByteArray {
|
||||||
ResourceLocation(BetterFoliageMod.DOMAIN, "${it.namespace}/textures/blocks/${it.path}")
|
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
|
||||||
}
|
|
||||||
resourceManager[handDrawnLoc]?.loadImage()?.let { return it }
|
|
||||||
|
|
||||||
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
|
|
||||||
val size = baseTexture.width
|
val size = baseTexture.width
|
||||||
val frames = baseTexture.height / size
|
val frames = baseTexture.height / size
|
||||||
|
|
||||||
@@ -36,7 +32,7 @@ class LeafGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
val graphics = leafTexture.createGraphics()
|
val graphics = leafTexture.createGraphics()
|
||||||
|
|
||||||
// iterate all frames
|
// iterate all frames
|
||||||
for (frame in 0 .. frames - 1) {
|
for (frame in 0 until frames) {
|
||||||
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
|
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
|
||||||
val leafFrame = BufferedImage(size * 2, size * 2, BufferedImage.TYPE_4BYTE_ABGR)
|
val leafFrame = BufferedImage(size * 2, size * 2, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
|
||||||
@@ -49,8 +45,8 @@ class LeafGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// overlay alpha mask
|
// overlay alpha mask
|
||||||
if (target.first == ResourceType.COLOR && maskTexture != null) {
|
if (maskTexture != null) {
|
||||||
for (x in 0 .. size * 2 - 1) for (y in 0 .. size * 2 - 1) {
|
for (x in 0 until size * 2) for (y in 0 until size * 2) {
|
||||||
val basePixel = leafFrame[x, y].toLong() and 0xFFFFFFFFL
|
val basePixel = leafFrame[x, y].toLong() and 0xFFFFFFFFL
|
||||||
val maskPixel = maskTexture[scale(x), scale(y)].toLong() and 0xFF000000L or 0xFFFFFFL
|
val maskPixel = maskTexture[scale(x), scale(y)].toLong() and 0xFF000000L or 0xFFFFFFL
|
||||||
leafFrame[x, y] = (basePixel and maskPixel).toInt()
|
leafFrame[x, y] = (basePixel and maskPixel).toInt()
|
||||||
@@ -61,7 +57,7 @@ class LeafGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
graphics.drawImage(leafFrame, 0, size * frame * 2, null)
|
graphics.drawImage(leafFrame, 0, size * frame * 2, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
return leafTexture
|
return leafTexture.bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +67,20 @@ class LeafGenerator(domain: String) : TextureGenerator(domain) {
|
|||||||
* @param[maxSize] Preferred mask size.
|
* @param[maxSize] Preferred mask size.
|
||||||
*/
|
*/
|
||||||
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
|
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
|
||||||
ResourceLocation(BetterFoliageMod.DOMAIN, "textures/blocks/leafmask_${size}_${type}.png")
|
ResourceLocation(BetterFoliageMod.MOD_ID, "textures/blocks/leafmask_${size}_${type}.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a texture resource when multiple sizes may exist.
|
||||||
|
*
|
||||||
|
* @param[maxSize] Maximum size to consider. This value is progressively halved when searching for smaller versions.
|
||||||
|
* @param[maskPath] Location of the texture of the given size
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->ResourceLocation): IResource? {
|
||||||
|
var size = maxSize
|
||||||
|
val sizes = mutableListOf<Int>()
|
||||||
|
while(size > 2) { sizes.add(size); size /= 2 }
|
||||||
|
return sizes.map { resourceManager[maskPath(it)] }.filterNotNull().firstOrNull()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,21 @@
|
|||||||
package mods.betterfoliage.client.texture
|
package mods.betterfoliage.client.texture
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.betterfoliage.client.config.Config
|
||||||
import mods.octarinecore.client.render.BlockContext
|
import mods.octarinecore.client.render.lighting.HSB
|
||||||
import mods.octarinecore.client.render.HSB
|
import mods.octarinecore.client.resource.AtlasFuture
|
||||||
import mods.octarinecore.client.resource.*
|
import mods.octarinecore.client.resource.ConfigurableModelDiscovery
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.client.resource.ModelRenderRegistryRoot
|
||||||
|
import mods.octarinecore.client.resource.averageColor
|
||||||
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
||||||
import mods.octarinecore.common.config.IBlockMatcher
|
|
||||||
import mods.octarinecore.common.config.ModelTextureList
|
import mods.octarinecore.common.config.ModelTextureList
|
||||||
import mods.octarinecore.findFirst
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
import org.apache.logging.log4j.Level
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
import java.lang.Math.min
|
import java.lang.Math.min
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
const val defaultGrassColor = 0
|
const val defaultGrassColor = 0
|
||||||
|
|
||||||
@@ -42,27 +35,34 @@ class GrassInfo(
|
|||||||
|
|
||||||
object GrassRegistry : ModelRenderRegistryRoot<GrassInfo>()
|
object GrassRegistry : ModelRenderRegistryRoot<GrassInfo>()
|
||||||
|
|
||||||
object StandardGrassRegistry : ModelRenderRegistryConfigurable<GrassInfo>() {
|
object AsyncGrassDiscovery : ConfigurableModelDiscovery<GrassInfo>() {
|
||||||
override val logger = BetterFoliageMod.logDetail
|
override val logger = BetterFoliage.logDetail
|
||||||
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.grassClasses
|
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
|
||||||
override val modelTextures: List<ModelTextureList> get() = Config.blocks.grassModels.list
|
override val modelTextures: List<ModelTextureList> get() = BlockConfig.grassModels.modelList
|
||||||
override fun processModel(state: IBlockState, textures: List<String>) = StandardGrassKey(logger, textures[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
class StandardGrassKey(override val logger: Logger, val textureName: String) : ModelRenderKey<GrassInfo> {
|
override fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture): CompletableFuture<GrassInfo> {
|
||||||
override fun resolveSprites(atlas: TextureMap): GrassInfo {
|
val textureName = textures[0]
|
||||||
val logName = "StandardGrassKey"
|
val spriteF = atlas.sprite(ResourceLocation(textureName))
|
||||||
val texture = atlas[textureName] ?: atlas.missingSprite
|
logger.log(Level.DEBUG, "$logName: texture $textureName")
|
||||||
logger.log(Level.DEBUG, "$logName: texture $textureName")
|
return atlas.mapAfter {
|
||||||
val hsb = HSB.fromColor(texture.averageColor ?: defaultGrassColor)
|
val sprite = spriteF.get()
|
||||||
val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) {
|
logger.log(Level.DEBUG, "$logName: block state $state")
|
||||||
logger.log(Level.DEBUG, "$logName: brightness ${hsb.brightness}")
|
logger.log(Level.DEBUG, "$logName: texture $textureName")
|
||||||
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} >= ${Config.shortGrass.saturationThreshold}, using texture color")
|
val hsb = HSB.fromColor(sprite.averageColor)
|
||||||
hsb.copy(brightness = min(0.9f, hsb.brightness * 2.0f)).asColor
|
val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) {
|
||||||
} else {
|
logger.log(Level.DEBUG, "$logName: brightness ${hsb.brightness}")
|
||||||
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} < ${Config.shortGrass.saturationThreshold}, using block color")
|
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} >= ${Config.shortGrass.saturationThreshold}, using texture color")
|
||||||
null
|
hsb.copy(brightness = min(0.9f, hsb.brightness * 2.0f)).asColor
|
||||||
|
} else {
|
||||||
|
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} < ${Config.shortGrass.saturationThreshold}, using block color")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
GrassInfo(sprite, overrideColor)
|
||||||
}
|
}
|
||||||
return GrassInfo(texture, overrideColor)
|
}
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
GrassRegistry.registries.add(this)
|
||||||
|
BetterFoliage.blockSprites.providers.add(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,41 +1,55 @@
|
|||||||
package mods.betterfoliage.client.texture
|
package mods.betterfoliage.client.texture
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
|
import mods.octarinecore.Sprite
|
||||||
import mods.octarinecore.client.resource.*
|
import mods.octarinecore.client.resource.*
|
||||||
|
import mods.octarinecore.common.sinkAsync
|
||||||
import mods.octarinecore.stripStart
|
import mods.octarinecore.stripStart
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
import net.minecraft.client.particle.ParticleManager
|
||||||
|
import net.minecraft.resources.IResourceManager
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent
|
import java.util.concurrent.CompletableFuture
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.EventPriority
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
|
|
||||||
object LeafParticleRegistry {
|
class FixedSpriteSet(val sprites: List<Sprite>) : SpriteSet {
|
||||||
|
override val num = sprites.size
|
||||||
|
override fun get(idx: Int) = sprites[idx % num]
|
||||||
|
}
|
||||||
|
|
||||||
|
object LeafParticleRegistry : AsyncSpriteProvider<ParticleManager> {
|
||||||
|
val targetAtlas = Atlas.PARTICLES
|
||||||
val typeMappings = TextureMatcher()
|
val typeMappings = TextureMatcher()
|
||||||
val particles = hashMapOf<String, IconSet>()
|
val particles = hashMapOf<String, SpriteSet>()
|
||||||
|
|
||||||
operator fun get(type: String) = particles[type] ?: particles["default"]!!
|
operator fun get(type: String) = particles[type] ?: particles["default"]!!
|
||||||
|
|
||||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
override fun setup(manager: IResourceManager, particleF: CompletableFuture<ParticleManager>, atlasFuture: AtlasFuture): StitchPhases {
|
||||||
|
|
||||||
@SubscribeEvent(priority = EventPriority.HIGH)
|
|
||||||
fun handleLoadModelData(event: LoadModelDataEvent) {
|
|
||||||
particles.clear()
|
particles.clear()
|
||||||
typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.DOMAIN, "leaf_texture_mappings.cfg"))
|
val futures = mutableMapOf<String, List<CompletableFuture<Sprite>>>()
|
||||||
|
|
||||||
|
return StitchPhases(
|
||||||
|
discovery = particleF.sinkAsync {
|
||||||
|
typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.MOD_ID, "leaf_texture_mappings.cfg"))
|
||||||
|
(typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
|
||||||
|
val ids = (0 until 16).map { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "falling_leaf_${leafType}_$idx") }
|
||||||
|
val wids = ids.map { Atlas.PARTICLES.wrap(it) }
|
||||||
|
futures[leafType] = (0 until 16).map { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "falling_leaf_${leafType}_$idx") }
|
||||||
|
.filter { manager.hasResource(Atlas.PARTICLES.wrap(it)) }
|
||||||
|
.map { atlasFuture.sprite(it) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cleanup = atlasFuture.runAfter {
|
||||||
|
futures.forEach { leafType, spriteFutures ->
|
||||||
|
val sprites = spriteFutures.filter { !it.isCompletedExceptionally }.map { it.get() }
|
||||||
|
if (sprites.isNotEmpty()) particles[leafType] = FixedSpriteSet(sprites)
|
||||||
|
}
|
||||||
|
if (particles["default"] == null) particles["default"] = FixedSpriteSet(listOf(atlasFuture.missing.get()!!))
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
fun init() {
|
||||||
fun handlePreStitch(event: TextureStitchEvent.Pre) {
|
BetterFoliage.particleSprites.providers.add(this)
|
||||||
val allTypes = (typeMappings.mappings.map { it.type } + "default").distinct()
|
|
||||||
allTypes.forEach { leafType ->
|
|
||||||
val particleSet = IconSet("betterfoliage", "blocks/falling_leaf_${leafType}_%d").apply { onPreStitch(event.map) }
|
|
||||||
if (leafType == "default" || particleSet.num > 0) particles[leafType] = particleSet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handlePostStitch(event: TextureStitchEvent.Post) {
|
|
||||||
particles.forEach { (_, particleSet) -> particleSet.onPostStitch(event.map) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,15 @@
|
|||||||
package mods.betterfoliage.client.texture
|
package mods.betterfoliage.client.texture
|
||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliage
|
||||||
import mods.betterfoliage.client.Client
|
import mods.betterfoliage.client.config.BlockConfig
|
||||||
import mods.betterfoliage.client.config.Config
|
import mods.octarinecore.HasLogger
|
||||||
import mods.octarinecore.client.render.BlockContext
|
|
||||||
import mods.octarinecore.client.resource.*
|
import mods.octarinecore.client.resource.*
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
||||||
import mods.octarinecore.common.config.IBlockMatcher
|
|
||||||
import mods.octarinecore.common.config.ModelTextureList
|
import mods.octarinecore.common.config.ModelTextureList
|
||||||
import mods.octarinecore.findFirst
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.math.BlockPos
|
import java.util.concurrent.CompletableFuture
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.EventPriority
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
|
||||||
import net.minecraftforge.fml.relauncher.SideOnly
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
|
|
||||||
const val defaultLeafColor = 0
|
const val defaultLeafColor = 0
|
||||||
|
|
||||||
@@ -37,34 +22,36 @@ class LeafInfo(
|
|||||||
val leafType: String,
|
val leafType: String,
|
||||||
|
|
||||||
/** Average color of the round leaf texture. */
|
/** Average color of the round leaf texture. */
|
||||||
val averageColor: Int = roundLeafTexture.averageColor ?: defaultLeafColor
|
val averageColor: Int = roundLeafTexture.averageColor
|
||||||
) {
|
) {
|
||||||
/** [IconSet] of the textures to use for leaf particles emitted from this block. */
|
/** [IconSet] of the textures to use for leaf particles emitted from this block. */
|
||||||
val particleTextures: IconSet get() = LeafParticleRegistry[leafType]
|
val particleTextures: SpriteSet get() = LeafParticleRegistry[leafType]
|
||||||
}
|
}
|
||||||
|
|
||||||
object LeafRegistry : ModelRenderRegistryRoot<LeafInfo>()
|
object LeafRegistry : ModelRenderRegistryRoot<LeafInfo>()
|
||||||
|
|
||||||
object StandardLeafRegistry : ModelRenderRegistryConfigurable<LeafInfo>() {
|
object AsyncLeafDiscovery : ConfigurableModelDiscovery<LeafInfo>() {
|
||||||
override val logger = BetterFoliageMod.logDetail
|
override val logger = BetterFoliage.logDetail
|
||||||
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.leavesClasses
|
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
|
||||||
override val modelTextures: List<ModelTextureList> get() = Config.blocks.leavesModels.list
|
override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
|
||||||
override fun processModel(state: IBlockState, textures: List<String>) = StandardLeafKey(logger, textures[0])
|
|
||||||
|
override fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture) =
|
||||||
|
defaultRegisterLeaf(ResourceLocation(textures[0]), atlas)
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
LeafRegistry.registries.add(this)
|
||||||
|
BetterFoliage.blockSprites.providers.add(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StandardLeafKey(override val logger: Logger, val textureName: String) : ModelRenderKey<LeafInfo> {
|
fun HasLogger.defaultRegisterLeaf(sprite: ResourceLocation, atlas: AtlasFuture): CompletableFuture<LeafInfo> {
|
||||||
lateinit var leafType: String
|
val leafType = LeafParticleRegistry.typeMappings.getType(sprite) ?: "default"
|
||||||
lateinit var generated: ResourceLocation
|
val generated = GeneratedLeaf(sprite, leafType).register(BetterFoliage.asyncPack)
|
||||||
|
val roundLeaf = atlas.sprite(generated)
|
||||||
|
|
||||||
override fun onPreStitch(atlas: TextureMap) {
|
log(" leaf texture $sprite")
|
||||||
val logName = "StandardLeafKey"
|
log(" particle $leafType")
|
||||||
leafType = LeafParticleRegistry.typeMappings.getType(textureName) ?: "default"
|
return atlas.mapAfter {
|
||||||
generated = Client.genLeaves.generatedResource(textureName, "type" to leafType)
|
LeafInfo(roundLeaf.get(), leafType)
|
||||||
atlas.registerSprite(generated)
|
|
||||||
|
|
||||||
logger.log(Level.DEBUG, "$logName: leaf texture $textureName")
|
|
||||||
logger.log(Level.DEBUG, "$logName: particle $leafType")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolveSprites(atlas: TextureMap) = LeafInfo(atlas[generated] ?: atlas.missingSprite, leafType)
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
@file:JvmName("Utils")
|
@file:JvmName("Utils")
|
||||||
package mods.betterfoliage.client.texture
|
package mods.betterfoliage.client.texture
|
||||||
|
|
||||||
|
import mods.octarinecore.client.resource.get
|
||||||
|
import mods.octarinecore.client.resource.loadImage
|
||||||
|
import net.minecraft.resources.IResourceManager
|
||||||
|
import net.minecraft.util.ResourceLocation
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
|
fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
|
||||||
val r = (((rgb1 shr 16) and 255) * weight1 + ((rgb2 shr 16) and 255) * weight2) / (weight1 + weight2)
|
val r = (((rgb1 shr 16) and 255) * weight1 + ((rgb2 shr 16) and 255) * weight2) / (weight1 + weight2)
|
||||||
val g = (((rgb1 shr 8) and 255) * weight1 + ((rgb2 shr 8) and 255) * weight2) / (weight1 + weight2)
|
val g = (((rgb1 shr 8) and 255) * weight1 + ((rgb2 shr 8) and 255) * weight2) / (weight1 + weight2)
|
||||||
@@ -8,4 +14,6 @@ fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
|
|||||||
val a = (rgb1 shr 24) and 255
|
val a = (rgb1 shr 24) and 255
|
||||||
val result = ((a shl 24) or (r shl 16) or (g shl 8) or b).toInt()
|
val result = ((a shl 24) or (r shl 16) or (g shl 8) or b).toInt()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun IResourceManager.loadSprite(id: ResourceLocation) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id")
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
package mods.betterfoliage.loader
|
|
||||||
|
|
||||||
import mods.octarinecore.metaprog.Transformer
|
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
|
||||||
import net.minecraftforge.fml.relauncher.FMLLaunchHandler
|
|
||||||
import org.objectweb.asm.ClassWriter
|
|
||||||
import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES
|
|
||||||
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
|
|
||||||
import org.objectweb.asm.Opcodes.*
|
|
||||||
|
|
||||||
class BetterFoliageTransformer : Transformer() {
|
|
||||||
|
|
||||||
val isOptifinePresent = allAvailable(Refs.OptifineClassTransformer)
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (FMLLaunchHandler.side().isClient) setupClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setupClient() {
|
|
||||||
// where: WorldClient.showBarrierParticles(), right after invoking Block.randomDisplayTick
|
|
||||||
// what: invoke BF code for every random display tick
|
|
||||||
// why: allows us to catch random display ticks, without touching block code
|
|
||||||
transformMethod(Refs.showBarrierParticles) {
|
|
||||||
find(invokeRef(Refs.randomDisplayTick))?.insertAfter {
|
|
||||||
log.info("[BetterFoliageLoader] Applying random display tick call hook")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
varinsn(ALOAD, 11)
|
|
||||||
varinsn(ALOAD, 7)
|
|
||||||
invokeStatic(Refs.onRandomDisplayTick)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply random display tick call hook!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: BlockStateContainer$StateImplementation.getAmbientOcclusionLightValue()
|
|
||||||
// what: invoke BF code to overrule AO transparency value
|
|
||||||
// why: allows us to have light behave properly on non-solid log blocks
|
|
||||||
transformMethod(Refs.getAmbientOcclusionLightValue) {
|
|
||||||
find(FRETURN)?.insertBefore {
|
|
||||||
log.info("[BetterFoliageLoader] Applying getAmbientOcclusionLightValue() override")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
invokeStatic(Refs.getAmbientOcclusionLightValueOverride)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply getAmbientOcclusionLightValue() override!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: BlockStateContainer$StateImplementation.useNeighborBrightness()
|
|
||||||
// what: invoke BF code to overrule _useNeighborBrightness_
|
|
||||||
// why: allows us to have light behave properly on non-solid log blocks
|
|
||||||
transformMethod(Refs.useNeighborBrightness) {
|
|
||||||
find(IRETURN)?.insertBefore {
|
|
||||||
log.info("[BetterFoliageLoader] Applying useNeighborBrightness() override")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
invokeStatic(Refs.useNeighborBrightnessOverride)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply useNeighborBrightness() override!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: BlockStateContainer$StateImplementation.doesSideBlockRendering()
|
|
||||||
// what: invoke BF code to overrule condition
|
|
||||||
// why: allows us to make log blocks non-solid
|
|
||||||
transformMethod(Refs.doesSideBlockRendering) {
|
|
||||||
find(IRETURN)?.insertBefore {
|
|
||||||
log.info("[BetterFoliageLoader] Applying doesSideBlockRendering() override")
|
|
||||||
varinsn(ALOAD, 1)
|
|
||||||
varinsn(ALOAD, 2)
|
|
||||||
varinsn(ALOAD, 3)
|
|
||||||
invokeStatic(Refs.doesSideBlockRenderingOverride)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply doesSideBlockRendering() override!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: BlockStateContainer$StateImplementation.isOpaqueCube()
|
|
||||||
// what: invoke BF code to overrule condition
|
|
||||||
// why: allows us to make log blocks non-solid
|
|
||||||
transformMethod(Refs.isOpaqueCube) {
|
|
||||||
find(IRETURN)?.insertBefore {
|
|
||||||
log.info("[BetterFoliageLoader] Applying isOpaqueCube() override")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
invokeStatic(Refs.isOpaqueCubeOverride)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply isOpaqueCube() override!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: ModelLoader.setupModelRegistry(), right before the textures are loaded
|
|
||||||
// what: invoke handler code with ModelLoader instance
|
|
||||||
// why: allows us to iterate the unbaked models in ModelLoader in time to register textures
|
|
||||||
transformMethod(Refs.setupModelRegistry) {
|
|
||||||
find(invokeName("addAll"))?.insertAfter {
|
|
||||||
log.info("[BetterFoliageLoader] Applying ModelLoader lifecycle callback")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
invokeStatic(Refs.onAfterLoadModelDefinitions)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply ModelLoader lifecycle callback!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: RenderChunk.rebuildChunk()
|
|
||||||
// what: replace call to BlockRendererDispatcher.renderBlock()
|
|
||||||
// why: allows us to perform additional rendering for each block
|
|
||||||
// what: invoke code to overrule result of Block.canRenderInLayer()
|
|
||||||
// why: allows us to render transparent quads for blocks which are only on the SOLID layer
|
|
||||||
transformMethod(Refs.rebuildChunk) {
|
|
||||||
applyWriterFlags(COMPUTE_FRAMES, COMPUTE_MAXS)
|
|
||||||
find(invokeRef(Refs.renderBlock))?.replace {
|
|
||||||
log.info("[BetterFoliageLoader] Applying RenderChunk block render override")
|
|
||||||
varinsn(ALOAD, if (isOptifinePresent) 22 else 20)
|
|
||||||
invokeStatic(Refs.renderWorldBlock)
|
|
||||||
}
|
|
||||||
if (isOptifinePresent) {
|
|
||||||
find(varinsn(ISTORE, 23))?.insertAfter {
|
|
||||||
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (Optifine)")
|
|
||||||
varinsn(ALOAD, 19)
|
|
||||||
varinsn(ALOAD, 18)
|
|
||||||
varinsn(ALOAD, 22)
|
|
||||||
invokeStatic(Refs.canRenderBlockInLayer)
|
|
||||||
varinsn(ISTORE, 23)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
find(invokeRef(Refs.canRenderInLayer))?.replace {
|
|
||||||
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (non-Optifine)")
|
|
||||||
invokeStatic(Refs.canRenderBlockInLayer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace
|
|
||||||
// what: make constructor public
|
|
||||||
// why: use vanilla AO calculation at will without duplicating code
|
|
||||||
transformMethod(Refs.AOF_constructor) {
|
|
||||||
log.info("[BetterFoliageLoader] Setting AmbientOcclusionFace constructor public")
|
|
||||||
makePublic()
|
|
||||||
}
|
|
||||||
|
|
||||||
// where: shadersmod.client.SVertexBuilder.pushEntity()
|
|
||||||
// what: invoke code to overrule block data
|
|
||||||
// why: allows us to change the block ID seen by shader programs
|
|
||||||
transformMethod(Refs.pushEntity_state) {
|
|
||||||
find(invokeRef(Refs.pushEntity_num))?.insertBefore {
|
|
||||||
log.info("[BetterFoliageLoader] Applying SVertexBuilder.pushEntity() block ID override")
|
|
||||||
varinsn(ALOAD, 0)
|
|
||||||
invokeStatic(Refs.getBlockIdOverride)
|
|
||||||
} ?: log.warn("[BetterFoliageLoader] Failed to apply SVertexBuilder.pushEntity() block ID override!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package mods.betterfoliage.loader
|
|
||||||
|
|
||||||
import mods.octarinecore.metaprog.ClassRef
|
|
||||||
import mods.octarinecore.metaprog.FieldRef
|
|
||||||
import mods.octarinecore.metaprog.MethodRef
|
|
||||||
import net.minecraftforge.fml.relauncher.FMLInjectionData
|
|
||||||
|
|
||||||
/** Singleton object holding references to foreign code elements. */
|
|
||||||
object Refs {
|
|
||||||
val mcVersion = FMLInjectionData.data()[4].toString()
|
|
||||||
|
|
||||||
// Java
|
|
||||||
val String = ClassRef("java.lang.String")
|
|
||||||
val Map = ClassRef("java.util.Map")
|
|
||||||
val List = ClassRef("java.util.List")
|
|
||||||
val Random = ClassRef("java.util.Random")
|
|
||||||
|
|
||||||
// Minecraft
|
|
||||||
val IBlockAccess = ClassRef("net.minecraft.world.IBlockAccess")
|
|
||||||
val IBlockState = ClassRef("net.minecraft.block.state.IBlockState")
|
|
||||||
val BlockStateBase = ClassRef("net.minecraft.block.state.BlockStateBase")
|
|
||||||
val BlockPos = ClassRef("net.minecraft.util.math.BlockPos")
|
|
||||||
val MutableBlockPos = ClassRef("net.minecraft.util.math.BlockPos\$MutableBlockPos")
|
|
||||||
val BlockRenderLayer = ClassRef("net.minecraft.util.BlockRenderLayer")
|
|
||||||
val EnumFacing = ClassRef("net.minecraft.util.EnumFacing")
|
|
||||||
|
|
||||||
val World = ClassRef("net.minecraft.world.World")
|
|
||||||
val WorldClient = ClassRef("net.minecraft.client.multiplayer.WorldClient")
|
|
||||||
val ChunkCache = ClassRef("net.minecraft.world.ChunkCache")
|
|
||||||
val showBarrierParticles = MethodRef(WorldClient, "showBarrierParticles", "func_184153_a", ClassRef.void, ClassRef.int, ClassRef.int, ClassRef.int, ClassRef.int, Random, ClassRef.boolean, MutableBlockPos)
|
|
||||||
|
|
||||||
val Block = ClassRef("net.minecraft.block.Block")
|
|
||||||
val StateImplementation = ClassRef("net.minecraft.block.state.BlockStateContainer\$StateImplementation")
|
|
||||||
val canRenderInLayer = MethodRef(Block, "canRenderInLayer", ClassRef.boolean, IBlockState, BlockRenderLayer)
|
|
||||||
val getAmbientOcclusionLightValue = MethodRef(StateImplementation, "getAmbientOcclusionLightValue", "func_185892_j", ClassRef.float)
|
|
||||||
val useNeighborBrightness = MethodRef(StateImplementation, "useNeighborBrightness", "func_185916_f", ClassRef.boolean)
|
|
||||||
val doesSideBlockRendering = MethodRef(StateImplementation, "doesSideBlockRendering", ClassRef.boolean, IBlockAccess, BlockPos, EnumFacing)
|
|
||||||
val isOpaqueCube = MethodRef(StateImplementation, "isOpaqueCube", "func_185914_p", ClassRef.boolean)
|
|
||||||
val randomDisplayTick = MethodRef(Block, "randomDisplayTick", "func_180655_c", ClassRef.void, IBlockState, World, BlockPos, Random)
|
|
||||||
|
|
||||||
val BlockModelRenderer = ClassRef("net.minecraft.client.renderer.BlockModelRenderer")
|
|
||||||
val AmbientOcclusionFace = ClassRef("net.minecraft.client.renderer.BlockModelRenderer\$AmbientOcclusionFace")
|
|
||||||
val ChunkCompileTaskGenerator = ClassRef("net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator")
|
|
||||||
val BufferBuilder = ClassRef("net.minecraft.client.renderer.BufferBuilder")
|
|
||||||
val AOF_constructor = MethodRef(AmbientOcclusionFace, "<init>", ClassRef.void, BlockModelRenderer)
|
|
||||||
|
|
||||||
val RenderChunk = ClassRef("net.minecraft.client.renderer.chunk.RenderChunk")
|
|
||||||
val rebuildChunk = MethodRef(RenderChunk, "rebuildChunk", "func_178581_b", ClassRef.void, ClassRef.float, ClassRef.float, ClassRef.float, ChunkCompileTaskGenerator)
|
|
||||||
|
|
||||||
val BlockRendererDispatcher = ClassRef("net.minecraft.client.renderer.BlockRendererDispatcher")
|
|
||||||
val renderBlock = MethodRef(BlockRendererDispatcher, "renderBlock", "func_175018_a", ClassRef.boolean, IBlockState, BlockPos, IBlockAccess, BufferBuilder)
|
|
||||||
|
|
||||||
val TextureAtlasSprite = ClassRef("net.minecraft.client.renderer.texture.TextureAtlasSprite")
|
|
||||||
|
|
||||||
val IRegistry = ClassRef("net.minecraft.util.registry.IRegistry")
|
|
||||||
val ModelLoader = ClassRef("net.minecraftforge.client.model.ModelLoader")
|
|
||||||
val stateModels = FieldRef(ModelLoader, "stateModels", Map)
|
|
||||||
val setupModelRegistry = MethodRef(ModelLoader, "setupModelRegistry", "func_177570_a", IRegistry)
|
|
||||||
|
|
||||||
val IModel = ClassRef("net.minecraftforge.client.model.IModel")
|
|
||||||
val ModelBlock = ClassRef("net.minecraft.client.renderer.block.model.ModelBlock")
|
|
||||||
val ResourceLocation = ClassRef("net.minecraft.util.ResourceLocation")
|
|
||||||
val ModelResourceLocation = ClassRef("net.minecraft.client.renderer.block.model.ModelResourceLocation")
|
|
||||||
val VanillaModelWrapper = ClassRef("net.minecraftforge.client.model.ModelLoader\$VanillaModelWrapper")
|
|
||||||
val model_VMW = FieldRef(VanillaModelWrapper, "model", ModelBlock)
|
|
||||||
val location_VMW = FieldRef(VanillaModelWrapper, "location", ModelBlock)
|
|
||||||
val WeightedRandomModel = ClassRef("net.minecraftforge.client.model.ModelLoader\$WeightedRandomModel")
|
|
||||||
val models_WRM = FieldRef(WeightedRandomModel, "models", List)
|
|
||||||
val MultiModel = ClassRef("net.minecraftforge.client.model.MultiModel")
|
|
||||||
val base_MM = FieldRef(MultiModel, "base", IModel)
|
|
||||||
val MultipartModel = ClassRef("net.minecraftforge.client.model.ModelLoader\$MultipartModel")
|
|
||||||
val partModels_MPM = FieldRef(MultipartModel, "partModels", List)
|
|
||||||
|
|
||||||
val BakedQuad = ClassRef("net.minecraft.client.renderer.block.model.BakedQuad")
|
|
||||||
|
|
||||||
val resetChangedState = MethodRef(ClassRef("net.minecraftforge.common.config.Configuration"), "resetChangedState", ClassRef.void)
|
|
||||||
|
|
||||||
|
|
||||||
// Better Foliage
|
|
||||||
val BetterFoliageHooks = ClassRef("mods.betterfoliage.client.Hooks")
|
|
||||||
val getAmbientOcclusionLightValueOverride = MethodRef(BetterFoliageHooks, "getAmbientOcclusionLightValueOverride", ClassRef.float, ClassRef.float, IBlockState)
|
|
||||||
val useNeighborBrightnessOverride = MethodRef(BetterFoliageHooks, "getUseNeighborBrightnessOverride", ClassRef.boolean, ClassRef.boolean, IBlockState)
|
|
||||||
val doesSideBlockRenderingOverride = MethodRef(BetterFoliageHooks, "doesSideBlockRenderingOverride", ClassRef.boolean, ClassRef.boolean, IBlockAccess, BlockPos, EnumFacing)
|
|
||||||
val isOpaqueCubeOverride = MethodRef(BetterFoliageHooks, "isOpaqueCubeOverride", ClassRef.boolean, ClassRef.boolean, IBlockState)
|
|
||||||
val onRandomDisplayTick = MethodRef(BetterFoliageHooks, "onRandomDisplayTick", ClassRef.void, World, IBlockState, BlockPos)
|
|
||||||
val onAfterLoadModelDefinitions = MethodRef(BetterFoliageHooks, "onAfterLoadModelDefinitions", ClassRef.void, ModelLoader)
|
|
||||||
val onAfterBakeModels = MethodRef(BetterFoliageHooks, "onAfterBakeModels", ClassRef.void, Map)
|
|
||||||
val renderWorldBlock = MethodRef(BetterFoliageHooks, "renderWorldBlock", ClassRef.boolean, BlockRendererDispatcher, IBlockState, BlockPos, IBlockAccess, BufferBuilder, BlockRenderLayer)
|
|
||||||
val canRenderBlockInLayer = MethodRef(BetterFoliageHooks, "canRenderBlockInLayer", ClassRef.boolean, Block, IBlockState, BlockRenderLayer)
|
|
||||||
|
|
||||||
// Optifine
|
|
||||||
val OptifineClassTransformer = ClassRef("optifine.OptiFineClassTransformer")
|
|
||||||
val OptifineChunkCache = ClassRef("net.optifine.override.ChunkCacheOF")
|
|
||||||
val CCOFChunkCache = FieldRef(OptifineChunkCache, "chunkCache", ChunkCache)
|
|
||||||
|
|
||||||
val getBlockId = MethodRef(BlockStateBase, "getBlockId", ClassRef.int);
|
|
||||||
val getMetadata = MethodRef(BlockStateBase, "getMetadata", ClassRef.int);
|
|
||||||
|
|
||||||
// Optifine
|
|
||||||
val RenderEnv = ClassRef("net.optifine.render.RenderEnv")
|
|
||||||
val RenderEnv_reset = MethodRef(RenderEnv, "reset", ClassRef.void, IBlockState, BlockPos)
|
|
||||||
val quadSprite = FieldRef(BufferBuilder, "quadSprite", TextureAtlasSprite)
|
|
||||||
|
|
||||||
// Optifine: custom colors
|
|
||||||
val CustomColors = ClassRef("net.optifine.CustomColors")
|
|
||||||
val getColorMultiplier = MethodRef(CustomColors, "getColorMultiplier", ClassRef.int, BakedQuad, IBlockState, IBlockAccess, BlockPos, RenderEnv)
|
|
||||||
|
|
||||||
// Optifine: shaders
|
|
||||||
val SVertexBuilder = ClassRef("net.optifine.shaders.SVertexBuilder")
|
|
||||||
val sVertexBuilder = FieldRef(BufferBuilder, "sVertexBuilder", SVertexBuilder)
|
|
||||||
val pushEntity_state = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, IBlockState, BlockPos, IBlockAccess, BufferBuilder)
|
|
||||||
val pushEntity_num = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, ClassRef.long)
|
|
||||||
val popEntity = MethodRef(SVertexBuilder, "popEntity", ClassRef.void)
|
|
||||||
|
|
||||||
val ShadersModIntegration = ClassRef("mods.betterfoliage.client.integration.ShadersModIntegration")
|
|
||||||
val getBlockIdOverride = MethodRef(ShadersModIntegration, "getBlockIdOverride", ClassRef.long, ClassRef.long, IBlockState)
|
|
||||||
}
|
|
||||||
74
src/main/kotlin/mods/octarinecore/CommonRefs.kt
Normal file
74
src/main/kotlin/mods/octarinecore/CommonRefs.kt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package mods.octarinecore
|
||||||
|
|
||||||
|
import mods.octarinecore.metaprog.ClassRef
|
||||||
|
import mods.octarinecore.metaprog.ClassRef.Companion.void
|
||||||
|
import mods.octarinecore.metaprog.FieldRef
|
||||||
|
import mods.octarinecore.metaprog.MethodRef
|
||||||
|
import net.minecraft.block.Block
|
||||||
|
import net.minecraft.block.BlockState
|
||||||
|
import net.minecraft.client.renderer.BlockRendererDispatcher
|
||||||
|
import net.minecraft.client.renderer.BufferBuilder
|
||||||
|
import net.minecraft.client.renderer.chunk.ChunkRenderCache
|
||||||
|
import net.minecraft.client.renderer.model.BakedQuad
|
||||||
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||||
|
import net.minecraft.util.BlockRenderLayer
|
||||||
|
import net.minecraft.util.ResourceLocation
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.minecraft.world.IBlockReader
|
||||||
|
import net.minecraft.world.IEnviromentBlockReader
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
typealias Sprite = TextureAtlasSprite
|
||||||
|
|
||||||
|
// Java
|
||||||
|
val String = ClassRef<String>("java.lang.String")
|
||||||
|
val Map = ClassRef<Map<*, *>>("java.util.Map")
|
||||||
|
val List = ClassRef<List<*>>("java.util.List")
|
||||||
|
val Random = ClassRef<Random>("java.util.Random")
|
||||||
|
|
||||||
|
// Minecraft
|
||||||
|
val IBlockReader = ClassRef<IBlockReader>("net.minecraft.world.IBlockReader")
|
||||||
|
val IEnvironmentBlockReader = ClassRef<IEnviromentBlockReader>("net.minecraft.world.IEnviromentBlockReader")
|
||||||
|
val BlockState = ClassRef<BlockState>("net.minecraft.block.BlockState")
|
||||||
|
val BlockPos = ClassRef<BlockPos>("net.minecraft.util.math.BlockPos")
|
||||||
|
val BlockRenderLayer = ClassRef<BlockRenderLayer>("net.minecraft.util.BlockRenderLayer")
|
||||||
|
val Block = ClassRef<Block>("net.minecraft.block.Block")
|
||||||
|
|
||||||
|
val TextureAtlasSprite = ClassRef<TextureAtlasSprite>("net.minecraft.client.renderer.texture.TextureAtlasSprite")
|
||||||
|
val BufferBuilder = ClassRef<BufferBuilder>("net.minecraft.client.renderer.BufferBuilder")
|
||||||
|
val BufferBuilder_setSprite = MethodRef(BufferBuilder, "setSprite", void, TextureAtlasSprite)
|
||||||
|
val BufferBuilder_sVertexBuilder = FieldRef(BufferBuilder, "sVertexBuilder", SVertexBuilder)
|
||||||
|
val BlockRendererDispatcher = ClassRef<BlockRendererDispatcher>("net.minecraft.client.renderer.BlockRendererDispatcher")
|
||||||
|
val ChunkRenderCache = ClassRef<ChunkRenderCache>("net.minecraft.client.renderer.chunk.ChunkRenderCache")
|
||||||
|
val ResourceLocation = ClassRef<ResourceLocation>("net.minecraft.util.ResourceLocation")
|
||||||
|
val BakedQuad = ClassRef<BakedQuad>("net.minecraft.client.renderer.model.BakedQuad")
|
||||||
|
|
||||||
|
// Optifine
|
||||||
|
val OptifineClassTransformer = ClassRef<Any>("optifine.OptiFineClassTransformer")
|
||||||
|
val BlockPosM = ClassRef<Any>("net.optifine.BlockPosM")
|
||||||
|
object ChunkCacheOF : ClassRef<Any>("net.optifine.override.ChunkCacheOF") {
|
||||||
|
val chunkCache = FieldRef(this, "chunkCache", ChunkRenderCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
object RenderEnv : ClassRef<Any>("net.optifine.render.RenderEnv") {
|
||||||
|
val reset = MethodRef(this, "reset", void, BlockState, BlockPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optifine custom colors
|
||||||
|
val IColorizer = ClassRef<Any>("net.optifine.CustomColors\$IColorizer")
|
||||||
|
object CustomColors : ClassRef<Any>("net.optifine.CustomColors") {
|
||||||
|
val getColorMultiplier = MethodRef(this, "getColorMultiplier", int, BakedQuad, BlockState, IEnvironmentBlockReader, BlockPos, RenderEnv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optifine shaders
|
||||||
|
object SVertexBuilder : ClassRef<Any>("net.optifine.shaders.SVertexBuilder") {
|
||||||
|
val pushState = MethodRef(this, "pushEntity", void, long)
|
||||||
|
val popState = MethodRef(this, "popEntity", void)
|
||||||
|
}
|
||||||
|
|
||||||
|
object BlockAliases : ClassRef<Any>("net.optifine.shaders.BlockAliases") {
|
||||||
|
val getAliasBlockId = MethodRef(this, "getAliasBlockId", int, BlockState)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -2,23 +2,24 @@
|
|||||||
@file:Suppress("NOTHING_TO_INLINE")
|
@file:Suppress("NOTHING_TO_INLINE")
|
||||||
package mods.octarinecore
|
package mods.octarinecore
|
||||||
|
|
||||||
import mods.betterfoliage.loader.Refs
|
|
||||||
import net.minecraft.tileentity.TileEntity
|
import net.minecraft.tileentity.TileEntity
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.world.ChunkCache
|
import net.minecraft.world.*
|
||||||
import net.minecraft.world.IBlockAccess
|
import org.apache.logging.log4j.Level
|
||||||
import net.minecraft.world.World
|
import org.apache.logging.log4j.Logger
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
import java.lang.Math.*
|
import java.lang.Math.*
|
||||||
|
|
||||||
const val PI2 = 2.0 * PI
|
const val PI2 = 2.0 * PI
|
||||||
|
|
||||||
/** Strip the given prefix off the start of the string, if present */
|
/** Strip the given prefix off the start of the string, if present */
|
||||||
inline fun String.stripStart(str: String) = if (startsWith(str)) substring(str.length) else this
|
inline fun String.stripStart(str: String, ignoreCase: Boolean = true) = if (startsWith(str, ignoreCase)) substring(str.length) else this
|
||||||
|
inline fun String.stripEnd(str: String, ignoreCase: Boolean = true) = if (endsWith(str, ignoreCase)) substring(0, length - str.length) else this
|
||||||
|
|
||||||
/** Strip the given prefix off the start of the resource path, if present */
|
/** Strip the given prefix off the start of the resource path, if present */
|
||||||
inline fun ResourceLocation.stripStart(str: String) = ResourceLocation(namespace, path.stripStart(str))
|
inline fun ResourceLocation.stripStart(str: String) = ResourceLocation(namespace, path.stripStart(str))
|
||||||
|
inline fun ResourceLocation.stripEnd(str: String) = ResourceLocation(namespace, path.stripEnd(str))
|
||||||
|
|
||||||
/** Mutating version of _map_. Replace each element of the list with the result of the given transformation. */
|
/** Mutating version of _map_. Replace each element of the list with the result of the given transformation. */
|
||||||
inline fun <reified T> MutableList<T>.replace(transform: (T) -> T) = forEachIndexed { idx, value -> this[idx] = transform(value) }
|
inline fun <reified T> MutableList<T>.replace(transform: (T) -> T) = forEachIndexed { idx, value -> this[idx] = transform(value) }
|
||||||
@@ -104,17 +105,25 @@ fun nextPowerOf2(x: Int): Int {
|
|||||||
* Check if the Chunk containing the given [BlockPos] is loaded.
|
* Check if the Chunk containing the given [BlockPos] is loaded.
|
||||||
* Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances.
|
* Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances.
|
||||||
*/
|
*/
|
||||||
fun IBlockAccess.isBlockLoaded(pos: BlockPos) = when {
|
//fun IWorldReader.isBlockLoaded(pos: BlockPos) = when {
|
||||||
this is World -> isBlockLoaded(pos, false)
|
// this is World -> isBlockLoaded(pos, false)
|
||||||
this is ChunkCache -> world.isBlockLoaded(pos, false)
|
// this is RenderChunkCache -> isworld.isBlockLoaded(pos, false)
|
||||||
Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkCache).world.isBlockLoaded(pos, false)
|
// Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkCache).world.isBlockLoaded(pos, false)
|
||||||
else -> false
|
// else -> false
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the [TileEntity] at the given position, suppressing exceptions.
|
* Get the [TileEntity] at the given position, suppressing exceptions.
|
||||||
* Also returns null if the chunk is unloaded, which can happen because of multithreaded rendering.
|
* Also returns null if the chunk is unloaded, which can happen because of multithreaded rendering.
|
||||||
*/
|
*/
|
||||||
fun IBlockAccess.getTileEntitySafe(pos: BlockPos): TileEntity? = tryDefault(null as TileEntity?) {
|
fun IWorldReader.getTileEntitySafe(pos: BlockPos): TileEntity? = tryDefault(null as TileEntity?) {
|
||||||
if (isBlockLoaded(pos)) getTileEntity(pos) else null
|
if (isBlockLoaded(pos)) getTileEntity(pos) else null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HasLogger {
|
||||||
|
val logger: Logger
|
||||||
|
val logName: String get() = this::class.simpleName!!
|
||||||
|
fun log(msg: String) = log(Level.DEBUG, msg)
|
||||||
|
fun log(level: Level, msg: String) = logger.log(level, "[$logName] $msg")
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package mods.octarinecore.client
|
package mods.octarinecore.client
|
||||||
|
|
||||||
import net.minecraft.client.settings.KeyBinding
|
import net.minecraft.client.settings.KeyBinding
|
||||||
|
import net.minecraftforge.client.event.InputEvent
|
||||||
|
import net.minecraftforge.common.MinecraftForge
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||||
import net.minecraftforge.fml.client.registry.ClientRegistry
|
import net.minecraftforge.fml.client.registry.ClientRegistry
|
||||||
import net.minecraftforge.fml.common.FMLCommonHandler
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
import net.minecraftforge.fml.common.gameevent.InputEvent
|
|
||||||
|
|
||||||
class KeyHandler(val modId: String, val defaultKey: Int, val lang: String, val action: (InputEvent.KeyInputEvent)->Unit) {
|
class KeyHandler(val modId: String, val defaultKey: Int, val lang: String, val action: (InputEvent.KeyInputEvent)->Unit) {
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ class KeyHandler(val modId: String, val defaultKey: Int, val lang: String, val a
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
ClientRegistry.registerKeyBinding(keyBinding)
|
ClientRegistry.registerKeyBinding(keyBinding)
|
||||||
FMLCommonHandler.instance().bus().register(this)
|
MinecraftForge.EVENT_BUS.register(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
package mods.octarinecore.client.gui
|
|
||||||
|
|
||||||
import net.minecraft.client.gui.GuiScreen
|
|
||||||
import net.minecraft.client.resources.I18n
|
|
||||||
import net.minecraft.util.text.TextFormatting.*
|
|
||||||
import net.minecraftforge.fml.client.config.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for a config GUI element.
|
|
||||||
* The GUI representation is a list of toggleable objects.
|
|
||||||
* The config representation is an integer list of the selected objects' IDs.
|
|
||||||
*/
|
|
||||||
abstract class IdListConfigEntry<T>(
|
|
||||||
owningScreen: GuiConfig,
|
|
||||||
owningEntryList: GuiConfigEntries,
|
|
||||||
configElement: IConfigElement
|
|
||||||
) : GuiConfigEntries.CategoryEntry(owningScreen, owningEntryList, configElement) {
|
|
||||||
|
|
||||||
/** Create the child GUI elements. */
|
|
||||||
fun createChildren() = baseSet.map {
|
|
||||||
ItemWrapperElement(it, it.itemId in configElement.list, it.itemId in configElement.defaults)
|
|
||||||
}
|
|
||||||
|
|
||||||
init { stripTooltipDefaultText(toolTip as MutableList<String>) }
|
|
||||||
|
|
||||||
override fun buildChildScreen(): GuiScreen {
|
|
||||||
return GuiConfig(
|
|
||||||
this.owningScreen,
|
|
||||||
createChildren(),
|
|
||||||
this.owningScreen.modID,
|
|
||||||
owningScreen.allRequireWorldRestart || this.configElement.requiresWorldRestart(),
|
|
||||||
owningScreen.allRequireMcRestart || this.configElement.requiresMcRestart(),
|
|
||||||
this.owningScreen.title,
|
|
||||||
(if (this.owningScreen.titleLine2 == null) "" else this.owningScreen.titleLine2) + " > " + this.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun saveConfigElement(): Boolean {
|
|
||||||
val requiresRestart = (childScreen as GuiConfig).entryList.saveConfigElements()
|
|
||||||
val children = (childScreen as GuiConfig).configElements as List<ItemWrapperElement>
|
|
||||||
val ids = children.filter { it.booleanValue == true }.map { it.item.itemId }
|
|
||||||
configElement.set(ids.sorted().toTypedArray())
|
|
||||||
return requiresRestart
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract val baseSet: List<T>
|
|
||||||
abstract val T.itemId: Int
|
|
||||||
abstract val T.itemName: String
|
|
||||||
|
|
||||||
/** Child config GUI element of a single toggleable object. */
|
|
||||||
inner class ItemWrapperElement(val item: T, value: Boolean, val default: Boolean) :
|
|
||||||
DummyConfigElement(item.itemName, default, ConfigGuiType.BOOLEAN, item.itemName) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.value = value
|
|
||||||
this.defaultValue = default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getComment() = I18n.format("${configElement.languageKey}.tooltip.element", "${GOLD}${item.itemName}${YELLOW}")
|
|
||||||
val booleanValue: Boolean get() = defaultValue as Boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package mods.octarinecore.client.gui
|
|
||||||
|
|
||||||
import net.minecraft.client.resources.I18n
|
|
||||||
import net.minecraft.util.text.TextFormatting.*
|
|
||||||
import net.minecraftforge.fml.client.config.GuiConfig
|
|
||||||
import net.minecraftforge.fml.client.config.GuiConfigEntries
|
|
||||||
import net.minecraftforge.fml.client.config.IConfigElement
|
|
||||||
|
|
||||||
class NonVerboseArrayEntry(
|
|
||||||
owningScreen: GuiConfig,
|
|
||||||
owningEntryList: GuiConfigEntries,
|
|
||||||
configElement: IConfigElement
|
|
||||||
) : GuiConfigEntries.ArrayEntry(owningScreen, owningEntryList, configElement) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
stripTooltipDefaultText(toolTip as MutableList<String>)
|
|
||||||
val shortDefaults = I18n.format("${configElement.languageKey}.arrayEntry", configElement.defaults.size)
|
|
||||||
toolTip.addAll(mc.fontRenderer.listFormattedStringToWidth("$AQUA${I18n.format("fml.configgui.tooltip.default", shortDefaults)}", 300))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateValueButtonText() {
|
|
||||||
btnValue.displayString = I18n.format("${configElement.languageKey}.arrayEntry", currentValues.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
@file:JvmName("Utils")
|
@file:JvmName("Utils")
|
||||||
package mods.octarinecore.client.gui
|
package mods.octarinecore.client.gui
|
||||||
|
|
||||||
|
import net.minecraft.util.text.StringTextComponent
|
||||||
import net.minecraft.util.text.Style
|
import net.minecraft.util.text.Style
|
||||||
import net.minecraft.util.text.TextComponentString
|
|
||||||
import net.minecraft.util.text.TextFormatting
|
import net.minecraft.util.text.TextFormatting
|
||||||
import net.minecraft.util.text.TextFormatting.AQUA
|
import net.minecraft.util.text.TextFormatting.AQUA
|
||||||
import net.minecraft.util.text.TextFormatting.GRAY
|
import net.minecraft.util.text.TextFormatting.GRAY
|
||||||
@@ -16,7 +16,7 @@ fun stripTooltipDefaultText(tooltip: MutableList<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun textComponent(msg: String, color: TextFormatting = GRAY): TextComponentString {
|
fun textComponent(msg: String, color: TextFormatting = GRAY): StringTextComponent {
|
||||||
val style = Style().apply { this.color = color }
|
val style = Style().apply { this.color = color }
|
||||||
return TextComponentString(msg).apply { this.style = style }
|
return StringTextComponent(msg).apply { this.style = style }
|
||||||
}
|
}
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
@file:JvmName("RendererHolder")
|
|
||||||
package mods.octarinecore.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.client.render.canRenderInCutout
|
|
||||||
import mods.betterfoliage.client.render.canRenderInLayer
|
|
||||||
import mods.betterfoliage.client.render.isCutout
|
|
||||||
import mods.octarinecore.ThreadLocalDelegate
|
|
||||||
import mods.octarinecore.client.resource.ResourceHandler
|
|
||||||
import mods.octarinecore.common.Double3
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.forgeDirOffsets
|
|
||||||
import mods.octarinecore.common.plus
|
|
||||||
import mods.octarinecore.semiRandom
|
|
||||||
import net.minecraft.block.Block
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.Minecraft
|
|
||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.client.renderer.color.BlockColors
|
|
||||||
import net.minecraft.util.BlockRenderLayer
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.util.math.MathHelper
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraft.world.biome.Biome
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [ThreadLocal] instance of [BlockContext] representing the block being rendered.
|
|
||||||
*/
|
|
||||||
val blockContext by ThreadLocalDelegate { BlockContext() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [ThreadLocal] instance of [ModelRenderer].
|
|
||||||
*/
|
|
||||||
val modelRenderer by ThreadLocalDelegate { ModelRenderer() }
|
|
||||||
|
|
||||||
val blockColors = ThreadLocal<BlockColors>()
|
|
||||||
|
|
||||||
abstract class AbstractBlockRenderingHandler(modId: String) : ResourceHandler(modId) {
|
|
||||||
|
|
||||||
open val addToCutout: Boolean get() = true
|
|
||||||
|
|
||||||
// ============================
|
|
||||||
// Custom rendering
|
|
||||||
// ============================
|
|
||||||
abstract fun isEligible(ctx: BlockContext): Boolean
|
|
||||||
abstract fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean
|
|
||||||
|
|
||||||
// ============================
|
|
||||||
// Vanilla rendering wrapper
|
|
||||||
// ============================
|
|
||||||
/**
|
|
||||||
* Render the block in the current [BlockContext]
|
|
||||||
*/
|
|
||||||
fun renderWorldBlockBase(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer?): Boolean {
|
|
||||||
ctx.blockState(Int3.zero).let { state ->
|
|
||||||
if (layer == null ||
|
|
||||||
state.canRenderInLayer(layer) ||
|
|
||||||
(state.canRenderInCutout() && layer.isCutout)) {
|
|
||||||
return dispatcher.renderBlock(state, ctx.pos, ctx.world, renderer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
data class BlockData(val state: IBlockState, val color: Int, val brightness: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the block being rendered. Has properties and methods to query the neighborhood of the block in
|
|
||||||
* block-relative coordinates.
|
|
||||||
*/
|
|
||||||
class BlockContext(
|
|
||||||
var world: IBlockAccess? = null,
|
|
||||||
var pos: BlockPos = BlockPos.ORIGIN
|
|
||||||
) {
|
|
||||||
fun set(world: IBlockAccess, pos: BlockPos) { this.world = world; this.pos = pos; }
|
|
||||||
|
|
||||||
val block: Block get() = block(Int3.zero)
|
|
||||||
fun block(offset: Int3) = blockState(offset).block
|
|
||||||
fun blockState(offset: Int3) = (pos + offset).let { world!!.getBlockState(it) }
|
|
||||||
fun blockData(offset: Int3) = (pos + offset).let { pos ->
|
|
||||||
world!!.getBlockState(pos).let { state ->
|
|
||||||
BlockData(
|
|
||||||
state,
|
|
||||||
Minecraft.getMinecraft().blockColors.colorMultiplier(state, world!!, pos, 0),
|
|
||||||
state.block.getPackedLightmapCoords(state, world!!, pos)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the biome ID at the block position. */
|
|
||||||
val biomeId: Int get() = Biome.getIdForBiome(world!!.getBiome(pos))
|
|
||||||
|
|
||||||
/** Get the centerpoint of the block being rendered. */
|
|
||||||
val blockCenter: Double3 get() = Double3(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5)
|
|
||||||
|
|
||||||
val chunkBase: Double3 get() {
|
|
||||||
val cX = if (pos.x >= 0) pos.x / 16 else (pos.x + 1) / 16 - 1
|
|
||||||
val cY = pos.y / 16
|
|
||||||
val cZ = if (pos.z >= 0) pos.z / 16 else (pos.z + 1) / 16 - 1
|
|
||||||
return Double3(cX * 16.0, cY * 16.0, cZ * 16.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Is the block surrounded by other blocks that satisfy the predicate on all sides? */
|
|
||||||
fun isSurroundedBy(predicate: (IBlockState)->Boolean) = forgeDirOffsets.all { predicate(blockState(it)) }
|
|
||||||
|
|
||||||
/** Get a semi-random value based on the block coordinate and the given seed. */
|
|
||||||
fun random(seed: Int) = semiRandom(pos.x, pos.y, pos.z, seed)
|
|
||||||
|
|
||||||
/** Get an array of semi-random values based on the block coordinate. */
|
|
||||||
fun semiRandomArray(num: Int): Array<Int> = Array(num) { random(it) }
|
|
||||||
|
|
||||||
/** Get the distance of the block from the camera (player). */
|
|
||||||
val cameraDistance: Int get() {
|
|
||||||
val camera = Minecraft.getMinecraft().renderViewEntity ?: return 0
|
|
||||||
return abs(pos.x - MathHelper.floor(camera.posX)) +
|
|
||||||
abs(pos.y - MathHelper.floor(camera.posY)) +
|
|
||||||
abs(pos.z - MathHelper.floor(camera.posZ))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,16 @@ package mods.octarinecore.client.render
|
|||||||
import mods.octarinecore.PI2
|
import mods.octarinecore.PI2
|
||||||
import mods.octarinecore.common.Double3
|
import mods.octarinecore.common.Double3
|
||||||
import net.minecraft.client.Minecraft
|
import net.minecraft.client.Minecraft
|
||||||
|
import net.minecraft.client.particle.IParticleRenderType
|
||||||
import net.minecraft.client.particle.Particle
|
import net.minecraft.client.particle.Particle
|
||||||
|
import net.minecraft.client.particle.SpriteTexturedParticle
|
||||||
|
import net.minecraft.client.renderer.ActiveRenderInfo
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.client.renderer.BufferBuilder
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||||
import net.minecraft.entity.Entity
|
import net.minecraft.entity.Entity
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
|
|
||||||
abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : Particle(world, x, y, z) {
|
abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : SpriteTexturedParticle(world, x, y, z) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic val sin = Array(64) { idx -> Math.sin(PI2 / 64.0 * idx) }
|
@JvmStatic val sin = Array(64) { idx -> Math.sin(PI2 / 64.0 * idx) }
|
||||||
@@ -21,8 +24,8 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) :
|
|||||||
val prevPos = Double3.zero
|
val prevPos = Double3.zero
|
||||||
val velocity = Double3.zero
|
val velocity = Double3.zero
|
||||||
|
|
||||||
override fun onUpdate() {
|
override fun tick() {
|
||||||
super.onUpdate()
|
super.tick()
|
||||||
currentPos.setTo(posX, posY, posZ)
|
currentPos.setTo(posX, posY, posZ)
|
||||||
prevPos.setTo(prevPosX, prevPosY, prevPosZ)
|
prevPos.setTo(prevPosX, prevPosY, prevPosZ)
|
||||||
velocity.setTo(motionX, motionY, motionZ)
|
velocity.setTo(motionX, motionY, motionZ)
|
||||||
@@ -41,12 +44,12 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) :
|
|||||||
abstract val isValid: Boolean
|
abstract val isValid: Boolean
|
||||||
|
|
||||||
/** Add the particle to the effect renderer if it is valid. */
|
/** Add the particle to the effect renderer if it is valid. */
|
||||||
fun addIfValid() { if (isValid) Minecraft.getMinecraft().effectRenderer.addEffect(this) }
|
fun addIfValid() { if (isValid) Minecraft.getInstance().particles.addEffect(this) }
|
||||||
|
|
||||||
override fun renderParticle(worldRenderer: BufferBuilder, entity: Entity, partialTickTime: Float, rotX: Float, rotZ: Float, rotYZ: Float, rotXY: Float, rotXZ: Float) {
|
override fun renderParticle(buffer: BufferBuilder, entity: ActiveRenderInfo, partialTicks: Float, rotX: Float, rotZ: Float, rotYZ: Float, rotXY: Float, rotXZ: Float) {
|
||||||
billboardRot.first.setTo(rotX + rotXY, rotZ, rotYZ + rotXZ)
|
billboardRot.first.setTo(rotX + rotXY, rotZ, rotYZ + rotXZ)
|
||||||
billboardRot.second.setTo(rotX - rotXY, -rotZ, rotYZ - rotXZ)
|
billboardRot.second.setTo(rotX - rotXY, -rotZ, rotYZ - rotXZ)
|
||||||
render(worldRenderer, partialTickTime)
|
render(buffer, partialTicks)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Render a particle quad.
|
* Render a particle quad.
|
||||||
@@ -67,7 +70,7 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) :
|
|||||||
prevPos: Double3 = this.prevPos,
|
prevPos: Double3 = this.prevPos,
|
||||||
size: Double = particleScale.toDouble(),
|
size: Double = particleScale.toDouble(),
|
||||||
rotation: Int = 0,
|
rotation: Int = 0,
|
||||||
icon: TextureAtlasSprite = particleTexture,
|
icon: TextureAtlasSprite = sprite,
|
||||||
isMirrored: Boolean = false,
|
isMirrored: Boolean = false,
|
||||||
alpha: Float = this.particleAlpha) {
|
alpha: Float = this.particleAlpha) {
|
||||||
|
|
||||||
@@ -115,7 +118,8 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) :
|
|||||||
.endVertex()
|
.endVertex()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFXLayer() = 1
|
// override fun getFXLayer() = 1
|
||||||
|
override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
|
||||||
|
|
||||||
fun setColor(color: Int) {
|
fun setColor(color: Int) {
|
||||||
particleBlue = (color and 255) / 256.0f
|
particleBlue = (color and 255) / 256.0f
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package mods.octarinecore.client.render
|
||||||
|
|
||||||
|
import mods.octarinecore.common.*
|
||||||
|
import mods.octarinecore.semiRandom
|
||||||
|
import net.minecraft.block.Block
|
||||||
|
import net.minecraft.block.BlockState
|
||||||
|
import net.minecraft.client.renderer.BlockRendererDispatcher
|
||||||
|
import net.minecraft.client.renderer.BufferBuilder
|
||||||
|
import net.minecraft.util.BlockRenderLayer
|
||||||
|
import net.minecraft.util.Direction
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.minecraft.world.IBlockReader
|
||||||
|
import net.minecraft.world.IEnviromentBlockReader
|
||||||
|
import net.minecraft.world.World
|
||||||
|
import net.minecraft.world.biome.Biome
|
||||||
|
import net.minecraftforge.client.model.data.IModelData
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the block being rendered. Has properties and methods to query the neighborhood of the block in
|
||||||
|
* block-relative coordinates.
|
||||||
|
*/
|
||||||
|
interface BlockCtx {
|
||||||
|
val world: IEnviromentBlockReader
|
||||||
|
val pos: BlockPos
|
||||||
|
|
||||||
|
fun offset(dir: Direction) = offset(dir.offset)
|
||||||
|
fun offset(offset: Int3): BlockCtx
|
||||||
|
|
||||||
|
val state: BlockState get() = world.getBlockState(pos)
|
||||||
|
fun state(dir: Direction) = world.getBlockState(pos + dir.offset)
|
||||||
|
fun state(offset: Int3) = world.getBlockState(pos + offset)
|
||||||
|
|
||||||
|
val biome: Biome get() = world.getBiome(pos)
|
||||||
|
|
||||||
|
val isNormalCube: Boolean get() = state.isNormalCube(world, pos)
|
||||||
|
|
||||||
|
fun shouldSideBeRendered(side: Direction) = Block.shouldSideBeRendered(state, world, pos, side)
|
||||||
|
|
||||||
|
/** Get a semi-random value based on the block coordinate and the given seed. */
|
||||||
|
fun semiRandom(seed: Int) = semiRandom(pos.x, pos.y, pos.z, seed)
|
||||||
|
|
||||||
|
/** Get an array of semi-random values based on the block coordinate. */
|
||||||
|
fun semiRandomArray(num: Int): Array<Int> = Array(num) { semiRandom(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
open class BasicBlockCtx(
|
||||||
|
override val world: IEnviromentBlockReader,
|
||||||
|
override val pos: BlockPos
|
||||||
|
) : BlockCtx {
|
||||||
|
override var state: BlockState = world.getBlockState(pos)
|
||||||
|
protected set
|
||||||
|
override fun offset(offset: Int3) = BasicBlockCtx(world, pos + offset)
|
||||||
|
fun cache() = CachedBlockCtx(world, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class CachedBlockCtx(world: IEnviromentBlockReader, pos: BlockPos) : BasicBlockCtx(world, pos) {
|
||||||
|
var neighbors = Array<BlockState>(6) { world.getBlockState(pos + allDirections[it].offset) }
|
||||||
|
override var biome: Biome = world.getBiome(pos)
|
||||||
|
override fun state(dir: Direction) = neighbors[dir.ordinal]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class RenderCtx(
|
||||||
|
val dispatcher: BlockRendererDispatcher,
|
||||||
|
val renderBuffer: BufferBuilder,
|
||||||
|
val layer: BlockRenderLayer,
|
||||||
|
val random: Random,
|
||||||
|
val modelData: IModelData
|
||||||
|
) {
|
||||||
|
fun render(worldBlock: BlockCtx) = dispatcher.renderBlock(worldBlock.state, worldBlock.pos, worldBlock.world, renderBuffer, random, modelData)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package mods.octarinecore.client.render
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.render.canRenderInCutout
|
||||||
|
import mods.betterfoliage.client.render.isCutout
|
||||||
|
import mods.octarinecore.BufferBuilder
|
||||||
|
import mods.octarinecore.BufferBuilder_setSprite
|
||||||
|
import mods.octarinecore.client.render.lighting.*
|
||||||
|
import mods.octarinecore.common.Double3
|
||||||
|
import mods.octarinecore.common.Int3
|
||||||
|
import mods.octarinecore.common.Rotation
|
||||||
|
import mods.octarinecore.common.plus
|
||||||
|
import mods.octarinecore.metaprog.get
|
||||||
|
import mods.octarinecore.metaprog.set
|
||||||
|
import net.minecraft.block.Blocks
|
||||||
|
import net.minecraft.fluid.Fluids
|
||||||
|
import net.minecraft.util.Direction
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.minecraft.world.IEnviromentBlockReader
|
||||||
|
import net.minecraft.world.LightType
|
||||||
|
import net.minecraft.world.biome.Biomes
|
||||||
|
|
||||||
|
class CombinedContext(
|
||||||
|
val blockCtx: BlockCtx, val renderCtx: RenderCtx, val lightingCtx: DefaultLightingCtx
|
||||||
|
) : BlockCtx by blockCtx, LightingCtx by lightingCtx {
|
||||||
|
|
||||||
|
var hasRendered = false
|
||||||
|
|
||||||
|
fun render(force: Boolean = false) = renderCtx.let {
|
||||||
|
if (force || state.canRenderInLayer(it.layer) || (state.canRenderInCutout() && it.layer.isCutout)) {
|
||||||
|
it.render(blockCtx)
|
||||||
|
hasRendered = true
|
||||||
|
}
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exchange(moddedOffset: Int3, targetOffset: Int3) = CombinedContext(
|
||||||
|
BasicBlockCtx(OffsetEnvBlockReader(blockCtx.world, pos + moddedOffset, pos + targetOffset), pos),
|
||||||
|
renderCtx,
|
||||||
|
lightingCtx
|
||||||
|
)
|
||||||
|
|
||||||
|
val isCutout = renderCtx.layer.isCutout
|
||||||
|
|
||||||
|
/** Get the centerpoint of the block being rendered. */
|
||||||
|
val blockCenter: Double3 get() = Double3(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5)
|
||||||
|
|
||||||
|
/** Holds final vertex data before it goes to the [Tessellator]. */
|
||||||
|
val temp = RenderVertex()
|
||||||
|
|
||||||
|
fun render(
|
||||||
|
model: Model,
|
||||||
|
rotation: Rotation = Rotation.identity,
|
||||||
|
translation: Double3 = blockCenter,
|
||||||
|
forceFlat: Boolean = false,
|
||||||
|
quadFilter: (Int, Quad) -> Boolean = { _, _ -> true },
|
||||||
|
icon: QuadIconResolver,
|
||||||
|
postProcess: PostProcessLambda = noPost
|
||||||
|
) {
|
||||||
|
lightingCtx.modelRotation = rotation
|
||||||
|
model.quads.forEachIndexed { quadIdx, quad ->
|
||||||
|
if (quadFilter(quadIdx, quad)) {
|
||||||
|
val drawIcon = icon(this, quadIdx, quad)
|
||||||
|
if (drawIcon != null) {
|
||||||
|
// let OptiFine know the texture we're using, so it can
|
||||||
|
// transform UV coordinates to quad-relative
|
||||||
|
BufferBuilder_setSprite.invoke(renderCtx.renderBuffer, drawIcon)
|
||||||
|
|
||||||
|
quad.verts.forEachIndexed { vertIdx, vert ->
|
||||||
|
temp.init(vert).rotate(lightingCtx.modelRotation).translate(translation)
|
||||||
|
val shader = if (lightingCtx.aoEnabled && !forceFlat) vert.aoShader else vert.flatShader
|
||||||
|
shader.shade(lightingCtx, temp)
|
||||||
|
temp.postProcess(this, quadIdx, quad, vertIdx, vert)
|
||||||
|
temp.setIcon(drawIcon)
|
||||||
|
|
||||||
|
renderCtx.renderBuffer
|
||||||
|
.pos(temp.x, temp.y, temp.z)
|
||||||
|
.color(temp.red, temp.green, temp.blue, 1.0f)
|
||||||
|
.tex(temp.u, temp.v)
|
||||||
|
.lightmap(temp.brightness shr 16 and 65535, temp.brightness and 65535)
|
||||||
|
.endVertex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasRendered = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val allFaces: (Direction) -> Boolean = { true }
|
||||||
|
val topOnly: (Direction) -> Boolean = { it == Direction.UP }
|
||||||
|
|
||||||
|
/** Perform no post-processing */
|
||||||
|
val noPost: PostProcessLambda = { _, _, _, _, _ -> }
|
||||||
|
|
||||||
|
object NonNullWorld : IEnviromentBlockReader {
|
||||||
|
override fun getBlockState(pos: BlockPos) = Blocks.AIR.defaultState
|
||||||
|
override fun getLightFor(type: LightType, pos: BlockPos) = 0
|
||||||
|
override fun getFluidState(pos: BlockPos) = Fluids.EMPTY.defaultState
|
||||||
|
override fun getTileEntity(pos: BlockPos) = null
|
||||||
|
override fun getBiome(pos: BlockPos) = Biomes.THE_VOID
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package mods.octarinecore.client.render
|
package mods.octarinecore.client.render
|
||||||
|
|
||||||
|
import mods.octarinecore.client.render.lighting.*
|
||||||
import mods.octarinecore.common.*
|
import mods.octarinecore.common.*
|
||||||
import mods.octarinecore.minmax
|
import mods.octarinecore.minmax
|
||||||
import mods.octarinecore.replace
|
import mods.octarinecore.replace
|
||||||
import net.minecraft.util.EnumFacing
|
import net.minecraft.util.Direction
|
||||||
import java.lang.Math.max
|
import java.lang.Math.max
|
||||||
import java.lang.Math.min
|
import java.lang.Math.min
|
||||||
|
|
||||||
@@ -40,13 +41,13 @@ data class UV(val u: Double, val v: Double) {
|
|||||||
*
|
*
|
||||||
* @param[xyz] x, y, z coordinates
|
* @param[xyz] x, y, z coordinates
|
||||||
* @param[uv] u, v coordinates
|
* @param[uv] u, v coordinates
|
||||||
* @param[aoShader] [Shader] instance to use with AO rendering
|
* @param[aoShader] [ModelLighter] instance to use with AO rendering
|
||||||
* @param[flatShader] [Shader] instance to use with non-AO rendering
|
* @param[flatShader] [ModelLighter] instance to use with non-AO rendering
|
||||||
*/
|
*/
|
||||||
data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0),
|
data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0),
|
||||||
val uv: UV = UV(0.0, 0.0),
|
val uv: UV = UV(0.0, 0.0),
|
||||||
val aoShader: Shader = NoShader,
|
val aoShader: ModelLighter = NoLighting,
|
||||||
val flatShader: Shader = NoShader)
|
val flatShader: ModelLighter = NoLighting)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model quad
|
* Model quad
|
||||||
@@ -59,7 +60,7 @@ data class Quad(val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex)
|
|||||||
val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize
|
val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize
|
||||||
|
|
||||||
fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) }
|
fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) }
|
||||||
fun move(trans: Pair<Double, EnumFacing>) = move(Double3(trans.second) * trans.first)
|
fun move(trans: Pair<Double, Direction>) = move(Double3(trans.second) * trans.first)
|
||||||
fun scale (scale: Double) = transformV { it.copy(xyz = it.xyz * scale) }
|
fun scale (scale: Double) = transformV { it.copy(xyz = it.xyz * scale) }
|
||||||
fun scale (scale: Double3) = transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) }
|
fun scale (scale: Double3) = transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) }
|
||||||
fun scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) }
|
fun scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) }
|
||||||
@@ -78,7 +79,7 @@ data class Quad(val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex)
|
|||||||
transformVI { vertex, idx ->
|
transformVI { vertex, idx ->
|
||||||
if (!predicate(vertex, idx)) vertex else vertex.copy(flatShader = factory(this@Quad, vertex))
|
if (!predicate(vertex, idx)) vertex else vertex.copy(flatShader = factory(this@Quad, vertex))
|
||||||
}
|
}
|
||||||
fun setFlatShader(shader: Shader) = transformVI { vertex, idx -> vertex.copy(flatShader = shader) }
|
fun setFlatShader(shader: ModelLighter) = transformVI { vertex, idx -> vertex.copy(flatShader = shader) }
|
||||||
val flipped: Quad get() = Quad(v4, v3, v2, v1)
|
val flipped: Quad get() = Quad(v4, v3, v2, v1)
|
||||||
|
|
||||||
fun cycleVertices(n: Int) = when(n % 4) {
|
fun cycleVertices(n: Int) = when(n % 4) {
|
||||||
@@ -123,10 +124,10 @@ class Model() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun faceQuad(face: EnumFacing): Quad {
|
fun faceQuad(face: Direction): Quad {
|
||||||
val base = face.vec * 0.5
|
val base = face.vec * 0.5
|
||||||
val top = faceCorners[face.ordinal].topLeft.first.vec * 0.5
|
val top = boxFaces[face].top * 0.5
|
||||||
val left = faceCorners[face.ordinal].topLeft.second.vec * 0.5
|
val left = boxFaces[face].left * 0.5
|
||||||
return Quad(
|
return Quad(
|
||||||
Vertex(base + top + left, UV.topLeft),
|
Vertex(base + top + left, UV.topLeft),
|
||||||
Vertex(base - top + left, UV.bottomLeft),
|
Vertex(base - top + left, UV.bottomLeft),
|
||||||
@@ -137,7 +138,7 @@ class Model() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val fullCube = Model().apply {
|
val fullCube = Model().apply {
|
||||||
forgeDirs.forEach {
|
allDirections.forEach {
|
||||||
faceQuad(it)
|
faceQuad(it)
|
||||||
.setAoShader(faceOrientedAuto(corner = cornerAo(it.axis), edge = null))
|
.setAoShader(faceOrientedAuto(corner = cornerAo(it.axis), edge = null))
|
||||||
.setFlatShader(faceOrientedAuto(corner = cornerFlat, edge = null))
|
.setFlatShader(faceOrientedAuto(corner = cornerFlat, edge = null))
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
package mods.octarinecore.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.loader.Refs
|
|
||||||
import mods.octarinecore.common.*
|
|
||||||
import net.minecraft.client.Minecraft
|
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
|
|
||||||
typealias QuadIconResolver = (ShadingContext, Int, Quad) -> TextureAtlasSprite?
|
|
||||||
typealias PostProcessLambda = RenderVertex.(ShadingContext, Int, Quad, Int, Vertex) -> Unit
|
|
||||||
|
|
||||||
class ModelRenderer : ShadingContext() {
|
|
||||||
|
|
||||||
/** Holds final vertex data before it goes to the [Tessellator]. */
|
|
||||||
val temp = RenderVertex()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render a [Model].
|
|
||||||
* The [blockContext] and [renderBlocks] need to be set up correctly, including first rendering the
|
|
||||||
* corresponding block to capture shading values!
|
|
||||||
*
|
|
||||||
* @param[model] model to render
|
|
||||||
* @param[rot] rotation to apply to the model
|
|
||||||
* @param[trans] translation to apply to the model
|
|
||||||
* @param[forceFlat] force flat shading even if AO is enabled
|
|
||||||
* @param[icon] lambda to resolve the texture to use for each quad
|
|
||||||
* @param[rotateUV] lambda to get amount of UV rotation for each quad
|
|
||||||
* @param[postProcess] lambda to perform arbitrary modifications on the [RenderVertex] just before it goes to the [Tessellator]
|
|
||||||
*/
|
|
||||||
fun render(
|
|
||||||
worldRenderer: BufferBuilder,
|
|
||||||
model: Model,
|
|
||||||
rot: Rotation = Rotation.identity,
|
|
||||||
trans: Double3 = blockContext.blockCenter,
|
|
||||||
forceFlat: Boolean = false,
|
|
||||||
quadFilter: (Int, Quad) -> Boolean = { _, _ -> true },
|
|
||||||
icon: QuadIconResolver,
|
|
||||||
postProcess: PostProcessLambda
|
|
||||||
) {
|
|
||||||
rotation = rot
|
|
||||||
aoEnabled = Minecraft.isAmbientOcclusionEnabled()
|
|
||||||
|
|
||||||
// make sure we have space in the buffer for our quads plus one
|
|
||||||
worldRenderer.ensureSpaceForQuads(model.quads.size + 1)
|
|
||||||
|
|
||||||
model.quads.forEachIndexed { quadIdx, quad ->
|
|
||||||
if (quadFilter(quadIdx, quad)) {
|
|
||||||
val drawIcon = icon(this, quadIdx, quad)
|
|
||||||
if (drawIcon != null) {
|
|
||||||
// let OptiFine know the texture we're using, so it can
|
|
||||||
// transform UV coordinates to quad-relative
|
|
||||||
Refs.quadSprite.set(worldRenderer, drawIcon)
|
|
||||||
|
|
||||||
quad.verts.forEachIndexed { vertIdx, vert ->
|
|
||||||
temp.init(vert).rotate(rotation).translate(trans)
|
|
||||||
val shader = if (aoEnabled && !forceFlat) vert.aoShader else vert.flatShader
|
|
||||||
shader.shade(this, temp)
|
|
||||||
temp.postProcess(this, quadIdx, quad, vertIdx, vert)
|
|
||||||
temp.setIcon(drawIcon)
|
|
||||||
|
|
||||||
worldRenderer
|
|
||||||
.pos(temp.x, temp.y, temp.z)
|
|
||||||
.color(temp.red, temp.green, temp.blue, 1.0f)
|
|
||||||
.tex(temp.u, temp.v)
|
|
||||||
.lightmap(temp.brightness shr 16 and 65535, temp.brightness and 65535)
|
|
||||||
.endVertex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queried by [Shader] objects to get rendering-relevant data of the current block in a rotated frame of reference.
|
|
||||||
*/
|
|
||||||
open class ShadingContext {
|
|
||||||
var rotation = Rotation.identity
|
|
||||||
var aoEnabled = Minecraft.isAmbientOcclusionEnabled()
|
|
||||||
val aoFaces = Array(6) { AoFaceData(forgeDirs[it]) }
|
|
||||||
|
|
||||||
val EnumFacing.aoMultiplier: Float get() = when(this) {
|
|
||||||
UP -> 1.0f
|
|
||||||
DOWN -> 0.5f
|
|
||||||
NORTH, SOUTH -> 0.8f
|
|
||||||
EAST, WEST -> 0.6f
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateShading(offset: Int3, predicate: (EnumFacing) -> Boolean = { true }) {
|
|
||||||
forgeDirs.forEach { if (predicate(it)) aoFaces[it.ordinal].update(offset, multiplier = it.aoMultiplier) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun aoShading(face: EnumFacing, corner1: EnumFacing, corner2: EnumFacing) =
|
|
||||||
aoFaces[face.rotate(rotation).ordinal][corner1.rotate(rotation), corner2.rotate(rotation)]
|
|
||||||
|
|
||||||
fun blockData(offset: Int3) = blockContext.blockData(offset.rotate(rotation))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
class RenderVertex() {
|
|
||||||
var x: Double = 0.0
|
|
||||||
var y: Double = 0.0
|
|
||||||
var z: Double = 0.0
|
|
||||||
var u: Double = 0.0
|
|
||||||
var v: Double = 0.0
|
|
||||||
var brightness: Int = 0
|
|
||||||
var red: Float = 0.0f
|
|
||||||
var green: Float = 0.0f
|
|
||||||
var blue: Float = 0.0f
|
|
||||||
|
|
||||||
val rawData = IntArray(7)
|
|
||||||
|
|
||||||
fun init(vertex: Vertex, rot: Rotation, trans: Double3): RenderVertex {
|
|
||||||
val result = vertex.xyz.rotate(rot) + trans
|
|
||||||
x = result.x; y = result.y; z = result.z
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
fun init(vertex: Vertex): RenderVertex {
|
|
||||||
x = vertex.xyz.x; y = vertex.xyz.y; z = vertex.xyz.z;
|
|
||||||
u = vertex.uv.u; v = vertex.uv.v
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
fun translate(trans: Double3): RenderVertex { x += trans.x; y += trans.y; z += trans.z; return this }
|
|
||||||
fun rotate(rot: Rotation): RenderVertex {
|
|
||||||
if (rot === Rotation.identity) return this
|
|
||||||
val rotX = rot.rotatedComponent(EAST, x, y, z)
|
|
||||||
val rotY = rot.rotatedComponent(UP, x, y, z)
|
|
||||||
val rotZ = rot.rotatedComponent(SOUTH, x, y, z)
|
|
||||||
x = rotX; y = rotY; z = rotZ
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
inline fun rotateUV(n: Int): RenderVertex {
|
|
||||||
when (n % 4) {
|
|
||||||
1 -> { val t = v; v = -u; u = t; return this }
|
|
||||||
2 -> { u = -u; v = -v; return this }
|
|
||||||
3 -> { val t = -v; v = u; u = t; return this }
|
|
||||||
else -> { return this }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inline fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) {
|
|
||||||
if (mirrorU) u = -u
|
|
||||||
if (mirrorV) v = -v
|
|
||||||
}
|
|
||||||
inline fun setIcon(icon: TextureAtlasSprite): RenderVertex {
|
|
||||||
u = (icon.maxU - icon.minU) * (u + 0.5) + icon.minU
|
|
||||||
v = (icon.maxV - icon.minV) * (v + 0.5) + icon.minV
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun setGrey(level: Float) {
|
|
||||||
val grey = Math.min((red + green + blue) * 0.333f * level, 1.0f)
|
|
||||||
red = grey; green = grey; blue = grey
|
|
||||||
}
|
|
||||||
inline fun multiplyColor(color: Int) {
|
|
||||||
red *= (color shr 16 and 255) / 256.0f
|
|
||||||
green *= (color shr 8 and 255) / 256.0f
|
|
||||||
blue *= (color and 255) / 256.0f
|
|
||||||
}
|
|
||||||
inline fun setColor(color: Int) {
|
|
||||||
red = (color shr 16 and 255) / 256.0f
|
|
||||||
green = (color shr 8 and 255) / 256.0f
|
|
||||||
blue = (color and 255) / 256.0f
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun BufferBuilder.ensureSpaceForQuads(num: Int) {
|
|
||||||
rawIntBuffer.position(bufferSize)
|
|
||||||
growBuffer(num * vertexFormat.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
val allFaces: (EnumFacing) -> Boolean = { true }
|
|
||||||
val topOnly: (EnumFacing) -> Boolean = { it == UP }
|
|
||||||
|
|
||||||
/** Perform no post-processing */
|
|
||||||
val noPost: PostProcessLambda = { _, _, _, _, _ -> }
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package mods.octarinecore.client.render
|
|
||||||
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.plus
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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")
|
|
||||||
class OffsetBlockAccess(val original: IBlockAccess, val modded: BlockPos, val target: BlockPos) : IBlockAccess {
|
|
||||||
|
|
||||||
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 getBiome(pos: BlockPos?) = original.getBiome(actualPos(pos))
|
|
||||||
override fun getBlockState(pos: BlockPos?) = original.getBlockState(actualPos(pos))
|
|
||||||
override fun getCombinedLight(pos: BlockPos?, lightValue: Int) = original.getCombinedLight(actualPos(pos), lightValue)
|
|
||||||
override fun getStrongPower(pos: BlockPos?, direction: EnumFacing?) = original.getStrongPower(actualPos(pos), direction)
|
|
||||||
override fun getTileEntity(pos: BlockPos?) = original.getTileEntity(actualPos(pos))
|
|
||||||
override fun getWorldType() = original.worldType
|
|
||||||
override fun isAirBlock(pos: BlockPos?) = original.isAirBlock(actualPos(pos))
|
|
||||||
override fun isSideSolid(pos: BlockPos?, side: EnumFacing?, _default: Boolean) = original.isSideSolid(actualPos(pos), side, _default)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Temporarily replaces the [IBlockAccess] used by this [BlockContext] and the corresponding [ExtendedRenderBlocks]
|
|
||||||
* to use an [OffsetBlockAccess] 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 = world!!
|
|
||||||
world = OffsetBlockAccess(original, pos + modded, pos + target)
|
|
||||||
val result = func()
|
|
||||||
world = original
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package mods.octarinecore.client.render
|
||||||
|
|
||||||
|
import mods.octarinecore.common.Int3
|
||||||
|
import mods.octarinecore.common.plus
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.minecraft.world.IBlockReader
|
||||||
|
import net.minecraft.world.IEnviromentBlockReader
|
||||||
|
import net.minecraft.world.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 OffsetBlockReader(open val original: IBlockReader, val modded: BlockPos, val target: BlockPos) : IBlockReader {
|
||||||
|
inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos
|
||||||
|
|
||||||
|
override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
|
||||||
|
override fun getTileEntity(pos: BlockPos) = original.getTileEntity(actualPos(pos))
|
||||||
|
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
|
||||||
|
class OffsetEnvBlockReader(val original: IEnviromentBlockReader, val modded: BlockPos, val target: BlockPos) : IEnviromentBlockReader by original {
|
||||||
|
inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos
|
||||||
|
|
||||||
|
override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
|
||||||
|
override fun getTileEntity(pos: BlockPos) = original.getTileEntity(actualPos(pos))
|
||||||
|
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
|
||||||
|
|
||||||
|
override fun getLightFor(type: LightType, pos: BlockPos) = original.getLightFor(type, actualPos(pos))
|
||||||
|
override fun getCombinedLight(pos: BlockPos, light: Int) = original.getCombinedLight(actualPos(pos), light)
|
||||||
|
override fun 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
|
||||||
|
//}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
@file:JvmName("PixelFormat")
|
|
||||||
package mods.octarinecore.client.render
|
|
||||||
|
|
||||||
import java.awt.Color
|
|
||||||
|
|
||||||
/** List of bit-shift offsets in packed brightness values where meaningful (4-bit) data is contained. */
|
|
||||||
var brightnessComponents = listOf(20, 4)
|
|
||||||
|
|
||||||
/** Multiply the components of this packed brightness value with the given [Float]. */
|
|
||||||
infix fun Int.brMul(f: Float): Int {
|
|
||||||
val weight = (f * 256.0f).toInt()
|
|
||||||
var result = 0
|
|
||||||
brightnessComponents.forEach { shift ->
|
|
||||||
val raw = (this shr shift) and 15
|
|
||||||
val weighted = (raw) * weight / 256
|
|
||||||
result = result or (weighted shl shift)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Multiply the components of this packed color value with the given [Float]. */
|
|
||||||
infix fun Int.colorMul(f: Float): Int {
|
|
||||||
val weight = (f * 256.0f).toInt()
|
|
||||||
val red = (this shr 16 and 255) * weight / 256
|
|
||||||
val green = (this shr 8 and 255) * weight / 256
|
|
||||||
val blue = (this and 255) * weight / 256
|
|
||||||
return (red shl 16) or (green shl 8) or blue
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sum the components of all packed brightness values given. */
|
|
||||||
fun brSum(multiplier: Float?, vararg brightness: Int): Int {
|
|
||||||
val sum = Array(brightnessComponents.size) { 0 }
|
|
||||||
brightnessComponents.forEachIndexed { idx, shift -> brightness.forEach { br ->
|
|
||||||
val comp = (br shr shift) and 15
|
|
||||||
sum[idx] += comp
|
|
||||||
} }
|
|
||||||
var result = 0
|
|
||||||
brightnessComponents.forEachIndexed { idx, shift ->
|
|
||||||
val comp = if (multiplier == null)
|
|
||||||
((sum[idx]) shl shift)
|
|
||||||
else
|
|
||||||
((sum[idx].toFloat() * multiplier).toInt() shl shift)
|
|
||||||
result = result or comp
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun brWeighted(br1: Int, weight1: Float, br2: Int, weight2: Float): Int {
|
|
||||||
val w1int = (weight1 * 256.0f + 0.5f).toInt()
|
|
||||||
val w2int = (weight2 * 256.0f + 0.5f).toInt()
|
|
||||||
var result = 0
|
|
||||||
brightnessComponents.forEachIndexed { idx, shift ->
|
|
||||||
val comp1 = (br1 shr shift) and 15
|
|
||||||
val comp2 = (br2 shr shift) and 15
|
|
||||||
val compWeighted = (comp1 * w1int + comp2 * w2int) / 256
|
|
||||||
result = result or ((compWeighted and 15) shl shift)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
|
|
||||||
companion object {
|
|
||||||
fun fromColor(color: Int): HSB {
|
|
||||||
val hsbVals = Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
|
|
||||||
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val asColor: Int get() = Color.HSBtoRGB(hue, saturation, brightness)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
@file:JvmName("RendererHolder")
|
||||||
|
package mods.octarinecore.client.render
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.render.canRenderInCutout
|
||||||
|
import mods.betterfoliage.client.render.isCutout
|
||||||
|
import mods.octarinecore.ThreadLocalDelegate
|
||||||
|
import mods.octarinecore.client.resource.ResourceHandler
|
||||||
|
import mods.octarinecore.common.Double3
|
||||||
|
import mods.octarinecore.common.Int3
|
||||||
|
import mods.octarinecore.common.allDirOffsets
|
||||||
|
import mods.octarinecore.common.plus
|
||||||
|
import mods.octarinecore.semiRandom
|
||||||
|
import net.minecraft.block.Block
|
||||||
|
import net.minecraft.block.BlockState
|
||||||
|
import net.minecraft.client.Minecraft
|
||||||
|
import net.minecraft.client.renderer.BlockRendererDispatcher
|
||||||
|
import net.minecraft.client.renderer.BufferBuilder
|
||||||
|
import net.minecraft.client.renderer.color.BlockColors
|
||||||
|
import net.minecraft.util.BlockRenderLayer
|
||||||
|
import net.minecraft.util.Direction
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.minecraft.util.math.MathHelper
|
||||||
|
import net.minecraft.world.IEnviromentBlockReader
|
||||||
|
import net.minecraft.world.biome.Biome
|
||||||
|
import net.minecraftforge.client.model.data.IModelData
|
||||||
|
import net.minecraftforge.eventbus.api.IEventBus
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
abstract class RenderDecorator(modId: String, modBus: IEventBus) : ResourceHandler(modId, modBus) {
|
||||||
|
|
||||||
|
open val renderOnCutout: Boolean get() = true
|
||||||
|
open val onlyOnCutout: Boolean get() = false
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// Custom rendering
|
||||||
|
// ============================
|
||||||
|
abstract fun isEligible(ctx: CombinedContext): Boolean
|
||||||
|
abstract fun render(ctx: CombinedContext)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class BlockData(val state: BlockState, val color: Int, val brightness: Int)
|
||||||
|
|
||||||
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
package mods.octarinecore.client.render
|
|
||||||
|
|
||||||
import mods.octarinecore.common.*
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
|
|
||||||
|
|
||||||
const val defaultCornerDimming = 0.5f
|
|
||||||
const val defaultEdgeDimming = 0.8f
|
|
||||||
|
|
||||||
// ================================
|
|
||||||
// Shader instantiation lambdas
|
|
||||||
// ================================
|
|
||||||
fun cornerAo(fallbackAxis: EnumFacing.Axis): CornerShaderFactory = { face, dir1, dir2 ->
|
|
||||||
val fallbackDir = listOf(face, dir1, dir2).find { it.axis == fallbackAxis }!!
|
|
||||||
CornerSingleFallback(face, dir1, dir2, fallbackDir)
|
|
||||||
}
|
|
||||||
val cornerFlat = { face: EnumFacing, dir1: EnumFacing, dir2: EnumFacing -> FaceFlat(face) }
|
|
||||||
fun cornerAoTri(func: (AoData, AoData)-> AoData) = { face: EnumFacing, dir1: EnumFacing, dir2: EnumFacing ->
|
|
||||||
CornerTri(face, dir1, dir2, func)
|
|
||||||
}
|
|
||||||
val cornerAoMaxGreen = cornerAoTri { s1, s2 -> if (s1.green > s2.green) s1 else s2 }
|
|
||||||
|
|
||||||
fun cornerInterpolate(edgeAxis: EnumFacing.Axis, weight: Float, dimming: Float): CornerShaderFactory = { dir1, dir2, dir3 ->
|
|
||||||
val edgeDir = listOf(dir1, dir2, dir3).find { it.axis == edgeAxis }!!
|
|
||||||
val faceDirs = listOf(dir1, dir2, dir3).filter { it.axis != edgeAxis }
|
|
||||||
CornerInterpolateDimming(faceDirs[0], faceDirs[1], edgeDir, weight, dimming)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================================
|
|
||||||
// Shaders
|
|
||||||
// ================================
|
|
||||||
object NoShader : Shader {
|
|
||||||
override fun shade(context: ShadingContext, vertex: RenderVertex) = vertex.shade(AoData.black)
|
|
||||||
override fun rotate(rot: Rotation) = this
|
|
||||||
}
|
|
||||||
|
|
||||||
class CornerSingleFallback(val face: EnumFacing, val dir1: EnumFacing, val dir2: EnumFacing, val fallbackDir: EnumFacing, val fallbackDimming: Float = defaultCornerDimming) : Shader {
|
|
||||||
val offset = Int3(fallbackDir)
|
|
||||||
override fun shade(context: ShadingContext, vertex: RenderVertex) {
|
|
||||||
val shading = context.aoShading(face, dir1, dir2)
|
|
||||||
if (shading.valid)
|
|
||||||
vertex.shade(shading)
|
|
||||||
else context.blockData(offset).let {
|
|
||||||
vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override fun rotate(rot: Rotation) = CornerSingleFallback(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), fallbackDir.rotate(rot), fallbackDimming)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun accumulate(v1: AoData?, v2: AoData?, func: ((AoData, AoData)-> AoData)): AoData? {
|
|
||||||
val v1ok = v1 != null && v1.valid
|
|
||||||
val v2ok = v2 != null && v2.valid
|
|
||||||
if (v1ok && v2ok) return func(v1!!, v2!!)
|
|
||||||
if (v1ok) return v1
|
|
||||||
if (v2ok) return v2
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
class CornerTri(val face: EnumFacing, val dir1: EnumFacing, val dir2: EnumFacing,
|
|
||||||
val func: ((AoData, AoData)-> AoData)) : Shader {
|
|
||||||
override fun shade(context: ShadingContext, vertex: RenderVertex) {
|
|
||||||
var acc = accumulate(
|
|
||||||
context.aoShading(face, dir1, dir2),
|
|
||||||
context.aoShading(dir1, face, dir2),
|
|
||||||
func)
|
|
||||||
acc = accumulate(
|
|
||||||
acc,
|
|
||||||
context.aoShading(dir2, face, dir1),
|
|
||||||
func)
|
|
||||||
vertex.shade(acc ?: AoData.black)
|
|
||||||
}
|
|
||||||
override fun rotate(rot: Rotation) = CornerTri(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), func)
|
|
||||||
}
|
|
||||||
|
|
||||||
class EdgeInterpolateFallback(val face: EnumFacing, val edgeDir: EnumFacing, val pos: Double, val fallbackDimming: Float = defaultEdgeDimming): Shader {
|
|
||||||
val offset = Int3(edgeDir)
|
|
||||||
val edgeAxis = axes.find { it != face.axis && it != edgeDir.axis }!!
|
|
||||||
val weightN = (0.5 - pos).toFloat()
|
|
||||||
val weightP = (0.5 + pos).toFloat()
|
|
||||||
|
|
||||||
override fun shade(context: ShadingContext, vertex: RenderVertex) {
|
|
||||||
val shadingP = context.aoShading(face, edgeDir, (edgeAxis to EnumFacing.AxisDirection.POSITIVE).face)
|
|
||||||
val shadingN = context.aoShading(face, edgeDir, (edgeAxis to EnumFacing.AxisDirection.NEGATIVE).face)
|
|
||||||
if (!shadingP.valid && !shadingN.valid) context.blockData(offset).let {
|
|
||||||
return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming)
|
|
||||||
}
|
|
||||||
if (!shadingP.valid) return vertex.shade(shadingN)
|
|
||||||
if (!shadingN.valid) return vertex.shade(shadingP)
|
|
||||||
vertex.shade(shadingP, shadingN, weightP, weightN)
|
|
||||||
}
|
|
||||||
override fun rotate(rot: Rotation) = EdgeInterpolateFallback(face.rotate(rot), edgeDir.rotate(rot), pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
class CornerInterpolateDimming(val face1: EnumFacing, val face2: EnumFacing, val edgeDir: EnumFacing,
|
|
||||||
val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming) : Shader {
|
|
||||||
val offset = Int3(edgeDir)
|
|
||||||
override fun shade(context: ShadingContext, vertex: RenderVertex) {
|
|
||||||
var shading1 = context.aoShading(face1, edgeDir, face2)
|
|
||||||
var shading2 = context.aoShading(face2, edgeDir, face1)
|
|
||||||
var weight1 = weight
|
|
||||||
var weight2 = 1.0f - weight
|
|
||||||
if (!shading1.valid && !shading2.valid) context.blockData(offset).let {
|
|
||||||
return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming)
|
|
||||||
}
|
|
||||||
if (!shading1.valid) { shading1 = shading2; weight1 *= dimming }
|
|
||||||
if (!shading2.valid) { shading2 = shading1; weight2 *= dimming }
|
|
||||||
vertex.shade(shading1, shading2, weight1, weight2)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun rotate(rot: Rotation) =
|
|
||||||
CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming)
|
|
||||||
}
|
|
||||||
|
|
||||||
class FaceCenter(val face: EnumFacing): Shader {
|
|
||||||
override fun shade(context: ShadingContext, vertex: RenderVertex) {
|
|
||||||
vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f;
|
|
||||||
val b = IntArray(4)
|
|
||||||
faceCorners[face.ordinal].asList.forEachIndexed { idx, corner ->
|
|
||||||
val shading = context.aoShading(face, corner.first, corner.second)
|
|
||||||
vertex.red += shading.red
|
|
||||||
vertex.green += shading.green
|
|
||||||
vertex.blue += shading.blue
|
|
||||||
b[idx] = shading.brightness
|
|
||||||
}
|
|
||||||
vertex.apply { red *= 0.25f; green *= 0.25f; blue *= 0.25f }
|
|
||||||
vertex.brightness = brSum(0.25f, *b)
|
|
||||||
}
|
|
||||||
override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot))
|
|
||||||
}
|
|
||||||
|
|
||||||
class FaceFlat(val face: EnumFacing): Shader {
|
|
||||||
override fun shade(context: ShadingContext, vertex: RenderVertex) {
|
|
||||||
val color = context.blockData(Int3.zero).color
|
|
||||||
vertex.shade(context.blockData(face.offset).brightness, color)
|
|
||||||
}
|
|
||||||
override fun rotate(rot: Rotation): Shader = FaceFlat(face.rotate(rot))
|
|
||||||
}
|
|
||||||
|
|
||||||
class FlatOffset(val offset: Int3): Shader {
|
|
||||||
override fun shade(context: ShadingContext, vertex: RenderVertex) {
|
|
||||||
context.blockData(offset).let {
|
|
||||||
vertex.brightness = it.brightness
|
|
||||||
vertex.setColor(it.color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override fun rotate(rot: Rotation): Shader = this
|
|
||||||
}
|
|
||||||
|
|
||||||
class FlatOffsetNoColor(val offset: Int3): Shader {
|
|
||||||
override fun shade(context: ShadingContext, vertex: RenderVertex) {
|
|
||||||
vertex.brightness = context.blockData(offset).brightness
|
|
||||||
vertex.red = 1.0f; vertex.green = 1.0f; vertex.blue = 1.0f
|
|
||||||
}
|
|
||||||
override fun rotate(rot: Rotation): Shader = this
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
package mods.octarinecore.client.render
|
|
||||||
|
|
||||||
import mods.betterfoliage.loader.Refs
|
|
||||||
import mods.octarinecore.common.*
|
|
||||||
import mods.octarinecore.metaprog.allAvailable
|
|
||||||
import net.minecraft.client.Minecraft
|
|
||||||
import net.minecraft.client.renderer.BlockModelRenderer
|
|
||||||
import net.minecraft.util.EnumFacing
|
|
||||||
import net.minecraft.util.EnumFacing.*
|
|
||||||
import java.lang.Math.min
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
typealias EdgeShaderFactory = (EnumFacing, EnumFacing) -> Shader
|
|
||||||
typealias CornerShaderFactory = (EnumFacing, EnumFacing, EnumFacing) -> Shader
|
|
||||||
typealias ShaderFactory = (Quad, Vertex) -> Shader
|
|
||||||
|
|
||||||
/** Holds shading values for block corners as calculated by vanilla Minecraft rendering. */
|
|
||||||
class AoData() {
|
|
||||||
var valid = false
|
|
||||||
var brightness = 0
|
|
||||||
var red: Float = 0.0f
|
|
||||||
var green: Float = 0.0f
|
|
||||||
var blue: Float = 0.0f
|
|
||||||
|
|
||||||
fun reset() { valid = false }
|
|
||||||
|
|
||||||
fun set(brightness: Int, red: Float, green: Float, blue: Float) {
|
|
||||||
if (valid) return
|
|
||||||
this.valid = true
|
|
||||||
this.brightness = brightness
|
|
||||||
this.red = red
|
|
||||||
this.green = green
|
|
||||||
this.blue = blue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun set(brightness: Int, colorMultiplier: Float) {
|
|
||||||
this.valid = true
|
|
||||||
this.brightness = brightness
|
|
||||||
this.red = colorMultiplier
|
|
||||||
this.green = colorMultiplier
|
|
||||||
this.blue = colorMultiplier
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val black = AoData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AoFaceData(val face: EnumFacing) {
|
|
||||||
val ao = Refs.AmbientOcclusionFace.element!!.getDeclaredConstructor(Refs.BlockModelRenderer.element!!)
|
|
||||||
.newInstance(BlockModelRenderer(Minecraft.getMinecraft().blockColors))
|
|
||||||
as BlockModelRenderer.AmbientOcclusionFace
|
|
||||||
|
|
||||||
val top = faceCorners[face.ordinal].topLeft.first
|
|
||||||
val left = faceCorners[face.ordinal].topLeft.second
|
|
||||||
|
|
||||||
val topLeft = AoData()
|
|
||||||
val topRight = AoData()
|
|
||||||
val bottomLeft = AoData()
|
|
||||||
val bottomRight = AoData()
|
|
||||||
val ordered = when(face) {
|
|
||||||
DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight)
|
|
||||||
UP -> listOf(bottomRight, topRight, topLeft, bottomLeft)
|
|
||||||
NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft)
|
|
||||||
SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight)
|
|
||||||
WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft)
|
|
||||||
EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(offset: Int3, useBounds: Boolean = false, multiplier: Float = 1.0f) {
|
|
||||||
val ctx = blockContext
|
|
||||||
val blockState = ctx.blockState(offset)
|
|
||||||
val quadBounds: FloatArray = FloatArray(12)
|
|
||||||
val flags = BitSet(3).apply { set(0) }
|
|
||||||
|
|
||||||
ao.updateVertexBrightness(ctx.world, blockState, ctx.pos + offset, face, quadBounds, flags)
|
|
||||||
ordered.forEachIndexed { idx, aoData -> aoData.set(ao.vertexBrightness[idx], ao.vertexColorMultiplier[idx] * multiplier) }
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun get(dir1: EnumFacing, dir2: EnumFacing): AoData {
|
|
||||||
val isTop = top == dir1 || top == dir2
|
|
||||||
val isLeft = left == dir1 || left == dir2
|
|
||||||
return if (isTop) {
|
|
||||||
if (isLeft) topLeft else topRight
|
|
||||||
} else {
|
|
||||||
if (isLeft) bottomLeft else bottomRight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instances of this interface are associated with [Model] vertices, and used to apply brightness and color
|
|
||||||
* values to a [RenderVertex].
|
|
||||||
*/
|
|
||||||
interface Shader {
|
|
||||||
/**
|
|
||||||
* Set shading values of a [RenderVertex]
|
|
||||||
*
|
|
||||||
* @param[context] context that can be queried for shading data in a [Model]-relative frame of reference
|
|
||||||
* @param[vertex] the [RenderVertex] to manipulate
|
|
||||||
*/
|
|
||||||
fun shade(context: ShadingContext, vertex: RenderVertex)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a new rotated version of this [Shader]. Used during [Model] setup when rotating the model itself.
|
|
||||||
*/
|
|
||||||
fun rotate(rot: Rotation): Shader
|
|
||||||
|
|
||||||
/** Set all shading values on the [RenderVertex] to match the given [AoData]. */
|
|
||||||
fun RenderVertex.shade(shading: AoData) {
|
|
||||||
brightness = shading.brightness; red = shading.red; green = shading.green; blue = shading.blue
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set the shading values on the [RenderVertex] to a weighted average of the two [AoData] instances. */
|
|
||||||
fun RenderVertex.shade(shading1: AoData, shading2: AoData, weight1: Float = 0.5f, weight2: Float = 0.5f) {
|
|
||||||
red = min(shading1.red * weight1 + shading2.red * weight2, 1.0f)
|
|
||||||
green = min(shading1.green * weight1 + shading2.green * weight2, 1.0f)
|
|
||||||
blue = min(shading1.blue * weight1 + shading2.blue * weight2, 1.0f)
|
|
||||||
brightness = brWeighted(shading1.brightness, weight1, shading2.brightness, weight2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the shading values on the [RenderVertex] directly.
|
|
||||||
*
|
|
||||||
* @param[brightness] packed brightness value
|
|
||||||
* @param[color] packed color value
|
|
||||||
*/
|
|
||||||
fun RenderVertex.shade(brightness: Int, color: Int) {
|
|
||||||
this.brightness = brightness; setColor(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a shader resolver for quads that point towards one of the 6 block faces.
|
|
||||||
* The resolver works the following way:
|
|
||||||
* - determines which face the _quad_ normal points towards (if not overridden)
|
|
||||||
* - determines the distance of the _vertex_ to the corners and edge midpoints on that block face
|
|
||||||
* - if _corner_ is given, and the _vertex_ is closest to a block corner, returns the [Shader] created by _corner_
|
|
||||||
* - if _edge_ is given, and the _vertex_ is closest to an edge midpoint, returns the [Shader] created by _edge_
|
|
||||||
*
|
|
||||||
* @param[overrideFace] assume the given face instead of going by the _quad_ normal
|
|
||||||
* @param[corner] shader instantiation lambda for corner vertices
|
|
||||||
* @param[edge] shader instantiation lambda for edge midpoint vertices
|
|
||||||
*/
|
|
||||||
fun faceOrientedAuto(overrideFace: EnumFacing? = null,
|
|
||||||
corner: CornerShaderFactory? = null,
|
|
||||||
edge: EdgeShaderFactory? = null) =
|
|
||||||
fun(quad: Quad, vertex: Vertex): Shader {
|
|
||||||
val quadFace = overrideFace ?: quad.normal.nearestCardinal
|
|
||||||
val nearestCorner = nearestPosition(vertex.xyz, faceCorners[quadFace.ordinal].asList) {
|
|
||||||
(quadFace.vec + it.first.vec + it.second.vec) * 0.5
|
|
||||||
}
|
|
||||||
val nearestEdge = nearestPosition(vertex.xyz, quadFace.perpendiculars) {
|
|
||||||
(quadFace.vec + it.vec) * 0.5
|
|
||||||
}
|
|
||||||
if (edge != null && (nearestEdge.second < nearestCorner.second || corner == null))
|
|
||||||
return edge(quadFace, nearestEdge.first)
|
|
||||||
else return corner!!(quadFace, nearestCorner.first.first, nearestCorner.first.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a shader resolver for quads that point towards one of the 12 block edges.
|
|
||||||
* The resolver works the following way:
|
|
||||||
* - determines which edge the _quad_ normal points towards (if not overridden)
|
|
||||||
* - determines which face midpoint the _vertex_ is closest to, of the 2 block faces that share this edge
|
|
||||||
* - determines which block corner _of this face_ the _vertex_ is closest to
|
|
||||||
* - returns the [Shader] created by _corner_
|
|
||||||
*
|
|
||||||
* @param[overrideEdge] assume the given edge instead of going by the _quad_ normal
|
|
||||||
* @param[corner] shader instantiation lambda
|
|
||||||
*/
|
|
||||||
fun edgeOrientedAuto(overrideEdge: Pair<EnumFacing, EnumFacing>? = null,
|
|
||||||
corner: CornerShaderFactory) =
|
|
||||||
fun(quad: Quad, vertex: Vertex): Shader {
|
|
||||||
val edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first
|
|
||||||
val nearestFace = nearestPosition(vertex.xyz, edgeDir.toList()) { it.vec }.first
|
|
||||||
val nearestCorner = nearestPosition(vertex.xyz, faceCorners[nearestFace.ordinal].asList) {
|
|
||||||
(nearestFace.vec + it.first.vec + it.second.vec) * 0.5
|
|
||||||
}.first
|
|
||||||
return corner(nearestFace, nearestCorner.first, nearestCorner.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun faceOrientedInterpolate(overrideFace: EnumFacing? = null) =
|
|
||||||
fun(quad: Quad, vertex: Vertex): Shader {
|
|
||||||
val resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir ->
|
|
||||||
val axis = axes.find { it != face.axis && it != edgeDir.axis }!!
|
|
||||||
val vec = Double3((axis to EnumFacing.AxisDirection.POSITIVE).face)
|
|
||||||
val pos = vertex.xyz.dot(vec)
|
|
||||||
EdgeInterpolateFallback(face, edgeDir, pos)
|
|
||||||
})
|
|
||||||
return resolver(quad, vertex)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package mods.octarinecore.client.render.lighting
|
||||||
|
|
||||||
|
import mods.octarinecore.client.render.*
|
||||||
|
import mods.octarinecore.common.*
|
||||||
|
import net.minecraft.util.Direction
|
||||||
|
import net.minecraft.util.Direction.*
|
||||||
|
import java.lang.Math.min
|
||||||
|
|
||||||
|
typealias EdgeShaderFactory = (Direction, Direction) -> ModelLighter
|
||||||
|
typealias CornerShaderFactory = (Direction, Direction, Direction) -> ModelLighter
|
||||||
|
typealias ShaderFactory = (Quad, Vertex) -> ModelLighter
|
||||||
|
|
||||||
|
/** Holds lighting values for block corners as calculated by vanilla Minecraft rendering. */
|
||||||
|
class CornerLightData {
|
||||||
|
var valid = false
|
||||||
|
var brightness = 0
|
||||||
|
var red: Float = 0.0f
|
||||||
|
var green: Float = 0.0f
|
||||||
|
var blue: Float = 0.0f
|
||||||
|
|
||||||
|
fun reset() { valid = false }
|
||||||
|
|
||||||
|
fun set(brightness: Int, red: Float, green: Float, blue: Float) {
|
||||||
|
if (valid) return
|
||||||
|
this.valid = true
|
||||||
|
this.brightness = brightness
|
||||||
|
this.red = red
|
||||||
|
this.green = green
|
||||||
|
this.blue = blue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun set(brightness: Int, colorMultiplier: Float) {
|
||||||
|
this.valid = true
|
||||||
|
this.brightness = brightness
|
||||||
|
this.red = colorMultiplier
|
||||||
|
this.green = colorMultiplier
|
||||||
|
this.blue = colorMultiplier
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val black: CornerLightData get() = CornerLightData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instances of this interface are associated with [Model] vertices, and used to apply brightness and color
|
||||||
|
* values to a [RenderVertex].
|
||||||
|
*/
|
||||||
|
interface ModelLighter {
|
||||||
|
/**
|
||||||
|
* Set shading values of a [RenderVertex]
|
||||||
|
*
|
||||||
|
* @param[context] context that can be queried for lighting data in a [Model]-relative frame of reference
|
||||||
|
* @param[vertex] the [RenderVertex] to manipulate
|
||||||
|
*/
|
||||||
|
fun shade(context: LightingCtx, vertex: RenderVertex)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new rotated version of this [ModelLighter]. Used during [Model] setup when rotating the model itself.
|
||||||
|
*/
|
||||||
|
fun rotate(rot: Rotation): ModelLighter
|
||||||
|
|
||||||
|
/** Set all lighting values on the [RenderVertex] to match the given [CornerLightData]. */
|
||||||
|
fun RenderVertex.shade(shading: CornerLightData) {
|
||||||
|
brightness = shading.brightness; red = shading.red; green = shading.green; blue = shading.blue
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the lighting values on the [RenderVertex] to a weighted average of the two [CornerLightData] instances. */
|
||||||
|
fun RenderVertex.shade(shading1: CornerLightData, shading2: CornerLightData, weight1: Float = 0.5f, weight2: Float = 0.5f) {
|
||||||
|
red = min(shading1.red * weight1 + shading2.red * weight2, 1.0f)
|
||||||
|
green = min(shading1.green * weight1 + shading2.green * weight2, 1.0f)
|
||||||
|
blue = min(shading1.blue * weight1 + shading2.blue * weight2, 1.0f)
|
||||||
|
brightness = brWeighted(shading1.brightness, weight1, shading2.brightness, weight2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the lighting values on the [RenderVertex] directly.
|
||||||
|
*
|
||||||
|
* @param[brightness] packed brightness value
|
||||||
|
* @param[color] packed color value
|
||||||
|
*/
|
||||||
|
fun RenderVertex.shade(brightness: Int, color: Int) {
|
||||||
|
this.brightness = brightness; setColor(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [ModelLighter] resolver for quads that point towards one of the 6 block faces.
|
||||||
|
* The resolver works the following way:
|
||||||
|
* - determines which face the _quad_ normal points towards (if not overridden)
|
||||||
|
* - determines the distance of the _vertex_ to the corners and edge midpoints on that block face
|
||||||
|
* - if _corner_ is given, and the _vertex_ is closest to a block corner, returns the [ModelLighter] created by _corner_
|
||||||
|
* - if _edge_ is given, and the _vertex_ is closest to an edge midpoint, returns the [ModelLighter] created by _edge_
|
||||||
|
*
|
||||||
|
* @param[overrideFace] assume the given face instead of going by the _quad_ normal
|
||||||
|
* @param[corner] [ModelLighter] instantiation lambda for corner vertices
|
||||||
|
* @param[edge] [ModelLighter] instantiation lambda for edge midpoint vertices
|
||||||
|
*/
|
||||||
|
fun faceOrientedAuto(overrideFace: Direction? = null,
|
||||||
|
corner: CornerShaderFactory? = null,
|
||||||
|
edge: EdgeShaderFactory? = null) =
|
||||||
|
fun(quad: Quad, vertex: Vertex): ModelLighter {
|
||||||
|
val quadFace = overrideFace ?: quad.normal.nearestCardinal
|
||||||
|
val nearestCorner = nearestPosition(vertex.xyz, boxFaces[quadFace].allCorners) {
|
||||||
|
(quadFace.vec + it.first.vec + it.second.vec) * 0.5
|
||||||
|
}
|
||||||
|
val nearestEdge = nearestPosition(vertex.xyz, quadFace.perpendiculars) {
|
||||||
|
(quadFace.vec + it.vec) * 0.5
|
||||||
|
}
|
||||||
|
if (edge != null && (nearestEdge.second < nearestCorner.second || corner == null))
|
||||||
|
return edge(quadFace, nearestEdge.first)
|
||||||
|
else return corner!!(quadFace, nearestCorner.first.first, nearestCorner.first.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a ModelLighter resolver for quads that point towards one of the 12 block edges.
|
||||||
|
* The resolver works the following way:
|
||||||
|
* - determines which edge the _quad_ normal points towards (if not overridden)
|
||||||
|
* - determines which face midpoint the _vertex_ is closest to, of the 2 block faces that share this edge
|
||||||
|
* - determines which block corner _of this face_ the _vertex_ is closest to
|
||||||
|
* - returns the [ModelLighter] created by _corner_
|
||||||
|
*
|
||||||
|
* @param[overrideEdge] assume the given edge instead of going by the _quad_ normal
|
||||||
|
* @param[corner] ModelLighter instantiation lambda
|
||||||
|
*/
|
||||||
|
fun edgeOrientedAuto(overrideEdge: Pair<Direction, Direction>? = null,
|
||||||
|
corner: CornerShaderFactory) =
|
||||||
|
fun(quad: Quad, vertex: Vertex): ModelLighter {
|
||||||
|
val edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first
|
||||||
|
val nearestFace = nearestPosition(vertex.xyz, edgeDir.toList()) { it.vec }.first
|
||||||
|
val nearestCorner = nearestPosition(vertex.xyz, boxFaces[nearestFace].allCorners) {
|
||||||
|
(nearestFace.vec + it.first.vec + it.second.vec) * 0.5
|
||||||
|
}.first
|
||||||
|
return corner(nearestFace, nearestCorner.first, nearestCorner.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun faceOrientedInterpolate(overrideFace: Direction? = null) =
|
||||||
|
fun(quad: Quad, vertex: Vertex): ModelLighter {
|
||||||
|
val resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir ->
|
||||||
|
val axis = axes.find { it != face.axis && it != edgeDir.axis }!!
|
||||||
|
val vec = Double3((axis to AxisDirection.POSITIVE).face)
|
||||||
|
val pos = vertex.xyz.dot(vec)
|
||||||
|
EdgeInterpolateFallback(face, edgeDir, pos)
|
||||||
|
})
|
||||||
|
return resolver(quad, vertex)
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package mods.octarinecore.client.render.lighting
|
||||||
|
|
||||||
|
import mods.octarinecore.client.render.BlockCtx
|
||||||
|
import mods.octarinecore.common.*
|
||||||
|
import net.minecraft.client.Minecraft
|
||||||
|
import net.minecraft.client.renderer.BlockModelRenderer
|
||||||
|
import net.minecraft.util.Direction
|
||||||
|
import net.minecraft.world.IEnviromentBlockReader
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
val Direction.aoMultiplier: Float get() = when(this) {
|
||||||
|
Direction.UP -> 1.0f
|
||||||
|
Direction.DOWN -> 0.5f
|
||||||
|
Direction.NORTH, Direction.SOUTH -> 0.8f
|
||||||
|
Direction.EAST, Direction.WEST -> 0.6f
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LightingCtx {
|
||||||
|
val modelRotation: Rotation
|
||||||
|
val blockContext: BlockCtx
|
||||||
|
val aoEnabled: Boolean
|
||||||
|
|
||||||
|
val brightness get() = brightness(Int3.zero)
|
||||||
|
val color get() = color(Int3.zero)
|
||||||
|
fun brightness(face: Direction) = brightness(face.offset)
|
||||||
|
fun color(face: Direction) = color(face.offset)
|
||||||
|
|
||||||
|
fun brightness(offset: Int3) = offset.rotate(modelRotation).let {
|
||||||
|
blockContext.state(it).getPackedLightmapCoords(blockContext.world, blockContext.pos + it)
|
||||||
|
}
|
||||||
|
fun color(offset: Int3) = blockContext.offset(offset.rotate(modelRotation)).let { Minecraft.getInstance().blockColors.getColor(it.state, it.world, it.pos, 0) }
|
||||||
|
|
||||||
|
fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultLightingCtx(blockContext: BlockCtx) : LightingCtx {
|
||||||
|
override var modelRotation = Rotation.identity
|
||||||
|
|
||||||
|
override var aoEnabled = false
|
||||||
|
protected set
|
||||||
|
override var blockContext: BlockCtx = blockContext
|
||||||
|
protected set
|
||||||
|
override var brightness = brightness(Int3.zero)
|
||||||
|
protected set
|
||||||
|
override var color = color(Int3.zero)
|
||||||
|
protected set
|
||||||
|
|
||||||
|
override fun brightness(face: Direction) = brightness(face.offset)
|
||||||
|
override fun color(face: Direction) = color(face.offset)
|
||||||
|
|
||||||
|
// smooth lighting stuff
|
||||||
|
val lightingData = Array(6) { FaceLightData(allDirections[it]) }
|
||||||
|
override fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData = lightingData[face.rotate(modelRotation)].let { faceData ->
|
||||||
|
if (!faceData.isValid) faceData.update(blockContext, faceData.face.aoMultiplier)
|
||||||
|
return faceData[corner1.rotate(modelRotation), corner2.rotate(modelRotation)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset(blockContext: BlockCtx) {
|
||||||
|
this.blockContext = blockContext
|
||||||
|
brightness = brightness(Int3.zero)
|
||||||
|
color = color(Int3.zero)
|
||||||
|
modelRotation = Rotation.identity
|
||||||
|
lightingData.forEach { it.isValid = false }
|
||||||
|
aoEnabled = Minecraft.isAmbientOcclusionEnabled()
|
||||||
|
// allDirections.forEach { lightingData[it].update(blockContext, it.aoMultiplier) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val vanillaAOFactory = BlockModelRenderer.AmbientOcclusionFace::class.java.let {
|
||||||
|
it.getDeclaredConstructor(BlockModelRenderer::class.java).apply { isAccessible = true }
|
||||||
|
}.let { ctor -> { ctor.newInstance(Minecraft.getInstance().blockRendererDispatcher.blockModelRenderer) } }
|
||||||
|
|
||||||
|
class FaceLightData(val face: Direction) {
|
||||||
|
val topDir = boxFaces[face].top
|
||||||
|
val leftDir = boxFaces[face].left
|
||||||
|
|
||||||
|
val topLeft = CornerLightData()
|
||||||
|
val topRight = CornerLightData()
|
||||||
|
val bottomLeft = CornerLightData()
|
||||||
|
val bottomRight = CornerLightData()
|
||||||
|
|
||||||
|
val vanillaOrdered = when(face) {
|
||||||
|
Direction.DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight)
|
||||||
|
Direction.UP -> listOf(bottomRight, topRight, topLeft, bottomLeft)
|
||||||
|
Direction.NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft)
|
||||||
|
Direction.SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight)
|
||||||
|
Direction.WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft)
|
||||||
|
Direction.EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
val delegate = vanillaAOFactory()
|
||||||
|
var isValid = false
|
||||||
|
|
||||||
|
fun update(blockCtx: BlockCtx, multiplier: Float) {
|
||||||
|
val quadBounds = FloatArray(12)
|
||||||
|
val flags = BitSet(3).apply { set(0) }
|
||||||
|
delegate.updateVertexBrightness(blockCtx.world, blockCtx.state, blockCtx.pos, face, quadBounds, flags)
|
||||||
|
vanillaOrdered.forEachIndexed { idx, corner -> corner.set(delegate.vertexBrightness[idx], delegate.vertexColorMultiplier[idx] * multiplier) }
|
||||||
|
isValid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(dir1: Direction, dir2: Direction): CornerLightData {
|
||||||
|
val isTop = topDir == dir1 || topDir == dir2
|
||||||
|
val isLeft = leftDir == dir1 || leftDir == dir2
|
||||||
|
return if (isTop) {
|
||||||
|
if (isLeft) topLeft else topRight
|
||||||
|
} else {
|
||||||
|
if (isLeft) bottomLeft else bottomRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package mods.octarinecore.client.render.lighting
|
||||||
|
|
||||||
|
import mods.octarinecore.common.*
|
||||||
|
import net.minecraft.util.Direction
|
||||||
|
|
||||||
|
|
||||||
|
const val defaultCornerDimming = 0.5f
|
||||||
|
const val defaultEdgeDimming = 0.8f
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// Shader instantiation lambdas
|
||||||
|
// ================================
|
||||||
|
fun cornerAo(fallbackAxis: Direction.Axis): CornerShaderFactory = { face, dir1, dir2 ->
|
||||||
|
val fallbackDir = listOf(face, dir1, dir2).find { it.axis == fallbackAxis }!!
|
||||||
|
CornerSingleFallback(face, dir1, dir2, fallbackDir)
|
||||||
|
}
|
||||||
|
val cornerFlat = { face: Direction, dir1: Direction, dir2: Direction -> FaceFlat(face) }
|
||||||
|
fun cornerAoTri(func: (CornerLightData, CornerLightData)-> CornerLightData) = { face: Direction, dir1: Direction, dir2: Direction ->
|
||||||
|
CornerTri(face, dir1, dir2, func)
|
||||||
|
}
|
||||||
|
val cornerAoMaxGreen = cornerAoTri { s1, s2 -> if (s1.green > s2.green) s1 else s2 }
|
||||||
|
|
||||||
|
fun cornerInterpolate(edgeAxis: Direction.Axis, weight: Float, dimming: Float): CornerShaderFactory = { dir1, dir2, dir3 ->
|
||||||
|
val edgeDir = listOf(dir1, dir2, dir3).find { it.axis == edgeAxis }!!
|
||||||
|
val faceDirs = listOf(dir1, dir2, dir3).filter { it.axis != edgeAxis }
|
||||||
|
CornerInterpolateDimming(faceDirs[0], faceDirs[1], edgeDir, weight, dimming)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// Shaders
|
||||||
|
// ================================
|
||||||
|
object NoLighting : ModelLighter {
|
||||||
|
override fun shade(context: LightingCtx, vertex: RenderVertex) = vertex.shade(CornerLightData.black)
|
||||||
|
override fun rotate(rot: Rotation) = this
|
||||||
|
}
|
||||||
|
|
||||||
|
class CornerSingleFallback(val face: Direction, val dir1: Direction, val dir2: Direction, val fallbackDir: Direction, val fallbackDimming: Float = defaultCornerDimming) : ModelLighter {
|
||||||
|
val offset = Int3(fallbackDir)
|
||||||
|
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||||
|
val shading = context.lighting(face, dir1, dir2)
|
||||||
|
if (shading.valid)
|
||||||
|
vertex.shade(shading)
|
||||||
|
else {
|
||||||
|
vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun rotate(rot: Rotation) = CornerSingleFallback(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), fallbackDir.rotate(rot), fallbackDimming)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun accumulate(v1: CornerLightData?, v2: CornerLightData?, func: ((CornerLightData, CornerLightData)-> CornerLightData)): CornerLightData? {
|
||||||
|
val v1ok = v1 != null && v1.valid
|
||||||
|
val v2ok = v2 != null && v2.valid
|
||||||
|
if (v1ok && v2ok) return func(v1!!, v2!!)
|
||||||
|
if (v1ok) return v1
|
||||||
|
if (v2ok) return v2
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
class CornerTri(val face: Direction, val dir1: Direction, val dir2: Direction,
|
||||||
|
val func: ((CornerLightData, CornerLightData)-> CornerLightData)) : ModelLighter {
|
||||||
|
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||||
|
var acc = accumulate(
|
||||||
|
context.lighting(face, dir1, dir2),
|
||||||
|
context.lighting(dir1, face, dir2),
|
||||||
|
func)
|
||||||
|
acc = accumulate(
|
||||||
|
acc,
|
||||||
|
context.lighting(dir2, face, dir1),
|
||||||
|
func)
|
||||||
|
vertex.shade(acc ?: CornerLightData.black)
|
||||||
|
}
|
||||||
|
override fun rotate(rot: Rotation) = CornerTri(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), func)
|
||||||
|
}
|
||||||
|
|
||||||
|
class EdgeInterpolateFallback(val face: Direction, val edgeDir: Direction, val pos: Double, val fallbackDimming: Float = defaultEdgeDimming): ModelLighter {
|
||||||
|
val offset = Int3(edgeDir)
|
||||||
|
val edgeAxis = axes.find { it != face.axis && it != edgeDir.axis }!!
|
||||||
|
val weightN = (0.5 - pos).toFloat()
|
||||||
|
val weightP = (0.5 + pos).toFloat()
|
||||||
|
|
||||||
|
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||||
|
val shadingP = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.POSITIVE).face)
|
||||||
|
val shadingN = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.NEGATIVE).face)
|
||||||
|
if (!shadingP.valid && !shadingN.valid) {
|
||||||
|
return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
|
||||||
|
}
|
||||||
|
if (!shadingP.valid) return vertex.shade(shadingN)
|
||||||
|
if (!shadingN.valid) return vertex.shade(shadingP)
|
||||||
|
vertex.shade(shadingP, shadingN, weightP, weightN)
|
||||||
|
}
|
||||||
|
override fun rotate(rot: Rotation) = EdgeInterpolateFallback(face.rotate(rot), edgeDir.rotate(rot), pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
class CornerInterpolateDimming(val face1: Direction, val face2: Direction, val edgeDir: Direction,
|
||||||
|
val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming) : ModelLighter {
|
||||||
|
val offset = Int3(edgeDir)
|
||||||
|
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||||
|
var shading1 = context.lighting(face1, edgeDir, face2)
|
||||||
|
var shading2 = context.lighting(face2, edgeDir, face1)
|
||||||
|
var weight1 = weight
|
||||||
|
var weight2 = 1.0f - weight
|
||||||
|
if (!shading1.valid && !shading2.valid) {
|
||||||
|
return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
|
||||||
|
}
|
||||||
|
if (!shading1.valid) { shading1 = shading2; weight1 *= dimming }
|
||||||
|
if (!shading2.valid) { shading2 = shading1; weight2 *= dimming }
|
||||||
|
vertex.shade(shading1, shading2, weight1, weight2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rotate(rot: Rotation) =
|
||||||
|
CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming)
|
||||||
|
}
|
||||||
|
|
||||||
|
class FaceCenter(val face: Direction): ModelLighter {
|
||||||
|
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||||
|
vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f;
|
||||||
|
val b = IntArray(4)
|
||||||
|
boxFaces[face].allCorners.forEachIndexed { idx, corner ->
|
||||||
|
val shading = context.lighting(face, corner.first, corner.second)
|
||||||
|
vertex.red += shading.red
|
||||||
|
vertex.green += shading.green
|
||||||
|
vertex.blue += shading.blue
|
||||||
|
b[idx] = shading.brightness
|
||||||
|
}
|
||||||
|
vertex.apply { red *= 0.25f; green *= 0.25f; blue *= 0.25f }
|
||||||
|
vertex.brightness = brSum(0.25f, *b)
|
||||||
|
}
|
||||||
|
override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot))
|
||||||
|
}
|
||||||
|
|
||||||
|
class FaceFlat(val face: Direction): ModelLighter {
|
||||||
|
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||||
|
vertex.shade(context.brightness(face.offset), context.color(Int3.zero))
|
||||||
|
}
|
||||||
|
override fun rotate(rot: Rotation): ModelLighter = FaceFlat(face.rotate(rot))
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlatOffset(val offset: Int3): ModelLighter {
|
||||||
|
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||||
|
vertex.brightness = context.brightness(offset)
|
||||||
|
vertex.setColor(context.color(offset))
|
||||||
|
}
|
||||||
|
override fun rotate(rot: Rotation): ModelLighter = this
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlatOffsetNoColor(val offset: Int3): ModelLighter {
|
||||||
|
override fun shade(context: LightingCtx, vertex: RenderVertex) {
|
||||||
|
vertex.brightness = context.brightness(offset)
|
||||||
|
vertex.red = 1.0f; vertex.green = 1.0f; vertex.blue = 1.0f
|
||||||
|
}
|
||||||
|
override fun rotate(rot: Rotation): ModelLighter = this
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@file:JvmName("PixelFormat")
|
||||||
|
package mods.octarinecore.client.render.lighting
|
||||||
|
|
||||||
|
import java.awt.Color
|
||||||
|
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package mods.octarinecore.client.render.lighting
|
||||||
|
|
||||||
|
import mods.octarinecore.client.render.CombinedContext
|
||||||
|
import mods.octarinecore.client.render.Quad
|
||||||
|
import mods.octarinecore.client.render.Vertex
|
||||||
|
import mods.octarinecore.common.Double3
|
||||||
|
import mods.octarinecore.common.Rotation
|
||||||
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||||
|
import net.minecraft.util.Direction.*
|
||||||
|
import java.awt.Color
|
||||||
|
|
||||||
|
typealias QuadIconResolver = (CombinedContext, Int, Quad) -> TextureAtlasSprite?
|
||||||
|
typealias PostProcessLambda = RenderVertex.(CombinedContext, Int, Quad, Int, Vertex) -> Unit
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
class RenderVertex {
|
||||||
|
var x: Double = 0.0
|
||||||
|
var y: Double = 0.0
|
||||||
|
var z: Double = 0.0
|
||||||
|
var u: Double = 0.0
|
||||||
|
var v: Double = 0.0
|
||||||
|
var brightness: Int = 0
|
||||||
|
var red: Float = 0.0f
|
||||||
|
var green: Float = 0.0f
|
||||||
|
var blue: Float = 0.0f
|
||||||
|
|
||||||
|
val rawData = IntArray(7)
|
||||||
|
|
||||||
|
fun init(vertex: Vertex, rot: Rotation, trans: Double3): RenderVertex {
|
||||||
|
val result = vertex.xyz.rotate(rot) + trans
|
||||||
|
x = result.x; y = result.y; z = result.z
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
fun init(vertex: Vertex): RenderVertex {
|
||||||
|
x = vertex.xyz.x; y = vertex.xyz.y; z = vertex.xyz.z;
|
||||||
|
u = vertex.uv.u; v = vertex.uv.v
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
fun translate(trans: Double3): RenderVertex { x += trans.x; y += trans.y; z += trans.z; return this }
|
||||||
|
fun rotate(rot: Rotation): RenderVertex {
|
||||||
|
if (rot === Rotation.identity) return this
|
||||||
|
val rotX = rot.rotatedComponent(EAST, x, y, z)
|
||||||
|
val rotY = rot.rotatedComponent(UP, x, y, z)
|
||||||
|
val rotZ = rot.rotatedComponent(SOUTH, x, y, z)
|
||||||
|
x = rotX; y = rotY; z = rotZ
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
inline fun rotateUV(n: Int): RenderVertex {
|
||||||
|
when (n % 4) {
|
||||||
|
1 -> { val t = v; v = -u; u = t; return this }
|
||||||
|
2 -> { u = -u; v = -v; return this }
|
||||||
|
3 -> { val t = -v; v = u; u = t; return this }
|
||||||
|
else -> { return this }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) {
|
||||||
|
if (mirrorU) u = -u
|
||||||
|
if (mirrorV) v = -v
|
||||||
|
}
|
||||||
|
inline fun setIcon(icon: TextureAtlasSprite): RenderVertex {
|
||||||
|
u = (icon.maxU - icon.minU) * (u + 0.5) + icon.minU
|
||||||
|
v = (icon.maxV - icon.minV) * (v + 0.5) + icon.minV
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun setGrey(level: Float) {
|
||||||
|
val grey = Math.min((red + green + blue) * 0.333f * level, 1.0f)
|
||||||
|
red = grey; green = grey; blue = grey
|
||||||
|
}
|
||||||
|
inline fun multiplyColor(color: Int) {
|
||||||
|
red *= (color shr 16 and 255) / 256.0f
|
||||||
|
green *= (color shr 8 and 255) / 256.0f
|
||||||
|
blue *= (color and 255) / 256.0f
|
||||||
|
}
|
||||||
|
inline fun setColor(color: Int) {
|
||||||
|
red = (color shr 16 and 255) / 256.0f
|
||||||
|
green = (color shr 8 and 255) / 256.0f
|
||||||
|
blue = (color and 255) / 256.0f
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** List of bit-shift offsets in packed brightness values where meaningful (4-bit) data is contained. */
|
||||||
|
var brightnessComponents = listOf(20, 4)
|
||||||
|
|
||||||
|
/** Multiply the components of this packed brightness value with the given [Float]. */
|
||||||
|
infix fun Int.brMul(f: Float): Int {
|
||||||
|
val weight = (f * 256.0f).toInt()
|
||||||
|
var result = 0
|
||||||
|
brightnessComponents.forEach { shift ->
|
||||||
|
val raw = (this shr shift) and 15
|
||||||
|
val weighted = (raw) * weight / 256
|
||||||
|
result = result or (weighted shl shift)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Multiply the components of this packed color value with the given [Float]. */
|
||||||
|
infix fun Int.colorMul(f: Float): Int {
|
||||||
|
val weight = (f * 256.0f).toInt()
|
||||||
|
val red = (this shr 16 and 255) * weight / 256
|
||||||
|
val green = (this shr 8 and 255) * weight / 256
|
||||||
|
val blue = (this and 255) * weight / 256
|
||||||
|
return (red shl 16) or (green shl 8) or blue
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sum the components of all packed brightness values given. */
|
||||||
|
fun brSum(multiplier: Float?, vararg brightness: Int): Int {
|
||||||
|
val sum = Array(brightnessComponents.size) { 0 }
|
||||||
|
brightnessComponents.forEachIndexed { idx, shift -> brightness.forEach { br ->
|
||||||
|
val comp = (br shr shift) and 15
|
||||||
|
sum[idx] += comp
|
||||||
|
} }
|
||||||
|
var result = 0
|
||||||
|
brightnessComponents.forEachIndexed { idx, shift ->
|
||||||
|
val comp = if (multiplier == null)
|
||||||
|
((sum[idx]) shl shift)
|
||||||
|
else
|
||||||
|
((sum[idx].toFloat() * multiplier).toInt() shl shift)
|
||||||
|
result = result or comp
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun brWeighted(br1: Int, weight1: Float, br2: Int, weight2: Float): Int {
|
||||||
|
val w1int = (weight1 * 256.0f + 0.5f).toInt()
|
||||||
|
val w2int = (weight2 * 256.0f + 0.5f).toInt()
|
||||||
|
var result = 0
|
||||||
|
brightnessComponents.forEachIndexed { idx, shift ->
|
||||||
|
val comp1 = (br1 shr shift) and 15
|
||||||
|
val comp2 = (br2 shr shift) and 15
|
||||||
|
val compWeighted = (comp1 * w1int + comp2 * w2int) / 256
|
||||||
|
result = result or ((compWeighted and 15) shl shift)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
|
||||||
|
companion object {
|
||||||
|
fun fromColor(color: Int): HSB {
|
||||||
|
val hsbVals = Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
|
||||||
|
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val asColor: Int get() = Color.HSBtoRGB(hue, saturation, brightness)
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package mods.octarinecore.client.resource
|
||||||
|
|
||||||
|
import mods.octarinecore.Sprite
|
||||||
|
import mods.octarinecore.common.map
|
||||||
|
import net.minecraft.client.renderer.texture.AtlasTexture
|
||||||
|
import net.minecraft.client.renderer.texture.MissingTextureSprite
|
||||||
|
import net.minecraft.profiler.IProfiler
|
||||||
|
import net.minecraft.resources.IResourceManager
|
||||||
|
import net.minecraft.util.ResourceLocation
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point to atlas manipulation. Called from mixins that wrap [AtlasTexture.stitch] calls.
|
||||||
|
*
|
||||||
|
* 1. All registered providers receive an [AsyncSpriteProvider.setup] call. Providers can set up their
|
||||||
|
* processing chain at this point, but should not do anything yet except configuration and housekeeping.
|
||||||
|
* 2. The [CompletableFuture] of the stitch source finishes, starting the "discovery" phase. Providers
|
||||||
|
* may register sprites in the [AtlasFuture].
|
||||||
|
* 3. After all providers finish their discovery, the atlas is stitched.
|
||||||
|
* 4. The [AtlasFuture] finishes, starting the "cleanup" phase. All [AtlasFuture.runAfter] and
|
||||||
|
* [AtlasFuture.mapAfter] tasks are processed.
|
||||||
|
* 5. After all providers finish their cleanup, we return to the original code path.
|
||||||
|
*/
|
||||||
|
class AsnycSpriteProviderManager<SOURCE: Any>(val profilerSection: String) {
|
||||||
|
|
||||||
|
val providers = mutableListOf<AsyncSpriteProvider<SOURCE>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needed in order to keep the actual [AtlasTexture.stitch] call in the original method, in case
|
||||||
|
* other modders want to modify it too.
|
||||||
|
*/
|
||||||
|
class StitchWrapper(val idList: Iterable<ResourceLocation>, val onComplete: (AtlasTexture.SheetData)->Unit) {
|
||||||
|
fun complete(sheet: AtlasTexture.SheetData) = onComplete(sheet)
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentAtlas: AtlasFuture? = null
|
||||||
|
var currentPhases: List<StitchPhases> = emptyList()
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun prepare(sourceObj: Any, manager: IResourceManager, idList: Iterable<ResourceLocation>, profiler: IProfiler): Set<ResourceLocation> {
|
||||||
|
profiler.startSection(profilerSection)
|
||||||
|
|
||||||
|
val source = CompletableFuture<SOURCE>()
|
||||||
|
currentAtlas = AtlasFuture(idList)
|
||||||
|
|
||||||
|
currentPhases = providers.map { it.setup(manager, source, currentAtlas!!) }
|
||||||
|
source.complete(sourceObj as SOURCE)
|
||||||
|
currentPhases.forEach { it.discovery.get() }
|
||||||
|
|
||||||
|
return currentAtlas!!.idSet
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finish(sheetData: AtlasTexture.SheetData, profiler: IProfiler): AtlasTexture.SheetData {
|
||||||
|
currentAtlas!!.complete(sheetData)
|
||||||
|
currentPhases.forEach { it.cleanup.get() }
|
||||||
|
currentAtlas = null
|
||||||
|
currentPhases = emptyList()
|
||||||
|
profiler.endSection()
|
||||||
|
return sheetData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a way for [AsyncSpriteProvider]s to register sprites to receive [CompletableFuture]s.
|
||||||
|
* Tracks sprite ids that need to be stitched.
|
||||||
|
*/
|
||||||
|
class AtlasFuture(initial: Iterable<ResourceLocation>) {
|
||||||
|
val idSet = Collections.synchronizedSet(mutableSetOf<ResourceLocation>().apply { addAll(initial) })
|
||||||
|
protected val sheet = CompletableFuture<AtlasTexture.SheetData>()
|
||||||
|
protected val finished = CompletableFuture<Void>()
|
||||||
|
|
||||||
|
fun complete(sheetData: AtlasTexture.SheetData) {
|
||||||
|
sheet.complete(sheetData)
|
||||||
|
finished.complete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sprite(id: String) = sprite(ResourceLocation(id))
|
||||||
|
fun sprite(id: ResourceLocation): CompletableFuture<Sprite> {
|
||||||
|
idSet.add(id)
|
||||||
|
return sheet.map { sheetData -> sheetData.sprites.find { it.name == id } ?: throw IllegalStateException("Atlas does not contain $id") }
|
||||||
|
}
|
||||||
|
val missing = sheet.map { sheetData -> sheetData.sprites.find { it.name == MissingTextureSprite.getLocation() } }
|
||||||
|
fun <T> mapAfter(supplier: ()->T): CompletableFuture<T> = finished.map{ supplier() }
|
||||||
|
fun runAfter(action: ()->Unit): CompletableFuture<Void> = finished.thenRun(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
class StitchPhases(
|
||||||
|
val discovery: CompletableFuture<Void>,
|
||||||
|
val cleanup: CompletableFuture<Void>
|
||||||
|
)
|
||||||
|
|
||||||
|
interface AsyncSpriteProvider<SOURCE: Any> {
|
||||||
|
fun setup(manager: IResourceManager, source: CompletableFuture<SOURCE>, atlas: AtlasFuture): StitchPhases
|
||||||
|
}
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
package mods.octarinecore.client.resource
|
package mods.octarinecore.client.resource
|
||||||
|
|
||||||
|
import mods.betterfoliage.client.texture.loadSprite
|
||||||
|
import net.minecraft.resources.IResourceManager
|
||||||
|
import net.minecraft.util.ResourceLocation
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.lang.Math.*
|
import java.lang.Math.max
|
||||||
|
|
||||||
class CenteringTextureGenerator(domain: String, val aspectWidth: Int, val aspectHeight: Int) : TextureGenerator(domain) {
|
data class CenteredSprite(val sprite: ResourceLocation, val atlas: Atlas = Atlas.BLOCKS, val aspectHeight: Int = 1, val aspectWidth: Int = 1) {
|
||||||
|
|
||||||
override fun generate(params: ParameterList): BufferedImage? {
|
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
|
||||||
val target = targetResource(params)!!
|
|
||||||
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
|
fun draw(resourceManager: IResourceManager): ByteArray {
|
||||||
|
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
|
||||||
|
|
||||||
val frameWidth = baseTexture.width
|
val frameWidth = baseTexture.width
|
||||||
val frameHeight = baseTexture.width * aspectHeight / aspectWidth
|
val frameHeight = baseTexture.width * aspectHeight / aspectWidth
|
||||||
@@ -18,7 +22,7 @@ class CenteringTextureGenerator(domain: String, val aspectWidth: Int, val aspect
|
|||||||
val graphics = resultTexture.createGraphics()
|
val graphics = resultTexture.createGraphics()
|
||||||
|
|
||||||
// iterate all frames
|
// iterate all frames
|
||||||
for (frame in 0 .. frames - 1) {
|
for (frame in 0 until frames) {
|
||||||
val baseFrame = baseTexture.getSubimage(0, size * frame, frameWidth, frameHeight)
|
val baseFrame = baseTexture.getSubimage(0, size * frame, frameWidth, frameHeight)
|
||||||
val resultFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
|
val resultFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
|
||||||
@@ -28,6 +32,6 @@ class CenteringTextureGenerator(domain: String, val aspectWidth: Int, val aspect
|
|||||||
graphics.drawImage(resultFrame, 0, size * frame, null)
|
graphics.drawImage(resultFrame, 0, size * frame, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultTexture
|
return resultTexture.bytes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package mods.octarinecore.client.resource
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner
|
||||||
|
import mods.octarinecore.HasLogger
|
||||||
|
import mods.octarinecore.client.render.BlockCtx
|
||||||
|
import mods.octarinecore.common.Int3
|
||||||
|
import mods.octarinecore.common.config.IBlockMatcher
|
||||||
|
import mods.octarinecore.common.config.ModelTextureList
|
||||||
|
import mods.octarinecore.common.plus
|
||||||
|
import mods.octarinecore.common.sinkAsync
|
||||||
|
import mods.octarinecore.findFirst
|
||||||
|
import net.minecraft.block.BlockState
|
||||||
|
import net.minecraft.client.renderer.BlockModelShapes
|
||||||
|
import net.minecraft.client.renderer.model.BlockModel
|
||||||
|
import net.minecraft.client.renderer.model.IUnbakedModel
|
||||||
|
import net.minecraft.client.renderer.model.ModelBakery
|
||||||
|
import net.minecraft.client.renderer.model.ModelResourceLocation
|
||||||
|
import net.minecraft.client.renderer.model.VariantList
|
||||||
|
import net.minecraft.resources.IResourceManager
|
||||||
|
import net.minecraft.util.ResourceLocation
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import net.minecraft.world.IBlockReader
|
||||||
|
import net.minecraftforge.registries.ForgeRegistries
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
|
interface ModelRenderRegistry<T> {
|
||||||
|
operator fun get(ctx: BlockCtx) = get(ctx.state, ctx.world, ctx.pos)
|
||||||
|
operator fun get(ctx: BlockCtx, offset: Int3) = get(ctx.state(offset), ctx.world, ctx.pos + offset)
|
||||||
|
operator fun get(state: BlockState, world: IBlockReader, pos: BlockPos): T?
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ModelRenderRegistryRoot<T> : ModelRenderRegistry<T> {
|
||||||
|
val registries = mutableListOf<ModelRenderRegistry<T>>()
|
||||||
|
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = registries.findFirst { it[state, world, pos] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about a single BlockState and all the IUnbakedModel it could render as.
|
||||||
|
*/
|
||||||
|
class ModelDiscoveryContext(
|
||||||
|
bakery: ModelBakery,
|
||||||
|
val state: BlockState,
|
||||||
|
val modelId: ModelResourceLocation
|
||||||
|
) {
|
||||||
|
val models = bakery.unwrapVariants(bakery.getUnbakedModel(modelId) to modelId)
|
||||||
|
.filter { it.second != bakery.getUnbakedModel(ModelBakery.MODEL_MISSING) }
|
||||||
|
|
||||||
|
fun ModelBakery.unwrapVariants(modelAndLoc: Pair<IUnbakedModel, ResourceLocation>): List<Pair<IUnbakedModel, ResourceLocation>> = when(val model = modelAndLoc.first) {
|
||||||
|
is VariantList -> model.variantList.flatMap { variant -> unwrapVariants(getUnbakedModel(variant.modelLocation) to variant.modelLocation) }
|
||||||
|
is BlockModel -> listOf(modelAndLoc)
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ModelDiscovery<T> : HasLogger, AsyncSpriteProvider<ModelBakery>, ModelRenderRegistry<T> {
|
||||||
|
|
||||||
|
var modelData: Map<BlockState, T> = emptyMap()
|
||||||
|
protected set
|
||||||
|
|
||||||
|
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = modelData[state]
|
||||||
|
|
||||||
|
abstract fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<T>?
|
||||||
|
|
||||||
|
override fun setup(manager: IResourceManager, bakeryF: CompletableFuture<ModelBakery>, atlas: AtlasFuture): StitchPhases {
|
||||||
|
val modelDataTemp = mutableMapOf<BlockState, CompletableFuture<T>>()
|
||||||
|
|
||||||
|
return StitchPhases(
|
||||||
|
discovery = bakeryF.sinkAsync { bakery ->
|
||||||
|
var errors = 0
|
||||||
|
bakery.iterateModels { ctx ->
|
||||||
|
try {
|
||||||
|
processModel(ctx, atlas)?.let { modelDataTemp[ctx.state] = it }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
errors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log("${modelDataTemp.size} BlockStates discovered, $errors errors")
|
||||||
|
},
|
||||||
|
cleanup = atlas.runAfter {
|
||||||
|
modelData = modelDataTemp.filterValues { !it.isCompletedExceptionally }.mapValues { it.value.get() }
|
||||||
|
val errors = modelDataTemp.values.filter { it.isCompletedExceptionally }.size
|
||||||
|
log("${modelData.size} BlockStates loaded, $errors errors")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ModelBakery.iterateModels(func: (ModelDiscoveryContext)->Unit) {
|
||||||
|
ForgeRegistries.BLOCKS.flatMap { block ->
|
||||||
|
block.stateContainer.validStates.map { state -> state to BlockModelShapes.getModelLocation(state) }
|
||||||
|
}.forEach { (state, stateModelResource) ->
|
||||||
|
func(ModelDiscoveryContext(this, state, stateModelResource))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ConfigurableModelDiscovery<T> : ModelDiscovery<T>() {
|
||||||
|
|
||||||
|
abstract val matchClasses: IBlockMatcher
|
||||||
|
abstract val modelTextures: List<ModelTextureList>
|
||||||
|
|
||||||
|
abstract fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture): CompletableFuture<T>?
|
||||||
|
|
||||||
|
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<T>? {
|
||||||
|
val matchClass = matchClasses.matchingClass(ctx.state.block) ?: return null
|
||||||
|
log("block state ${ctx.state.toString()}")
|
||||||
|
log(" class ${ctx.state.block.javaClass.name} matches ${matchClass.name}")
|
||||||
|
|
||||||
|
if (ctx.models.isEmpty()) {
|
||||||
|
log(" no models found")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.models.filter { it.first is BlockModel }.forEach { (model, location) ->
|
||||||
|
model as BlockModel
|
||||||
|
val modelMatch = modelTextures.firstOrNull { (model to location).derivesFrom(it.modelLocation) }
|
||||||
|
if (modelMatch != null) {
|
||||||
|
log(" model ${model} matches ${modelMatch.modelLocation}")
|
||||||
|
|
||||||
|
val textures = modelMatch.textureNames.map { it to model.resolveTextureName(it) }
|
||||||
|
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
|
||||||
|
log(" sprites [$texMapString]")
|
||||||
|
|
||||||
|
if (textures.all { it.second != "missingno" }) {
|
||||||
|
// found a valid model (all required textures exist)
|
||||||
|
return processModel(ctx.state, textures.map { it.second}, atlas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
package mods.octarinecore.client.resource
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner
|
|
||||||
import mods.betterfoliage.client.Client
|
|
||||||
import mods.betterfoliage.loader.Refs
|
|
||||||
import mods.octarinecore.client.render.BlockContext
|
|
||||||
import mods.octarinecore.common.Int3
|
|
||||||
import mods.octarinecore.common.config.IBlockMatcher
|
|
||||||
import mods.octarinecore.common.config.ModelTextureList
|
|
||||||
import mods.octarinecore.filterValuesNotNull
|
|
||||||
import mods.octarinecore.findFirst
|
|
||||||
import net.minecraft.block.Block
|
|
||||||
import net.minecraft.block.state.IBlockState
|
|
||||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation
|
|
||||||
import net.minecraft.client.renderer.block.statemap.DefaultStateMapper
|
|
||||||
import net.minecraft.client.renderer.block.statemap.IStateMapper
|
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
|
||||||
import net.minecraft.util.ResourceLocation
|
|
||||||
import net.minecraft.util.math.BlockPos
|
|
||||||
import net.minecraft.world.IBlockAccess
|
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent
|
|
||||||
import net.minecraftforge.client.model.IModel
|
|
||||||
import net.minecraftforge.client.model.ModelLoader
|
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.Event
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.EventPriority
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
import org.apache.logging.log4j.Level
|
|
||||||
import org.apache.logging.log4j.Logger
|
|
||||||
|
|
||||||
class LoadModelDataEvent(val loader: ModelLoader) : Event()
|
|
||||||
|
|
||||||
interface ModelRenderRegistry<T> {
|
|
||||||
operator fun get(ctx: BlockContext) = get(ctx.blockState(Int3.zero), ctx.world!!, ctx.pos)
|
|
||||||
operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos): T?
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ModelRenderDataExtractor<T> {
|
|
||||||
fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<T>?
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ModelRenderKey<T> {
|
|
||||||
val logger: Logger?
|
|
||||||
fun onPreStitch(atlas: TextureMap) {}
|
|
||||||
fun resolveSprites(atlas: TextureMap): T
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class ModelRenderRegistryRoot<T> : ModelRenderRegistry<T> {
|
|
||||||
val subRegistries = mutableListOf<ModelRenderRegistry<T>>()
|
|
||||||
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos) = subRegistries.findFirst { it[state, world, pos] }
|
|
||||||
fun addRegistry(registry: ModelRenderRegistry<T>) {
|
|
||||||
subRegistries.add(registry)
|
|
||||||
MinecraftForge.EVENT_BUS.register(registry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class ModelRenderRegistryBase<T> : ModelRenderRegistry<T>, ModelRenderDataExtractor<T> {
|
|
||||||
open val logger: Logger? = null
|
|
||||||
open val logName: String get() = this::class.java.name
|
|
||||||
|
|
||||||
val stateToKey = mutableMapOf<IBlockState, ModelRenderKey<T>>()
|
|
||||||
var stateToValue = mapOf<IBlockState, T>()
|
|
||||||
|
|
||||||
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos) = stateToValue[state]
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handleLoadModelData(event: LoadModelDataEvent) {
|
|
||||||
stateToValue = emptyMap()
|
|
||||||
|
|
||||||
val stateMappings = Block.REGISTRY.flatMap { block ->
|
|
||||||
val mapper = event.loader.blockModelShapes.blockStateMapper.blockStateMap[block] as? IStateMapper ?: DefaultStateMapper()
|
|
||||||
(mapper.putStateModelLocations(block as Block) as Map<IBlockState, ModelResourceLocation>).entries
|
|
||||||
}
|
|
||||||
val stateModels = Refs.stateModels.get(event.loader) as Map<ModelResourceLocation, IModel>
|
|
||||||
|
|
||||||
stateMappings.forEach { mapping ->
|
|
||||||
if (mapping.key.block != null) stateModels[mapping.value]?.let { model ->
|
|
||||||
try {
|
|
||||||
processModel(mapping.key, mapping.value, model)?.let { stateToKey[mapping.key] = it }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logger?.warn("Exception while trying to process model ${mapping.value}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent(priority = EventPriority.LOW)
|
|
||||||
fun handlePreStitch(event: TextureStitchEvent.Pre) {
|
|
||||||
stateToKey.forEach { (_, key) -> key.onPreStitch(event.map) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent(priority = EventPriority.LOW)
|
|
||||||
fun handlePostStitch(event: TextureStitchEvent.Post) {
|
|
||||||
stateToValue = stateToKey.mapValues { (_, key) -> key.resolveSprites(event.map) }
|
|
||||||
stateToKey.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class ModelRenderRegistryConfigurable<T> : ModelRenderRegistryBase<T>() {
|
|
||||||
|
|
||||||
abstract val matchClasses: IBlockMatcher
|
|
||||||
abstract val modelTextures: List<ModelTextureList>
|
|
||||||
|
|
||||||
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<T>? {
|
|
||||||
val matchClass = matchClasses.matchingClass(state.block) ?: return null
|
|
||||||
logger?.log(Level.DEBUG, "$logName: block state ${state.toString()}")
|
|
||||||
logger?.log(Level.DEBUG, "$logName: class ${state.block.javaClass.name} matches ${matchClass.name}")
|
|
||||||
|
|
||||||
val allModels = model.modelBlockAndLoc.distinctBy { it.second }
|
|
||||||
if (allModels.isEmpty()) {
|
|
||||||
logger?.log(Level.DEBUG, "$logName: no models found")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
allModels.forEach { blockLoc ->
|
|
||||||
val modelMatch = modelTextures.firstOrNull { blockLoc.derivesFrom(it.modelLocation) }
|
|
||||||
if (modelMatch != null) {
|
|
||||||
logger?.log(Level.DEBUG, "$logName: model ${blockLoc.second} matches ${modelMatch.modelLocation}")
|
|
||||||
|
|
||||||
val textures = modelMatch.textureNames.map { it to blockLoc.first.resolveTextureName(it) }
|
|
||||||
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
|
|
||||||
logger?.log(Level.DEBUG, "$logName: textures [$texMapString]")
|
|
||||||
|
|
||||||
if (textures.all { it.second != "missingno" }) {
|
|
||||||
// found a valid model (all required textures exist)
|
|
||||||
return processModel(state, textures.map { it.second} )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun processModel(state: IBlockState, textures: List<String>) : ModelRenderKey<T>?
|
|
||||||
}
|
|
||||||
@@ -1,120 +1,84 @@
|
|||||||
package mods.octarinecore.client.resource
|
package mods.octarinecore.client.resource
|
||||||
|
|
||||||
import mods.octarinecore.metaprog.reflectField
|
import mods.octarinecore.HasLogger
|
||||||
import net.minecraft.client.resources.IResourcePack
|
import mods.octarinecore.common.completedVoid
|
||||||
import net.minecraft.client.resources.data.IMetadataSection
|
import mods.octarinecore.common.map
|
||||||
import net.minecraft.client.resources.data.IMetadataSectionSerializer
|
import net.minecraft.client.renderer.model.ModelBakery
|
||||||
import net.minecraft.client.resources.data.MetadataSerializer
|
import net.minecraft.client.resources.ClientResourcePackInfo
|
||||||
import net.minecraft.client.resources.data.PackMetadataSection
|
import net.minecraft.resources.*
|
||||||
|
import net.minecraft.resources.ResourcePackType.CLIENT_RESOURCES
|
||||||
|
import net.minecraft.resources.data.IMetadataSectionSerializer
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.text.TextComponentString
|
import net.minecraft.util.text.StringTextComponent
|
||||||
import net.minecraftforge.fml.client.FMLClientHandler
|
import org.apache.logging.log4j.Logger
|
||||||
import java.io.InputStream
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.ExecutionException
|
||||||
|
import java.util.function.Predicate
|
||||||
|
import java.util.function.Supplier
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [IResourcePack] containing generated resources. Adds itself to the default resource pack list
|
* [IResourcePack] containing generated resources
|
||||||
* of Minecraft, so it is invisible and always active.
|
|
||||||
*
|
*
|
||||||
* @param[name] Name of the resource pack
|
* @param[name] Name of the resource pack
|
||||||
* @param[generators] List of resource generators
|
* @param[generators] List of resource generators
|
||||||
*/
|
*/
|
||||||
class GeneratorPack(val name: String, vararg val generators: GeneratorBase) : IResourcePack {
|
class GeneratedBlockTexturePack(val nameSpace: String, val packName: String, override val logger: Logger) : HasLogger, IResourcePack, AsyncSpriteProvider<ModelBakery> {
|
||||||
|
|
||||||
fun inject() {
|
override fun getName() = packName
|
||||||
FMLClientHandler.instance().reflectField<MutableList<IResourcePack>>("resourcePackList")!!.add(this)
|
override fun getResourceNamespaces(type: ResourcePackType) = setOf(nameSpace)
|
||||||
|
override fun <T : Any?> getMetadata(deserializer: IMetadataSectionSerializer<T>) = null
|
||||||
|
override fun getRootResourceStream(id: String) = null
|
||||||
|
override fun getAllResourceLocations(type: ResourcePackType, path: String, maxDepth: Int, filter: Predicate<String>) = emptyList<ResourceLocation>()
|
||||||
|
override fun close() {}
|
||||||
|
|
||||||
|
protected var manager: CompletableFuture<IResourceManager>? = null
|
||||||
|
val identifiers = Collections.synchronizedMap(mutableMapOf<Any, ResourceLocation>())
|
||||||
|
val resources = Collections.synchronizedMap(mutableMapOf<ResourceLocation, CompletableFuture<ByteArray>>())
|
||||||
|
|
||||||
|
fun register(key: Any, func: (IResourceManager)->ByteArray): ResourceLocation {
|
||||||
|
if (manager == null) throw IllegalStateException("Cannot register resources unless block textures are being reloaded")
|
||||||
|
identifiers[key]?.let { return it }
|
||||||
|
|
||||||
|
val id = ResourceLocation(nameSpace, UUID.randomUUID().toString())
|
||||||
|
val resource = manager!!.map { func(it) }
|
||||||
|
|
||||||
|
identifiers[key] = id
|
||||||
|
resources[Atlas.BLOCKS.wrap(id)] = resource
|
||||||
|
log("generated resource $key -> $id")
|
||||||
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPackName() = name
|
override fun getResourceStream(type: ResourcePackType, id: ResourceLocation) =
|
||||||
override fun getPackImage() = null
|
if (type != CLIENT_RESOURCES) null else
|
||||||
override fun getResourceDomains() = HashSet(generators.map { it.domain })
|
try { resources[id]!!.get().inputStream() }
|
||||||
override fun <T : IMetadataSection?> getPackMetadata(serializer: MetadataSerializer?, sectionName: String?) =
|
catch (e: ExecutionException) { (e.cause as? IOException)?.let { throw it } } // rethrow wrapped IOException if present
|
||||||
if (sectionName == "pack") PackMetadataSection(TextComponentString("Generated resources"), 1) as? T else null
|
|
||||||
|
|
||||||
override fun resourceExists(location: ResourceLocation?): Boolean =
|
override fun resourceExists(type: ResourcePackType, id: ResourceLocation) =
|
||||||
if (location == null) false
|
type == CLIENT_RESOURCES && resources.containsKey(id)
|
||||||
else generators.find {
|
|
||||||
it.domain == location.namespace && it.resourceExists(location)
|
|
||||||
} != null
|
|
||||||
|
|
||||||
override fun getInputStream(location: ResourceLocation?): InputStream? =
|
override fun setup(manager: IResourceManager, bakeryF: CompletableFuture<ModelBakery>, atlas: AtlasFuture): StitchPhases {
|
||||||
if (location == null) null
|
this.manager = CompletableFuture.completedFuture(manager)
|
||||||
else generators.filter {
|
return StitchPhases(
|
||||||
it.domain == location.namespace && it.resourceExists(location)
|
completedVoid(),
|
||||||
}.map { it.getInputStream(location) }
|
atlas.runAfter {
|
||||||
.filterNotNull().first()
|
this.manager = null
|
||||||
|
identifiers.clear()
|
||||||
// override fun <T : IMetadataSection?> getPackMetadata(p_135058_1_: IMetadataSerializer?, p_135058_2_: String?): T {
|
resources.clear()
|
||||||
// return if (type == "pack") PackMetadataSection(ChatComponentText("Generated resources"), 1) else null
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract base class for resource generators
|
|
||||||
*
|
|
||||||
* @param[domain] Resource domain of generator
|
|
||||||
*/
|
|
||||||
abstract class GeneratorBase(val domain: String) {
|
|
||||||
/** @see [IResourcePack.resourceExists] */
|
|
||||||
abstract fun resourceExists(location: ResourceLocation?): Boolean
|
|
||||||
|
|
||||||
/** @see [IResourcePack.getInputStream] */
|
|
||||||
abstract fun getInputStream(location: ResourceLocation?): InputStream?
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collection of named [String]-valued key-value pairs, with an extra unnamed (keyless) value.
|
|
||||||
* Meant to be encoded as a pipe-delimited list, and used as a [ResourceLocation] path
|
|
||||||
* to parametrized generated resources.
|
|
||||||
*
|
|
||||||
* @param[params] key-value pairs
|
|
||||||
* @param[value] keyless extra value
|
|
||||||
*/
|
|
||||||
class ParameterList(val params: Map<String, String>, val value: String?) {
|
|
||||||
override fun toString() =
|
|
||||||
params.entries
|
|
||||||
.sortedBy { it.key }
|
|
||||||
.fold("") { result, entry -> result + "|${entry.key}=${entry.value}"} +
|
|
||||||
(value?.let { "|$it" } ?: "")
|
|
||||||
|
|
||||||
/** Return the value of the given parameter. */
|
|
||||||
operator fun get(key: String) = params[key]
|
|
||||||
|
|
||||||
/** Check if the given parameter exists in this list. */
|
|
||||||
operator fun contains(key: String) = key in params
|
|
||||||
|
|
||||||
/** Return a new [ParameterList] with the given key-value pair appended to it. */
|
|
||||||
operator fun plus(pair: Pair<String, String>) = ParameterList(params + pair, this.value)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Recreate the parameter list from the encoded string, i.e. the opposite of [toString].
|
|
||||||
*
|
|
||||||
* Everything before the first pipe character is dropped, so the decoding works even if
|
|
||||||
* something is prepended to the list (like _textures/blocks/_)
|
|
||||||
*/
|
|
||||||
fun fromString(input: String): ParameterList {
|
|
||||||
val params = hashMapOf<String, String>()
|
|
||||||
var value: String? = null
|
|
||||||
val slices = input.dropWhile { it != '|'}.split('|')
|
|
||||||
slices.forEach {
|
|
||||||
if (it.contains('=')) {
|
|
||||||
val keyValue = it.split('=')
|
|
||||||
if (keyValue.size == 2) params.put(keyValue[0], keyValue[1])
|
|
||||||
} else value = it
|
|
||||||
}
|
}
|
||||||
return ParameterList(params, value)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
abstract class ParameterBasedGenerator(domain: String) : GeneratorBase(domain) {
|
val finder = object : IPackFinder {
|
||||||
abstract fun resourceExists(params: ParameterList): Boolean
|
val packInfo = ClientResourcePackInfo(
|
||||||
abstract fun getInputStream(params: ParameterList): InputStream?
|
packName, true, Supplier { this@GeneratedBlockTexturePack },
|
||||||
|
StringTextComponent(packName),
|
||||||
override fun resourceExists(location: ResourceLocation?) =
|
StringTextComponent("Generated block textures resource pack"),
|
||||||
resourceExists(ParameterList.fromString(location?.path ?: ""))
|
PackCompatibility.COMPATIBLE, ResourcePackInfo.Priority.TOP, true, null, true
|
||||||
override fun getInputStream(location: ResourceLocation?) =
|
)
|
||||||
getInputStream(ParameterList.fromString(location?.path ?: ""))
|
override fun <T : ResourcePackInfo> addPackInfosToMap(nameToPackMap: MutableMap<String, T>, packInfoFactory: ResourcePackInfo.IFactory<T>) {
|
||||||
|
(nameToPackMap as MutableMap<String, ResourcePackInfo>).put(packName, packInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,44 @@
|
|||||||
package mods.octarinecore.client.resource
|
package mods.octarinecore.client.resource
|
||||||
|
|
||||||
|
import mods.betterfoliage.BetterFoliage
|
||||||
|
import mods.octarinecore.Sprite
|
||||||
import mods.octarinecore.client.render.Model
|
import mods.octarinecore.client.render.Model
|
||||||
import mods.octarinecore.common.Double3
|
import mods.octarinecore.common.Double3
|
||||||
import mods.octarinecore.common.Int3
|
import mods.octarinecore.common.Int3
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
import mods.octarinecore.common.completedVoid
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
import mods.octarinecore.common.sink
|
||||||
|
import mods.octarinecore.stripEnd
|
||||||
|
import mods.octarinecore.stripStart
|
||||||
|
import net.minecraft.resources.IResourceManager
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.util.math.MathHelper
|
import net.minecraft.util.math.MathHelper
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.IWorld
|
||||||
import net.minecraft.world.gen.NoiseGeneratorSimplex
|
import net.minecraft.world.gen.SimplexNoiseGenerator
|
||||||
import net.minecraftforge.client.event.TextureStitchEvent
|
import net.minecraftforge.client.event.TextureStitchEvent
|
||||||
import net.minecraftforge.common.MinecraftForge
|
|
||||||
import net.minecraftforge.event.world.WorldEvent
|
import net.minecraftforge.event.world.WorldEvent
|
||||||
import net.minecraftforge.fml.client.event.ConfigChangedEvent
|
import net.minecraftforge.eventbus.api.IEventBus
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||||
import java.util.*
|
import net.minecraftforge.fml.config.ModConfig
|
||||||
|
import java.util.Random
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import kotlin.properties.ReadOnlyProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
enum class Atlas(val basePath: String) {
|
||||||
|
BLOCKS("textures"),
|
||||||
|
PARTICLES("textures/particle");
|
||||||
|
|
||||||
|
fun wrap(resource: ResourceLocation) = ResourceLocation(resource.namespace, "$basePath/${resource.path}.png")
|
||||||
|
fun unwrap(resource: ResourceLocation) = resource.stripStart("$basePath/").stripEnd(".png")
|
||||||
|
fun matches(event: TextureStitchEvent) = event.map.basePath == basePath
|
||||||
|
}
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
// Resource types
|
// Resource types
|
||||||
// ============================
|
// ============================
|
||||||
interface IStitchListener {
|
|
||||||
fun onPreStitch(atlas: TextureMap)
|
|
||||||
fun onPostStitch(atlas: TextureMap)
|
|
||||||
}
|
|
||||||
interface IConfigChangeListener { fun onConfigChange() }
|
interface IConfigChangeListener { fun onConfigChange() }
|
||||||
interface IWorldLoadListener { fun onWorldLoad(world: World) }
|
interface IWorldLoadListener { fun onWorldLoad(world: IWorld) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for declarative resource handling.
|
* Base class for declarative resource handling.
|
||||||
@@ -34,24 +47,26 @@ interface IWorldLoadListener { fun onWorldLoad(world: World) }
|
|||||||
*
|
*
|
||||||
* @param[modId] mod ID associated with this handler (used to filter config change events)
|
* @param[modId] mod ID associated with this handler (used to filter config change events)
|
||||||
*/
|
*/
|
||||||
open class ResourceHandler(val modId: String) {
|
open class ResourceHandler(
|
||||||
|
val modId: String,
|
||||||
|
val modBus: IEventBus,
|
||||||
|
val targetAtlas: Atlas = Atlas.BLOCKS
|
||||||
|
) {
|
||||||
|
|
||||||
val resources = mutableListOf<Any>()
|
val resources = mutableListOf<Any>()
|
||||||
open fun afterPreStitch() {}
|
|
||||||
open fun afterPostStitch() {}
|
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
// Self-registration
|
// Self-registration
|
||||||
// ============================
|
// ============================
|
||||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
init { modBus.register(this) }
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
// Resource declarations
|
// Resource declarations
|
||||||
// ============================
|
// ============================
|
||||||
fun iconStatic(domain: String, path: String) = IconHolder(domain, path).apply { resources.add(this) }
|
fun sprite(id: ResourceLocation) = sprite { id }
|
||||||
fun iconStatic(location: ResourceLocation) = iconStatic(location.namespace, location.path)
|
fun sprite(idFunc: ()->ResourceLocation) = AsyncSpriteDelegate(idFunc).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) }
|
||||||
fun iconSet(domain: String, pathPattern: String) = IconSet(domain, pathPattern).apply { this@ResourceHandler.resources.add(this) }
|
fun spriteSet(idFunc: (Int)->ResourceLocation) = AsyncSpriteSet(targetAtlas, idFunc).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) }
|
||||||
fun iconSet(location: ResourceLocation) = iconSet(location.namespace, location.path)
|
fun spriteSetTransformed(check: (Int)->ResourceLocation, register: (ResourceLocation)->ResourceLocation) =
|
||||||
|
AsyncSpriteSet(targetAtlas, check, register).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) }
|
||||||
fun model(init: Model.()->Unit) = ModelHolder(init).apply { resources.add(this) }
|
fun model(init: Model.()->Unit) = ModelHolder(init).apply { resources.add(this) }
|
||||||
fun modelSet(num: Int, init: Model.(Int)->Unit) = ModelSet(num, init).apply { resources.add(this) }
|
fun modelSet(num: Int, init: Model.(Int)->Unit) = ModelSet(num, init).apply { resources.add(this) }
|
||||||
fun vectorSet(num: Int, init: (Int)-> Double3) = VectorSet(num, init).apply { resources.add(this) }
|
fun vectorSet(num: Int, init: (Int)-> Double3) = VectorSet(num, init).apply { resources.add(this) }
|
||||||
@@ -61,20 +76,8 @@ open class ResourceHandler(val modId: String) {
|
|||||||
// Event registration
|
// Event registration
|
||||||
// ============================
|
// ============================
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
fun onPreStitch(event: TextureStitchEvent.Pre) {
|
fun handleModConfigChange(event: ModConfig.ModConfigEvent) {
|
||||||
resources.forEach { (it as? IStitchListener)?.onPreStitch(event.map) }
|
resources.forEach { (it as? IConfigChangeListener)?.onConfigChange() }
|
||||||
afterPreStitch()
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
fun onPostStitch(event: TextureStitchEvent.Post) {
|
|
||||||
resources.forEach { (it as? IStitchListener)?.onPostStitch(event.map) }
|
|
||||||
afterPostStitch()
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handleConfigChange(event: ConfigChangedEvent.OnConfigChangedEvent) {
|
|
||||||
if (event.modID == modId) resources.forEach { (it as? IConfigChangeListener)?.onConfigChange() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
@@ -85,56 +88,73 @@ open class ResourceHandler(val modId: String) {
|
|||||||
// ============================
|
// ============================
|
||||||
// Resource container classes
|
// Resource container classes
|
||||||
// ============================
|
// ============================
|
||||||
class IconHolder(val domain: String, val name: String) : IStitchListener {
|
class AsyncSpriteDelegate(val idFunc: ()->ResourceLocation) : ReadOnlyProperty<Any, Sprite>, AsyncSpriteProvider<Any> {
|
||||||
val iconRes = ResourceLocation(domain, name)
|
protected lateinit var value: Sprite
|
||||||
var icon: TextureAtlasSprite? = null
|
override fun getValue(thisRef: Any, property: KProperty<*>) = value
|
||||||
override fun onPreStitch(atlas: TextureMap) { atlas.registerSprite(iconRes) }
|
|
||||||
override fun onPostStitch(atlas: TextureMap) { icon = atlas[iconRes] }
|
override fun setup(manager: IResourceManager, sourceF: CompletableFuture<Any>, atlas: AtlasFuture): StitchPhases {
|
||||||
|
sourceF.thenRun {
|
||||||
|
val sprite = atlas.sprite(idFunc())
|
||||||
|
atlas.runAfter {
|
||||||
|
sprite.handle { sprite, error -> value = sprite ?: atlas.missing.get()!! }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StitchPhases(completedVoid(), completedVoid())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpriteSet {
|
||||||
|
val num: Int
|
||||||
|
operator fun get(idx: Int): Sprite
|
||||||
|
}
|
||||||
|
|
||||||
|
class AsyncSpriteSet(val targetAtlas: Atlas = Atlas.BLOCKS, val idFunc: (Int)->ResourceLocation, val transform: (ResourceLocation)->ResourceLocation = { it }) : AsyncSpriteProvider<Any> {
|
||||||
|
var num = 0
|
||||||
|
protected set
|
||||||
|
protected var sprites: List<Sprite> = emptyList()
|
||||||
|
|
||||||
|
override fun setup(manager: IResourceManager, sourceF: CompletableFuture<Any>, atlas: AtlasFuture): StitchPhases {
|
||||||
|
var list: List<CompletableFuture<Sprite>> = emptyList()
|
||||||
|
|
||||||
|
return StitchPhases(
|
||||||
|
discovery = sourceF.sink {
|
||||||
|
list = (0 until 16).map { idFunc(it) }
|
||||||
|
.filter { manager.hasResource( targetAtlas.wrap(it)) }
|
||||||
|
.map { transform(it) }
|
||||||
|
.map { atlas.sprite(it) }
|
||||||
|
},
|
||||||
|
cleanup = atlas.runAfter {
|
||||||
|
sprites = list.filter { !it.isCompletedExceptionally }.map { it.get() }
|
||||||
|
if (sprites.isEmpty()) sprites = listOf(atlas.missing.get()!!)
|
||||||
|
num = sprites.size
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
operator fun get(idx: Int) = sprites[idx % num]
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
|
class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
|
||||||
var model: Model = Model().apply(init)
|
var model: Model = Model()
|
||||||
override fun onConfigChange() { model = Model().apply(init) }
|
override fun onConfigChange() { model = Model().apply(init) }
|
||||||
}
|
}
|
||||||
|
|
||||||
class IconSet(val domain: String, val namePattern: String) : IStitchListener {
|
|
||||||
val resources = arrayOfNulls<ResourceLocation>(16)
|
|
||||||
val icons = arrayOfNulls<TextureAtlasSprite>(16)
|
|
||||||
var num = 0
|
|
||||||
|
|
||||||
override fun onPreStitch(atlas: TextureMap) {
|
|
||||||
num = 0
|
|
||||||
(0..15).forEach { idx ->
|
|
||||||
icons[idx] = null
|
|
||||||
val locReal = ResourceLocation(domain, "textures/${namePattern.format(idx)}.png")
|
|
||||||
if (resourceManager[locReal] != null) resources[num++] = ResourceLocation(domain, namePattern.format(idx)).apply { atlas.registerSprite(this) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPostStitch(atlas: TextureMap) {
|
|
||||||
(0 until num).forEach { idx -> icons[idx] = atlas[resources[idx]!!] }
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun get(idx: Int) = if (num == 0) null else icons[idx % num]
|
|
||||||
}
|
|
||||||
|
|
||||||
class ModelSet(val num: Int, val init: Model.(Int)->Unit): IConfigChangeListener {
|
class ModelSet(val num: Int, val init: Model.(Int)->Unit): IConfigChangeListener {
|
||||||
val models = Array(num) { Model().apply{ init(it) } }
|
val models = Array(num) { Model() }
|
||||||
override fun onConfigChange() { (0 until num).forEach { models[it] = Model().apply{ init(it) } } }
|
override fun onConfigChange() { (0 until num).forEach { models[it] = Model().apply{ init(it) } } }
|
||||||
operator fun get(idx: Int) = models[idx % num]
|
operator fun get(idx: Int) = models[idx % num]
|
||||||
}
|
}
|
||||||
|
|
||||||
class VectorSet(val num: Int, val init: (Int)->Double3): IConfigChangeListener {
|
class VectorSet(val num: Int, val init: (Int)->Double3): IConfigChangeListener {
|
||||||
val models = Array(num) { init(it) }
|
val models = Array(num) { Double3.zero }
|
||||||
override fun onConfigChange() { (0 until num).forEach { models[it] = init(it) } }
|
override fun onConfigChange() { (0 until num).forEach { models[it] = init(it) } }
|
||||||
operator fun get(idx: Int) = models[idx % num]
|
operator fun get(idx: Int) = models[idx % num]
|
||||||
}
|
}
|
||||||
|
|
||||||
class SimplexNoise() : IWorldLoadListener {
|
class SimplexNoise : IWorldLoadListener {
|
||||||
var noise = NoiseGeneratorSimplex()
|
var noise = SimplexNoiseGenerator(Random())
|
||||||
override fun onWorldLoad(world: World) { noise = NoiseGeneratorSimplex(Random(world.worldInfo.seed))
|
override fun onWorldLoad(world: IWorld) { noise = SimplexNoiseGenerator(Random(world.worldInfo.seed))
|
||||||
}
|
}
|
||||||
operator fun get(x: Int, z: Int) = MathHelper.floor((noise.getValue(x.toDouble(), z.toDouble()) + 1.0) * 32.0)
|
operator fun get(x: Int, z: Int) = MathHelper.floor((noise.getValue(x.toDouble(), z.toDouble()) + 1.0) * 32.0)
|
||||||
operator fun get(pos: Int3) = get(pos.x, pos.z)
|
operator fun get(pos: Int3) = get(pos.x, pos.z)
|
||||||
operator fun get(pos: BlockPos) = get(pos.x, pos.z)
|
operator fun get(pos: BlockPos) = get(pos.x, pos.z)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
package mods.octarinecore.client.resource
|
|
||||||
|
|
||||||
import mods.octarinecore.client.resource.ResourceType.*
|
|
||||||
import net.minecraft.client.resources.IResource
|
|
||||||
import net.minecraft.util.ResourceLocation
|
|
||||||
import java.awt.image.BufferedImage
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/** Type of generated texture resource */
|
|
||||||
enum class ResourceType {
|
|
||||||
COLOR, // regular diffuse map
|
|
||||||
METADATA, // texture metadata
|
|
||||||
NORMAL, // ShadersMod normal map
|
|
||||||
SPECULAR // ShadersMod specular map
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generator returning textures based on a single other texture. This texture is located with the
|
|
||||||
* _dom_ and _path_ parameters of a [ParameterList].
|
|
||||||
*
|
|
||||||
* @param[domain] Resource domain of generator
|
|
||||||
*/
|
|
||||||
abstract class TextureGenerator(domain: String) : ParameterBasedGenerator(domain) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtain a [ResourceLocation] to a generated texture
|
|
||||||
*
|
|
||||||
* @param[iconName] the name of the [TextureAtlasSprite] (not the full location) backing the generated texture
|
|
||||||
* @param[extraParams] additional parameters of the generated texture
|
|
||||||
*/
|
|
||||||
fun generatedResource(iconName: String, vararg extraParams: Pair<String, Any>) = ResourceLocation(
|
|
||||||
domain,
|
|
||||||
textureLocation(iconName).let {
|
|
||||||
ParameterList(
|
|
||||||
mapOf("dom" to it.namespace, "path" to it.path) +
|
|
||||||
extraParams.map { Pair(it.first, it.second.toString()) },
|
|
||||||
"generate"
|
|
||||||
).toString()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Get the type and location of the texture resource encoded by the given [ParameterList]. */
|
|
||||||
fun targetResource(params: ParameterList): Pair<ResourceType, ResourceLocation>? {
|
|
||||||
val baseTexture =
|
|
||||||
if (listOf("dom", "path").all { it in params }) ResourceLocation(params["dom"]!!, params["path"]!!)
|
|
||||||
else return null
|
|
||||||
return when(params.value?.toLowerCase()) {
|
|
||||||
"generate.png" -> COLOR to baseTexture + ".png"
|
|
||||||
"generate.png.mcmeta" -> METADATA to baseTexture + ".png.mcmeta"
|
|
||||||
"generate_n.png" -> NORMAL to baseTexture + "_n.png"
|
|
||||||
"generate_s.png" -> SPECULAR to baseTexture + "_s.png"
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resourceExists(params: ParameterList) =
|
|
||||||
targetResource(params)?.second?.let { resourceManager[it] != null } ?: false
|
|
||||||
|
|
||||||
override fun getInputStream(params: ParameterList): InputStream? {
|
|
||||||
val target = targetResource(params)
|
|
||||||
return when(target?.first) {
|
|
||||||
null -> null
|
|
||||||
METADATA -> resourceManager[target!!.second]?.inputStream
|
|
||||||
else -> generate(params)?.asStream
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate image data from the parameter list.
|
|
||||||
*/
|
|
||||||
abstract fun generate(params: ParameterList): BufferedImage?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a texture resource when multiple sizes may exist.
|
|
||||||
*
|
|
||||||
* @param[maxSize] Maximum size to consider. This value is progressively halved when searching for smaller versions.
|
|
||||||
* @param[maskPath] Location of the texture of the given size
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->ResourceLocation): IResource? {
|
|
||||||
var size = maxSize
|
|
||||||
val sizes = mutableListOf<Int>()
|
|
||||||
while(size > 2) { sizes.add(size); size /= 2 }
|
|
||||||
return sizes.map { resourceManager[maskPath(it)] }.filterNotNull().firstOrNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +1,31 @@
|
|||||||
@file:JvmName("Utils")
|
@file:JvmName("Utils")
|
||||||
package mods.octarinecore.client.resource
|
package mods.octarinecore.client.resource
|
||||||
|
|
||||||
import mods.betterfoliage.loader.Refs
|
|
||||||
import mods.octarinecore.PI2
|
import mods.octarinecore.PI2
|
||||||
import mods.octarinecore.client.render.HSB
|
import mods.octarinecore.client.render.lighting.HSB
|
||||||
import mods.octarinecore.metaprog.reflectField
|
|
||||||
import mods.octarinecore.stripStart
|
import mods.octarinecore.stripStart
|
||||||
import mods.octarinecore.tryDefault
|
import mods.octarinecore.tryDefault
|
||||||
import net.minecraft.client.Minecraft
|
import net.minecraft.client.Minecraft
|
||||||
import net.minecraft.client.renderer.block.model.ModelBlock
|
import net.minecraft.client.renderer.model.BlockModel
|
||||||
|
import net.minecraft.client.renderer.texture.AtlasTexture
|
||||||
|
import net.minecraft.client.renderer.texture.MissingTextureSprite
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||||
import net.minecraft.client.renderer.texture.TextureMap
|
import net.minecraft.resources.IResource
|
||||||
import net.minecraft.client.resources.IResource
|
import net.minecraft.resources.IResourceManager
|
||||||
import net.minecraft.client.resources.IResourceManager
|
import net.minecraft.resources.SimpleReloadableResourceManager
|
||||||
import net.minecraft.client.resources.SimpleReloadableResourceManager
|
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
import net.minecraftforge.client.model.IModel
|
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.lang.Math.*
|
import java.lang.Math.*
|
||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
|
import kotlin.math.atan2
|
||||||
|
|
||||||
/** Concise getter for the Minecraft resource manager. */
|
/** Concise getter for the Minecraft resource manager. */
|
||||||
val resourceManager: SimpleReloadableResourceManager get() =
|
val resourceManager: SimpleReloadableResourceManager
|
||||||
Minecraft.getMinecraft().resourceManager as SimpleReloadableResourceManager
|
get() =
|
||||||
|
Minecraft.getInstance().resourceManager as SimpleReloadableResourceManager
|
||||||
|
|
||||||
/** Append a string to the [ResourceLocation]'s path. */
|
/** Append a string to the [ResourceLocation]'s path. */
|
||||||
operator fun ResourceLocation.plus(str: String) = ResourceLocation(namespace, path + str)
|
operator fun ResourceLocation.plus(str: String) = ResourceLocation(namespace, path + str)
|
||||||
@@ -36,8 +36,10 @@ operator fun IResourceManager.get(domain: String, path: String): IResource? = ge
|
|||||||
operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryDefault(null) { getResource(location) }
|
operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryDefault(null) { getResource(location) }
|
||||||
|
|
||||||
/** Index operator to get a texture sprite. */
|
/** Index operator to get a texture sprite. */
|
||||||
operator fun TextureMap.get(name: String): TextureAtlasSprite? = getTextureExtry(ResourceLocation(name).toString())
|
operator fun AtlasTexture.get(res: ResourceLocation): TextureAtlasSprite? = getSprite(res)
|
||||||
operator fun TextureMap.get(res: ResourceLocation): TextureAtlasSprite? = getTextureExtry(res.toString())
|
operator fun AtlasTexture.get(name: String): TextureAtlasSprite? = getSprite(ResourceLocation(name))
|
||||||
|
|
||||||
|
val missingSprite: TextureAtlasSprite get() = MissingTextureSprite.func_217790_a()
|
||||||
|
|
||||||
/** Load an image resource. */
|
/** Load an image resource. */
|
||||||
fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream)
|
fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream)
|
||||||
@@ -57,26 +59,23 @@ operator fun BufferedImage.set(x: Int, y: Int, value: Int) = this.setRGB(x, y, v
|
|||||||
/** Get an [InputStream] to an image object in PNG format. */
|
/** Get an [InputStream] to an image object in PNG format. */
|
||||||
val BufferedImage.asStream: InputStream get() =
|
val BufferedImage.asStream: InputStream get() =
|
||||||
ByteArrayInputStream(ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() })
|
ByteArrayInputStream(ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() })
|
||||||
|
val BufferedImage.bytes: ByteArray get() =
|
||||||
|
ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() }
|
||||||
/**
|
/**
|
||||||
* Calculate the average color of a texture.
|
* Calculate the average color of a texture.
|
||||||
*
|
*
|
||||||
* Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average),
|
* Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average),
|
||||||
* and the result transformed back to the RGB color space.
|
* and the result transformed back to the RGB color space.
|
||||||
*/
|
*/
|
||||||
val TextureAtlasSprite.averageColor: Int? get() {
|
val TextureAtlasSprite.averageColor: Int get() {
|
||||||
val locationNoDirs = ResourceLocation(iconName).stripStart("blocks/")
|
|
||||||
val locationWithDirs = ResourceLocation(locationNoDirs.namespace, "textures/blocks/%s.png".format(locationNoDirs.path))
|
|
||||||
val image = resourceManager[locationWithDirs]?.loadImage() ?: return null
|
|
||||||
|
|
||||||
var numOpaque = 0
|
var numOpaque = 0
|
||||||
var sumHueX = 0.0
|
var sumHueX = 0.0
|
||||||
var sumHueY = 0.0
|
var sumHueY = 0.0
|
||||||
var sumSaturation = 0.0f
|
var sumSaturation = 0.0f
|
||||||
var sumBrightness = 0.0f
|
var sumBrightness = 0.0f
|
||||||
for (x in 0..image.width - 1)
|
for (x in 0 until width)
|
||||||
for (y in 0..image.height - 1) {
|
for (y in 0 until height) {
|
||||||
val pixel = image[x, y]
|
val pixel = getPixelRGBA(0, x, y)
|
||||||
val alpha = (pixel shr 24) and 255
|
val alpha = (pixel shr 24) and 255
|
||||||
val hsb = HSB.fromColor(pixel)
|
val hsb = HSB.fromColor(pixel)
|
||||||
if (alpha == 255) {
|
if (alpha == 255) {
|
||||||
@@ -89,7 +88,7 @@ val TextureAtlasSprite.averageColor: Int? get() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// circular average - transform sum vector to polar angle
|
// circular average - transform sum vector to polar angle
|
||||||
val avgHue = (atan2(sumHueY.toDouble(), sumHueX.toDouble()) / PI2 + 0.5).toFloat()
|
val avgHue = (atan2(sumHueY, sumHueX) / PI2 + 0.5).toFloat()
|
||||||
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat()).asColor
|
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat()).asColor
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,27 +100,9 @@ fun textureLocation(iconName: String) = ResourceLocation(iconName).let {
|
|||||||
else ResourceLocation(it.namespace, "textures/${it.path}")
|
else ResourceLocation(it.namespace, "textures/${it.path}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
fun Pair<BlockModel, ResourceLocation>.derivesFrom(targetLocation: ResourceLocation): Boolean {
|
||||||
val IModel.modelBlockAndLoc: List<Pair<ModelBlock, ResourceLocation>> get() {
|
|
||||||
if (Refs.VanillaModelWrapper.isInstance(this))
|
|
||||||
return listOf(Pair(Refs.model_VMW.get(this) as ModelBlock, Refs.location_VMW.get(this) as ResourceLocation))
|
|
||||||
else if (Refs.WeightedRandomModel.isInstance(this)) Refs.models_WRM.get(this)?.let {
|
|
||||||
return (it as List<IModel>).flatMap(IModel::modelBlockAndLoc)
|
|
||||||
}
|
|
||||||
else if (Refs.MultipartModel.isInstance(this)) Refs.partModels_MPM.get(this)?.let {
|
|
||||||
return (it as Map<Any, IModel>).flatMap { it.value.modelBlockAndLoc }
|
|
||||||
} else {
|
|
||||||
this::class.java.declaredFields.find { it.type.isInstance(IModel::class.java) }?.let { modelField ->
|
|
||||||
modelField.isAccessible = true
|
|
||||||
return (modelField.get(this) as IModel).modelBlockAndLoc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Pair<ModelBlock, ResourceLocation>.derivesFrom(targetLocation: ResourceLocation): Boolean {
|
|
||||||
if (second.stripStart("models/") == targetLocation) return true
|
if (second.stripStart("models/") == targetLocation) return true
|
||||||
if (first.parent != null && first.parentLocation != null)
|
if (first.parent != null && first.parentLocation != null)
|
||||||
return Pair(first.parent, first.parentLocation!!).derivesFrom(targetLocation)
|
return Pair(first.parent!!, first.parentLocation!!).derivesFrom(targetLocation)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/main/kotlin/mods/octarinecore/common/Futures.kt
Normal file
15
src/main/kotlin/mods/octarinecore/common/Futures.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package mods.octarinecore.common
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.CompletionStage
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
fun completedVoid() = CompletableFuture.completedFuture<Void>(null)!!
|
||||||
|
|
||||||
|
fun <T, U> CompletionStage<T>.map(func: (T)->U) = thenApply(Function(func)).toCompletableFuture()!!
|
||||||
|
fun <T, U> CompletionStage<T>.mapAsync(func: (T)->U) = thenApplyAsync(Function(func), Minecraft.getInstance()).toCompletableFuture()!!
|
||||||
|
|
||||||
|
fun <T> CompletionStage<T>.sink(func: (T)->Unit) = thenAccept(Consumer(func)).toCompletableFuture()!!
|
||||||
|
fun <T> CompletionStage<T>.sinkAsync(func: (T)->Unit) = thenAcceptAsync(Consumer(func), Minecraft.getInstance()).toCompletableFuture()!!
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package mods.octarinecore.common
|
package mods.octarinecore.common
|
||||||
|
|
||||||
import mods.octarinecore.cross
|
import mods.octarinecore.cross
|
||||||
import net.minecraft.util.EnumFacing
|
import net.minecraft.util.Direction
|
||||||
import net.minecraft.util.EnumFacing.*
|
import net.minecraft.util.Direction.*
|
||||||
import net.minecraft.util.EnumFacing.Axis.*
|
import net.minecraft.util.Direction.Axis.*
|
||||||
import net.minecraft.util.EnumFacing.AxisDirection.NEGATIVE
|
import net.minecraft.util.Direction.AxisDirection.NEGATIVE
|
||||||
import net.minecraft.util.EnumFacing.AxisDirection.POSITIVE
|
import net.minecraft.util.Direction.AxisDirection.POSITIVE
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
@@ -13,19 +13,19 @@ import net.minecraft.util.math.BlockPos
|
|||||||
// ================================
|
// ================================
|
||||||
val axes = listOf(X, Y, Z)
|
val axes = listOf(X, Y, Z)
|
||||||
val axisDirs = listOf(POSITIVE, NEGATIVE)
|
val axisDirs = listOf(POSITIVE, NEGATIVE)
|
||||||
val EnumFacing.dir: AxisDirection get() = axisDirection
|
val Direction.dir: AxisDirection get() = axisDirection
|
||||||
val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" }
|
val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" }
|
||||||
val forgeDirs = EnumFacing.values()
|
val allDirections = Direction.values()
|
||||||
val forgeDirsHorizontal = listOf(NORTH, SOUTH, EAST, WEST)
|
val horizontalDirections = listOf(NORTH, SOUTH, EAST, WEST)
|
||||||
val forgeDirOffsets = forgeDirs.map { Int3(it) }
|
val allDirOffsets = allDirections.map { Int3(it) }
|
||||||
val Pair<Axis, AxisDirection>.face: EnumFacing get() = when(this) {
|
val Pair<Axis, AxisDirection>.face: Direction get() = when(this) {
|
||||||
X to POSITIVE -> EAST; X to NEGATIVE -> WEST;
|
X to POSITIVE -> EAST; X to NEGATIVE -> WEST;
|
||||||
Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN;
|
Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN;
|
||||||
Z to POSITIVE -> SOUTH; else -> NORTH;
|
Z to POSITIVE -> SOUTH; else -> NORTH;
|
||||||
}
|
}
|
||||||
val EnumFacing.perpendiculars: List<EnumFacing> get() =
|
val Direction.perpendiculars: List<Direction> get() =
|
||||||
axes.filter { it != this.axis }.cross(axisDirs).map { it.face }
|
axes.filter { it != this.axis }.cross(axisDirs).map { it.face }
|
||||||
val EnumFacing.offset: Int3 get() = forgeDirOffsets[ordinal]
|
val Direction.offset: Int3 get() = allDirOffsets[ordinal]
|
||||||
|
|
||||||
/** Old ForgeDirection rotation matrix yanked from 1.7.10 */
|
/** Old ForgeDirection rotation matrix yanked from 1.7.10 */
|
||||||
val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
|
val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
|
||||||
@@ -40,15 +40,16 @@ val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
|
|||||||
// ================================
|
// ================================
|
||||||
// Vectors
|
// Vectors
|
||||||
// ================================
|
// ================================
|
||||||
operator fun EnumFacing.times(scale: Double) =
|
operator fun Direction.times(scale: Double) =
|
||||||
Double3(directionVec.x.toDouble() * scale, directionVec.y.toDouble() * scale, directionVec.z.toDouble() * scale)
|
Double3(directionVec.x.toDouble() * scale, directionVec.y.toDouble() * scale, directionVec.z.toDouble() * scale)
|
||||||
val EnumFacing.vec: Double3 get() = Double3(directionVec.x.toDouble(), directionVec.y.toDouble(), directionVec.z.toDouble())
|
val Direction.vec: Double3 get() = Double3(directionVec.x.toDouble(), directionVec.y.toDouble(), directionVec.z.toDouble())
|
||||||
|
|
||||||
operator fun BlockPos.plus(other: Int3) = BlockPos(x + other.x, y + other.y, z + other.z)
|
operator fun BlockPos.plus(other: Int3) = BlockPos(x + other.x, y + other.y, z + other.z)
|
||||||
|
|
||||||
/** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */
|
/** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */
|
||||||
data class Double3(var x: Double, var y: Double, var z: Double) {
|
data class Double3(var x: Double, var y: Double, var z: Double) {
|
||||||
constructor(x: Float, y: Float, z: Float) : this(x.toDouble(), y.toDouble(), z.toDouble())
|
constructor(x: Float, y: Float, z: Float) : this(x.toDouble(), y.toDouble(), z.toDouble())
|
||||||
constructor(dir: EnumFacing) : this(dir.directionVec.x.toDouble(), dir.directionVec.y.toDouble(), dir.directionVec.z.toDouble())
|
constructor(dir: Direction) : this(dir.directionVec.x.toDouble(), dir.directionVec.y.toDouble(), dir.directionVec.z.toDouble())
|
||||||
companion object {
|
companion object {
|
||||||
val zero: Double3 get() = Double3(0.0, 0.0, 0.0)
|
val zero: Double3 get() = Double3(0.0, 0.0, 0.0)
|
||||||
fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) =
|
fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) =
|
||||||
@@ -92,13 +93,13 @@ data class Double3(var x: Double, var y: Double, var z: Double) {
|
|||||||
infix fun cross(o: Double3) = Double3(y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x)
|
infix fun cross(o: Double3) = Double3(y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x)
|
||||||
val length: Double get() = Math.sqrt(x * x + y * y + z * z)
|
val length: Double get() = Math.sqrt(x * x + y * y + z * z)
|
||||||
val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) }
|
val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) }
|
||||||
val nearestCardinal: EnumFacing get() = nearestAngle(this, forgeDirs.asIterable()) { it.vec }.first
|
val nearestCardinal: Direction get() = nearestAngle(this, allDirections.asIterable()) { it.vec }.first
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */
|
/** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */
|
||||||
data class Int3(var x: Int, var y: Int, var z: Int) {
|
data class Int3(var x: Int, var y: Int, var z: Int) {
|
||||||
constructor(dir: EnumFacing) : this(dir.directionVec.x, dir.directionVec.y, dir.directionVec.z)
|
constructor(dir: Direction) : this(dir.directionVec.x, dir.directionVec.y, dir.directionVec.z)
|
||||||
constructor(offset: Pair<Int, EnumFacing>) : this(
|
constructor(offset: Pair<Int, Direction>) : this(
|
||||||
offset.first * offset.second.directionVec.x,
|
offset.first * offset.second.directionVec.x,
|
||||||
offset.first * offset.second.directionVec.y,
|
offset.first * offset.second.directionVec.y,
|
||||||
offset.first * offset.second.directionVec.z
|
offset.first * offset.second.directionVec.z
|
||||||
@@ -109,7 +110,7 @@ data class Int3(var x: Int, var y: Int, var z: Int) {
|
|||||||
|
|
||||||
// immutable operations
|
// immutable operations
|
||||||
operator fun plus(other: Int3) = Int3(x + other.x, y + other.y, z + other.z)
|
operator fun plus(other: Int3) = Int3(x + other.x, y + other.y, z + other.z)
|
||||||
operator fun plus(other: Pair<Int, EnumFacing>) = Int3(
|
operator fun plus(other: Pair<Int, Direction>) = Int3(
|
||||||
x + other.first * other.second.directionVec.x,
|
x + other.first * other.second.directionVec.x,
|
||||||
y + other.first * other.second.directionVec.y,
|
y + other.first * other.second.directionVec.y,
|
||||||
z + other.first * other.second.directionVec.z
|
z + other.first * other.second.directionVec.z
|
||||||
@@ -145,17 +146,17 @@ data class Int3(var x: Int, var y: Int, var z: Int) {
|
|||||||
// ================================
|
// ================================
|
||||||
// Rotation
|
// Rotation
|
||||||
// ================================
|
// ================================
|
||||||
val EnumFacing.rotations: Array<EnumFacing> get() =
|
val Direction.rotations: Array<Direction> get() =
|
||||||
Array(6) { idx -> EnumFacing.values()[ROTATION_MATRIX[ordinal][idx]] }
|
Array(6) { idx -> Direction.values()[ROTATION_MATRIX[ordinal][idx]] }
|
||||||
fun EnumFacing.rotate(rot: Rotation) = rot.forward[ordinal]
|
fun Direction.rotate(rot: Rotation) = rot.forward[ordinal]
|
||||||
fun rot(axis: EnumFacing) = Rotation.rot90[axis.ordinal]
|
fun rot(axis: Direction) = Rotation.rot90[axis.ordinal]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing an arbitrary rotation (or combination of rotations) around cardinal axes by 90 degrees.
|
* Class representing an arbitrary rotation (or combination of rotations) around cardinal axes by 90 degrees.
|
||||||
* In effect, a permutation of [ForgeDirection]s.
|
* In effect, a permutation of [ForgeDirection]s.
|
||||||
*/
|
*/
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
class Rotation(val forward: Array<EnumFacing>, val reverse: Array<EnumFacing>) {
|
class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
|
||||||
operator fun plus(other: Rotation) = Rotation(
|
operator fun plus(other: Rotation) = Rotation(
|
||||||
Array(6) { idx -> forward[other.forward[idx].ordinal] },
|
Array(6) { idx -> forward[other.forward[idx].ordinal] },
|
||||||
Array(6) { idx -> other.reverse[reverse[idx].ordinal] }
|
Array(6) { idx -> other.reverse[reverse[idx].ordinal] }
|
||||||
@@ -163,15 +164,15 @@ class Rotation(val forward: Array<EnumFacing>, val reverse: Array<EnumFacing>) {
|
|||||||
operator fun unaryMinus() = Rotation(reverse, forward)
|
operator fun unaryMinus() = Rotation(reverse, forward)
|
||||||
operator fun times(num: Int) = when(num % 4) { 1 -> this; 2 -> this + this; 3 -> -this; else -> identity }
|
operator fun times(num: Int) = when(num % 4) { 1 -> this; 2 -> this + this; 3 -> -this; else -> identity }
|
||||||
|
|
||||||
inline fun rotatedComponent(dir: EnumFacing, x: Int, y: Int, z: Int) =
|
inline fun rotatedComponent(dir: Direction, x: Int, y: Int, z: Int) =
|
||||||
when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0 }
|
when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0 }
|
||||||
inline fun rotatedComponent(dir: EnumFacing, x: Double, y: Double, z: Double) =
|
inline fun rotatedComponent(dir: Direction, x: Double, y: Double, z: Double) =
|
||||||
when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0.0 }
|
when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0.0 }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Forge rotation matrix is left-hand
|
// Forge rotation matrix is left-hand
|
||||||
val rot90 = Array(6) { idx -> Rotation(forgeDirs[idx].opposite.rotations, forgeDirs[idx].rotations) }
|
val rot90 = Array(6) { idx -> Rotation(allDirections[idx].opposite.rotations, allDirections[idx].rotations) }
|
||||||
val identity = Rotation(forgeDirs, forgeDirs)
|
val identity = Rotation(allDirections, allDirections)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -179,8 +180,24 @@ class Rotation(val forward: Array<EnumFacing>, val reverse: Array<EnumFacing>) {
|
|||||||
// ================================
|
// ================================
|
||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
// ================================
|
// ================================
|
||||||
|
|
||||||
|
inline operator fun <reified T> Array<T>.get(face: Direction): T = get(face.ordinal)
|
||||||
|
|
||||||
|
data class BoxFace(val top: Direction, val left: Direction) {
|
||||||
|
|
||||||
|
val allCorners = listOf(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite)
|
||||||
|
}
|
||||||
|
val boxFaces = allDirections.map { when(it) {
|
||||||
|
DOWN -> BoxFace(SOUTH, WEST)
|
||||||
|
UP -> BoxFace(SOUTH, EAST)
|
||||||
|
NORTH -> BoxFace(WEST, UP)
|
||||||
|
SOUTH -> BoxFace(UP, WEST)
|
||||||
|
WEST -> BoxFace(SOUTH, UP)
|
||||||
|
EAST -> BoxFace(SOUTH, DOWN)
|
||||||
|
}}.toTypedArray()
|
||||||
|
|
||||||
/** List of all 12 box edges, represented as a [Pair] of [ForgeDirection]s */
|
/** List of all 12 box edges, represented as a [Pair] of [ForgeDirection]s */
|
||||||
val boxEdges = forgeDirs.flatMap { face1 -> forgeDirs.filter { it.axis > face1.axis }.map { face1 to it } }
|
val boxEdges = allDirections.flatMap { face1 -> allDirections.filter { it.axis > face1.axis }.map { face1 to it } }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the closest object to the specified point from a list of objects.
|
* Get the closest object to the specified point from a list of objects.
|
||||||
@@ -203,23 +220,3 @@ fun <T> nearestPosition(vertex: Double3, objs: Iterable<T>, objPos: (T)-> Double
|
|||||||
*/
|
*/
|
||||||
fun <T> nearestAngle(vector: Double3, objs: Iterable<T>, objAngle: (T)-> Double3): Pair<T, Double> =
|
fun <T> nearestAngle(vector: Double3, objs: Iterable<T>, objAngle: (T)-> Double3): Pair<T, Double> =
|
||||||
objs.map { it to objAngle(it).dot(vector) }.maxBy { it.second }!!
|
objs.map { it to objAngle(it).dot(vector) }.maxBy { it.second }!!
|
||||||
|
|
||||||
data class FaceCorners(val topLeft: Pair<EnumFacing, EnumFacing>,
|
|
||||||
val topRight: Pair<EnumFacing, EnumFacing>,
|
|
||||||
val bottomLeft: Pair<EnumFacing, EnumFacing>,
|
|
||||||
val bottomRight: Pair<EnumFacing, EnumFacing>) {
|
|
||||||
constructor(top: EnumFacing, left: EnumFacing) :
|
|
||||||
this(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite)
|
|
||||||
|
|
||||||
val asArray = arrayOf(topLeft, topRight, bottomLeft, bottomRight)
|
|
||||||
val asList = listOf(topLeft, topRight, bottomLeft, bottomRight)
|
|
||||||
}
|
|
||||||
|
|
||||||
val faceCorners = forgeDirs.map { when(it) {
|
|
||||||
DOWN -> FaceCorners(SOUTH, WEST)
|
|
||||||
UP -> FaceCorners(SOUTH, EAST)
|
|
||||||
NORTH -> FaceCorners(WEST, UP)
|
|
||||||
SOUTH -> FaceCorners(UP, WEST)
|
|
||||||
WEST -> FaceCorners(SOUTH, UP)
|
|
||||||
EAST -> FaceCorners(SOUTH, DOWN)
|
|
||||||
}}
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
package mods.octarinecore.common.config
|
|
||||||
|
|
||||||
import mods.octarinecore.client.gui.NonVerboseArrayEntry
|
|
||||||
import mods.octarinecore.client.resource.get
|
|
||||||
import mods.octarinecore.client.resource.getLines
|
|
||||||
import mods.octarinecore.client.resource.resourceManager
|
|
||||||
import net.minecraftforge.common.config.Configuration
|
|
||||||
import net.minecraftforge.common.config.Property
|
|
||||||
|
|
||||||
abstract class BlackWhiteListConfigOption<VALUE>(val domain: String, val path: String) : ConfigPropertyBase() {
|
|
||||||
|
|
||||||
val blackList = mutableListOf<VALUE>()
|
|
||||||
val whiteList = mutableListOf<VALUE>()
|
|
||||||
var blacklistProperty: Property? = null
|
|
||||||
var whitelistProperty: Property? = null
|
|
||||||
|
|
||||||
override val hasChanged: Boolean
|
|
||||||
get() = blacklistProperty?.hasChanged() ?: false || whitelistProperty?.hasChanged() ?: false
|
|
||||||
|
|
||||||
override val guiProperties: List<Property> get() = listOf(whitelistProperty!!, blacklistProperty!!)
|
|
||||||
|
|
||||||
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
|
|
||||||
lang = null
|
|
||||||
val defaults = readDefaults(domain, path)
|
|
||||||
blacklistProperty = target.get(categoryName, "${propertyName}Blacklist", defaults.first)
|
|
||||||
whitelistProperty = target.get(categoryName, "${propertyName}Whitelist", defaults.second)
|
|
||||||
listOf(blacklistProperty!!, whitelistProperty!!).forEach {
|
|
||||||
it.configEntryClass = NonVerboseArrayEntry::class.java
|
|
||||||
it.languageKey = "$langPrefix.$categoryName.${it.name}"
|
|
||||||
}
|
|
||||||
read()
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun convertValue(line: String): VALUE?
|
|
||||||
|
|
||||||
override fun read() {
|
|
||||||
listOf(Pair(blackList, blacklistProperty!!), Pair(whiteList, whitelistProperty!!)).forEach {
|
|
||||||
it.first.clear()
|
|
||||||
it.second.stringList.forEach { line ->
|
|
||||||
val value = convertValue(line)
|
|
||||||
if (value != null) it.first.add(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readDefaults(domain: String, path: String): Pair<Array<String>, Array<String>> {
|
|
||||||
val blackList = arrayListOf<String>()
|
|
||||||
val whiteList = arrayListOf<String>()
|
|
||||||
val defaults = resourceManager[domain, path]?.getLines()
|
|
||||||
defaults?.map{ it.trim() }?.filter { !it.startsWith("//") && it.isNotEmpty() }?.forEach {
|
|
||||||
if (it.startsWith("-")) { blackList.add(it.substring(1)) }
|
|
||||||
else { whiteList.add(it) }
|
|
||||||
}
|
|
||||||
return (blackList.toTypedArray() to whiteList.toTypedArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,266 +1,89 @@
|
|||||||
|
@file:JvmName("DelegatingConfigKt")
|
||||||
|
|
||||||
package mods.octarinecore.common.config
|
package mods.octarinecore.common.config
|
||||||
|
|
||||||
import com.google.common.collect.LinkedListMultimap
|
import mods.octarinecore.metaprog.reflectDelegates
|
||||||
import mods.betterfoliage.loader.Refs
|
|
||||||
import mods.octarinecore.metaprog.reflectField
|
|
||||||
import mods.octarinecore.metaprog.reflectFieldsOfType
|
|
||||||
import mods.octarinecore.metaprog.reflectNestedObjects
|
import mods.octarinecore.metaprog.reflectNestedObjects
|
||||||
import net.minecraftforge.common.MinecraftForge
|
import net.minecraftforge.common.ForgeConfigSpec
|
||||||
import net.minecraftforge.common.config.ConfigElement
|
import kotlin.properties.ReadOnlyProperty
|
||||||
import net.minecraftforge.common.config.Configuration
|
|
||||||
import net.minecraftforge.common.config.Property
|
|
||||||
import net.minecraftforge.fml.client.config.GuiConfigEntries
|
|
||||||
import net.minecraftforge.fml.client.config.IConfigElement
|
|
||||||
import net.minecraftforge.fml.client.event.ConfigChangedEvent
|
|
||||||
import net.minecraftforge.fml.common.FMLCommonHandler
|
|
||||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
// ============================
|
open class DelegatingConfig(val modId: String, val langPrefix: String) {
|
||||||
// Configuration object base
|
fun build() = ForgeConfigSpec.Builder().apply { ConfigBuildContext(langPrefix, emptyList(), this).addCategory(this@DelegatingConfig) }.build()
|
||||||
// ============================
|
}
|
||||||
/**
|
|
||||||
* Base class for declarative configuration handling.
|
|
||||||
*
|
|
||||||
* Subclasses should be singleton objects, containing one layer of further singleton objects representing
|
|
||||||
* config categories (nesting is not supported).
|
|
||||||
*
|
|
||||||
* Both the root object (maps to the category _global_) and category objects can contain [ConfigPropertyBase]
|
|
||||||
* instances (either directly or as a delegate), which handle the Forge [Configuration] itself.
|
|
||||||
*
|
|
||||||
* Config properties map to language keys by their field names.
|
|
||||||
*
|
|
||||||
* @param[modId] mod ID this configuration is linked to
|
|
||||||
* @param[langPrefix] prefix to use for language keys
|
|
||||||
*/
|
|
||||||
abstract class DelegatingConfig(val modId: String, val langPrefix: String) {
|
|
||||||
|
|
||||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
class ConfigBuildContext(val langPrefix: String, val path: List<String>, val builder: ForgeConfigSpec.Builder) {
|
||||||
|
|
||||||
/** The [Configuration] backing this config object. */
|
fun addCategory(configObj: Any) {
|
||||||
var config: Configuration? = null
|
configObj.reflectNestedObjects.forEach { (name, category) ->
|
||||||
val rootGuiElements = mutableListOf<IConfigElement>()
|
builder.push(name)
|
||||||
|
descend(name).addCategory(category)
|
||||||
/** Attach this config object to the given [Configuration] and update all properties. */
|
builder.pop()
|
||||||
fun attach(config: Configuration) {
|
|
||||||
this.config = config
|
|
||||||
val subProperties = LinkedListMultimap.create<String, String>()
|
|
||||||
rootGuiElements.clear()
|
|
||||||
|
|
||||||
forEachProperty { category, name, property ->
|
|
||||||
property.lang = property.lang ?: "$category.$name"
|
|
||||||
property.attach(config, langPrefix, category, name)
|
|
||||||
property.guiProperties.forEach { guiProperty ->
|
|
||||||
property.guiClass?.let { guiProperty.setConfigEntryClass(it) }
|
|
||||||
if (category == "global") rootGuiElements.add(ConfigElement(guiProperty))
|
|
||||||
else subProperties.put(category, guiProperty.name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (category in subProperties.keySet()) {
|
configObj.reflectDelegates(ConfigDelegate::class.java).forEach { (name, delegate) ->
|
||||||
val configCategory = config.getCategory(category)
|
descend(name).apply { delegate.addToBuilder(this) }
|
||||||
configCategory.setLanguageKey("$langPrefix.$category")
|
|
||||||
configCategory.setPropertyOrder(subProperties[category])
|
|
||||||
rootGuiElements.add(ConfigElement(configCategory))
|
|
||||||
}
|
|
||||||
save()
|
|
||||||
|
|
||||||
// hide all categories not in the config singleton
|
|
||||||
config.categoryNames.forEach {
|
|
||||||
config.getCategory(it).setShowInGui(it in subProperties.keySet())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun descend(pathName: String) = ConfigBuildContext(langPrefix, path + pathName, builder)
|
||||||
* Execute the given lambda for all config properties.
|
}
|
||||||
* Lambda params: (category name, property name, property instance)
|
|
||||||
*/
|
open class ConfigCategory(val comment: String? = null) {
|
||||||
inline fun forEachProperty(init: (String, String, ConfigPropertyBase)->Unit) {
|
}
|
||||||
reflectFieldsOfType(ConfigPropertyBase::class.java).forEach { property ->
|
|
||||||
init("global", property.first.split("$")[0], property.second as ConfigPropertyBase)
|
abstract class ConfigDelegate<T> : ReadOnlyProperty<Any, T> {
|
||||||
}
|
lateinit var configValue: ForgeConfigSpec.ConfigValue<T>
|
||||||
for (category in reflectNestedObjects) {
|
var cachedValue: T? = null
|
||||||
category.second.reflectFieldsOfType(ConfigPropertyBase::class.java).forEach { property ->
|
|
||||||
init(category.first, property.first.split("$")[0], property.second as ConfigPropertyBase)
|
override fun getValue(thisRef: Any, property: KProperty<*>): T {
|
||||||
}
|
if (cachedValue == null) cachedValue = configValue.get()
|
||||||
}
|
return cachedValue!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Save changes to the [Configuration]. */
|
abstract fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder): ForgeConfigSpec.ConfigValue<T>
|
||||||
fun save() { if (config?.hasChanged() ?: false) config!!.save() }
|
fun addToBuilder(ctx: ConfigBuildContext) {
|
||||||
|
val langKey = ctx.langPrefix + "." + (langPrefixOverride ?: ctx.path.joinToString("."))
|
||||||
/**
|
ctx.builder.translation(langKey)
|
||||||
* Returns true if any of the given configuration elements have changed.
|
configValue = getConfigValue(ctx.path.last(), ctx.builder)
|
||||||
* Supports both categories and
|
|
||||||
*/
|
|
||||||
fun hasChanged(elements: List<ConfigPropertyBase>): Boolean {
|
|
||||||
reflectNestedObjects.forEach { category ->
|
|
||||||
if (category.second in elements && config?.getCategory(category.first)?.hasChanged() ?: false) return true
|
|
||||||
}
|
|
||||||
forEachProperty { category, name, property ->
|
|
||||||
if (property in elements && property.hasChanged) return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called when the configuration for the mod changes. */
|
var langPrefixOverride: String? = null
|
||||||
abstract fun onChange(event: ConfigChangedEvent.PostConfigChangedEvent)
|
fun lang(prefix: String) = apply { langPrefixOverride = prefix }
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
fun handleConfigChange(event: ConfigChangedEvent.PostConfigChangedEvent) {
|
|
||||||
if (event.modID == modId) {
|
|
||||||
// refresh values
|
|
||||||
forEachProperty { c, n, prop -> prop.read() }
|
|
||||||
|
|
||||||
// call mod-specific handler
|
|
||||||
onChange(event)
|
|
||||||
|
|
||||||
// save to file
|
|
||||||
save()
|
|
||||||
Refs.resetChangedState.invoke(config!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Extension to get the underlying delegate of a field */
|
|
||||||
operator fun Any.get(name: String) = this.reflectField<ConfigPropertyBase>("$name\$delegate")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================
|
class DelegatingBooleanValue(val defaultValue: Boolean) : ConfigDelegate<Boolean>() {
|
||||||
// Property delegates
|
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.define(name, defaultValue)
|
||||||
// ============================
|
|
||||||
|
|
||||||
/** Base class for config property delegates. */
|
|
||||||
abstract class ConfigPropertyBase {
|
|
||||||
/** Language key of the property. */
|
|
||||||
var lang: String? = null
|
|
||||||
|
|
||||||
/** GUI class to use. */
|
|
||||||
var guiClass: Class<out GuiConfigEntries.IConfigEntry>? = null
|
|
||||||
|
|
||||||
/** @return true if the property has changed. */
|
|
||||||
abstract val hasChanged: Boolean
|
|
||||||
|
|
||||||
/** Attach this delegate to a Forge [Configuration]. */
|
|
||||||
abstract fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String)
|
|
||||||
|
|
||||||
/** List of [Property] instances backing this delegate. */
|
|
||||||
abstract val guiProperties: List<Property>
|
|
||||||
|
|
||||||
/** Re-read the property value from the [Configuration]. */
|
|
||||||
open fun read() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObsoleteConfigProperty : ConfigPropertyBase() {
|
class DelegatingIntValue(
|
||||||
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
|
val minValue: Int = 0,
|
||||||
target.getCategory(categoryName)?.remove(propertyName)
|
val maxValue: Int = 1,
|
||||||
}
|
val defaultValue: Int = 0
|
||||||
override val guiProperties = emptyList<Property>()
|
) : ConfigDelegate<Int>() {
|
||||||
override val hasChanged: Boolean get() = false
|
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Delegate for a property backed by a single [Property] instance. */
|
class DelegatingLongValue(
|
||||||
abstract class ConfigPropertyDelegate<T>() : ConfigPropertyBase() {
|
val minValue: Long = 0,
|
||||||
/** Cached value of the property. */
|
val maxValue: Long = 1,
|
||||||
var cached: T? = null
|
val defaultValue: Long = 0
|
||||||
/** The [Property] backing this delegate. */
|
) : ConfigDelegate<Long>() {
|
||||||
var property: Property? = null
|
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
|
||||||
|
|
||||||
override val guiProperties: List<Property> get() = listOf(property!!)
|
|
||||||
override val hasChanged: Boolean get() = property?.hasChanged() ?: false
|
|
||||||
|
|
||||||
/** Chained setter for the language key. */
|
|
||||||
fun lang(lang: String) = apply { this.lang = lang }
|
|
||||||
|
|
||||||
/** Read the backing [Property] instance. */
|
|
||||||
abstract fun Property.read(): T
|
|
||||||
|
|
||||||
/** Write the backing [Property] instance. */
|
|
||||||
abstract fun Property.write(value: T)
|
|
||||||
|
|
||||||
/** Get the backing [Property] instance. */
|
|
||||||
abstract fun resolve(target: Configuration, category: String, name: String): Property
|
|
||||||
|
|
||||||
/** Kotlin deleagation implementation. */
|
|
||||||
operator fun getValue(thisRef: Any, delegator: KProperty<*>): T {
|
|
||||||
if (cached != null) return cached!!
|
|
||||||
cached = property!!.read()
|
|
||||||
return cached!!
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Kotlin deleagation implementation. */
|
|
||||||
operator fun setValue(thisRef: Any, delegator: KProperty<*>, value: T) {
|
|
||||||
cached = value
|
|
||||||
property!!.write(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read() { cached = null }
|
|
||||||
|
|
||||||
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
|
|
||||||
cached = null
|
|
||||||
property = resolve(target, categoryName, propertyName)
|
|
||||||
property!!.setLanguageKey("$langPrefix.$lang")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** [Double]-typed property delegate. */
|
class DelegatingDoubleValue(
|
||||||
class ConfigPropertyDouble(val min: Double, val max: Double, val default: Double) :
|
val minValue: Double = 0.0,
|
||||||
ConfigPropertyDelegate<Double>() {
|
val maxValue: Double = 1.0,
|
||||||
override fun resolve(target: Configuration, category: String, name: String) =
|
val defaultValue: Double = 0.0
|
||||||
target.get(category, name, default, null).apply { setMinValue(min); setMaxValue(max) }
|
) : ConfigDelegate<Double>() {
|
||||||
override fun Property.read() = property!!.double
|
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
|
||||||
override fun Property.write(value: Double) = property!!.set(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** [Float]-typed property delegate. */
|
|
||||||
class ConfigPropertyFloat(val min: Double, val max: Double, val default: Double) :
|
|
||||||
ConfigPropertyDelegate<Float>() {
|
|
||||||
override fun resolve(target: Configuration, category: String, name: String) =
|
|
||||||
target.get(category, name, default, null).apply { setMinValue(min); setMaxValue(max) }
|
|
||||||
override fun Property.read() = property!!.double.toFloat()
|
|
||||||
override fun Property.write(value: Float) = property!!.set(value.toDouble())
|
|
||||||
}
|
|
||||||
|
|
||||||
/** [Int]-typed property delegate. */
|
|
||||||
class ConfigPropertyInt(val min: Int, val max: Int, val default: Int) :
|
|
||||||
ConfigPropertyDelegate<Int>() {
|
|
||||||
override fun resolve(target: Configuration, category: String, name: String) =
|
|
||||||
target.get(category, name, default, null).apply { setMinValue(min); setMaxValue(max) }
|
|
||||||
override fun Property.read() = property!!.int
|
|
||||||
override fun Property.write(value: Int) = property!!.set(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** [Long]-typed property delegate. Still uses [Int] internally */
|
|
||||||
class ConfigPropertyLong(val min: Int, val max: Int, val default: Int) :
|
|
||||||
ConfigPropertyDelegate<Long>() {
|
|
||||||
override fun resolve(target: Configuration, category: String, name: String) =
|
|
||||||
target.get(category, name, default, null).apply { setMinValue(min); setMaxValue(max) }
|
|
||||||
override fun Property.read() = property!!.long
|
|
||||||
override fun Property.write(value: Long) = property!!.set(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** [Boolean]-typed property delegate. */
|
|
||||||
class ConfigPropertyBoolean(val default: Boolean) :
|
|
||||||
ConfigPropertyDelegate<Boolean>() {
|
|
||||||
override fun resolve(target: Configuration, category: String, name: String) =
|
|
||||||
target.get(category, name, default, null)
|
|
||||||
override fun Property.read() = property!!.boolean
|
|
||||||
override fun Property.write(value: Boolean) = property!!.set(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** [Int] array typed property delegate. */
|
|
||||||
class ConfigPropertyIntList(val defaults: ()->Array<Int>) :
|
|
||||||
ConfigPropertyDelegate<Array<Int>>() {
|
|
||||||
override fun resolve(target: Configuration, category: String, name: String) =
|
|
||||||
target.get(category, name, defaults().toIntArray(), null)
|
|
||||||
override fun Property.read() = property!!.intList.toTypedArray()
|
|
||||||
override fun Property.write(value: Array<Int>) = property!!.set(value.toIntArray())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
// Delegate factory methods
|
// Delegate factory methods
|
||||||
// ============================
|
// ============================
|
||||||
fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = ConfigPropertyDouble(min, max, default)
|
fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = DelegatingDoubleValue(min, max, default)
|
||||||
fun float(min: Double = 0.0, max: Double = 1.0, default: Double) = ConfigPropertyFloat(min, max, default)
|
fun int(min: Int = 0, max: Int, default: Int) = DelegatingIntValue(min, max, default)
|
||||||
fun int(min: Int = 0, max: Int, default: Int) = ConfigPropertyInt(min, max, default)
|
fun long(min: Long = 0, max: Long, default: Long) = DelegatingLongValue(min, max, default)
|
||||||
fun long(min: Int = 0, max: Int, default: Int) = ConfigPropertyLong(min, max, default)
|
fun boolean(default: Boolean) = DelegatingBooleanValue(default)
|
||||||
fun intList(defaults: ()->Array<Int>) = ConfigPropertyIntList(defaults)
|
|
||||||
fun boolean(default: Boolean) = ConfigPropertyBoolean(default)
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package mods.octarinecore.common.config
|
package mods.octarinecore.common.config
|
||||||
|
|
||||||
|
import mods.octarinecore.client.resource.getLines
|
||||||
|
import mods.octarinecore.client.resource.resourceManager
|
||||||
import mods.octarinecore.metaprog.getJavaClass
|
import mods.octarinecore.metaprog.getJavaClass
|
||||||
import net.minecraft.block.Block
|
import net.minecraft.block.Block
|
||||||
import net.minecraft.util.ResourceLocation
|
import net.minecraft.util.ResourceLocation
|
||||||
|
import org.apache.logging.log4j.Logger
|
||||||
|
|
||||||
interface IBlockMatcher {
|
interface IBlockMatcher {
|
||||||
fun matchesClass(block: Block): Boolean
|
fun matchesClass(block: Block): Boolean
|
||||||
@@ -19,8 +22,11 @@ class SimpleBlockMatcher(vararg val classes: Class<*>) : IBlockMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigurableBlockMatcher(domain: String, path: String) : IBlockMatcher, BlackWhiteListConfigOption<Class<*>>(domain, path) {
|
class ConfigurableBlockMatcher(val logger: Logger, val location: ResourceLocation) : IBlockMatcher {
|
||||||
override fun convertValue(line: String) = getJavaClass(line)
|
|
||||||
|
val blackList = mutableListOf<Class<*>>()
|
||||||
|
val whiteList = mutableListOf<Class<*>>()
|
||||||
|
// override fun convertValue(line: String) = getJavaClass(line)
|
||||||
|
|
||||||
override fun matchesClass(block: Block): Boolean {
|
override fun matchesClass(block: Block): Boolean {
|
||||||
val blockClass = block.javaClass
|
val blockClass = block.javaClass
|
||||||
@@ -35,16 +41,34 @@ class ConfigurableBlockMatcher(domain: String, path: String) : IBlockMatcher, Bl
|
|||||||
whiteList.forEach { if (it.isAssignableFrom(blockClass)) return it }
|
whiteList.forEach { if (it.isAssignableFrom(blockClass)) return it }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun readDefaults() {
|
||||||
|
blackList.clear()
|
||||||
|
whiteList.clear()
|
||||||
|
resourceManager.getAllResources(location).forEach { resource ->
|
||||||
|
logger.debug("Reading resource $location from pack ${resource.packName}")
|
||||||
|
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
|
||||||
|
if (line.startsWith("-")) getJavaClass(line.substring(1))?.let { blackList.add(it) }
|
||||||
|
else getJavaClass(line)?.let { whiteList.add(it) }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>) {
|
data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>) {
|
||||||
constructor(vararg args: String) : this(ResourceLocation(args[0]), listOf(*args).drop(1))
|
constructor(vararg args: String) : this(ResourceLocation(args[0]), listOf(*args).drop(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModelTextureListConfigOption(domain: String, path: String, val minTextures: Int) : StringListConfigOption<ModelTextureList>(domain, path) {
|
class ModelTextureListConfiguration(val logger: Logger, val location: ResourceLocation) {
|
||||||
override fun convertValue(line: String): ModelTextureList? {
|
val modelList = mutableListOf<ModelTextureList>()
|
||||||
val elements = line.split(",")
|
fun readDefaults() {
|
||||||
if (elements.size < minTextures + 1) return null
|
resourceManager.getAllResources(location).forEach { resource ->
|
||||||
return ModelTextureList(ResourceLocation(elements.first()), elements.drop(1))
|
logger.debug("Reading resource $location from pack ${resource.packName}")
|
||||||
|
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
|
||||||
|
val elements = line.split(",")
|
||||||
|
modelList.add(ModelTextureList(ResourceLocation(elements.first()), elements.drop(1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package mods.octarinecore.common.config
|
|
||||||
|
|
||||||
import mods.octarinecore.client.gui.NonVerboseArrayEntry
|
|
||||||
import mods.octarinecore.client.resource.get
|
|
||||||
import mods.octarinecore.client.resource.getLines
|
|
||||||
import mods.octarinecore.client.resource.resourceManager
|
|
||||||
import net.minecraftforge.common.config.Configuration
|
|
||||||
import net.minecraftforge.common.config.Property
|
|
||||||
|
|
||||||
abstract class StringListConfigOption<VALUE>(val domain: String, val path: String) : ConfigPropertyBase() {
|
|
||||||
|
|
||||||
val list = mutableListOf<VALUE>()
|
|
||||||
lateinit var listProperty: Property
|
|
||||||
|
|
||||||
override val hasChanged: Boolean get() = listProperty.hasChanged() ?: false
|
|
||||||
override val guiProperties: List<Property> get() = listOf(listProperty)
|
|
||||||
|
|
||||||
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
|
|
||||||
lang = null
|
|
||||||
val defaults = readDefaults(domain, path)
|
|
||||||
listProperty = target.get(categoryName, "${propertyName}", defaults)
|
|
||||||
listProperty.configEntryClass = NonVerboseArrayEntry::class.java
|
|
||||||
listProperty.languageKey = "$langPrefix.$categoryName.${listProperty.name}"
|
|
||||||
read()
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun convertValue(line: String): VALUE?
|
|
||||||
|
|
||||||
override fun read() {
|
|
||||||
list.clear()
|
|
||||||
listProperty.stringList.forEach { line ->
|
|
||||||
val value = convertValue(line)
|
|
||||||
if (value != null) list.add(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readDefaults(domain: String, path: String): Array<String> {
|
|
||||||
val list = arrayListOf<String>()
|
|
||||||
val defaults = resourceManager[domain, path]?.getLines()
|
|
||||||
defaults?.map { it.trim() }?.filter { !it.startsWith("//") && it.isNotEmpty() }?.forEach { list.add(it) }
|
|
||||||
return list.toTypedArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,8 @@ import java.lang.reflect.Field
|
|||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import mods.octarinecore.metaprog.Namespace.*
|
import mods.octarinecore.metaprog.Namespace.*
|
||||||
import mods.octarinecore.tryDefault
|
import mods.octarinecore.tryDefault
|
||||||
|
import kotlin.reflect.KCallable
|
||||||
|
import kotlin.reflect.KFunction
|
||||||
|
|
||||||
/** Get a Java class with the given name. */
|
/** Get a Java class with the given name. */
|
||||||
fun getJavaClass(name: String) = tryDefault(null) { Class.forName(name) }
|
fun getJavaClass(name: String) = tryDefault(null) { Class.forName(name) }
|
||||||
@@ -43,6 +45,14 @@ fun Any.reflectFieldsOfType(vararg types: Class<*>) = this.javaClass.declaredFie
|
|||||||
.map { field -> field.name to field.let { it.isAccessible = true; it.get(this) } }
|
.map { field -> field.name to field.let { it.isAccessible = true; it.get(this) } }
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
|
|
||||||
|
fun <T> Any.reflectFields(type: Class<T>) = this.javaClass.declaredFields
|
||||||
|
.filter { field -> type.isAssignableFrom(field.type) }
|
||||||
|
.map { field -> field.name to field.let { it.isAccessible = true; it.get(this) as T } }
|
||||||
|
|
||||||
|
fun <T> Any.reflectDelegates(type: Class<T>) = this.javaClass.declaredFields
|
||||||
|
.filter { field -> type.isAssignableFrom(field.type) && field.name.contains("$") }
|
||||||
|
.map { field -> field.name.split("$")[0] to field.let { it.isAccessible = true; it.get(this) } as T }
|
||||||
|
|
||||||
enum class Namespace { MCP, SRG }
|
enum class Namespace { MCP, SRG }
|
||||||
|
|
||||||
abstract class Resolvable<T> {
|
abstract class Resolvable<T> {
|
||||||
@@ -58,19 +68,19 @@ fun allAvailable(vararg codeElement: Resolvable<*>) = codeElement.all { it.eleme
|
|||||||
*
|
*
|
||||||
* @param[name] MCP name of the class
|
* @param[name] MCP name of the class
|
||||||
*/
|
*/
|
||||||
open class ClassRef(val name: String) : Resolvable<Class<*>>() {
|
open class ClassRef<T: Any?>(val name: String) : Resolvable<Class<T>>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val int = ClassRefPrimitive("I", Int::class.java)
|
val int = ClassRefPrimitive("I", Int::class.java)
|
||||||
val long = ClassRefPrimitive("J", Long::class.java)
|
val long = ClassRefPrimitive("J", Long::class.java)
|
||||||
val float = ClassRefPrimitive("F", Float::class.java)
|
val float = ClassRefPrimitive("F", Float::class.java)
|
||||||
val boolean = ClassRefPrimitive("Z", Boolean::class.java)
|
val boolean = ClassRefPrimitive("Z", Boolean::class.java)
|
||||||
val void = ClassRefPrimitive("V", null)
|
val void = ClassRefPrimitive("V", Void::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun asmDescriptor(namespace: Namespace) = "L${name.replace(".", "/")};"
|
open fun asmDescriptor(namespace: Namespace) = "L${name.replace(".", "/")};"
|
||||||
|
|
||||||
override fun resolve() = getJavaClass(name)
|
override fun resolve() = getJavaClass(name) as Class<T>?
|
||||||
|
|
||||||
fun isInstance(obj: Any) = element?.isInstance(obj) ?: false
|
fun isInstance(obj: Any) = element?.isInstance(obj) ?: false
|
||||||
}
|
}
|
||||||
@@ -81,18 +91,11 @@ open class ClassRef(val name: String) : Resolvable<Class<*>>() {
|
|||||||
* @param[name] ASM descriptor of this primitive type
|
* @param[name] ASM descriptor of this primitive type
|
||||||
* @param[clazz] class of this primitive type
|
* @param[clazz] class of this primitive type
|
||||||
*/
|
*/
|
||||||
class ClassRefPrimitive(name: String, val clazz: Class<*>?) : ClassRef(name) {
|
class ClassRefPrimitive<T>(name: String, val clazz: Class<T>?) : ClassRef<T>(name) {
|
||||||
override fun asmDescriptor(namespace: Namespace) = name
|
override fun asmDescriptor(namespace: Namespace) = name
|
||||||
override fun resolve() = clazz
|
override fun resolve() = clazz
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClassRefArray(name: String) : ClassRef(name) {
|
|
||||||
override fun asmDescriptor(namespace: Namespace) = "[" + super.asmDescriptor(namespace)
|
|
||||||
override fun resolve() = getJavaClass("[L$name;")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ClassRef.array() = ClassRefArray(name)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to a method.
|
* Reference to a method.
|
||||||
*
|
*
|
||||||
@@ -102,13 +105,13 @@ fun ClassRef.array() = ClassRefArray(name)
|
|||||||
* @param[returnType] reference to the return type
|
* @param[returnType] reference to the return type
|
||||||
* @param[returnType] references to the argument types
|
* @param[returnType] references to the argument types
|
||||||
*/
|
*/
|
||||||
class MethodRef(val parentClass: ClassRef,
|
class MethodRef<T: Any?>(val parentClass: ClassRef<*>,
|
||||||
val mcpName: String,
|
val mcpName: String,
|
||||||
val srgName: String,
|
val srgName: String,
|
||||||
val returnType: ClassRef,
|
val returnType: ClassRef<T>,
|
||||||
vararg val argTypes: ClassRef
|
vararg val argTypes: ClassRef<*>
|
||||||
) : Resolvable<Method>() {
|
) : Resolvable<Method>() {
|
||||||
constructor(parentClass: ClassRef, mcpName: String, returnType: ClassRef, vararg argTypes: ClassRef) :
|
constructor(parentClass: ClassRef<*>, mcpName: String, returnType: ClassRef<T>, vararg argTypes: ClassRef<*>) :
|
||||||
this(parentClass, mcpName, mcpName, returnType, *argTypes)
|
this(parentClass, mcpName, mcpName, returnType, *argTypes)
|
||||||
|
|
||||||
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
|
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
|
||||||
@@ -125,11 +128,10 @@ class MethodRef(val parentClass: ClassRef,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Invoke this method using reflection. */
|
/** Invoke this method using reflection. */
|
||||||
fun invoke(receiver: Any, vararg args: Any?) = element?.invoke(receiver, *args)
|
operator fun invoke(receiver: Any, vararg args: Any?) = element?.invoke(receiver, *args) as T
|
||||||
|
|
||||||
/** Invoke this static method using reflection. */
|
/** Invoke this static method using reflection. */
|
||||||
fun invokeStatic(vararg args: Any) = element?.invoke(null, *args)
|
fun invokeStatic(vararg args: Any) = element?.invoke(null, *args)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,30 +142,41 @@ class MethodRef(val parentClass: ClassRef,
|
|||||||
* @param[srgName] SRG name of the field
|
* @param[srgName] SRG name of the field
|
||||||
* @param[type] reference to the field type
|
* @param[type] reference to the field type
|
||||||
*/
|
*/
|
||||||
class FieldRef(val parentClass: ClassRef,
|
class FieldRef<T>(val parentClass: ClassRef<*>,
|
||||||
val mcpName: String,
|
val mcpName: String,
|
||||||
val srgName: String,
|
val srgName: String,
|
||||||
val type: ClassRef?
|
val type: ClassRef<T>
|
||||||
) : Resolvable<Field>() {
|
) : Resolvable<Field>() {
|
||||||
constructor(parentClass: ClassRef, mcpName: String, type: ClassRef?) : this(parentClass, mcpName, mcpName, type)
|
constructor(parentClass: ClassRef<*>, mcpName: String, type: ClassRef<T>) : this(parentClass, mcpName, mcpName, type)
|
||||||
|
|
||||||
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
|
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
|
||||||
fun asmDescriptor(namespace: Namespace) = type!!.asmDescriptor(namespace)
|
fun asmDescriptor(namespace: Namespace) = type.asmDescriptor(namespace)
|
||||||
|
|
||||||
override fun resolve(): Field? =
|
override fun resolve(): Field? =
|
||||||
if (parentClass.element == null) null
|
if (parentClass.element == null) null
|
||||||
else {
|
else {
|
||||||
listOf(srgName, mcpName).map { tryDefault(null) {
|
listOf(srgName, mcpName).mapNotNull { tryDefault(null) {
|
||||||
parentClass.element!!.getDeclaredField(it)
|
parentClass.element!!.getDeclaredField(it)
|
||||||
}}.filterNotNull().firstOrNull()
|
} }.firstOrNull()
|
||||||
?.apply{ isAccessible = true }
|
?.apply{ isAccessible = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get this field using reflection. */
|
/** Get this field using reflection. */
|
||||||
fun get(receiver: Any?) = element?.get(receiver)
|
operator fun get(receiver: Any?) = element?.get(receiver) as T
|
||||||
|
|
||||||
/** Get this static field using reflection. */
|
/** Get this static field using reflection. */
|
||||||
fun getStatic() = get(null)
|
fun getStatic() = get(null)
|
||||||
|
|
||||||
fun set(receiver: Any?, obj: Any?) { element?.set(receiver, obj) }
|
fun set(receiver: Any?, obj: Any?) { element?.set(receiver, obj) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Any.isInstance(cls: ClassRef<*>) = cls.isInstance(this)
|
||||||
|
interface ReflectionCallable<T> {
|
||||||
|
operator fun invoke(vararg args: Any): T
|
||||||
|
}
|
||||||
|
inline operator fun <reified T> Any.get(field: FieldRef<T>) = field.get(this)
|
||||||
|
inline operator fun <reified T> Any.set(field: FieldRef<T>, value: T) = field.set(this, value)
|
||||||
|
inline operator fun <T> Any.get(methodRef: MethodRef<T>) = object : ReflectionCallable<T> {
|
||||||
|
override fun invoke(vararg args: Any) = methodRef.invoke(this@get, args)
|
||||||
}
|
}
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
package mods.octarinecore.metaprog
|
|
||||||
|
|
||||||
import mods.octarinecore.metaprog.Namespace.*
|
|
||||||
import net.minecraft.launchwrapper.IClassTransformer
|
|
||||||
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin
|
|
||||||
import org.apache.logging.log4j.LogManager
|
|
||||||
import org.objectweb.asm.ClassReader
|
|
||||||
import org.objectweb.asm.ClassWriter
|
|
||||||
import org.objectweb.asm.Opcodes
|
|
||||||
import org.objectweb.asm.tree.*
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for convenient bytecode transformers.
|
|
||||||
*/
|
|
||||||
open class Transformer : IClassTransformer {
|
|
||||||
|
|
||||||
val log = LogManager.getLogger(this)
|
|
||||||
|
|
||||||
/** The list of transformers and targets. */
|
|
||||||
var methodTransformers: MutableList<Pair<MethodRef, MethodTransformContext.()->Unit>> = arrayListOf()
|
|
||||||
|
|
||||||
/** Add a transformation to perform. Call this during instance initialization.
|
|
||||||
*
|
|
||||||
* @param[method] the target method of the transformation
|
|
||||||
* @param[trans] method transformation lambda
|
|
||||||
*/
|
|
||||||
fun transformMethod(method: MethodRef, trans: MethodTransformContext.()->Unit) = methodTransformers.add(method to trans)
|
|
||||||
|
|
||||||
override fun transform(name: String?, transformedName: String?, classData: ByteArray?): ByteArray? {
|
|
||||||
if (classData == null) return null
|
|
||||||
val classNode = ClassNode().apply { val reader = ClassReader(classData); reader.accept(this, 0) }
|
|
||||||
var workDone = false
|
|
||||||
var writerFlags = 0
|
|
||||||
|
|
||||||
synchronized(this) {
|
|
||||||
methodTransformers.forEach { (targetMethod, transform) ->
|
|
||||||
if (transformedName != targetMethod.parentClass.name) return@forEach
|
|
||||||
|
|
||||||
for (method in classNode.methods) {
|
|
||||||
val namespace = Namespace.values().find {
|
|
||||||
method.name == targetMethod.name(it) && method.desc == targetMethod.asmDescriptor(it)
|
|
||||||
} ?: continue
|
|
||||||
when (namespace) {
|
|
||||||
MCP -> log.info("Found method ${targetMethod.parentClass.name}.${targetMethod.name(MCP)} ${targetMethod.asmDescriptor(MCP)}")
|
|
||||||
SRG -> log.info("Found method ${targetMethod.parentClass.name}.${targetMethod.name(namespace)} ${targetMethod.asmDescriptor(namespace)} (matching ${targetMethod.name(MCP)})")
|
|
||||||
}
|
|
||||||
|
|
||||||
// write input bytecode for debugging - definitely not in production...
|
|
||||||
//File("BF_debug").mkdir()
|
|
||||||
//FileOutputStream(File("BF_debug/$transformedName.class")).apply {
|
|
||||||
// write(classData)
|
|
||||||
// close()
|
|
||||||
//}
|
|
||||||
|
|
||||||
// transform
|
|
||||||
writerFlags = MethodTransformContext(method, namespace, writerFlags).apply(transform).writerFlags
|
|
||||||
workDone = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (!workDone) classData else ClassWriter(writerFlags).apply { classNode.accept(this) }.toByteArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows builder-style declarative definition of transformations. Transformation lambdas are extension
|
|
||||||
* methods on this class.
|
|
||||||
*
|
|
||||||
* @param[method] the [MethodNode] currently being transformed
|
|
||||||
* @param[environment] the type of environment we are in
|
|
||||||
*/
|
|
||||||
class MethodTransformContext(val method: MethodNode, val environment: Namespace, var writerFlags: Int) {
|
|
||||||
|
|
||||||
fun applyWriterFlags(vararg flagValue: Int) { flagValue.forEach { writerFlags = writerFlags or it } }
|
|
||||||
|
|
||||||
fun makePublic() {
|
|
||||||
method.access = (method.access or Opcodes.ACC_PUBLIC) and (Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED).inv()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the first instruction that matches a predicate.
|
|
||||||
*
|
|
||||||
* @param[start] the instruction node to start iterating from
|
|
||||||
* @param[predicate] the predicate to check
|
|
||||||
*/
|
|
||||||
fun find(start: AbstractInsnNode, predicate: (AbstractInsnNode) -> Boolean): AbstractInsnNode? {
|
|
||||||
var current: AbstractInsnNode? = start
|
|
||||||
while (current != null && !predicate(current)) current = current.next
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Find the first instruction in the current [MethodNode] that matches a predicate. */
|
|
||||||
fun find(predicate: (AbstractInsnNode)->Boolean): AbstractInsnNode? = find(method.instructions.first, predicate)
|
|
||||||
|
|
||||||
/** Find the first instruction in the current [MethodNode] with the given opcode. */
|
|
||||||
fun find(opcode: Int) = find { it.opcode == opcode }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert new instructions after this one.
|
|
||||||
*
|
|
||||||
* @param[init] builder-style lambda to assemble instruction list
|
|
||||||
*/
|
|
||||||
fun AbstractInsnNode.insertAfter(init: InstructionList.()->Unit) = InstructionList(environment).apply{
|
|
||||||
this.init(); list.reversed().forEach { method.instructions.insert(this@insertAfter, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert new instructions before this one.
|
|
||||||
*
|
|
||||||
* @param[init] builder-style lambda to assemble instruction list
|
|
||||||
*/
|
|
||||||
fun AbstractInsnNode.insertBefore(init: InstructionList.()->Unit) = InstructionList(environment).apply{
|
|
||||||
val insertBeforeNode = this@insertBefore //.let { if (it.previous is FrameNode) it.previous else it }
|
|
||||||
this.init(); list.forEach { method.instructions.insertBefore(insertBeforeNode, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun AbstractInsnNode.replace(init: InstructionList.()->Unit) = InstructionList(environment).apply {
|
|
||||||
insertAfter(init)
|
|
||||||
method.instructions.remove(this@replace)
|
|
||||||
}
|
|
||||||
/** Remove all isntructiuons between the given two (inclusive). */
|
|
||||||
fun Pair<AbstractInsnNode, AbstractInsnNode>.remove() {
|
|
||||||
var current: AbstractInsnNode? = first
|
|
||||||
while (current != null && current != second) {
|
|
||||||
val next = current.next
|
|
||||||
method.instructions.remove(current)
|
|
||||||
current = next
|
|
||||||
}
|
|
||||||
if (current != null) method.instructions.remove(current)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace all isntructiuons between the given two (inclusive) with the specified instruction list.
|
|
||||||
*
|
|
||||||
* @param[init] builder-style lambda to assemble instruction list
|
|
||||||
*/
|
|
||||||
fun Pair<AbstractInsnNode, AbstractInsnNode>.replace(init: InstructionList.()->Unit) {
|
|
||||||
val beforeInsn = first.previous
|
|
||||||
remove()
|
|
||||||
beforeInsn.insertAfter(init)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Matches variable instructions.
|
|
||||||
*
|
|
||||||
* @param[opcode] instruction opcode
|
|
||||||
* @param[idx] variable the opcode references
|
|
||||||
*/
|
|
||||||
fun varinsn(opcode: Int, idx: Int): (AbstractInsnNode)->Boolean = { insn ->
|
|
||||||
insn.opcode == opcode && insn is VarInsnNode && insn.`var` == idx
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invokeName(name: String): (AbstractInsnNode)->Boolean = { insn ->
|
|
||||||
(insn as? MethodInsnNode)?.name == name
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invokeRef(ref: MethodRef): (AbstractInsnNode)->Boolean = { insn ->
|
|
||||||
(insn as? MethodInsnNode)?.let {
|
|
||||||
it.name == ref.name(environment) && it.owner == ref.parentClass.name.replace(".", "/")
|
|
||||||
} ?: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows builder-style declarative definition of instruction lists.
|
|
||||||
*
|
|
||||||
* @param[environment] the type of environment we are in
|
|
||||||
*/
|
|
||||||
class InstructionList(val environment: Namespace) {
|
|
||||||
|
|
||||||
fun insn(opcode: Int) = list.add(InsnNode(opcode))
|
|
||||||
|
|
||||||
/** The instruction list being assembled. */
|
|
||||||
val list: MutableList<AbstractInsnNode> = arrayListOf()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a variable instruction.
|
|
||||||
*
|
|
||||||
* @param[opcode] instruction opcode
|
|
||||||
* @param[idx] variable the opcode references
|
|
||||||
*/
|
|
||||||
fun varinsn(opcode: Int, idx: Int) = list.add(VarInsnNode(opcode, idx))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an INVOKESTATIC instruction.
|
|
||||||
*
|
|
||||||
* @param[target] the target method of the instruction
|
|
||||||
* @param[isInterface] true if the target method is defined by an interface
|
|
||||||
*/
|
|
||||||
fun invokeStatic(target: MethodRef, isInterface: Boolean = false) = list.add(MethodInsnNode(
|
|
||||||
Opcodes.INVOKESTATIC,
|
|
||||||
target.parentClass.name.replace(".", "/"),
|
|
||||||
target.name(environment),
|
|
||||||
target.asmDescriptor(environment),
|
|
||||||
isInterface
|
|
||||||
))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a GETFIELD instruction.
|
|
||||||
*
|
|
||||||
* @param[target] the target field of the instruction
|
|
||||||
*/
|
|
||||||
fun getField(target: FieldRef) = list.add(FieldInsnNode(
|
|
||||||
Opcodes.GETFIELD,
|
|
||||||
target.parentClass.name.replace(".", "/"),
|
|
||||||
target.name(environment),
|
|
||||||
target.asmDescriptor(environment)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace
|
|
||||||
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178206_b # vertexColorMultiplier
|
|
||||||
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178207_c # vertexBrightness
|
|
||||||
|
|
||||||
public net.minecraft.client.renderer.block.model.ModelBakery field_177610_k # blockModelShapes
|
|
||||||
|
|
||||||
public net.minecraft.client.renderer.block.statemap.BlockStateMapper field_178450_a # blockStateMap
|
|
||||||
|
|
||||||
public net.minecraft.client.renderer.BufferBuilder field_178999_b # rawIntBuffer
|
|
||||||
public net.minecraft.client.renderer.BufferBuilder func_181670_b(I)V # growBuffer
|
|
||||||
public net.minecraft.client.renderer.BufferBuilder func_181664_j()I # getBufferSize
|
|
||||||
|
|
||||||
public net.minecraft.client.renderer.BlockModelRenderer field_187499_a # blockColors
|
|
||||||
|
|
||||||
public net.minecraft.world.ChunkCache field_72815_e # world
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user