[WIP] 1.14.4 port

This commit is contained in:
octarine-noise
2020-01-01 16:57:47 +01:00
parent 1ea2b6b946
commit 46cbe64328
164 changed files with 1715 additions and 2115 deletions

View File

@@ -1,62 +1,99 @@
apply plugin: "net.minecraftforge.gradle.forge"
apply plugin: 'kotlin'
archivesBaseName = jarName
buildscript {
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven {
name = "forge"
url = "http://files.minecraftforge.net/maven"
}
maven {
name = 'sponge'
url = 'https://repo.spongepowered.org/maven'
}
}
dependencies {
classpath "net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT"
classpath(group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true) {
exclude group: 'trove', module: 'trove'
}
classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: "net.minecraftforge.gradle"
apply plugin: 'org.spongepowered.mixin'
apply plugin: 'kotlin'
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8'
repositories {
mavenCentral()
jcenter()
maven {
name = "shadowfacts"
url = "https://maven.shadowfacts.net/"
name = "forge"
url = "http://files.minecraftforge.net/maven"
}
maven {
name = 'Curse'
url = 'https://minecraft.curseforge.com/api/maven/'
}
maven {
name = 'sponge'
url = 'https://repo.spongepowered.org/maven'
}
}
dependencies {
compile "net.shadowfacts:Forgelin:$forgelin_version"
minecraft "net.minecraftforge:forge:$mc_version-$forge_version"
implementation "kottle:Kottle:$kottle_version"
implementation "org.spongepowered:mixin:0.8-SNAPSHOT"
}
mixin {
add sourceSets.main, "betterfoliage.refmap.json"
}
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 }
mappings channel: "$mcp_mappings_channel", version: "$mcp_mappings_version"
accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
into "${buildDir}/classes/main"
runs {
client {
workingDirectory project.file('run')
property 'forge.logging.markers', 'CORE'
property 'forge.logging.console.level', 'debug'
mods {
betterfoliage {
source sourceSets.main
}
}
}
server {
workingDirectory project.file('run')
property 'forge.logging.markers', 'CORE'
property 'forge.logging.console.level', 'debug'
mods {
betterfoliage {
source sourceSets.main
}
}
}
}
}
def manifestCfg = {
attributes "FMLCorePlugin": "mods.betterfoliage.loader.BetterFoliageLoader"
attributes "FMLCorePluginContainsFMLMod": "mods.betterfoliage.BetterFoliageMod"
attributes "FMLAT": "BetterFoliage_at.cfg"
compileKotlin {
kotlinOptions {
freeCompilerArgs += ("-Xno-param-assertions")
freeCompilerArgs += ("-Xno-call-assertions")
}
}
jar {
manifest manifestCfg
exclude "optifine"
}
archiveName = "BetterFoliage-${version}-Forge-${mc_version}.jar"
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 }
manifest {
from "src/main/resources/META-INF/MANIFEST.MF"
attributes "Implementation-Version": "${version}"
}
}

View File

@@ -1,11 +1,15 @@
org.gradle.jvmargs=-Xmx2G
group = com.github.octarine-noise
jarName = BetterFoliage-MC1.12
jarName = BetterFoliage-Forge
version = 2.3.0
mc_version = 1.12.2
forge_version = 14.23.5.2847
mcp_mappings = stable_39
mc_version = 1.14.4
forge_version = 28.1.109
mcp_mappings_channel = snapshot
mcp_mappings_version = 20190719-1.14.3
kotlin_version = 1.3.40
forgelin_version = 1.8.4
kotlin_version = 1.3.61
forgelin_version = 1.8.4
kottle_version = 1.4.0

View File

@@ -0,0 +1,20 @@
package mods.betterfoliage;
import net.minecraftforge.fml.ModLoader;
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");
}
}
}

View File

@@ -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;
}
}

View 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 BlockMixin {
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);
}
}

View File

@@ -0,0 +1,26 @@
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)
public class BlockStateMixin {
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);
}
}

View File

@@ -0,0 +1,29 @@
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.BlockRenderLayer;
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 ChunkRenderMixin {
private static final String rebuildChunk = "Lnet/minecraft/client/renderer/chunk/ChunkRender;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());
}
}

View File

@@ -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 ChunkRenderOptifineMixin {
private static final String rebuildChunk = "Lnet/minecraft/client/renderer/chunk/ChunkRender;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]);
}
}

View File

@@ -0,0 +1,29 @@
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.BlockRenderLayer;
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 ChunkRenderVanillaMixin {
private static final String rebuildChunk = "Lnet/minecraft/client/renderer/chunk/ChunkRender;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);
}
}

View 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 ClientWorldMixin {
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);
}
}

View File

@@ -0,0 +1,21 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.client.Hooks;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.profiler.IProfiler;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ModelBakery.class)
public class ModelBakeryMixin {
private static final String processLoading = "Lnet/minecraft/client/renderer/model/ModelBakery;processLoading(Lnet/minecraft/profiler/IProfiler;)V";
private static final String endStartSection = "Lnet/minecraft/profiler/IProfiler;endStartSection(Ljava/lang/String;)V";
@Inject(method = processLoading, at = @At(value = "INVOKE_STRING", target = endStartSection, args = "ldc=stitching"))
void preStitchTextures(IProfiler profiler, CallbackInfo ci) {
Hooks.onLoadModelDefinitions(this);
}
}

View File

@@ -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();
}
}

View File

@@ -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];
}
}

View File

@@ -0,0 +1,57 @@
package mods.betterfoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.isAfterPostInit
import mods.octarinecore.client.resource.GeneratorPack
import net.alexwells.kottle.FMLKotlinModLoadingContext
import net.minecraft.client.Minecraft
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.ModLoadingContext
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent
import org.apache.logging.log4j.Level.DEBUG
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil
import java.io.File
import java.io.PrintStream
import java.util.*
@Mod(BetterFoliage.MOD_ID)
object BetterFoliage {
const val MOD_ID = "betterfoliage"
const val MOD_NAME = "Better Foliage"
val modBus = FMLKotlinModLoadingContext.get().modEventBus
var log = LogManager.getLogger("BetterFoliage")
var logDetail = SimpleLogger(
"BetterFoliage",
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 genPack = GeneratorPack(
"bf_gen",
"Better Foliage generated assets",
"bf_generated_pack.png"
)
init {
log.log(DEBUG, "Constructing mod")
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.build())
Minecraft.getInstance().resourcePackList.addPackFinder(genPack.packFinder)
Client.init()
}
}

View File

@@ -1,78 +0,0 @@
package mods.betterfoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.isAfterPostInit
import net.minecraftforge.common.config.Configuration
import net.minecraftforge.fml.common.FMLCommonHandler
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent
import net.minecraftforge.fml.common.network.NetworkCheckHandler
import net.minecraftforge.fml.relauncher.Side
import org.apache.logging.log4j.Level.DEBUG
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil
import java.io.File
import java.io.PrintStream
import java.util.*
@Mod(
modid = BetterFoliageMod.MOD_ID,
name = BetterFoliageMod.MOD_NAME,
acceptedMinecraftVersions = BetterFoliageMod.MC_VERSIONS,
guiFactory = BetterFoliageMod.GUI_FACTORY,
dependencies = "after:forgelin",
modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter",
clientSideOnly = true
)
object BetterFoliageMod {
const val MOD_ID = "betterfoliage"
const val MOD_NAME = "Better Foliage"
const val DOMAIN = "betterfoliage"
const val LEGACY_DOMAIN = "bettergrassandleaves"
const val MC_VERSIONS = "[1.12]"
const val GUI_FACTORY = "mods.betterfoliage.client.gui.ConfigGuiFactory"
lateinit var log: Logger
lateinit var logDetail: Logger
var config: Configuration? = null
init {
// inject pack into default list at construction time to get domains enumerated
// there's no 2nd resource reload pass anymore
Client.generatorPack.inject()
}
@Mod.EventHandler
fun preInit(event: FMLPreInitializationEvent) {
log = event.modLog
val logDetailFile = File(event.modConfigurationDirectory.parentFile, "logs/betterfoliage.log").apply {
parentFile.mkdirs()
if (!exists()) createNewFile()
}
logDetail = SimpleLogger(
"BetterFoliage",
DEBUG,
false, false, true, false,
"yyyy-MM-dd HH:mm:ss",
null,
PropertiesUtil(Properties()),
PrintStream(logDetailFile)
)
config = Configuration(event.suggestedConfigurationFile, null, true)
Config.attach(config!!)
Client.init()
Client.log(INFO, "BetterFoliage initialized")
isAfterPostInit = true
}
/** Mod is cosmetic only, always allow connection. */
@NetworkCheckHandler
fun checkVersion(mods: Map<String, String>, side: Side) = true
}

View File

@@ -1,53 +1,48 @@
package mods.betterfoliage.client
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.gui.ConfigGuiFactory
import mods.betterfoliage.client.integration.*
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.integration.ForestryIntegration
import mods.betterfoliage.client.integration.IC2RubberIntegration
import mods.betterfoliage.client.integration.OptifineCustomColors
import mods.betterfoliage.client.integration.TechRebornRubberIntegration
import mods.betterfoliage.client.render.*
import mods.betterfoliage.client.texture.*
import mods.octarinecore.client.KeyHandler
import mods.octarinecore.client.gui.textComponent
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.resource.CenteringTextureGenerator
import mods.octarinecore.client.resource.GeneratorPack
import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState
import mods.octarinecore.client.resource.IConfigChangeListener
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.util.math.BlockPos
import net.minecraft.util.text.TextComponentTranslation
import net.minecraft.util.text.TextFormatting
import net.minecraftforge.fml.client.FMLClientHandler
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraft.util.text.TranslationTextComponent
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.registries.ForgeRegistries
import org.apache.logging.log4j.Level
/**
* Object responsible for initializing (and holding a reference to) all the infrastructure of the mod
* except for the call hooks.
*
* 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 {
var renderers= emptyList<AbstractBlockRenderingHandler>()
var configListeners = emptyList<IConfigChangeListener>()
lateinit var renderers: List<AbstractBlockRenderingHandler>
val suppressRenderErrors = mutableSetOf<IBlockState>()
val suppressRenderErrors = mutableSetOf<BlockState>()
// texture generation stuff
// texture generators
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() {
// add resource generators to pack
listOf(genGrass, genLeaves, genReeds).forEach { BetterFoliage.genPack.generators.add(it) }
// init renderers
renderers = listOf(
RenderGrass(),
@@ -66,6 +61,7 @@ object Client {
// init other singletons
val singletons = listOf(
BlockConfig,
StandardCactusRegistry,
LeafParticleRegistry,
ChunkOverlayManager,
@@ -75,7 +71,7 @@ object Client {
// init mod integrations
val integrations = listOf(
ShadersModIntegration,
// ShadersModIntegration,
OptifineCustomColors,
ForestryIntegration,
IC2RubberIntegration,
@@ -87,30 +83,26 @@ object Client {
LeafRegistry.addRegistry(StandardLeafRegistry)
LogRegistry.addRegistry(StandardLogRegistry)
// init config hotkey
val configKey = KeyHandler(BetterFoliageMod.MOD_NAME, 66, "key.betterfoliage.gui") {
FMLClientHandler.instance().showGuiScreen(
ConfigGuiFactory.createBFConfigGui(Minecraft.getMinecraft().currentScreen)
)
}
configListeners = listOf(renderers, singletons, integrations).flatten().filterIsInstance<IConfigChangeListener>()
configListeners.forEach { it.onConfigChange() }
}
fun log(level: Level, msg: String) {
BetterFoliageMod.log.log(level, "[BetterFoliage] $msg")
BetterFoliageMod.logDetail.log(level, msg)
BetterFoliage.log.log(level, "[BetterFoliage] $msg")
BetterFoliage.logDetail.log(level, msg)
}
fun logDetail(msg: String) {
BetterFoliageMod.logDetail.log(Level.DEBUG, msg)
BetterFoliage.logDetail.log(Level.DEBUG, msg)
}
fun logRenderError(state: IBlockState, location: BlockPos) {
fun logRenderError(state: BlockState, location: BlockPos) {
if (state in suppressRenderErrors) return
suppressRenderErrors.add(state)
val blockName = Block.REGISTRY.getNameForObject(state.block).toString()
val blockName = ForgeRegistries.BLOCKS.getKey(state.block).toString()
val blockLoc = "${location.x},${location.y},${location.z}"
Minecraft.getMinecraft().ingameGUI.chatGUI.printChatMessage(TextComponentTranslation(
Minecraft.getInstance().ingameGUI.chatGUI.printChatMessage(TranslationTextComponent(
"betterfoliage.rendererror",
textComponent(blockName, TextFormatting.GOLD),
textComponent(blockLoc, TextFormatting.GOLD)

View File

@@ -1,7 +1,9 @@
@file:JvmName("Hooks")
@file:SideOnly(Side.CLIENT)
package mods.betterfoliage.client
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.*
import mods.betterfoliage.loader.Refs
@@ -10,46 +12,43 @@ import mods.octarinecore.client.resource.LoadModelDataEvent
import mods.octarinecore.common.plus
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.init.Blocks
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.BlockRenderLayer.CUTOUT
import net.minecraft.util.BlockRenderLayer.CUTOUT_MIPPED
import net.minecraft.util.EnumFacing
import net.minecraft.util.Direction
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.minecraftforge.client.model.ModelLoader
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraftforge.client.model.data.IModelData
import java.util.*
var isAfterPostInit = false
val isOptifinePresent = allAvailable(Refs.OptifineClassTransformer)
fun doesSideBlockRenderingOverride(original: Boolean, blockAccess: IBlockAccess, pos: BlockPos, side: EnumFacing): Boolean {
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(blockAccess.getBlockState(pos).block));
fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float {
if (Config.enabled && Config.roundLogs.enabled && BlockConfig.logBlocks.matchesClass(state.block)) return Config.roundLogs.dimming.toFloat();
return original
}
fun isOpaqueCubeOverride(original: Boolean, state: IBlockState): Boolean {
// caution: blocks are initialized and the method called during startup
if (!isAfterPostInit) return original
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block))
fun getUseNeighborBrightnessOverride(original: Boolean, state: BlockState): Boolean {
return original || (Config.enabled && Config.roundLogs.enabled && BlockConfig.logBlocks.matchesClass(state.block));
}
fun getAmbientOcclusionLightValueOverride(original: Float, state: IBlockState): Float {
if (Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block)) return Config.roundLogs.dimming;
return original;
fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: BlockState, newState: BlockState, flags: Int) {
ChunkOverlayManager.onBlockChange(worldClient, pos)
}
fun getUseNeighborBrightnessOverride(original: Boolean, state: IBlockState): Boolean {
return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block));
}
fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) {
fun onRandomDisplayTick(block: Block, state: BlockState, world: World, pos: BlockPos, random: Random) {
if (Config.enabled &&
Config.risingSoul.enabled &&
state.block == Blocks.SOUL_SAND &&
@@ -60,42 +59,52 @@ fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) {
if (Config.enabled &&
Config.fallingLeaves.enabled &&
Config.blocks.leavesClasses.matchesClass(state.block) &&
BlockConfig.leafBlocks.matchesClass(state.block) &&
world.isAirBlock(pos + down1) &&
Math.random() < Config.fallingLeaves.chance) {
EntityFallingLeavesFX(world, pos).addIfValid()
}
}
fun onAfterLoadModelDefinitions(loader: ModelLoader) {
MinecraftForge.EVENT_BUS.post(LoadModelDataEvent(loader))
fun onLoadModelDefinitions(bakery: Any) {
BetterFoliage.modBus.post(LoadModelDataEvent(bakery as ModelBakery))
}
fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos, dir: Direction): VoxelShape {
if (LogRegistry[state, reader, pos] != null) return VoxelShapes.empty()
return state.func_215702_a(reader, pos, dir)
}
fun renderWorldBlock(dispatcher: BlockRendererDispatcher,
state: IBlockState,
state: BlockState,
pos: BlockPos,
blockAccess: IBlockAccess,
worldRenderer: BufferBuilder,
reader: IEnviromentBlockReader,
buffer: BufferBuilder,
random: Random,
modelData: IModelData,
layer: BlockRenderLayer
): Boolean {
val doBaseRender = state.canRenderInLayer(layer) || (layer == targetCutoutLayer && state.canRenderInLayer(otherCutoutLayer))
blockContext.let { ctx ->
ctx.set(blockAccess, pos)
ctx.set(reader, pos)
Client.renderers.forEach { renderer ->
if (renderer.isEligible(ctx)) {
// render on the block's default layer
// also render on the cutout layer if the renderer requires it
if (doBaseRender || (renderer.addToCutout && layer == targetCutoutLayer)) {
return renderer.render(ctx, dispatcher, worldRenderer, layer)
return renderer.render(ctx, dispatcher, buffer, random, modelData, layer)
}
}
}
}
return if (doBaseRender) dispatcher.renderBlock(state, pos, blockAccess, worldRenderer) else false
return if (doBaseRender) dispatcher.renderBlock(state, pos, reader, buffer, random, modelData) else false
}
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
val otherCutoutLayer: BlockRenderLayer get() = if (Minecraft.getMinecraft().gameSettings.mipmapLevels > 0) CUTOUT else CUTOUT_MIPPED
fun canRenderInLayerOverrideOptifine(state: BlockState, optifineReflector: Any?, layerArray: Array<Any>) =
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

View File

@@ -1,56 +1,66 @@
package mods.betterfoliage.client.chunk
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import net.minecraft.block.state.IBlockState
import net.minecraft.client.multiplayer.WorldClient
import net.minecraft.entity.Entity
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.util.SoundCategory
import net.minecraft.util.SoundEvent
import mods.betterfoliage.loader.Refs
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.ChunkPos
import net.minecraft.world.IBlockAccess
import net.minecraft.world.IWorldEventListener
import net.minecraft.world.World
import net.minecraft.world.chunk.EmptyChunk
import net.minecraft.world.IBlockReader
import net.minecraft.world.IEnviromentBlockReader
import net.minecraft.world.IWorldReader
import net.minecraft.world.dimension.DimensionType
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.world.ChunkEvent
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import org.apache.logging.log4j.Level
import java.util.*
val IEnviromentBlockReader.dimType: DimensionType get() = when {
this is IWorldReader -> dimension.type
this is ChunkRenderCache -> world.dimension.type
Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkRenderCache).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
*/
interface ChunkOverlayLayer<T> {
abstract fun calculate(world: IBlockAccess, pos: BlockPos): T
abstract fun onBlockUpdate(world: IBlockAccess, pos: BlockPos)
abstract fun calculate(reader: IEnviromentBlockReader, pos: BlockPos): T
abstract fun onBlockUpdate(reader: IEnviromentBlockReader, pos: BlockPos)
}
/**
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
*/
object ChunkOverlayManager : IBlockUpdateListener {
object ChunkOverlayManager {
var tempCounter = 0
init {
Client.log(Level.INFO, "Initializing client overlay manager")
MinecraftForge.EVENT_BUS.register(this)
}
val chunkData = mutableMapOf<ChunkPos, ChunkOverlayData>()
val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>()
val layers = mutableListOf<ChunkOverlayLayer<*>>()
/**
* Get the overlay data for a given layer and position
*
* @param layer Overlay layer to query
* @param 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
*/
fun <T> get(layer: ChunkOverlayLayer<T>, world: IBlockAccess, pos: BlockPos): T? {
val data = chunkData[ChunkPos(pos)] ?: return null
fun <T> get(layer: ChunkOverlayLayer<T>, reader: IEnviromentBlockReader, pos: BlockPos): T? {
val data = chunkData[reader.dimType]?.get(ChunkPos(pos)) ?: return null
data.get(layer, pos).let { value ->
if (value !== ChunkOverlayData.UNCALCULATED) return value
val newValue = layer.calculate(world, pos)
val newValue = layer.calculate(reader, pos)
data.set(layer, pos, newValue)
return newValue
}
@@ -62,32 +72,39 @@ object ChunkOverlayManager : IBlockUpdateListener {
* @param layer Overlay layer to clear
* @param pos Block position
*/
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) {
chunkData[ChunkPos(pos)]?.clear(layer, pos)
fun <T> clear(dimension: DimensionType, layer: ChunkOverlayLayer<T>, pos: BlockPos) {
chunkData[dimension]?.get(ChunkPos(pos))?.clear(layer, pos)
}
override fun notifyBlockUpdate(world: World, pos: BlockPos, oldState: IBlockState, newState: IBlockState, flags: Int) {
if (chunkData.containsKey(ChunkPos(pos))) layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
}
@SubscribeEvent
fun handleLoadWorld(event: WorldEvent.Load) {
if (event.world is WorldClient) {
event.world.addEventListener(this)
fun onBlockChange(world: ClientWorld, pos: BlockPos) {
if (chunkData[world.dimType]?.containsKey(ChunkPos(pos)) == true) {
layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
}
}
@SubscribeEvent
fun handleLoadChunk(event: ChunkEvent.Load) {
if (event.world is WorldClient && event.chunk !is EmptyChunk) {
chunkData[event.chunk.pos] = ChunkOverlayData(layers)
fun handleLoadWorld(event: WorldEvent.Load) = (event.world as? ClientWorld)?.let { world ->
BetterFoliage.log.debug("Unloaded world: id=${world.dimType.id} name=${world.dimType.registryName}")
chunkData[world.dimType] = mutableMapOf()
}
@SubscribeEvent
fun handleUnloadWorld(event: WorldEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
BetterFoliage.log.debug("Unloaded world: id=${world.dimType.id} name=${world.dimType.registryName}")
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
fun handleUnloadChunk(event: ChunkEvent.Unload) {
if (event.world is WorldClient) {
chunkData.remove(event.chunk.pos)
}
fun handleUnloadChunk(event: ChunkEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
chunkData[world.dimType]?.remove(event.chunk.pos)
}
}
@@ -107,6 +124,7 @@ class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
* 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) {}
@@ -119,4 +137,8 @@ interface IBlockUpdateListener : IWorldEventListener {
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) {}
}
override fun addParticle(p0: IParticleData, p1: Boolean, p2: Double, p3: Double, p4: Double, p5: Double, p6: Double, p7: Double) {}
override fun addParticle(p0: IParticleData, p1: Boolean, p2: Boolean, p3: Double, p4: Double, p5: Double, p6: Double, p7: Double, p8: Double) {}
}
*/

View File

@@ -1,18 +1,16 @@
package mods.betterfoliage.client.config
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.gui.BiomeListConfigEntry
import mods.betterfoliage.BetterFoliage
import mods.octarinecore.client.resource.LoadModelDataEvent
import mods.octarinecore.common.config.*
import net.minecraft.client.Minecraft
import net.minecraft.world.biome.Biome
import net.minecraftforge.fml.client.event.ConfigChangedEvent
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.lwjgl.opengl.GL11
import net.minecraft.util.ResourceLocation
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.eventbus.api.SubscribeEvent
// BetterFoliage-specific property delegates
private val OBSOLETE = ObsoleteConfigProperty()
//private val OBSOLETE = ObsoleteConfigProperty()
private fun featureEnable() = boolean(true).lang("enabled")
/*
fun biomeList(defaults: (Biome) -> Boolean) = intList {
Biome.REGISTRY
.filter { it != null && defaults(it) }
@@ -24,14 +22,15 @@ fun biomeList(defaults: (Biome) -> Boolean) = intList {
private fun Biome.filterTemp(min: Float?, max: Float?) = (min == null || min <= defaultTemperature) && (max == null || max >= defaultTemperature)
private fun Biome.filterRain(min: Float?, max: Float?) = (min == null || min <= rainfall) && (max == null || max >= rainfall)
private fun Biome.filterClass(vararg name: String) = name.any { it in this.javaClass.name.toLowerCase() }
*/
// Config singleton
@SideOnly(Side.CLIENT)
object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAIN) {
object Config : DelegatingConfig(BetterFoliage.MOD_ID, BetterFoliage.MOD_ID) {
var enabled by boolean(true)
var nVidia by boolean(GL11.glGetString(GL11.GL_VENDOR).toLowerCase().contains("nvidia"))
val enabled by boolean(true)
val nVidia by boolean(false)
/*
object blocks {
val leavesClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "leaves_blocks_default.cfg")
val leavesModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "leaves_models_default.cfg", 1)
@@ -54,8 +53,9 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
val logsWhitelist = OBSOLETE
val logsBlacklist = OBSOLETE
}
*/
object leaves {
object leaves : ConfigCategory() {
val enabled by featureEnable()
val snowEnabled by boolean(true)
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
@@ -65,7 +65,7 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
val hideInternal by boolean(true)
}
object shortGrass {
object shortGrass : ConfigCategory(){
val grassEnabled by boolean(true)
val myceliumEnabled by boolean(true)
val snowEnabled by boolean(true)
@@ -79,23 +79,16 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
val saturationThreshold by double(default=0.1)
}
// object hangingGrass {
// var enabled by featureEnable()
// var distance by distanceLimit()
// var size by double(min=0.25, max=1.5, default=0.75).lang("size")
// var separation by double(max=0.5, default=0.25)
// }
object connectedGrass {
object connectedGrass : ConfigCategory(){
val enabled by boolean(true)
val snowEnabled by boolean(false)
}
object roundLogs {
object roundLogs : ConfigCategory(){
val enabled by featureEnable()
val radiusSmall by double(max=0.5, default=0.25)
val radiusLarge by double(max=0.5, default=0.44)
val dimming by float(default = 0.7)
val dimming by double(default = 0.7)
val connectSolids by boolean(false)
val lenientConnect by boolean(true)
val connectPerpendicular by boolean(true)
@@ -104,41 +97,43 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
val zProtection by double(min = 0.9, default = 0.99)
}
object cactus {
object cactus : ConfigCategory(){
val enabled by featureEnable()
val size by double(min=0.5, max=1.5, default=0.8).lang("size")
val sizeVariation by double(max=0.5, default=0.1)
val hOffset by double(max=0.5, default=0.1).lang("hOffset")
}
object lilypad {
object lilypad : ConfigCategory(){
val enabled by featureEnable()
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
val flowerChance by int(max=64, default=16, min=0)
}
object reed {
object reed : ConfigCategory(){
val enabled by featureEnable()
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=1.5, max=3.5, default=1.7).lang("heightMin")
val heightMax by double(min=1.5, max=3.5, default=2.2).lang("heightMax")
val population by int(max=64, default=32).lang("population")
val 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")
}
object algae {
object algae : ConfigCategory(){
val enabled by featureEnable()
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
val heightMin by double(min=0.1, max=1.5, default=0.5).lang("heightMin")
val heightMax by double(min=0.1, max=1.5, default=1.0).lang("heightMax")
val population by int(max=64, default=48).lang("population")
val biomes by biomeList { it.filterClass("river", "ocean") }
// val biomes by biomeList { it.filterClass("river", "ocean") }
val shaderWind by boolean(true).lang("shaderWind")
}
object coral {
object coral : ConfigCategory(){
val enabled by featureEnable()
val shallowWater by boolean(false)
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
@@ -147,10 +142,10 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
val crustSize by double(min=0.5, max=1.5, default=1.4)
val chance by int(max=64, default=32)
val population by int(max=64, default=48).lang("population")
val biomes by biomeList { it.filterClass("river", "ocean", "beach") }
// val biomes by biomeList { it.filterClass("river", "ocean", "beach") }
}
object netherrack {
object netherrack : ConfigCategory(){
val enabled by featureEnable()
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=0.1, max=1.5, default=0.6).lang("heightMin")
@@ -158,7 +153,7 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
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 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)
@@ -170,20 +165,20 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
val opacityHack by boolean(true)
}
object risingSoul {
object risingSoul : ConfigCategory(){
val enabled by featureEnable()
val chance by double(min=0.001, max=1.0, default=0.02)
val perturb by double(min=0.01, max=0.25, default=0.05)
val headSize by double(min=0.25, max=1.5, default=1.0)
val trailSize by double(min=0.25, max=1.5, default=0.75)
val opacity by 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 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 trailLength by int(min=2, max=128, default=48)
val trailDensity by int(min=1, max=16, default=3)
}
/*
val forceReloadOptions = listOf(
blocks.leavesClasses,
blocks.leavesModels,
@@ -194,8 +189,39 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
override fun onChange(event: ConfigChangedEvent.PostConfigChangedEvent) {
if (hasChanged(forceReloadOptions))
Minecraft.getMinecraft().refreshResources()
Minecraft.getInstance().refreshResources()
else
Minecraft.getMinecraft().renderGlobal.loadRenderers()
Minecraft.getInstance().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 sand = blocks("sand_default.cfg")
val lilypad = blocks("lilypad_default.cfg")
val cactus = blocks("cactus_default.cfg")
val netherrack = blocks("netherrack_blocks_default.cfg")
init { BetterFoliage.modBus.register(this) }
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(BetterFoliage.logDetail, ResourceLocation(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(BetterFoliage.logDetail, ResourceLocation(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) }
@SubscribeEvent
fun handleLoadModelData(event: LoadModelDataEvent) {
list.forEach { when(it) {
is ConfigurableBlockMatcher -> it.readDefaults()
is ModelTextureListConfiguration -> it.readDefaults()
} }
}
}

View File

@@ -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
}

View File

@@ -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
)
}
}

View File

@@ -1,8 +1,8 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.render.LogRegistry
import mods.betterfoliage.client.render.StandardLogRegistry
import mods.betterfoliage.client.render.column.ColumnTextureInfo
@@ -19,19 +19,19 @@ import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.FieldRef
import mods.octarinecore.metaprog.MethodRef
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.state.IBlockState
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.block.model.ModelResourceLocation
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.ModelResourceLocation
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraft.world.IBlockReader
import net.minecraft.world.IWorldReader
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.client.model.IModel
import net.minecraftforge.fml.common.Loader
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.ModList
import org.apache.logging.log4j.Level
import kotlin.collections.Map
import kotlin.collections.component1
@@ -45,7 +45,6 @@ import kotlin.collections.mapValues
import kotlin.collections.mutableMapOf
import kotlin.collections.set
@SideOnly(Side.CLIENT)
object ForestryIntegration {
val TextureLeaves = ClassRef("forestry.arboriculture.models.TextureLeaves")
@@ -71,7 +70,7 @@ object ForestryIntegration {
val getSprite = MethodRef(ILeafSpriteProvider, "getSprite", Refs.ResourceLocation, ClassRef.boolean, ClassRef.boolean)
init {
if (Loader.isModLoaded("forestry") && allAvailable(TiLgetLeaveSprite, getLeafSpriteProvider, getSprite)) {
if (ModList.get().isLoaded("forestry") && allAvailable(TiLgetLeaveSprite, getLeafSpriteProvider, getSprite)) {
Client.log(Level.INFO, "Forestry support initialized")
LeafRegistry.addRegistry(ForestryLeafRegistry)
LogRegistry.addRegistry(ForestryLogRegistry)
@@ -80,13 +79,13 @@ object ForestryIntegration {
}
object ForestryLeafRegistry : ModelRenderRegistry<LeafInfo> {
val logger = BetterFoliageMod.logDetail
val logger = BetterFoliage.logDetail
val textureToKey = mutableMapOf<ResourceLocation, ModelRenderKey<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)
state.properties.entries.find {
state.values.entries.find {
ForestryIntegration.PropertyTreeType.isInstance(it.key) && ForestryIntegration.TreeDefinition.isInstance(it.value)
} ?.let {
val species = ForestryIntegration.TdSpecies.get(it.value)
@@ -96,7 +95,7 @@ object ForestryLeafRegistry : ModelRenderRegistry<LeafInfo> {
}
// 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
val textureLoc = ForestryIntegration.TiLgetLeaveSprite.invoke(tile, Minecraft.isFancyGraphicsEnabled()) ?: return null
return textureToValue[textureLoc]
@@ -115,7 +114,7 @@ object ForestryLeafRegistry : ModelRenderRegistry<LeafInfo> {
ForestryIntegration.TeLpollplain.get(it.value) as ResourceLocation,
ForestryIntegration.TeLpollfancy.get(it.value) as ResourceLocation
).forEach { textureLocation ->
val key = StandardLeafKey(logger, textureLocation.toString()).apply { onPreStitch(event.map) }
val key = StandardLeafKey(logger, textureLocation.toString()).apply { onPreStitch(event) }
textureToKey[textureLocation] = key
}
}
@@ -129,14 +128,14 @@ object ForestryLeafRegistry : ModelRenderRegistry<LeafInfo> {
}
object ForestryLogRegistry : ModelRenderRegistryBase<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(state: BlockState, modelLoc: ModelResourceLocation, models: List<Pair<IUnbakedModel, ResourceLocation>>): ModelRenderKey<ColumnTextureInfo>? {
// respect class list to avoid triggering on fences, stairs, etc.
if (!Config.blocks.logClasses.matchesClass(state.block)) return null
if (!BlockConfig.logBlocks.matchesClass(state.block)) return null
// find wood type property
val woodType = state.properties.entries.find {
val woodType = state.values.entries.find {
ForestryIntegration.PropertyWoodType.isInstance(it.key) && ForestryIntegration.IWoodType.isInstance(it.value)
} ?: return null

View File

@@ -7,22 +7,19 @@ import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.common.Int3
import mods.octarinecore.metaprog.allAvailable
import mods.octarinecore.metaprog.reflectField
import net.minecraft.block.state.IBlockState
import net.minecraft.block.BlockState
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.util.EnumFacing
import net.minecraft.util.Direction.UP
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraft.world.IBlockReader
import org.apache.logging.log4j.Level
/**
* Integration for OptiFine custom block colors.
*/
@Suppress("UNCHECKED_CAST")
@SideOnly(Side.CLIENT)
object OptifineCustomColors {
val isColorAvailable = allAvailable(
@@ -34,27 +31,26 @@ object OptifineCustomColors {
}
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 {
val ofColor = if (isColorAvailable && Minecraft.getMinecraft().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
renderEnv.reset(ctx.world!!, ctx.blockState(Int3.zero), ctx.pos)
Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, renderEnv.wrapped) as? Int
val ofColor = if (isColorAvailable && Minecraft.getInstance().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
renderEnv.reset(ctx.reader!!, ctx.blockState(Int3.zero), ctx.pos)
Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.reader!!, ctx.pos, renderEnv.wrapped) as? Int
} else null
return if (ofColor == null || ofColor == -1) ctx.blockData(Int3.zero).color else ofColor
}
}
@SideOnly(Side.CLIENT)
class OptifineRenderEnv {
val wrapped: Any = Refs.RenderEnv.element!!.getDeclaredConstructor(
Refs.IBlockAccess.element, Refs.IBlockState.element, Refs.BlockPos.element
Refs.IBlockReader.element, Refs.BlockState.element, Refs.BlockPos.element
).let {
it.isAccessible = true
it.newInstance(null, null, null)
}
fun reset(blockAccess: IBlockAccess, state: IBlockState, pos: BlockPos) {
fun reset(blockAccess: IBlockReader, state: BlockState, pos: BlockPos) {
Refs.RenderEnv_reset.invoke(wrapped, blockAccess, state, pos)
}
}

View File

@@ -1,6 +1,6 @@
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
@@ -13,39 +13,39 @@ import mods.octarinecore.client.resource.*
import mods.octarinecore.common.rotate
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.block.model.ModelResourceLocation
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.ModelResourceLocation
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.IModel
import net.minecraftforge.fml.common.Loader
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraftforge.fml.ModList
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Level.DEBUG
import org.apache.logging.log4j.Logger
@SideOnly(Side.CLIENT)
object IC2RubberIntegration {
val BlockRubWood = ClassRef("ic2.core.block.BlockRubWood")
init {
if (Loader.isModLoaded("ic2") && allAvailable(BlockRubWood)) {
if (ModList.get().isLoaded("ic2") && allAvailable(BlockRubWood)) {
Client.log(Level.INFO, "IC2 rubber support initialized")
LogRegistry.addRegistry(IC2LogSupport)
}
}
}
@SideOnly(Side.CLIENT)
object TechRebornRubberIntegration {
val BlockRubberLog = ClassRef("techreborn.blocks.BlockRubberLog")
init {
if (Loader.isModLoaded("techreborn") && allAvailable(BlockRubberLog)) {
if (ModList.get().isLoaded("techreborn") && allAvailable(BlockRubberLog)) {
Client.log(Level.INFO, "TechReborn rubber support initialized")
LogRegistry.addRegistry(TechRebornLogSupport)
}
@@ -53,8 +53,8 @@ object TechRebornRubberIntegration {
}
class RubberLogInfo(
axis: EnumFacing.Axis?,
val spotDir: EnumFacing,
axis: Axis?,
val spotDir: Direction,
topTexture: TextureAtlasSprite,
bottomTexture: TextureAtlasSprite,
val spotTexture: TextureAtlasSprite,
@@ -62,85 +62,85 @@ class RubberLogInfo(
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
override val side: QuadIconResolver = { ctx: ShadingContext, idx: Int, quad: Quad ->
val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.rotation)
if (worldFace == spotDir) spotTexture else {
val sideIdx = if (this.sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
this.sideTextures[sideIdx]
}
}
class Key(override val logger: Logger, val axis: EnumFacing.Axis?, val spotDir: EnumFacing, val textures: List<String>): ModelRenderKey<ColumnTextureInfo> {
override fun resolveSprites(atlas: TextureMap) = RubberLogInfo(
class Key(override val logger: Logger, val axis: Axis?, val spotDir: Direction, val textures: List<String>): ModelRenderKey<ColumnTextureInfo> {
override fun resolveSprites(atlas: AtlasTexture) = 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 }
atlas[textures[0]] ?: missingSprite,
atlas[textures[1]] ?: missingSprite,
atlas[textures[2]] ?: missingSprite,
textures.drop(3).map { atlas[it] ?: missingSprite }
)
}
}
object IC2LogSupport : ModelRenderRegistryBase<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(state: BlockState, modelLoc: ModelResourceLocation, models: List<Pair<IUnbakedModel, ResourceLocation>>): ModelRenderKey<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock, and "state" blockstate property
if (!IC2RubberIntegration.BlockRubWood.isInstance(state.block)) return null
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
val type = state.properties.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
val blockLoc = models.firstOrNull() as Pair<BlockModel, ResourceLocation> ?: return null
val type = state.values.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
// logs with no rubber spot
if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) {
val axis = when(type) {
"plain_y" -> EnumFacing.Axis.Y
"plain_x" -> EnumFacing.Axis.X
"plain_z" -> EnumFacing.Axis.Z
"plain_y" -> Axis.Y
"plain_x" -> Axis.X
"plain_z" -> Axis.Z
else -> null
}
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
if (textureNames.any { it == "missingno" }) return null
logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}")
logger.log(Level.DEBUG, "IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[2]}")
logger.log(DEBUG, "IC2LogSupport: block state ${state.toString()}")
logger.log(DEBUG, "IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[2]}")
return SimpleColumnInfo.Key(logger, axis, textureNames)
}
// logs with rubber spot
val spotDir = when(type) {
"dry_north", "wet_north" -> EnumFacing.NORTH
"dry_south", "wet_south" -> EnumFacing.SOUTH
"dry_west", "wet_west" -> EnumFacing.WEST
"dry_east", "wet_east" -> EnumFacing.EAST
"dry_north", "wet_north" -> NORTH
"dry_south", "wet_south" -> SOUTH
"dry_west", "wet_west" -> WEST
"dry_east", "wet_east" -> EAST
else -> null
}
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTextureName(it) }
if (textureNames.any { it == "missingno" }) return null
logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}")
logger.log(Level.DEBUG, "IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
return if (spotDir != null) RubberLogInfo.Key(logger, EnumFacing.Axis.Y, spotDir, textureNames) else SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
logger.log(DEBUG, "IC2LogSupport: block state ${state.toString()}")
logger.log(DEBUG, "IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
return if (spotDir != null) RubberLogInfo.Key(logger, Axis.Y, spotDir, textureNames) else SimpleColumnInfo.Key(logger, Axis.Y, textureNames)
}
}
object TechRebornLogSupport : ModelRenderRegistryBase<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(state: BlockState, modelLoc: ModelResourceLocation, models: List<Pair<IUnbakedModel, ResourceLocation>>): ModelRenderKey<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(state.block)) return null
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
val blockLoc = models.firstOrNull() as Pair<BlockModel, ResourceLocation> ?: return null
val hasSap = state.properties.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
val sapSide = state.properties.entries.find { it.key.getName() == "sapside" }?.value as? EnumFacing ?: return null
val hasSap = state.values.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
val sapSide = state.values.entries.find { it.key.getName() == "sapside" }?.value as? Direction ?: return null
logger.log(Level.DEBUG, "$logName: block state $state")
logger.log(DEBUG, "$logName: block state $state")
if (hasSap) {
val textureNames = listOf("end", "end", "sapside", "side").map { blockLoc.first.resolveTextureName(it) }
logger.log(Level.DEBUG, "$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) return RubberLogInfo.Key(logger, EnumFacing.Axis.Y, sapSide, textureNames)
logger.log(DEBUG, "$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) return RubberLogInfo.Key(logger, Axis.Y, sapSide, textureNames)
} else {
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
logger.log(Level.DEBUG, "$logName: end=${textureNames[0]}, side=${textureNames[2]}")
if (textureNames.all { it != "missingno" })return SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
logger.log(DEBUG, "$logName: end=${textureNames[0]}, side=${textureNames[2]}")
if (textureNames.all { it != "missingno" })return SimpleColumnInfo.Key(logger, Axis.Y, textureNames)
}
return null
}

View File

@@ -1,41 +1,36 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.loader.Refs
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.Block
import net.minecraft.block.BlockTallGrass
import net.minecraft.block.state.IBlockState
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.init.Blocks
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
/**
* Integration for ShadersMod.
*/
@SideOnly(Side.CLIENT)
object ShadersModIntegration {
@JvmStatic var isAvailable = allAvailable(Refs.sVertexBuilder, Refs.pushEntity_state, Refs.pushEntity_num, Refs.popEntity)
@JvmStatic val tallGrassEntityData = entityDataFor(Blocks.TALLGRASS.defaultState.withProperty(BlockTallGrass.TYPE, BlockTallGrass.EnumType.GRASS))
@JvmStatic val leavesEntityData = entityDataFor(Blocks.LEAVES.defaultState)
@JvmStatic val tallGrassEntityData = entityDataFor(Blocks.TALL_GRASS.defaultState)
@JvmStatic val leavesEntityData = entityDataFor(Blocks.OAK_LEAVES.defaultState)
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 entityDataFor(blockState: BlockState) = 0L
// (ForgeRegistries.BLOCKS.getIDForObject(blockState.block).toLong() and 65535) or
// ((blockState.renderType.ordinal.toLong() and 65535) shl 16) or
// (blockState.block.getMetaFromState(blockState).toLong() shl 32)
/**
* Called from transformed ShadersMod code.
* @see mods.betterfoliage.loader.BetterFoliageTransformer
*/
@JvmStatic fun getBlockIdOverride(original: Long, blockState: IBlockState): Long {
if (Config.blocks.leavesClasses.matchesClass(blockState.block)) return leavesEntityData
if (Config.blocks.crops.matchesClass(blockState.block)) return tallGrassEntityData
@JvmStatic fun getBlockIdOverride(original: Long, blockState: BlockState): Long {
if (BlockConfig.leafBlocks.matchesClass(blockState.block)) return leavesEntityData
if (BlockConfig.crops.matchesClass(blockState.block)) return tallGrassEntityData
return original
}
@@ -56,7 +51,7 @@ object ShadersModIntegration {
}
/** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(state: IBlockState, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
inline fun renderAs(state: BlockState, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
renderAs(entityDataFor(state), renderer, enabled, func)
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */

View File

@@ -15,18 +15,22 @@ import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.common.gameevent.TickEvent
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraftforge.eventbus.api.SubscribeEvent
import org.lwjgl.opengl.GL11
import java.lang.Math.*
import java.util.*
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
@SideOnly(Side.CLIENT)
class EntityFallingLeavesFX(world: World, pos: BlockPos) :
AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5) {
const val rotationFactor = PI2.toFloat() / 64.0f
class EntityFallingLeavesFX(
world: World, pos: BlockPos
) : AbstractEntityFX(
world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5
) {
companion object {
@JvmStatic val biomeBrightnessMultiplier = 0.5f
@@ -38,38 +42,40 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble
var wasCollided = false
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
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
val state = world.getBlockState(pos)
val blockColor = Minecraft.getMinecraft().blockColors.colorMultiplier(state, world, pos, 0)
val leafInfo = LeafRegistry[state, world, pos]
val blockColor = Minecraft.getInstance().blockColors.getColor(state, world, pos, 0)
if (leafInfo != null) {
particleTexture = leafInfo.particleTextures[rand.nextInt(1024)]
sprite = leafInfo.particleTextures[rand.nextInt(1024)]
calculateParticleColor(leafInfo.averageColor, blockColor)
} else {
particleTexture = LeafParticleRegistry["default"][rand.nextInt(1024)]
sprite = LeafParticleRegistry["default"][rand.nextInt(1024)]
setColor(blockColor)
}
}
override val isValid: Boolean get() = (particleTexture != null)
override val isValid: Boolean get() = (sprite != null)
override fun update() {
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) {
velocity.setTo(0.0, 0.0, 0.0)
if (!wasCollided) {
particleAge = Math.max(particleAge, particleMaxAge - 20)
age = Math.max(age, maxAge - 20)
wasCollided = true
}
} else {
velocity.setTo(cos[particleRot], 0.0, sin[particleRot]).mul(Config.fallingLeaves.perturb)
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed)
particleRot = (particleRot + (if (rotPositive) 1 else -1)) and 63
particleAngle = rotationFactor * particleRot.toFloat()
}
}
@@ -96,7 +102,6 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble
}
}
@SideOnly(Side.CLIENT)
object LeafWindTracker {
var random = Random()
val target = Double3.zero
@@ -108,7 +113,7 @@ object LeafWindTracker {
}
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 speed = abs(random.nextGaussian()) * Config.fallingLeaves.windStrength +
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * Config.fallingLeaves.stormStrength)
@@ -117,9 +122,9 @@ object LeafWindTracker {
@SubscribeEvent
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
if (world.worldInfo.worldTime >= nextChange) changeWind(world)
if (world.worldInfo.dayTime >= nextChange) changeWind(world)
// change current wind speed
val changeRate = if (world.isRaining) 0.015 else 0.005
@@ -132,5 +137,5 @@ object LeafWindTracker {
}
@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) }
}

View File

@@ -1,22 +1,21 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.AbstractEntityFX
import mods.octarinecore.client.resource.Atlas
import mods.octarinecore.client.resource.ResourceHandler
import mods.octarinecore.common.Double3
import mods.octarinecore.forEachPairIndexed
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
@SideOnly(Side.CLIENT)
class EntityRisingSoulFX(world: World, pos: BlockPos) :
AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5) {
@@ -26,14 +25,14 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
init {
motionY = 0.1
particleGravity = 0.0f
particleTexture = RisingSoulTextures.headIcons[rand.nextInt(256)]
particleMaxAge = MathHelper.floor((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
sprite = RisingSoulTextures.headIcons[rand.nextInt(256)]
maxAge = MathHelper.floor((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
}
override val isValid: Boolean get() = true
override fun update() {
val phase = (initialPhase + particleAge) % 64
val phase = (initialPhase + age) % 64
velocity.setTo(cos[phase] * Config.risingSoul.perturb, 0.1, sin[phase] * Config.risingSoul.perturb)
particleTrail.addFirst(currentPos.copy())
@@ -43,8 +42,8 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
}
override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
var alpha = Config.risingSoul.opacity
if (particleAge > particleMaxAge - 40) alpha *= (particleMaxAge - particleAge) / 40.0f
var alpha = Config.risingSoul.opacity.toFloat()
if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f
renderParticleQuad(worldRenderer, partialTickTime,
size = Config.risingSoul.headSize * 0.25,
@@ -54,7 +53,7 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
var scale = Config.risingSoul.trailSize * 0.25
particleTrail.forEachPairIndexed { idx, current, previous ->
scale *= Config.risingSoul.sizeDecay
alpha *= Config.risingSoul.opacityDecay
alpha *= Config.risingSoul.opacityDecay.toFloat()
if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime,
currentPos = current,
prevPos = previous,
@@ -66,12 +65,11 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
}
}
@SideOnly(Side.CLIENT)
object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID) {
val headIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/rising_soul_%d")
val trackIcon = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/soul_track")
object RisingSoulTextures : ResourceHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus, targetAtlas = Atlas.PARTICLES) {
val headIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "rising_soul_$idx") }
val trackIcon = iconStatic(BetterFoliage.MOD_ID, "soul_track")
override fun afterPreStitch() {
Client.log(INFO, "Registered ${headIcons.num} soul particle textures")
Client.log(DEBUG, "Registered ${headIcons.num} soul particle textures")
}
}

View File

@@ -5,7 +5,8 @@ import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Double3
import mods.octarinecore.exchange
import net.minecraft.util.EnumFacing.*
import net.minecraft.util.Direction
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. */
@@ -25,10 +26,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)
.clampUV(minU = 0.5 - radius)
.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(
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}
)
).forEach { transform(it.setFlatShader(FaceFlat(SOUTH))).add() }
@@ -37,9 +38,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)
.clampUV(maxU = radius - 0.5)
.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(
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}
),

View File

@@ -1,42 +1,46 @@
package mods.betterfoliage.client.render
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.integration.ShadersModIntegration
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import net.minecraft.block.Block
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.tags.BlockTags
import net.minecraft.util.BlockRenderLayer
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
@SideOnly(Side.CLIENT)
class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val noise = simplexNoise()
val algaeIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_algae_%d")
val algaeModels = modelSet(64, RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax))
val algaeIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_algae_$idx") }
val algaeModels = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax)(idx) }
override fun afterPreStitch() {
Client.log(INFO, "Registered ${algaeIcons.num} algae textures")
Client.log(DEBUG, "Registered ${algaeIcons.num} algae textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.algae.enabled &&
ctx.blockState(up2).material == Material.WATER &&
ctx.blockState(up1).material == Material.WATER &&
Config.blocks.dirt.matchesClass(ctx.block) &&
ctx.biomeId in Config.algae.biomes &&
BlockTags.DIRT_LIKE.contains(ctx.block) &&
ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH || it == Biome.Category.RIVER } &&
noise[ctx.pos] < Config.algae.population
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
if (!layer.isCutout) return baseRender
modelRenderer.updateShading(Int3.zero, allFaces)

View File

@@ -1,7 +1,8 @@
package mods.betterfoliage.client.render
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.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
@@ -11,33 +12,32 @@ import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.common.config.SimpleBlockMatcher
import net.minecraft.block.BlockCactus
import net.minecraft.block.state.IBlockState
import net.minecraft.block.BlockState
import net.minecraft.block.CactusBlock
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.*
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
object StandardCactusRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail
override val matchClasses = SimpleBlockMatcher(BlockCactus::class.java)
override val logger = BetterFoliage.logDetail
override val matchClasses = SimpleBlockMatcher(CactusBlock::class.java)
override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side"))
override fun processModel(state: IBlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, Axis.Y, textures)
init { MinecraftForge.EVENT_BUS.register(this) }
override fun processModel(state: BlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, Axis.Y, textures)
init { BetterFoliage.modBus.register(this) }
}
@SideOnly(Side.CLIENT)
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val cactusStemRadius = 0.4375
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
val iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus")
val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus_arm_%d")
val iconCross = iconStatic(ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_cactus"))
val iconArm = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_cactus_arm_$idx") }
val modelStem = model {
horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5)
@@ -68,20 +68,20 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
}
override fun afterPreStitch() {
Client.log(Level.INFO, "Registered ${iconArm.num} cactus arm textures")
Client.log(DEBUG, "Registered ${iconArm.num} cactus arm textures")
}
override fun isEligible(ctx: BlockContext): Boolean =
Config.enabled && Config.cactus.enabled &&
Config.blocks.cactus.matchesClass(ctx.block)
StandardCactusRegistry[ctx] != null
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
// get AO data
modelRenderer.updateShading(Int3.zero, allFaces)
val icons = StandardCactusRegistry[ctx] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null)
val icons = StandardCactusRegistry[ctx] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
modelRenderer.render(
renderer,

View File

@@ -1,36 +1,36 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.withOffset
import mods.octarinecore.client.render.offset
import mods.octarinecore.common.Int3
import mods.octarinecore.common.forgeDirsHorizontal
import mods.octarinecore.common.offset
import net.minecraft.block.Block
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.tags.BlockTags
import net.minecraft.util.BlockRenderLayer
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraftforge.client.model.data.IModelData
import java.util.*
@SideOnly(Side.CLIENT)
class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.connectedGrass.enabled &&
Config.blocks.dirt.matchesClass(ctx.block) &&
Config.blocks.grassClasses.matchesClass(ctx.block(up1)) &&
BlockTags.DIRT_LIKE.contains(ctx.block) &&
GrassRegistry[ctx, up1] != null &&
(Config.connectedGrass.snowEnabled || !ctx.blockState(up2).isSnow)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
// if the block sides are not visible anyway, render normally
if (forgeDirsHorizontal.all { ctx.blockState(it.offset).isOpaqueCube }) return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
if (forgeDirsHorizontal.none { ctx.shouldSideBeRendered(it) }) return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
if (ctx.isSurroundedBy { it.isOpaqueCube } ) return false
return ctx.withOffset(Int3.zero, up1) {
ctx.withOffset(up1, up2) {
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
}
return ctx.offset(Int3.zero, up1).offset(up1, up2).let { offsetCtx ->
renderWorldBlockBase(offsetCtx, dispatcher, renderer, random, modelData, layer)
}
}
}

View File

@@ -1,36 +1,39 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.withOffset
import mods.octarinecore.client.render.offset
import mods.octarinecore.common.Int3
import mods.octarinecore.common.offset
import net.minecraft.block.Block
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.tags.BlockTags
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.*
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraft.util.Direction.*
import net.minecraftforge.client.model.data.IModelData
import java.util.*
@SideOnly(Side.CLIENT)
class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
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))
BlockTags.DIRT_LIKE.contains(ctx.block) &&
LogRegistry[ctx, up1] != null
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
val grassDir = grassCheckDirs.find {
Config.blocks.grassClasses.matchesClass(ctx.block(it.offset))
} ?: return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
GrassRegistry[ctx, it.offset] != null
} ?: return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
return ctx.withOffset(Int3.zero, grassDir.offset) {
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
return ctx.offset(Int3.zero, grassDir.offset).let { offsetCtx ->
renderWorldBlockBase(offsetCtx, dispatcher, renderer, random, modelData, layer)
}
}
}

View File

@@ -1,7 +1,8 @@
package mods.betterfoliage.client.render
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.client.render.*
import mods.octarinecore.common.Int3
@@ -12,19 +13,22 @@ import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.Axis
import net.minecraft.util.EnumFacing.UP
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.client.model.data.IModelData
import net.minecraftforge.fml.common.Mod
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
@SideOnly(Side.CLIENT)
class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderCoral : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val noise = simplexNoise()
val coralIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_coral_%d")
val crustIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_crust_%d")
val coralIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_coral_$idx") }
val crustIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_crust_$idx") }
val coralModels = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(Config.coral.size).move(0.5 to UP)
@@ -41,26 +45,26 @@ class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
}
override fun afterPreStitch() {
Client.log(INFO, "Registered ${coralIcons.num} coral textures")
Client.log(INFO, "Registered ${crustIcons.num} coral crust textures")
Client.log(DEBUG, "Registered ${coralIcons.num} coral textures")
Client.log(DEBUG, "Registered ${crustIcons.num} coral crust textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.coral.enabled &&
(ctx.blockState(up2).material == Material.WATER || Config.coral.shallowWater) &&
ctx.blockState(up1).material == Material.WATER &&
Config.blocks.sand.matchesClass(ctx.block) &&
ctx.biomeId in Config.coral.biomes &&
BlockConfig.sand.matchesClass(ctx.block) &&
ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH } &&
noise[ctx.pos] < Config.coral.population
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
if (!layer.isCutout) return baseRender
modelRenderer.updateShading(Int3.zero, allFaces)
forgeDirs.forEachIndexed { idx, face ->
if (!ctx.blockState(forgeDirOffsets[idx]).isOpaqueCube && blockContext.random(idx) < Config.coral.chance) {
if (!ctx.isNormalCube(forgeDirOffsets[idx]) && blockContext.random(idx) < Config.coral.chance) {
var variation = blockContext.random(6)
modelRenderer.render(
renderer,

View File

@@ -1,6 +1,6 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCustomColors
@@ -9,17 +9,19 @@ import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.*
import mods.octarinecore.common.*
import mods.octarinecore.random
import net.minecraft.block.Block
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.tags.BlockTags
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.Axis
import net.minecraft.util.EnumFacing.UP
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
@SideOnly(Side.CLIENT)
class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderGrass : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
companion object {
@JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx ->
@@ -34,16 +36,16 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val noise = simplexNoise()
val normalIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_grass_long_%d")
val snowedIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_grass_snowed_%d")
val normalGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:blocks/tallgrass", "snowed" to false))
val snowedGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:blocks/tallgrass", "snowed" to true))
val normalIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx") }
val snowedIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_grass_snowed_$idx") }
val normalGenIcon = iconStatic { Client.genGrass.register(texture = "minecraft:blocks/tallgrass", isSnowed = false) }
val snowedGenIcon = iconStatic { Client.genGrass.register(texture = "minecraft:blocks/tallgrass", isSnowed = true) }
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() {
Client.log(INFO, "Registered ${normalIcons.num} grass textures")
Client.log(INFO, "Registered ${snowedIcons.num} snowed grass textures")
Client.log(DEBUG, "Registered ${normalIcons.num} grass textures")
Client.log(DEBUG, "Registered ${snowedIcons.num} snowed grass textures")
}
override fun isEligible(ctx: BlockContext) =
@@ -51,14 +53,11 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
GrassRegistry[ctx] != null
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
val isConnected = ctx.block(down1).let {
Config.blocks.dirt.matchesClass(it) ||
Config.blocks.grassClasses.matchesClass(it)
}
val isConnected = BlockTags.DIRT_LIKE.contains(ctx.block(down1)) || GrassRegistry[ctx, down1] != null
val isSnowed = ctx.blockState(up1).isSnow
val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
@@ -66,7 +65,7 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
if (grass == null) {
// shouldn't happen
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
return renderWorldBlockBase(ctx, dispatcher, renderer, null)
return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
}
val blockColor = OptifineCustomColors.getBlockColor(ctx)
@@ -75,14 +74,14 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
modelRenderer.updateShading(Int3.zero, allFaces)
// check occlusion
val isHidden = forgeDirs.map { ctx.blockState(it.offset).isOpaqueCube }
val isVisible = forgeDirs.map { ctx.shouldSideBeRendered(it) }
// render full grass block
ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), renderer) {
modelRenderer.render(
renderer,
fullCube,
quadFilter = { qi, _ -> !isHidden[qi] },
quadFilter = { qi, _ -> isVisible[qi] },
icon = { _, _, _ -> grass.grassTopTexture },
postProcess = { ctx, _, _, _, _ ->
rotateUV(2)
@@ -93,7 +92,7 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
)
}
} else {
renderWorldBlockBase(ctx, dispatcher, renderer, null)
renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
// get AO data only for block top
modelRenderer.updateShading(Int3.zero, topOnly)
@@ -101,7 +100,7 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
if (!Config.shortGrass.grassEnabled) return true
if (isSnowed && !Config.shortGrass.snowEnabled) return true
if (ctx.blockState(up1).isOpaqueCube) return true
if (ctx.isNormalCube(up1)) return true
if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return true
// render grass quads

View File

@@ -1,6 +1,6 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCustomColors
@@ -17,15 +17,14 @@ import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.DOWN
import net.minecraft.util.EnumFacing.UP
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import java.lang.Math.cos
import java.lang.Math.sin
import java.util.*
@SideOnly(Side.CLIENT)
class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
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)
@@ -34,7 +33,7 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
.scale(Config.leaves.size)
.toCross(UP).addAll()
}
val snowedIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_leaves_snowed_%d")
val snowedIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_leaves_snowed_$idx") }
val perturbs = vectorSet(64) { idx ->
val angle = PI2 * idx / 64.0
@@ -46,21 +45,19 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
Config.enabled &&
Config.leaves.enabled &&
LeafRegistry[ctx] != null &&
!(Config.leaves.hideInternal && ctx.isSurroundedBy { it.isFullCube || it.material == Material.LEAVES } )
!(Config.leaves.hideInternal && ctx.isSurroundedByNormal)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val isSnowed = ctx.blockState(up1).material.let {
it == Material.SNOW || it == Material.CRAFTED_SNOW
}
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
val isSnowed = ctx.blockState(up1).isSnow
val leafInfo = LeafRegistry[ctx]
if (leafInfo == null) {
// shouldn't happen
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
return renderWorldBlockBase(ctx, dispatcher, renderer, layer)
return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
}
val blockColor = OptifineCustomColors.getBlockColor(ctx)
renderWorldBlockBase(ctx, dispatcher, renderer, layer)
renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
if (!layer.isCutout) return true
modelRenderer.updateShading(Int3.zero, allFaces)

View File

@@ -1,7 +1,8 @@
package mods.betterfoliage.client.render
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.integration.ShadersModIntegration
import mods.octarinecore.client.render.*
@@ -10,14 +11,14 @@ import mods.octarinecore.common.Rotation
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.DOWN
import net.minecraft.util.EnumFacing.UP
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
@SideOnly(Side.CLIENT)
class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val rootModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5)
@@ -30,24 +31,24 @@ class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
.setFlatShader(FlatOffsetNoColor(Int3.zero))
.toCross(UP).addAll()
}
val rootIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_roots_%d")
val flowerIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_flower_%d")
val rootIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_lilypad_roots_$idx") }
val flowerIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_lilypad_flower_$idx") }
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset }
override fun afterPreStitch() {
Client.log(Level.INFO, "Registered ${rootIcon.num} lilypad root textures")
Client.log(Level.INFO, "Registered ${flowerIcon.num} lilypad flower textures")
Client.log(DEBUG, "Registered ${rootIcon.num} lilypad root textures")
Client.log(DEBUG, "Registered ${flowerIcon.num} lilypad flower textures")
}
override fun isEligible(ctx: BlockContext): Boolean =
Config.enabled && Config.lilypad.enabled &&
Config.blocks.lilypad.matchesClass(ctx.block)
BlockConfig.lilypad.matchesClass(ctx.block)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
renderWorldBlockBase(ctx, dispatcher, renderer, null)
renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
modelRenderer.updateShading(Int3.zero, allFaces)
val rand = ctx.semiRandomArray(5)

View File

@@ -1,30 +1,32 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.column.AbstractRenderColumn
import mods.betterfoliage.client.render.column.ColumnRenderLayer
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.resource.*
import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
import mods.octarinecore.client.resource.ModelRenderRegistryRoot
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.tryDefault
import net.minecraft.block.BlockLog
import net.minecraft.block.state.IBlockState
import net.minecraft.util.EnumFacing.Axis
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraft.block.BlockState
import net.minecraft.block.LogBlock
import net.minecraft.util.Direction.Axis
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
class RenderLog : AbstractRenderColumn(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
override val addToCutout: Boolean get() = false
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.roundLogs.enabled &&
Config.blocks.logClasses.matchesClass(ctx.block)
LogRegistry[ctx] != null
override val overlayLayer = RoundLogOverlayLayer()
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
@@ -37,26 +39,26 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
class RoundLogOverlayLayer : ColumnRenderLayer() {
override val registry: ModelRenderRegistry<ColumnTextureInfo> get() = LogRegistry
override val blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) }
override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logClasses.matchesClass(state.block) }
override val blockPredicate = { state: BlockState -> BlockConfig.logBlocks.matchesClass(state.block) }
override val surroundPredicate = { state: BlockState -> !BlockConfig.logBlocks.matchesClass(state.block) }
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
override val defaultToY: Boolean get() = Config.roundLogs.defaultY
}
@SideOnly(Side.CLIENT)
object LogRegistry : ModelRenderRegistryRoot<ColumnTextureInfo>()
object StandardLogRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.logClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.logModels.list
override fun processModel(state: IBlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, getAxis(state), textures)
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.logModels.modelList
override fun processModel(state: BlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, getAxis(state), textures)
init { BetterFoliage.modBus.register(this) }
fun getAxis(state: IBlockState): Axis? {
val axis = tryDefault(null) { state.getValue(BlockLog.LOG_AXIS).toString() } ?:
state.properties.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
fun getAxis(state: BlockState): Axis? {
val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?:
state.values.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
return when (axis) {
"x" -> Axis.X
"y" -> Axis.Y

View File

@@ -1,7 +1,8 @@
package mods.betterfoliage.client.render
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.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.render.BlockContext
@@ -12,35 +13,35 @@ import mods.octarinecore.common.Rotation
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
@SideOnly(Side.CLIENT)
class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_mycel_%d")
val myceliumModel = modelSet(64, RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax))
val myceliumIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx") }
val myceliumModel = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) }
override fun afterPreStitch() {
Client.log(INFO, "Registered ${myceliumIcon.num} mycelium textures")
Client.log(DEBUG, "Registered ${myceliumIcon.num} mycelium textures")
}
override fun isEligible(ctx: BlockContext): Boolean {
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
return Config.blocks.mycelium.matchesClass(ctx.block)
return BlockConfig.mycelium.matchesClass(ctx.block)
}
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
val isSnowed = ctx.blockState(up1).isSnow
renderWorldBlockBase(ctx, dispatcher, renderer, null)
renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
if (isSnowed && !Config.shortGrass.snowEnabled) return true
if (ctx.blockState(up1).isOpaqueCube) return true
if (ctx.isNormalCube(up1)) return true
val rand = ctx.semiRandomArray(2)
modelRenderer.render(

View File

@@ -1,7 +1,8 @@
package mods.betterfoliage.client.render
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.client.render.*
import mods.octarinecore.common.Int3
@@ -10,15 +11,16 @@ import mods.octarinecore.random
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.*
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
@SideOnly(Side.CLIENT)
class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val netherrackIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_netherrack_%d")
val netherrackIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx") }
val netherrackModel = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yTop = -0.5,
yBottom = -0.5 - random(Config.netherrack.heightMin, Config.netherrack.heightMax))
@@ -29,19 +31,19 @@ class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID)
}
override fun afterPreStitch() {
Client.log(INFO, "Registered ${netherrackIcon.num} netherrack textures")
Client.log(DEBUG, "Registered ${netherrackIcon.num} netherrack textures")
}
override fun isEligible(ctx: BlockContext): Boolean {
if (!Config.enabled || !Config.netherrack.enabled) return false
return Config.blocks.netherrack.matchesClass(ctx.block)
return BlockConfig.netherrack.matchesClass(ctx.block)
}
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
if (!layer.isCutout) return baseRender
if (ctx.blockState(down1).isOpaqueCube) return baseRender
if (ctx.isNormalCube(down1)) return baseRender
modelRenderer.updateShading(Int3.zero, allFaces)

View File

@@ -1,27 +1,30 @@
package mods.betterfoliage.client.render
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.integration.ShadersModIntegration
import mods.octarinecore.client.render.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.random
import net.minecraft.block.Block
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.tags.BlockTags
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.UP
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
@SideOnly(Side.CLIENT)
class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
class RenderReeds : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val noise = simplexNoise()
val reedIcons = iconSet(Client.genReeds.generatedResource("${BetterFoliageMod.LEGACY_DOMAIN}:blocks/better_reed_%d"))
val reedIcons = iconSet { idx -> Client.genReeds.registerResource(ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_reed_$idx")) }
val reedModels = modelSet(64) { modelIdx ->
val height = random(Config.reed.heightMin, Config.reed.heightMax)
val waterline = 0.875f
@@ -41,19 +44,19 @@ class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
}
override fun afterPreStitch() {
Client.log(Level.INFO, "Registered ${reedIcons.num} reed textures")
Client.log(DEBUG, "Registered ${reedIcons.num} reed textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.reed.enabled &&
ctx.blockState(up2).material == Material.AIR &&
ctx.blockState(up1).material == Material.WATER &&
Config.blocks.dirt.matchesClass(ctx.block) &&
ctx.biomeId in Config.reed.biomes &&
BlockTags.DIRT_LIKE.contains(ctx.block) &&
ctx.biome.let { it.downfall > Config.reed.minBiomeRainfall && it.defaultTemperature >= Config.reed.minBiomeTemp } &&
noise[ctx.pos] < Config.reed.population
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, layer)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
if (!layer.isCutout) return baseRender
modelRenderer.updateShading(Int3.zero, allFaces)

View File

@@ -2,17 +2,20 @@
package mods.betterfoliage.client.render
import mods.octarinecore.PI2
import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.Model
import mods.octarinecore.client.render.PostProcessLambda
import mods.octarinecore.client.render.Quad
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.times
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.material.Material
import net.minecraft.block.state.IBlockState
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing
import net.minecraft.util.EnumFacing.*
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import kotlin.math.cos
import kotlin.math.sin
val up1 = Int3(1 to UP)
val up2 = Int3(2 to UP)
@@ -25,15 +28,15 @@ val denseLeavesRot = arrayOf(Rotation.identity, Rotation.rot90[EAST.ordinal], Ro
val whitewash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.4f) }
val greywash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.0f) }
val IBlockState.isSnow: Boolean get() = material.let { it == Material.SNOW || it == Material.CRAFTED_SNOW }
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 ->
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(
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)
fun IBlockState.canRenderInLayer(layer: BlockRenderLayer) = this.block.canRenderInLayer(this, layer)
fun IBlockState.canRenderInCutout() = this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT) || this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT_MIPPED)
fun BlockState.canRenderInLayer(layer: BlockRenderLayer) = this.block.canRenderInLayer(this, layer)
fun BlockState.canRenderInCutout() = this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT) || this.block.canRenderInLayer(this, BlockRenderLayer.CUTOUT_MIPPED)

View File

@@ -1,31 +1,29 @@
package mods.betterfoliage.client.render.column
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.chunk.ChunkOverlayLayer
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs
import mods.betterfoliage.client.render.*
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.octarinecore.client.render.*
import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.common.*
import net.minecraft.block.state.IBlockState
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.face
import mods.octarinecore.common.rot
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.*
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraft.util.Direction.*
import net.minecraftforge.client.model.data.IModelData
import net.minecraftforge.eventbus.api.IEventBus
import java.util.*
@SideOnly(Side.CLIENT)
@Suppress("NOTHING_TO_INLINE")
abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) {
abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : AbstractBlockRenderingHandler(modId, modBus) {
/** The rotations necessary to bring the models in position for the 4 quadrants */
val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it }
@@ -96,21 +94,21 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE))
@Suppress("NON_EXHAUSTIVE_WHEN")
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx.world!!, ctx.pos)
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx.reader!!, ctx.pos)
when(roundLog) {
ColumnLayerData.SkipRender -> return true
ColumnLayerData.NormalRender -> return renderWorldBlockBase(ctx, dispatcher, renderer, null)
ColumnLayerData.NormalRender -> return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
ColumnLayerData.ResolveError, null -> {
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
return renderWorldBlockBase(ctx, dispatcher, renderer, null)
return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
}
}
// if log axis is not defined and "Default to vertical" config option is not set, render normally
if ((roundLog as ColumnLayerData.SpecialRender).column.axis == null && !overlayLayer.defaultToY) {
return renderWorldBlockBase(ctx, dispatcher, renderer, null)
return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
}
// get AO data

View File

@@ -2,6 +2,7 @@ package mods.betterfoliage.client.render.column
import mods.betterfoliage.client.chunk.ChunkOverlayLayer
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.chunk.dimType
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
@@ -13,12 +14,11 @@ import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.face
import mods.octarinecore.common.plus
import net.minecraft.block.state.IBlockState
import net.minecraft.util.EnumFacing
import net.minecraft.block.BlockState
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.AxisDirection
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraft.world.IEnviromentBlockReader
/** Index of SOUTH-EAST quadrant. */
const val SE = 0
@@ -32,13 +32,11 @@ const val SW = 3
/**
* Sealed class hierarchy for all possible render outcomes
*/
@SideOnly(Side.CLIENT)
sealed class ColumnLayerData {
/**
* Data structure to cache texture and world neighborhood data relevant to column rendering
*/
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
@SideOnly(Side.CLIENT)
data class SpecialRender(
val column: ColumnTextureInfo,
val upType: BlockType,
@@ -52,15 +50,12 @@ sealed class ColumnLayerData {
}
/** Column block should not be rendered at all */
@SideOnly(Side.CLIENT)
object SkipRender : ColumnLayerData()
/** Column block must be rendered normally */
@SideOnly(Side.CLIENT)
object NormalRender : ColumnLayerData()
/** Error while resolving render data, column block must be rendered normally */
@SideOnly(Side.CLIENT)
object ResolveError : ColumnLayerData()
}
@@ -68,29 +63,29 @@ sealed class ColumnLayerData {
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
abstract val registry: ModelRenderRegistry<ColumnTextureInfo>
abstract val blockPredicate: (IBlockState)->Boolean
abstract val surroundPredicate: (IBlockState) -> Boolean
abstract val blockPredicate: (BlockState)->Boolean
abstract val surroundPredicate: (BlockState) -> Boolean
abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean
abstract val defaultToY: Boolean
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) {
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(this, pos + offset) }
override fun onBlockUpdate(reader: IEnviromentBlockReader, pos: BlockPos) {
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(reader.dimType, this, pos + offset) }
}
override fun calculate(world: IBlockAccess, pos: BlockPos) = calculate(BlockContext(world, pos))
override fun calculate(reader: IEnviromentBlockReader, pos: BlockPos) = calculate(BlockContext(reader, pos))
fun calculate(ctx: BlockContext): ColumnLayerData {
if (ctx.isSurroundedBy(surroundPredicate)) return ColumnLayerData.SkipRender
if (ctx.isSurroundedBy(surroundPredicate) && ctx.isSurroundedByNormal) return ColumnLayerData.SkipRender
val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
// if log axis is not defined and "Default to vertical" config option is not set, render normally
val logAxis = columnTextures.axis ?: if (defaultToY) EnumFacing.Axis.Y else return ColumnLayerData.NormalRender
val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender
// 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 downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
@@ -109,7 +104,7 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
}
/** 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: BlockContext, rotation: Rotation, logAxis: Axis, yOff: Int): Array<QuadrantType> {
val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1))
val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0))
val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1))
@@ -176,13 +171,13 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
/**
* 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 BlockContext.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
val offsetRot = offset.rotate(rotation)
val state = blockState(offsetRot)
return if (!blockPredicate(state)) {
if (state.isOpaqueCube) SOLID else NONSOLID
if (isNormalCube(offsetRot)) SOLID else NONSOLID
} else {
(registry[state, world!!, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) EnumFacing.Axis.Y else null)?.let {
(registry[state, reader!!, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let {
if (it == axis) PARALLEL else PERPENDICULAR
} ?: SOLID
}

View File

@@ -4,25 +4,22 @@ import mods.octarinecore.client.render.QuadIconResolver
import mods.octarinecore.client.render.blockContext
import mods.octarinecore.client.resource.ModelRenderKey
import mods.octarinecore.client.resource.get
import mods.octarinecore.client.resource.missingSprite
import mods.octarinecore.common.rotate
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import net.minecraft.util.Direction.*
import org.apache.logging.log4j.Logger
@SideOnly(Side.CLIENT)
interface ColumnTextureInfo {
val axis: EnumFacing.Axis?
val axis: Axis?
val top: QuadIconResolver
val bottom: QuadIconResolver
val side: QuadIconResolver
}
@SideOnly(Side.CLIENT)
open class SimpleColumnInfo(
override val axis: EnumFacing.Axis?,
override val axis: Axis?,
val topTexture: TextureAtlasSprite,
val bottomTexture: TextureAtlasSprite,
val sideTextures: List<TextureAtlasSprite>
@@ -34,17 +31,17 @@ open class SimpleColumnInfo(
override val top: QuadIconResolver = { _, _, _ -> topTexture }
override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture }
override val side: QuadIconResolver = { ctx, idx, _ ->
val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.rotation)
val sideIdx = if (sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0
sideTextures[sideIdx]
}
class Key(override val logger: Logger, val axis: EnumFacing.Axis?, val textures: List<String>) : ModelRenderKey<ColumnTextureInfo> {
override fun resolveSprites(atlas: TextureMap) = SimpleColumnInfo(
class Key(override val logger: Logger, val axis: Axis?, val textures: List<String>) : ModelRenderKey<ColumnTextureInfo> {
override fun resolveSprites(atlas: AtlasTexture) = SimpleColumnInfo(
axis,
atlas[textures[0]] ?: atlas.missingSprite,
atlas[textures[1]] ?: atlas.missingSprite,
textures.drop(2).map { atlas[it] ?: atlas.missingSprite }
atlas[textures[0]] ?: missingSprite,
atlas[textures[1]] ?: missingSprite,
textures.drop(2).map { atlas[it] ?: missingSprite }
)
}
}

View File

@@ -2,7 +2,9 @@ package mods.betterfoliage.client.texture
import mods.octarinecore.client.resource.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.resource.VanillaResourceType.TEXTURES
import java.awt.image.BufferedImage
import java.io.InputStream
/**
* Generate Short Grass textures from [Blocks.tallgrass] block textures.
@@ -10,13 +12,16 @@ import java.awt.image.BufferedImage
*
* @param[domain] Resource domain of generator
*/
class GrassGenerator(domain: String) : TextureGenerator(domain) {
class GrassGenerator(domain: String) : GeneratorBase<GrassGenerator.Key>(domain, TEXTURES) {
override fun generate(params: ParameterList): BufferedImage? {
val target = targetResource(params)!!
val isSnowed = params["snowed"]?.toBoolean() ?: false
override val locationMapper = Atlas.BLOCKS::unwrap
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
fun register(texture: String, isSnowed: Boolean) = registerResource(Key(ResourceLocation(texture), isSnowed))
override fun exists(key: Key) = resourceManager.hasResource(Atlas.BLOCKS.wrap(key.texture))
override fun get(key: Key): InputStream? {
val baseTexture = resourceManager[Atlas.BLOCKS.wrap(key.texture)]?.loadImage() ?: return null
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = result.createGraphics()
@@ -39,12 +44,14 @@ class GrassGenerator(domain: String) : TextureGenerator(domain) {
}
// blend with white if snowed
if (isSnowed && target.first == ResourceType.COLOR) {
if (key.isSnowed) {
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
}
}
return result
return result.asStream
}
data class Key(val texture: ResourceLocation, val isSnowed: Boolean)
}

View File

@@ -1,25 +1,15 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.HSB
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst
import net.minecraft.block.state.IBlockState
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.lang.Math.min
@@ -43,16 +33,17 @@ class GrassInfo(
object GrassRegistry : ModelRenderRegistryRoot<GrassInfo>()
object StandardGrassRegistry : ModelRenderRegistryConfigurable<GrassInfo>() {
override val logger = BetterFoliageMod.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.grassClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.grassModels.list
override fun processModel(state: IBlockState, textures: List<String>) = StandardGrassKey(logger, textures[0])
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.grassModels.modelList
override fun processModel(state: BlockState, textures: List<String>) = StandardGrassKey(logger, textures[0])
init { BetterFoliage.modBus.register(this) }
}
class StandardGrassKey(override val logger: Logger, val textureName: String) : ModelRenderKey<GrassInfo> {
override fun resolveSprites(atlas: TextureMap): GrassInfo {
override fun resolveSprites(atlas: AtlasTexture): GrassInfo {
val logName = "StandardGrassKey"
val texture = atlas[textureName] ?: atlas.missingSprite
val texture = atlas[textureName] ?: missingSprite
logger.log(Level.DEBUG, "$logName: texture $textureName")
val hsb = HSB.fromColor(texture.averageColor ?: defaultGrassColor)
val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) {

View File

@@ -1,10 +1,13 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.octarinecore.client.resource.*
import mods.octarinecore.stripStart
import net.minecraft.resources.IResource
import net.minecraft.util.ResourceLocation
import net.minecraftforge.resource.VanillaResourceType.TEXTURES
import java.awt.image.BufferedImage
import java.io.InputStream
/**
* Generate round leaf textures from leaf block textures.
@@ -14,22 +17,24 @@ import java.awt.image.BufferedImage
*
* @param[domain] Resource domain of generator
*/
class LeafGenerator(domain: String) : TextureGenerator(domain) {
class LeafGenerator(domain: String) : GeneratorBase<LeafGenerator.Key>(domain, TEXTURES) {
override fun generate(params: ParameterList): BufferedImage? {
val target = targetResource(params)!!
val leafType = params["type"] ?: "default"
override val locationMapper = Atlas.BLOCKS::unwrap
val handDrawnLoc = target.second.stripStart("textures/").stripStart("blocks/").let {
ResourceLocation(BetterFoliageMod.DOMAIN, "${it.namespace}/textures/blocks/${it.path}")
}
resourceManager[handDrawnLoc]?.loadImage()?.let { return it }
fun register(texture: ResourceLocation, leafType: String) = registerResource(Key(texture, leafType))
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
override fun exists(key: Key) = resourceManager.hasResource(Atlas.BLOCKS.wrap(key.texture))
override fun get(key: Key): InputStream? {
// val handDrawnLoc = Atlas.BLOCKS.wrap(key.texture)
// resourceManager[handDrawnLoc]?.loadImage()?.let { return it.asStream }
val baseTexture = resourceManager[Atlas.BLOCKS.wrap(key.texture)]?.loadImage() ?: return null
val size = baseTexture.width
val frames = baseTexture.height / size
val maskTexture = (getLeafMask(leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage()
val maskTexture = (getLeafMask(key.leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage()
fun scale(i: Int) = i * maskTexture!!.width / (size * 2)
val leafTexture = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR)
@@ -49,7 +54,7 @@ class LeafGenerator(domain: String) : TextureGenerator(domain) {
}
// 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) {
val basePixel = leafFrame[x, y].toLong() and 0xFFFFFFFFL
val maskPixel = maskTexture[scale(x), scale(y)].toLong() and 0xFF000000L or 0xFFFFFFL
@@ -61,7 +66,7 @@ class LeafGenerator(domain: String) : TextureGenerator(domain) {
graphics.drawImage(leafFrame, 0, size * frame * 2, null)
}
return leafTexture
return leafTexture.asStream
}
/**
@@ -71,7 +76,22 @@ class LeafGenerator(domain: String) : TextureGenerator(domain) {
* @param[maxSize] Preferred mask size.
*/
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
ResourceLocation(BetterFoliageMod.DOMAIN, "textures/blocks/leafmask_${size}_${type}.png")
ResourceLocation(BetterFoliage.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()
}
data class Key(val texture: ResourceLocation, val leafType: String)
}

View File

@@ -1,40 +1,42 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.octarinecore.client.resource.*
import mods.octarinecore.stripStart
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.eventbus.api.SubscribeEvent
object LeafParticleRegistry {
val targetAtlas = Atlas.PARTICLES
val typeMappings = TextureMatcher()
val particles = hashMapOf<String, IconSet>()
operator fun get(type: String) = particles[type] ?: particles["default"]!!
init { MinecraftForge.EVENT_BUS.register(this) }
@SubscribeEvent(priority = EventPriority.HIGH)
fun handleLoadModelData(event: LoadModelDataEvent) {
particles.clear()
typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.DOMAIN, "leaf_texture_mappings.cfg"))
}
init { BetterFoliage.modBus.register(this) }
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
if (!targetAtlas.matches(event)) return
particles.clear()
typeMappings.loadMappings(ResourceLocation(BetterFoliage.MOD_ID, "leaf_texture_mappings.cfg"))
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) }
val particleSet = IconSet(Atlas.PARTICLES) {
idx -> ResourceLocation(BetterFoliage.MOD_ID, "falling_leaf_${leafType}_$idx")
}.apply { onPreStitch(event) }
if (leafType == "default" || particleSet.num > 0) particles[leafType] = particleSet
}
}
@SubscribeEvent
fun handlePostStitch(event: TextureStitchEvent.Post) {
if (!targetAtlas.matches(event)) return
particles.forEach { (_, particleSet) -> particleSet.onPostStitch(event.map) }
}
}

View File

@@ -1,28 +1,16 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.BlockContext
import mods.betterfoliage.client.config.BlockConfig
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst
import net.minecraft.block.state.IBlockState
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
@@ -46,25 +34,26 @@ class LeafInfo(
object LeafRegistry : ModelRenderRegistryRoot<LeafInfo>()
object StandardLeafRegistry : ModelRenderRegistryConfigurable<LeafInfo>() {
override val logger = BetterFoliageMod.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.leavesClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.leavesModels.list
override fun processModel(state: IBlockState, textures: List<String>) = StandardLeafKey(logger, textures[0])
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
override fun processModel(state: BlockState, textures: List<String>) = StandardLeafKey(logger, textures[0])
init { BetterFoliage.modBus.register(this) }
}
class StandardLeafKey(override val logger: Logger, val textureName: String) : ModelRenderKey<LeafInfo> {
lateinit var leafType: String
lateinit var generated: ResourceLocation
override fun onPreStitch(atlas: TextureMap) {
override fun onPreStitch(event: TextureStitchEvent.Pre) {
val logName = "StandardLeafKey"
leafType = LeafParticleRegistry.typeMappings.getType(textureName) ?: "default"
generated = Client.genLeaves.generatedResource(textureName, "type" to leafType)
atlas.registerSprite(generated)
generated = Client.genLeaves.register(ResourceLocation(textureName), leafType)
event.addSprite(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)
override fun resolveSprites(atlas: AtlasTexture) = LeafInfo(atlas[generated] ?: missingSprite, leafType)
}

View File

@@ -1,31 +1,23 @@
package mods.betterfoliage.loader
import mods.octarinecore.metaprog.Transformer
//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
// where: WorldClient.animateTick(), replacing invocation to Block.animateTick
// 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 {
// why: allows us to catch random display ticks
transformMethod(Refs.WC_animeteTick) {
find(invokeRef(Refs.B_animateTick))?.replace {
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!")
}
@@ -58,9 +50,7 @@ class BetterFoliageTransformer : Transformer() {
transformMethod(Refs.doesSideBlockRendering) {
find(IRETURN)?.insertBefore {
log.info("[BetterFoliageLoader] Applying doesSideBlockRendering() override")
varinsn(ALOAD, 1)
varinsn(ALOAD, 2)
varinsn(ALOAD, 3)
varinsn(ALOAD, 0)
invokeStatic(Refs.doesSideBlockRenderingOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply doesSideBlockRendering() override!")
}
@@ -76,11 +66,11 @@ class BetterFoliageTransformer : Transformer() {
} ?: 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
// where: ModelBakery.setupModelRegistry(), after all models are loaded
// what: invoke handler code with ModelBakery instance
// why: allows us to iterate the unbaked models in ModelBakery in time to register textures
transformMethod(Refs.setupModelRegistry) {
find(invokeName("addAll"))?.insertAfter {
find(invokeName("newLinkedHashSet"))?.insertBefore {
log.info("[BetterFoliageLoader] Applying ModelLoader lifecycle callback")
varinsn(ALOAD, 0)
invokeStatic(Refs.onAfterLoadModelDefinitions)
@@ -135,4 +125,6 @@ class BetterFoliageTransformer : Transformer() {
} ?: log.warn("[BetterFoliageLoader] Failed to apply SVertexBuilder.pushEntity() block ID override!")
}
}
}
}
*/

View File

@@ -3,11 +3,10 @@ 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()
// val mcVersion = FMLInjectionData.data()[4].toString()
// Java
val String = ClassRef("java.lang.String")
@@ -16,102 +15,44 @@ object Refs {
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 IBlockReader = ClassRef("net.minecraft.world.IBlockReader")
val IEnvironmentBlockReader = ClassRef("net.minecraft.world.IEnvironmentBlockReader")
val BlockState = ClassRef("net.minecraft.block.state.BlockState")
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 ChunkCache = ClassRef("net.minecraft.client.renderer.chunk.ChunkRenderCache")
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);
val getBlockId = MethodRef(BlockState, "getBlockId", ClassRef.int);
val getMetadata = MethodRef(BlockState, "getMetadata", ClassRef.int);
// Optifine
val RenderEnv = ClassRef("net.optifine.render.RenderEnv")
val RenderEnv_reset = MethodRef(RenderEnv, "reset", ClassRef.void, IBlockAccess, IBlockState, BlockPos)
val RenderEnv_reset = MethodRef(RenderEnv, "reset", ClassRef.void, IBlockReader, BlockState, BlockPos)
val quadSprite = FieldRef(BufferBuilder, "quadSprite", TextureAtlasSprite)
val BlockPosM = ClassRef("net.optifine.BlockPosM")
val IColorizer = ClassRef("net.optifine.CustomColors\$IColorizer")
// Optifine: custom colors
val CustomColors = ClassRef("net.optifine.CustomColors")
val getColorMultiplier = MethodRef(CustomColors, "getColorMultiplier", ClassRef.int, BakedQuad, IBlockState, IBlockAccess, BlockPos, RenderEnv)
val getColorMultiplier = MethodRef(CustomColors, "getSmoothColorMultiplier", ClassRef.int, BlockState, IEnvironmentBlockReader, BlockPos, IColorizer, BlockPosM)
// 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_state = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, BlockState, BlockPos, IBlockReader, 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)
val getBlockIdOverride = MethodRef(ShadersModIntegration, "getBlockIdOverride", ClassRef.long, ClassRef.long, BlockState)
}

View File

@@ -6,19 +6,19 @@ import mods.betterfoliage.loader.Refs
import net.minecraft.tileentity.TileEntity
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ChunkCache
import net.minecraft.world.IBlockAccess
import net.minecraft.world.World
import net.minecraft.world.*
import kotlin.reflect.KProperty
import java.lang.Math.*
const val PI2 = 2.0 * PI
/** Strip the given prefix off the start of the string, if present */
inline fun String.stripStart(str: String) = 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 */
inline fun ResourceLocation.stripStart(str: String) = ResourceLocation(namespace, path.stripStart(str))
inline fun ResourceLocation.stripEnd(str: String) = ResourceLocation(namespace, path.stripEnd(str))
/** Mutating version of _map_. Replace each element of the list with the result of the given transformation. */
inline fun <reified T> MutableList<T>.replace(transform: (T) -> T) = forEachIndexed { idx, value -> this[idx] = transform(value) }
@@ -104,17 +104,18 @@ fun nextPowerOf2(x: Int): Int {
* Check if the Chunk containing the given [BlockPos] is loaded.
* Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances.
*/
fun IBlockAccess.isBlockLoaded(pos: BlockPos) = when {
this is World -> isBlockLoaded(pos, false)
this is ChunkCache -> world.isBlockLoaded(pos, false)
Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkCache).world.isBlockLoaded(pos, false)
else -> false
}
//fun IWorldReader.isBlockLoaded(pos: BlockPos) = when {
// this is World -> isBlockLoaded(pos, false)
// this is RenderChunkCache -> isworld.isBlockLoaded(pos, false)
// Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkCache).world.isBlockLoaded(pos, false)
// else -> false
//}
/**
* Get the [TileEntity] at the given position, suppressing exceptions.
* Also returns null if the chunk is unloaded, which can happen because of multithreaded rendering.
*/
fun 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
}

View File

@@ -1,10 +1,10 @@
package mods.octarinecore.client
import net.minecraft.client.settings.KeyBinding
import net.minecraftforge.client.event.InputEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.client.registry.ClientRegistry
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) {
@@ -12,7 +12,7 @@ class KeyHandler(val modId: String, val defaultKey: Int, val lang: String, val a
init {
ClientRegistry.registerKeyBinding(keyBinding)
FMLCommonHandler.instance().bus().register(this)
MinecraftForge.EVENT_BUS.register(this)
}
@SubscribeEvent

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -1,8 +1,8 @@
@file:JvmName("Utils")
package mods.octarinecore.client.gui
import net.minecraft.util.text.StringTextComponent
import net.minecraft.util.text.Style
import net.minecraft.util.text.TextComponentString
import net.minecraft.util.text.TextFormatting
import net.minecraft.util.text.TextFormatting.AQUA
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 }
return TextComponentString(msg).apply { this.style = style }
return StringTextComponent(msg).apply { this.style = style }
}

View File

@@ -2,7 +2,6 @@
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
@@ -12,16 +11,21 @@ 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.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.IBlockAccess
import net.minecraft.world.IBlockReader
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
/**
@@ -36,7 +40,7 @@ val modelRenderer by ThreadLocalDelegate { ModelRenderer() }
val blockColors = ThreadLocal<BlockColors>()
abstract class AbstractBlockRenderingHandler(modId: String) : ResourceHandler(modId) {
abstract class AbstractBlockRenderingHandler(modId: String, modBus: IEventBus) : ResourceHandler(modId, modBus) {
open val addToCutout: Boolean get() = true
@@ -44,7 +48,7 @@ abstract class AbstractBlockRenderingHandler(modId: String) : ResourceHandler(mo
// Custom rendering
// ============================
abstract fun isEligible(ctx: BlockContext): Boolean
abstract fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean
abstract fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean
// ============================
// Vanilla rendering wrapper
@@ -52,12 +56,12 @@ abstract class AbstractBlockRenderingHandler(modId: String) : ResourceHandler(mo
/**
* Render the block in the current [BlockContext]
*/
fun renderWorldBlockBase(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer?): Boolean {
fun renderWorldBlockBase(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, 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 dispatcher.renderBlock(state, ctx.pos, ctx.reader!!, renderer, random, modelData)
}
}
return false
@@ -65,33 +69,23 @@ abstract class AbstractBlockRenderingHandler(modId: String) : ResourceHandler(mo
}
data class BlockData(val state: IBlockState, val color: Int, val brightness: Int)
data class BlockData(val state: BlockState, 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; }
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
open class BlockContext(
var reader: IEnviromentBlockReader? = null,
open var pos: BlockPos = BlockPos.ZERO
) {
fun set(blockReader: IEnviromentBlockReader, pos: BlockPos) { this.reader = blockReader; 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))
fun blockState(offset: Int3) = (pos + offset).let { reader!!.getBlockState(it) }
fun isNormalCube(offset: Int3) = (pos + offset).let { reader!!.getBlockState(it).isNormalCube(reader, it) }
/** 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)
@@ -103,8 +97,24 @@ class BlockContext(
return Double3(cX * 16.0, cY * 16.0, cZ * 16.0)
}
/** Get the biome at the block position. */
val biome: Biome get() = reader!!.getBiome(pos)
fun blockData(offset: Int3) = (pos + offset).let { pos ->
reader!!.getBlockState(pos).let { state ->
BlockData(
state,
Minecraft.getInstance().blockColors.getColor(state, reader!!, pos, 0),
state.getPackedLightmapCoords(reader!!, pos)
)
}
}
fun shouldSideBeRendered(dir: Direction) = Block.shouldSideBeRendered(blockState(Int3.zero), reader, pos, dir)
/** Is the block surrounded by other blocks that satisfy the predicate on all sides? */
fun isSurroundedBy(predicate: (IBlockState)->Boolean) = forgeDirOffsets.all { predicate(blockState(it)) }
fun isSurroundedBy(predicate: (BlockState)->Boolean) = forgeDirOffsets.all { predicate(blockState(it)) }
val isSurroundedByNormal: Boolean get() = forgeDirOffsets.all { isNormalCube(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)
@@ -114,9 +124,9 @@ class BlockContext(
/** Get the distance of the block from the camera (player). */
val cameraDistance: Int get() {
val camera = Minecraft.getMinecraft().renderViewEntity ?: return 0
val camera = Minecraft.getInstance().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))
abs(pos.y - MathHelper.floor(camera.posY)) +
abs(pos.z - MathHelper.floor(camera.posZ))
}
}
}

View File

@@ -3,13 +3,16 @@ package mods.octarinecore.client.render
import mods.octarinecore.PI2
import mods.octarinecore.common.Double3
import net.minecraft.client.Minecraft
import net.minecraft.client.particle.IParticleRenderType
import net.minecraft.client.particle.Particle
import net.minecraft.client.particle.SpriteTexturedParticle
import net.minecraft.client.renderer.ActiveRenderInfo
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.entity.Entity
import net.minecraft.world.World
abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : Particle(world, x, y, z) {
abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : SpriteTexturedParticle(world, x, y, z) {
companion object {
@JvmStatic val sin = Array(64) { idx -> Math.sin(PI2 / 64.0 * idx) }
@@ -21,8 +24,8 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) :
val prevPos = Double3.zero
val velocity = Double3.zero
override fun onUpdate() {
super.onUpdate()
override fun tick() {
super.tick()
currentPos.setTo(posX, posY, posZ)
prevPos.setTo(prevPosX, prevPosY, prevPosZ)
velocity.setTo(motionX, motionY, motionZ)
@@ -41,12 +44,12 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) :
abstract val isValid: Boolean
/** Add the particle to the effect renderer if it is valid. */
fun addIfValid() { if (isValid) Minecraft.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.second.setTo(rotX - rotXY, -rotZ, rotYZ - rotXZ)
render(worldRenderer, partialTickTime)
render(buffer, partialTicks)
}
/**
* Render a particle quad.
@@ -67,7 +70,7 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) :
prevPos: Double3 = this.prevPos,
size: Double = particleScale.toDouble(),
rotation: Int = 0,
icon: TextureAtlasSprite = particleTexture,
icon: TextureAtlasSprite = sprite,
isMirrored: Boolean = false,
alpha: Float = this.particleAlpha) {
@@ -115,7 +118,8 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) :
.endVertex()
}
override fun getFXLayer() = 1
// override fun getFXLayer() = 1
override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
fun setColor(color: Int) {
particleBlue = (color and 255) / 256.0f

View File

@@ -3,7 +3,7 @@ package mods.octarinecore.client.render
import mods.octarinecore.common.*
import mods.octarinecore.minmax
import mods.octarinecore.replace
import net.minecraft.util.EnumFacing
import net.minecraft.util.Direction
import java.lang.Math.max
import java.lang.Math.min
@@ -59,7 +59,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
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: 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)) }
@@ -123,7 +123,7 @@ class Model() {
)
}
fun faceQuad(face: EnumFacing): Quad {
fun faceQuad(face: Direction): Quad {
val base = face.vec * 0.5
val top = faceCorners[face.ordinal].topLeft.first.vec * 0.5
val left = faceCorners[face.ordinal].topLeft.second.vec * 0.5

View File

@@ -5,8 +5,8 @@ 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.*
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
typealias QuadIconResolver = (ShadingContext, Int, Quad) -> TextureAtlasSprite?
typealias PostProcessLambda = RenderVertex.(ShadingContext, Int, Quad, Int, Vertex) -> Unit
@@ -43,7 +43,7 @@ class ModelRenderer : ShadingContext() {
aoEnabled = Minecraft.isAmbientOcclusionEnabled()
// make sure we have space in the buffer for our quads plus one
worldRenderer.ensureSpaceForQuads(model.quads.size + 1)
// worldRenderer.ensureSpaceForQuads(model.quads.size + 1)
model.quads.forEachIndexed { quadIdx, quad ->
if (quadFilter(quadIdx, quad)) {
@@ -81,18 +81,18 @@ open class ShadingContext {
var aoEnabled = Minecraft.isAmbientOcclusionEnabled()
val aoFaces = Array(6) { AoFaceData(forgeDirs[it]) }
val EnumFacing.aoMultiplier: Float get() = when(this) {
val Direction.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 }) {
fun updateShading(offset: Int3, predicate: (Direction) -> Boolean = { true }) {
forgeDirs.forEach { if (predicate(it)) aoFaces[it.ordinal].update(offset, multiplier = it.aoMultiplier) }
}
fun aoShading(face: EnumFacing, corner1: EnumFacing, corner2: EnumFacing) =
fun aoShading(face: Direction, corner1: Direction, corner2: Direction) =
aoFaces[face.rotate(rotation).ordinal][corner1.rotate(rotation), corner2.rotate(rotation)]
fun blockData(offset: Int3) = blockContext.blockData(offset.rotate(rotation))
@@ -169,13 +169,13 @@ class RenderVertex() {
}
fun BufferBuilder.ensureSpaceForQuads(num: Int) {
rawIntBuffer.position(bufferSize)
growBuffer(num * vertexFormat.size)
}
//fun BufferBuilder.ensureSpaceForQuads(num: Int) {
// rawIntBuffer.position(bufferSize)
// growBuffer(num * vertexFormat.size)
//}
val allFaces: (EnumFacing) -> Boolean = { true }
val topOnly: (EnumFacing) -> Boolean = { it == UP }
val allFaces: (Direction) -> Boolean = { true }
val topOnly: (Direction) -> Boolean = { it == UP }
/** Perform no post-processing */
val noPost: PostProcessLambda = { _, _, _, _, _ -> }

View File

@@ -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
}

View File

@@ -0,0 +1,54 @@
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
//}
inline fun BlockContext.offset(modded: Int3, target: Int3) = BlockContext(OffsetEnvBlockReader(reader!!, pos + modded, pos + target), pos)

View File

@@ -1,7 +1,7 @@
package mods.octarinecore.client.render
import mods.octarinecore.common.*
import net.minecraft.util.EnumFacing
import net.minecraft.util.Direction
const val defaultCornerDimming = 0.5f
@@ -10,17 +10,17 @@ const val defaultEdgeDimming = 0.8f
// ================================
// Shader instantiation lambdas
// ================================
fun cornerAo(fallbackAxis: EnumFacing.Axis): CornerShaderFactory = { face, dir1, dir2 ->
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: EnumFacing, dir1: EnumFacing, dir2: EnumFacing -> FaceFlat(face) }
fun cornerAoTri(func: (AoData, AoData)-> AoData) = { face: EnumFacing, dir1: EnumFacing, dir2: EnumFacing ->
val cornerFlat = { face: Direction, dir1: Direction, dir2: Direction -> FaceFlat(face) }
fun cornerAoTri(func: (AoData, AoData)-> AoData) = { 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: EnumFacing.Axis, weight: Float, dimming: Float): CornerShaderFactory = { dir1, dir2, dir3 ->
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)
@@ -34,7 +34,7 @@ object NoShader : Shader {
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 {
class CornerSingleFallback(val face: Direction, val dir1: Direction, val dir2: Direction, val fallbackDir: Direction, val fallbackDimming: Float = defaultCornerDimming) : Shader {
val offset = Int3(fallbackDir)
override fun shade(context: ShadingContext, vertex: RenderVertex) {
val shading = context.aoShading(face, dir1, dir2)
@@ -56,7 +56,7 @@ inline fun accumulate(v1: AoData?, v2: AoData?, func: ((AoData, AoData)-> AoData
return null
}
class CornerTri(val face: EnumFacing, val dir1: EnumFacing, val dir2: EnumFacing,
class CornerTri(val face: Direction, val dir1: Direction, val dir2: Direction,
val func: ((AoData, AoData)-> AoData)) : Shader {
override fun shade(context: ShadingContext, vertex: RenderVertex) {
var acc = accumulate(
@@ -72,15 +72,15 @@ class CornerTri(val face: EnumFacing, val dir1: EnumFacing, val dir2: EnumFacing
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 {
class EdgeInterpolateFallback(val face: Direction, val edgeDir: Direction, 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)
val shadingP = context.aoShading(face, edgeDir, (edgeAxis to Direction.AxisDirection.POSITIVE).face)
val shadingN = context.aoShading(face, edgeDir, (edgeAxis to Direction.AxisDirection.NEGATIVE).face)
if (!shadingP.valid && !shadingN.valid) context.blockData(offset).let {
return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming)
}
@@ -91,7 +91,7 @@ class EdgeInterpolateFallback(val face: EnumFacing, val edgeDir: EnumFacing, val
override fun rotate(rot: Rotation) = EdgeInterpolateFallback(face.rotate(rot), edgeDir.rotate(rot), pos)
}
class CornerInterpolateDimming(val face1: EnumFacing, val face2: EnumFacing, val edgeDir: EnumFacing,
class CornerInterpolateDimming(val face1: Direction, val face2: Direction, val edgeDir: Direction,
val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming) : Shader {
val offset = Int3(edgeDir)
override fun shade(context: ShadingContext, vertex: RenderVertex) {
@@ -111,7 +111,7 @@ class CornerInterpolateDimming(val face1: EnumFacing, val face2: EnumFacing, val
CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming)
}
class FaceCenter(val face: EnumFacing): Shader {
class FaceCenter(val face: Direction): Shader {
override fun shade(context: ShadingContext, vertex: RenderVertex) {
vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f;
val b = IntArray(4)
@@ -128,7 +128,7 @@ class FaceCenter(val face: EnumFacing): Shader {
override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot))
}
class FaceFlat(val face: EnumFacing): Shader {
class FaceFlat(val face: Direction): Shader {
override fun shade(context: ShadingContext, vertex: RenderVertex) {
val color = context.blockData(Int3.zero).color
vertex.shade(context.blockData(face.offset).brightness, color)

View File

@@ -1,17 +1,20 @@
package mods.octarinecore.client.render
import mods.betterfoliage.BetterFoliage
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 net.minecraft.client.renderer.BlockModelRenderer.AmbientOcclusionFace
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import org.apache.logging.log4j.Level
import java.lang.Math.min
import java.util.*
typealias EdgeShaderFactory = (EnumFacing, EnumFacing) -> Shader
typealias CornerShaderFactory = (EnumFacing, EnumFacing, EnumFacing) -> Shader
typealias EdgeShaderFactory = (Direction, Direction) -> Shader
typealias CornerShaderFactory = (Direction, Direction, Direction) -> Shader
typealias ShaderFactory = (Quad, Vertex) -> Shader
/** Holds shading values for block corners as calculated by vanilla Minecraft rendering. */
@@ -46,12 +49,13 @@ class AoData() {
}
}
class AoFaceData(val face: EnumFacing) {
val ao = Refs.AmbientOcclusionFace.element!!.let {
if (allAvailable(Refs.OptifineClassTransformer)) it.getDeclaredConstructor().newInstance()
else it.getDeclaredConstructor(Refs.BlockModelRenderer.element!!)
.newInstance(BlockModelRenderer(Minecraft.getMinecraft().blockColors))
} as BlockModelRenderer.AmbientOcclusionFace
class AoFaceData(val face: Direction) {
companion object {
private val aoFactory = BlockModelRenderer.AmbientOcclusionFace::class.java.let {
it.getDeclaredConstructor(BlockModelRenderer::class.java).apply { isAccessible = true }
}.let { ctor -> { ctor.newInstance(Minecraft.getInstance().blockRendererDispatcher.blockModelRenderer) } }
}
val ao = aoFactory()
val top = faceCorners[face.ordinal].topLeft.first
val left = faceCorners[face.ordinal].topLeft.second
@@ -74,11 +78,11 @@ class AoFaceData(val face: EnumFacing) {
val quadBounds: FloatArray = FloatArray(12)
val flags = BitSet(3).apply { set(0) }
ao.updateVertexBrightness(ctx.world, blockState, ctx.pos + offset, face, quadBounds, flags)
ao.updateVertexBrightness(ctx.reader!!, 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 {
operator fun get(dir1: Direction, dir2: Direction): AoData {
val isTop = top == dir1 || top == dir2
val isLeft = left == dir1 || left == dir2
return if (isTop) {
@@ -143,7 +147,7 @@ interface Shader {
* @param[corner] shader instantiation lambda for corner vertices
* @param[edge] shader instantiation lambda for edge midpoint vertices
*/
fun faceOrientedAuto(overrideFace: EnumFacing? = null,
fun faceOrientedAuto(overrideFace: Direction? = null,
corner: CornerShaderFactory? = null,
edge: EdgeShaderFactory? = null) =
fun(quad: Quad, vertex: Vertex): Shader {
@@ -170,7 +174,7 @@ fun faceOrientedAuto(overrideFace: EnumFacing? = null,
* @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,
fun edgeOrientedAuto(overrideEdge: Pair<Direction, Direction>? = null,
corner: CornerShaderFactory) =
fun(quad: Quad, vertex: Vertex): Shader {
val edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first
@@ -181,11 +185,11 @@ fun edgeOrientedAuto(overrideEdge: Pair<EnumFacing, EnumFacing>? = null,
return corner(nearestFace, nearestCorner.first, nearestCorner.second)
}
fun faceOrientedInterpolate(overrideFace: EnumFacing? = null) =
fun faceOrientedInterpolate(overrideFace: Direction? = 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 vec = Double3((axis to Direction.AxisDirection.POSITIVE).face)
val pos = vertex.xyz.dot(vec)
EdgeInterpolateFallback(face, edgeDir, pos)
})

View File

@@ -1,13 +1,23 @@
package mods.octarinecore.client.resource
import net.minecraft.util.ResourceLocation
import net.minecraftforge.resource.VanillaResourceType
import java.awt.image.BufferedImage
import java.io.InputStream
import java.lang.Math.*
class CenteringTextureGenerator(domain: String, val aspectWidth: Int, val aspectHeight: Int) : TextureGenerator(domain) {
class CenteringTextureGenerator(
domain: String,
val aspectWidth: Int,
val aspectHeight: Int
) : GeneratorBase<ResourceLocation>(domain, VanillaResourceType.TEXTURES) {
override fun generate(params: ParameterList): BufferedImage? {
val target = targetResource(params)!!
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
override val locationMapper = Atlas.BLOCKS::unwrap
override fun exists(key: ResourceLocation) = resourceManager.hasResource(Atlas.BLOCKS.wrap(key))
override fun get(key: ResourceLocation): InputStream? {
val baseTexture = resourceManager[Atlas.BLOCKS.wrap(key)]?.loadImage() ?: return null
val frameWidth = baseTexture.width
val frameHeight = baseTexture.width * aspectHeight / aspectWidth
@@ -28,6 +38,6 @@ class CenteringTextureGenerator(domain: String, val aspectWidth: Int, val aspect
graphics.drawImage(resultFrame, 0, size * frame, null)
}
return resultTexture
return resultTexture.asStream
}
}

View File

@@ -1,57 +1,51 @@
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.common.plus
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.block.BlockState
import net.minecraft.client.renderer.BlockModelShapes
import net.minecraft.client.renderer.model.*
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.util.ResourceLocation
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.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 net.minecraftforge.eventbus.api.Event
import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.registries.ForgeRegistries
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
class LoadModelDataEvent(val loader: ModelLoader) : Event()
class LoadModelDataEvent(val bakery: ModelBakery) : 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?
operator fun get(ctx: BlockContext) = get(ctx.blockState(Int3.zero), ctx.reader!!, ctx.pos)
operator fun get(ctx: BlockContext, offset: Int3) = get(ctx.blockState(offset), ctx.reader!!, ctx.pos + offset)
operator fun get(state: BlockState, world: IBlockReader, pos: BlockPos): T?
}
interface ModelRenderDataExtractor<T> {
fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<T>?
fun processModel(state: BlockState, modelLoc: ModelResourceLocation, models: List<Pair<IUnbakedModel, ResourceLocation>>): ModelRenderKey<T>?
}
interface ModelRenderKey<T> {
val logger: Logger?
fun onPreStitch(atlas: TextureMap) {}
fun resolveSprites(atlas: TextureMap): T
fun onPreStitch(event: TextureStitchEvent.Pre) {}
fun resolveSprites(atlas: AtlasTexture): 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] }
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = subRegistries.findFirst { it[state, world, pos] }
fun addRegistry(registry: ModelRenderRegistry<T>) {
subRegistries.add(registry)
MinecraftForge.EVENT_BUS.register(registry)
}
}
@@ -59,40 +53,41 @@ abstract class ModelRenderRegistryBase<T> : ModelRenderRegistry<T>, ModelRenderD
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>()
val stateToKey = mutableMapOf<BlockState, ModelRenderKey<T>>()
var stateToValue = mapOf<BlockState, T>()
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos) = stateToValue[state]
override fun get(state: BlockState, world: IBlockReader, 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 stateMappings = ForgeRegistries.BLOCKS.flatMap { block ->
block.stateContainer.validStates.map { state -> state to BlockModelShapes.getModelLocation(state) }
}
val stateModels = Refs.stateModels.get(event.loader) as Map<ModelResourceLocation, IModel>
// val unbakedModels = (Refs.unbakedModels.get(event.loader) as Map<*, *>).filter { it.value is IUnbakedModel } as Map<ModelResourceLocation, IUnbakedModel>
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)
}
val missingModel = event.bakery.getUnbakedModel(ModelBakery.MODEL_MISSING)
stateMappings.forEach { (state, stateModelResource) ->
val allModels = event.bakery.let { it.unwrapVariants(it.getUnbakedModel(stateModelResource) to stateModelResource) }.filter { it.second != missingModel }
try {
processModel(state, stateModelResource, allModels)?.let { stateToKey[state] = it }
} catch (e: Exception) {
logger?.warn("Exception while trying to process model ${stateModelResource}", e)
}
}
}
@SubscribeEvent(priority = EventPriority.LOW)
fun handlePreStitch(event: TextureStitchEvent.Pre) {
stateToKey.forEach { (_, key) -> key.onPreStitch(event.map) }
if (event.map.basePath != "textures") return
stateToKey.forEach { (_, key) -> key.onPreStitch(event) }
}
@SubscribeEvent(priority = EventPriority.LOW)
fun handlePostStitch(event: TextureStitchEvent.Post) {
if (event.map.basePath != "textures") return
stateToValue = stateToKey.mapValues { (_, key) -> key.resolveSprites(event.map) }
stateToKey.clear()
}
@@ -103,23 +98,23 @@ 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>? {
override fun processModel(state: BlockState, modelLoc: ModelResourceLocation, models: List<Pair<IUnbakedModel, ResourceLocation>>): 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()) {
if (models.isEmpty()) {
logger?.log(Level.DEBUG, "$logName: no models found")
return null
}
allModels.forEach { blockLoc ->
val modelMatch = modelTextures.firstOrNull { blockLoc.derivesFrom(it.modelLocation) }
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) {
logger?.log(Level.DEBUG, "$logName: model ${blockLoc.second} matches ${modelMatch.modelLocation}")
logger?.log(Level.DEBUG, "$logName: model ${model} matches ${modelMatch.modelLocation}")
val textures = modelMatch.textureNames.map { it to blockLoc.first.resolveTextureName(it) }
val textures = modelMatch.textureNames.map { it to model.resolveTextureName(it) }
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
logger?.log(Level.DEBUG, "$logName: textures [$texMapString]")
@@ -132,5 +127,11 @@ abstract class ModelRenderRegistryConfigurable<T> : ModelRenderRegistryBase<T>()
return null
}
abstract fun processModel(state: IBlockState, textures: List<String>) : ModelRenderKey<T>?
}
abstract fun processModel(state: BlockState, textures: List<String>) : ModelRenderKey<T>?
}
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()
}

View File

@@ -1,65 +1,111 @@
package mods.octarinecore.client.resource
import mods.octarinecore.metaprog.reflectField
import net.minecraft.client.resources.IResourcePack
import net.minecraft.client.resources.data.IMetadataSection
import net.minecraft.client.resources.data.IMetadataSectionSerializer
import net.minecraft.client.resources.data.MetadataSerializer
import net.minecraft.client.resources.data.PackMetadataSection
import net.minecraft.client.Minecraft
import net.minecraft.resources.*
import net.minecraft.resources.ResourcePackType.CLIENT_RESOURCES
import net.minecraft.resources.data.IMetadataSectionSerializer
import net.minecraft.resources.data.PackMetadataSection
import net.minecraft.util.ResourceLocation
import net.minecraft.util.text.TextComponentString
import net.minecraftforge.fml.client.FMLClientHandler
import net.minecraft.util.text.StringTextComponent
import net.minecraftforge.resource.IResourceType
import net.minecraftforge.resource.ISelectiveResourceReloadListener
import java.io.InputStream
import java.util.*
import java.util.function.Predicate
import java.util.function.Supplier
/**
* [IResourcePack] containing generated resources. Adds itself to the default resource pack list
* of Minecraft, so it is invisible and always active.
* [IResourcePack] containing generated resources
*
* @param[name] Name of the resource pack
* @param[generators] List of resource generators
*/
class GeneratorPack(val name: String, vararg val generators: GeneratorBase) : IResourcePack {
class GeneratorPack(val packName: String, val packDescription: String, val packImage: String) : IResourcePack {
fun inject() {
FMLClientHandler.instance().reflectField<MutableList<IResourcePack>>("resourcePackList")!!.add(this)
val generators = mutableListOf<GeneratorBase<*>>()
val packFinder = Finder(this)
override fun getName() = packName
override fun getResourceNamespaces(type: ResourcePackType) = if (type == CLIENT_RESOURCES) generators.map { it.namespace }.toSet() else emptySet()
override fun <T : Any?> getMetadata(deserializer: IMetadataSectionSerializer<T>): T? {
if (deserializer.sectionName != "pack") return null
return PackMetadataSection(StringTextComponent(packDescription), 4) as? T
}
override fun getPackName() = name
override fun getPackImage() = null
override fun getResourceDomains() = HashSet(generators.map { it.domain })
override fun <T : IMetadataSection?> getPackMetadata(serializer: MetadataSerializer?, sectionName: String?) =
if (sectionName == "pack") PackMetadataSection(TextComponentString("Generated resources"), 1) as? T else null
override fun resourceExists(type: ResourcePackType, location: ResourceLocation?) =
location != null &&
type == CLIENT_RESOURCES &&
generators.find { it.namespace == location.namespace && it.resourceExists(location) } != null
override fun resourceExists(location: ResourceLocation?): Boolean =
if (location == null) false
else generators.find {
it.domain == location.namespace && it.resourceExists(location)
} != null
override fun getResourceStream(type: ResourcePackType, location: ResourceLocation) =
if (location != null && type == CLIENT_RESOURCES)
generators.firstOrNull { it.namespace == location.namespace && it.resourceExists(location) }?.getInputStream(location)
else
null
override fun getInputStream(location: ResourceLocation?): InputStream? =
if (location == null) null
else generators.filter {
it.domain == location.namespace && it.resourceExists(location)
}.map { it.getInputStream(location) }
.filterNotNull().first()
override fun getAllResourceLocations(type: ResourcePackType, pathIn: String, maxDepth: Int, filter: Predicate<String>) = emptyList<ResourceLocation>()
override fun getRootResourceStream(fileName: String) = fileName.let { if (it == "pack.png") packImage else it }.let { this::class.java.classLoader.getResourceAsStream(it) }
override fun close() {}
// override fun <T : IMetadataSection?> getPackMetadata(p_135058_1_: IMetadataSerializer?, p_135058_2_: String?): T {
// return if (type == "pack") PackMetadataSection(ChatComponentText("Generated resources"), 1) else null
// }
class Finder(val pack: GeneratorPack) : IPackFinder {
override fun <T : ResourcePackInfo> addPackInfosToMap(nameToPackMap: MutableMap<String, T>, packInfoFactory: ResourcePackInfo.IFactory<T>) {
val packInfo = ResourcePackInfo.createResourcePack(
pack.packName,
true,
Supplier { pack } as Supplier<IResourcePack>,
packInfoFactory,
ResourcePackInfo.Priority.BOTTOM
)
nameToPackMap[pack.packName] = packInfo!!
}
}
}
/**
* Abstract base class for resource generators
*
* @param[domain] Resource domain of generator
* @param[namespace] Resource namespace of generator
* @param[generatedType] IResourceType of generated resources
*/
abstract class GeneratorBase(val domain: String) {
/** @see [IResourcePack.resourceExists] */
abstract fun resourceExists(location: ResourceLocation?): Boolean
abstract class GeneratorBase<T>(val namespace: String, val generatedType: IResourceType) : ISelectiveResourceReloadListener {
val keyToId = mutableMapOf<T, String>()
val idToKey = mutableMapOf<String, T>()
open val locationMapper: (ResourceLocation)->ResourceLocation = { it }
init { resourceManager.addReloadListener(this) }
abstract fun get(key: T): InputStream?
abstract fun exists(key: T): Boolean
fun registerResource(key: T): ResourceLocation {
keyToId[key]?.let { return ResourceLocation(namespace, it) }
val id = UUID.randomUUID().toString()
keyToId[key] = id
idToKey[id] = key
return ResourceLocation(namespace, id)
}
fun resourceExists(location: ResourceLocation?): Boolean {
val key = location?.let { locationMapper(it) }?.path?.let { idToKey[it] } ?: return false
return exists(key)
}
fun getInputStream(location: ResourceLocation?): InputStream? {
val key = location?.let { locationMapper(it) }?.path?.let { idToKey[it] } ?: return null
return get(key)
}
open fun onReload(resourceManager: IResourceManager) {
keyToId.clear()
idToKey.clear()
}
override fun onResourceManagerReload(resourceManager: IResourceManager, resourcePredicate: Predicate<IResourceType>) {
if (resourcePredicate.test(generatedType)) onReload(resourceManager)
}
/** @see [IResourcePack.getInputStream] */
abstract fun getInputStream(location: ResourceLocation?): InputStream?
}
/**
@@ -70,6 +116,7 @@ abstract class GeneratorBase(val domain: String) {
* @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
@@ -117,4 +164,5 @@ abstract class ParameterBasedGenerator(domain: String) : GeneratorBase(domain) {
resourceExists(ParameterList.fromString(location?.path ?: ""))
override fun getInputStream(location: ResourceLocation?) =
getInputStream(ParameterList.fromString(location?.path ?: ""))
}
}
*/

View File

@@ -3,29 +3,42 @@ package mods.octarinecore.client.resource
import mods.octarinecore.client.render.Model
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3
import mods.octarinecore.stripEnd
import mods.octarinecore.stripStart
import net.minecraft.client.renderer.texture.AtlasTexture
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.util.math.MathHelper
import net.minecraft.world.World
import net.minecraft.world.gen.NoiseGeneratorSimplex
import net.minecraft.world.IWorld
import net.minecraft.world.gen.SimplexNoiseGenerator
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.client.event.ConfigChangedEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
import java.util.*
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
// ============================
interface IStitchListener {
fun onPreStitch(atlas: TextureMap)
fun onPostStitch(atlas: TextureMap)
fun onPreStitch(event: TextureStitchEvent.Pre)
fun onPostStitch(atlas: AtlasTexture)
}
interface IConfigChangeListener { fun onConfigChange() }
interface IWorldLoadListener { fun onWorldLoad(world: World) }
interface IWorldLoadListener { fun onWorldLoad(world: IWorld) }
/**
* Base class for declarative resource handling.
@@ -34,7 +47,11 @@ interface IWorldLoadListener { fun onWorldLoad(world: World) }
*
* @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>()
open fun afterPreStitch() {}
@@ -43,15 +60,15 @@ open class ResourceHandler(val modId: String) {
// ============================
// Self-registration
// ============================
init { MinecraftForge.EVENT_BUS.register(this) }
init { modBus.register(this) }
// ============================
// Resource declarations
// ============================
fun iconStatic(domain: String, path: String) = IconHolder(domain, path).apply { resources.add(this) }
fun iconStatic(location: ResourceLocation) = iconStatic(location.namespace, location.path)
fun iconSet(domain: String, pathPattern: String) = IconSet(domain, pathPattern).apply { this@ResourceHandler.resources.add(this) }
fun iconSet(location: ResourceLocation) = iconSet(location.namespace, location.path)
fun iconStatic(location: ()->ResourceLocation) = IconHolder(location).apply { resources.add(this) }
fun iconStatic(location: ResourceLocation) = iconStatic { location }
fun iconStatic(domain: String, path: String) = iconStatic(ResourceLocation(domain, path))
fun iconSet(targetAtlas: Atlas = Atlas.BLOCKS, location: (Int)->ResourceLocation) = IconSet(targetAtlas, location).apply { this@ResourceHandler.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 vectorSet(num: Int, init: (Int)-> Double3) = VectorSet(num, init).apply { resources.add(this) }
@@ -62,19 +79,21 @@ open class ResourceHandler(val modId: String) {
// ============================
@SubscribeEvent
fun onPreStitch(event: TextureStitchEvent.Pre) {
resources.forEach { (it as? IStitchListener)?.onPreStitch(event.map) }
if (!targetAtlas.matches(event)) return
resources.forEach { (it as? IStitchListener)?.onPreStitch(event) }
afterPreStitch()
}
@SubscribeEvent
fun onPostStitch(event: TextureStitchEvent.Post) {
if (!targetAtlas.matches(event)) return
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() }
fun handleModConfigChange(event: ModConfig.ModConfigEvent) {
resources.forEach { (it as? IConfigChangeListener)?.onConfigChange() }
}
@SubscribeEvent
@@ -85,33 +104,38 @@ open class ResourceHandler(val modId: String) {
// ============================
// Resource container classes
// ============================
class IconHolder(val domain: String, val name: String) : IStitchListener {
val iconRes = ResourceLocation(domain, name)
class IconHolder(val location: ()->ResourceLocation) : IStitchListener {
var iconRes: ResourceLocation? = null
var icon: TextureAtlasSprite? = null
override fun onPreStitch(atlas: TextureMap) { atlas.registerSprite(iconRes) }
override fun onPostStitch(atlas: TextureMap) { icon = atlas[iconRes] }
override fun onPreStitch(event: TextureStitchEvent.Pre) {
iconRes = location()
event.addSprite(iconRes)
}
override fun onPostStitch(atlas: AtlasTexture) {
icon = atlas[iconRes!!]
}
}
class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
var model: Model = Model().apply(init)
var model: Model = Model()
override fun onConfigChange() { model = Model().apply(init) }
}
class IconSet(val domain: String, val namePattern: String) : IStitchListener {
class IconSet(val targetAtlas: Atlas, val location: (Int)->ResourceLocation) : IStitchListener {
val resources = arrayOfNulls<ResourceLocation>(16)
val icons = arrayOfNulls<TextureAtlasSprite>(16)
var num = 0
override fun onPreStitch(atlas: TextureMap) {
override fun onPreStitch(event: TextureStitchEvent.Pre) {
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) }
val loc = location(idx)
if (resourceManager[targetAtlas.wrap(loc)] != null) resources[num++] = loc.apply { event.addSprite(this) }
}
}
override fun onPostStitch(atlas: TextureMap) {
override fun onPostStitch(atlas: AtlasTexture) {
(0 until num).forEach { idx -> icons[idx] = atlas[resources[idx]!!] }
}
@@ -119,20 +143,20 @@ class IconSet(val domain: String, val namePattern: String) : IStitchListener {
}
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) } } }
operator fun get(idx: Int) = models[idx % num]
}
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) } }
operator fun get(idx: Int) = models[idx % num]
}
class SimplexNoise() : IWorldLoadListener {
var noise = NoiseGeneratorSimplex()
override fun onWorldLoad(world: World) { noise = NoiseGeneratorSimplex(Random(world.worldInfo.seed))
class SimplexNoise : IWorldLoadListener {
var noise = SimplexNoiseGenerator(Random())
override fun onWorldLoad(world: IWorld) { noise = SimplexNoiseGenerator(Random(world.worldInfo.seed))
}
operator fun get(x: Int, z: Int) = MathHelper.floor((noise.getValue(x.toDouble(), z.toDouble()) + 1.0) * 32.0)
operator fun get(pos: Int3) = get(pos.x, pos.z)

View File

@@ -1,7 +1,7 @@
package mods.octarinecore.client.resource
import mods.octarinecore.client.resource.ResourceType.*
import net.minecraft.client.resources.IResource
import net.minecraft.resources.IResource
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage
import java.io.InputStream
@@ -20,6 +20,7 @@ enum class ResourceType {
*
* @param[domain] Resource domain of generator
*/
/*
abstract class TextureGenerator(domain: String) : ParameterBasedGenerator(domain) {
/**
@@ -29,7 +30,7 @@ abstract class TextureGenerator(domain: String) : ParameterBasedGenerator(domain
* @param[extraParams] additional parameters of the generated texture
*/
fun generatedResource(iconName: String, vararg extraParams: Pair<String, Any>) = ResourceLocation(
domain,
namespace,
textureLocation(iconName).let {
ParameterList(
mapOf("dom" to it.namespace, "path" to it.path) +
@@ -83,4 +84,6 @@ abstract class TextureGenerator(domain: String) : ParameterBasedGenerator(domain
while(size > 2) { sizes.add(size); size /= 2 }
return sizes.map { resourceManager[maskPath(it)] }.filterNotNull().firstOrNull()
}
}
}
*/

View File

@@ -4,16 +4,17 @@ package mods.octarinecore.client.resource
import mods.betterfoliage.loader.Refs
import mods.octarinecore.PI2
import mods.octarinecore.client.render.HSB
import mods.octarinecore.metaprog.reflectField
import mods.octarinecore.stripEnd
import mods.octarinecore.stripStart
import mods.octarinecore.tryDefault
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.TextureMap
import net.minecraft.client.resources.IResource
import net.minecraft.client.resources.IResourceManager
import net.minecraft.client.resources.SimpleReloadableResourceManager
import net.minecraft.resources.IResource
import net.minecraft.resources.IResourceManager
import net.minecraft.resources.SimpleReloadableResourceManager
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.IModel
import java.awt.image.BufferedImage
@@ -24,8 +25,9 @@ import java.lang.Math.*
import javax.imageio.ImageIO
/** Concise getter for the Minecraft resource manager. */
val resourceManager: SimpleReloadableResourceManager get() =
Minecraft.getMinecraft().resourceManager as SimpleReloadableResourceManager
val resourceManager: SimpleReloadableResourceManager
get() =
Minecraft.getInstance().resourceManager as SimpleReloadableResourceManager
/** Append a string to the [ResourceLocation]'s path. */
operator fun ResourceLocation.plus(str: String) = ResourceLocation(namespace, path + str)
@@ -36,8 +38,10 @@ operator fun IResourceManager.get(domain: String, path: String): IResource? = ge
operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryDefault(null) { getResource(location) }
/** Index operator to get a texture sprite. */
operator fun TextureMap.get(name: String): TextureAtlasSprite? = getTextureExtry(ResourceLocation(name).toString())
operator fun TextureMap.get(res: ResourceLocation): TextureAtlasSprite? = getTextureExtry(res.toString())
operator fun AtlasTexture.get(res: ResourceLocation): TextureAtlasSprite? = getSprite(res)
operator fun AtlasTexture.get(name: String): TextureAtlasSprite? = getSprite(ResourceLocation(name))
val missingSprite: TextureAtlasSprite get() = MissingTextureSprite.func_217790_a()
/** Load an image resource. */
fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream)
@@ -65,18 +69,18 @@ val BufferedImage.asStream: InputStream get() =
* and the result transformed back to the RGB color space.
*/
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
// 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 sumHueX = 0.0
var sumHueY = 0.0
var sumSaturation = 0.0f
var sumBrightness = 0.0f
for (x in 0..image.width - 1)
for (y in 0..image.height - 1) {
val pixel = image[x, y]
for (x in 0..width - 1)
for (y in 0..height - 1) {
val pixel = getPixelRGBA(0, x, y);
val alpha = (pixel shr 24) and 255
val hsb = HSB.fromColor(pixel)
if (alpha == 255) {
@@ -101,27 +105,9 @@ fun textureLocation(iconName: String) = ResourceLocation(iconName).let {
else ResourceLocation(it.namespace, "textures/${it.path}")
}
@Suppress("UNCHECKED_CAST")
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 {
fun Pair<BlockModel, ResourceLocation>.derivesFrom(targetLocation: ResourceLocation): Boolean {
if (second.stripStart("models/") == targetLocation) return true
if (first.parent != null && first.parentLocation != null)
return Pair(first.parent, first.parentLocation!!).derivesFrom(targetLocation)
return Pair(first.parent!!, first.parentLocation!!).derivesFrom(targetLocation)
return false
}

View File

@@ -1,11 +1,11 @@
package mods.octarinecore.common
import mods.octarinecore.cross
import net.minecraft.util.EnumFacing
import net.minecraft.util.EnumFacing.*
import net.minecraft.util.EnumFacing.Axis.*
import net.minecraft.util.EnumFacing.AxisDirection.NEGATIVE
import net.minecraft.util.EnumFacing.AxisDirection.POSITIVE
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import net.minecraft.util.Direction.Axis.*
import net.minecraft.util.Direction.AxisDirection.NEGATIVE
import net.minecraft.util.Direction.AxisDirection.POSITIVE
import net.minecraft.util.math.BlockPos
// ================================
@@ -13,19 +13,19 @@ import net.minecraft.util.math.BlockPos
// ================================
val axes = listOf(X, Y, Z)
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 forgeDirs = EnumFacing.values()
val forgeDirs = Direction.values()
val forgeDirsHorizontal = listOf(NORTH, SOUTH, EAST, WEST)
val forgeDirOffsets = forgeDirs.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;
Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN;
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 }
val EnumFacing.offset: Int3 get() = forgeDirOffsets[ordinal]
val Direction.offset: Int3 get() = forgeDirOffsets[ordinal]
/** Old ForgeDirection rotation matrix yanked from 1.7.10 */
val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
@@ -40,15 +40,15 @@ val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
// ================================
// 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)
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)
/** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */
data class Double3(var x: Double, var y: Double, var z: Double) {
constructor(x: Float, y: Float, z: Float) : this(x.toDouble(), y.toDouble(), z.toDouble())
constructor(dir: 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 {
val zero: Double3 get() = Double3(0.0, 0.0, 0.0)
fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) =
@@ -92,13 +92,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)
val length: Double get() = Math.sqrt(x * x + y * y + z * z)
val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) }
val nearestCardinal: EnumFacing get() = nearestAngle(this, forgeDirs.asIterable()) { it.vec }.first
val nearestCardinal: Direction get() = nearestAngle(this, forgeDirs.asIterable()) { it.vec }.first
}
/** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */
data class Int3(var x: Int, var y: Int, var z: Int) {
constructor(dir: EnumFacing) : this(dir.directionVec.x, dir.directionVec.y, dir.directionVec.z)
constructor(offset: Pair<Int, EnumFacing>) : this(
constructor(dir: Direction) : this(dir.directionVec.x, dir.directionVec.y, dir.directionVec.z)
constructor(offset: Pair<Int, Direction>) : this(
offset.first * offset.second.directionVec.x,
offset.first * offset.second.directionVec.y,
offset.first * offset.second.directionVec.z
@@ -109,7 +109,7 @@ data class Int3(var x: Int, var y: Int, var z: Int) {
// immutable operations
operator fun plus(other: Int3) = Int3(x + other.x, y + other.y, z + other.z)
operator fun plus(other: Pair<Int, EnumFacing>) = Int3(
operator fun plus(other: Pair<Int, Direction>) = Int3(
x + other.first * other.second.directionVec.x,
y + other.first * other.second.directionVec.y,
z + other.first * other.second.directionVec.z
@@ -145,17 +145,17 @@ data class Int3(var x: Int, var y: Int, var z: Int) {
// ================================
// Rotation
// ================================
val EnumFacing.rotations: Array<EnumFacing> get() =
Array(6) { idx -> EnumFacing.values()[ROTATION_MATRIX[ordinal][idx]] }
fun EnumFacing.rotate(rot: Rotation) = rot.forward[ordinal]
fun rot(axis: EnumFacing) = Rotation.rot90[axis.ordinal]
val Direction.rotations: Array<Direction> get() =
Array(6) { idx -> Direction.values()[ROTATION_MATRIX[ordinal][idx]] }
fun Direction.rotate(rot: Rotation) = rot.forward[ordinal]
fun rot(axis: Direction) = Rotation.rot90[axis.ordinal]
/**
* Class representing an arbitrary rotation (or combination of rotations) around cardinal axes by 90 degrees.
* In effect, a permutation of [ForgeDirection]s.
*/
@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(
Array(6) { idx -> forward[other.forward[idx].ordinal] },
Array(6) { idx -> other.reverse[reverse[idx].ordinal] }
@@ -163,9 +163,9 @@ class Rotation(val forward: Array<EnumFacing>, val reverse: Array<EnumFacing>) {
operator fun unaryMinus() = Rotation(reverse, forward)
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 }
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 }
companion object {
@@ -204,11 +204,11 @@ 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> =
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) :
data class FaceCorners(val topLeft: Pair<Direction, Direction>,
val topRight: Pair<Direction, Direction>,
val bottomLeft: Pair<Direction, Direction>,
val bottomRight: Pair<Direction, Direction>) {
constructor(top: Direction, left: Direction) :
this(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite)
val asArray = arrayOf(topLeft, topRight, bottomLeft, bottomRight)

View File

@@ -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())
}
}

View File

@@ -1,256 +1,80 @@
@file:JvmName("DelegatingConfigKt")
package mods.octarinecore.common.config
import com.google.common.collect.LinkedListMultimap
import mods.betterfoliage.loader.Refs
import mods.octarinecore.metaprog.reflectField
import mods.octarinecore.metaprog.reflectFieldsOfType
import mods.octarinecore.metaprog.reflectDelegates
import mods.octarinecore.metaprog.reflectNestedObjects
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.common.config.ConfigElement
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 net.minecraftforge.common.ForgeConfigSpec
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
// ============================
// Configuration object base
// ============================
/**
* 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) {
open class DelegatingConfig(val modId: String, val langPrefix: String) {
fun build() = ForgeConfigSpec.Builder().apply { ConfigBuildContext(langPrefix, emptyList(), this).addCategory(this@DelegatingConfig) }.build()
}
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. */
var config: Configuration? = null
val rootGuiElements = mutableListOf<IConfigElement>()
/** Attach this config object to the given [Configuration] and update all properties. */
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)
}
fun addCategory(configObj: Any) {
configObj.reflectNestedObjects.forEach { (name, category) ->
builder.push(name)
descend(name).addCategory(category)
builder.pop()
}
for (category in subProperties.keySet()) {
val configCategory = config.getCategory(category)
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())
configObj.reflectDelegates(ConfigDelegate::class.java).forEach { (name, delegate) ->
descend(name).apply { delegate.addToBuilder(this) }
}
}
/**
* Execute the given lambda for all config properties.
* Lambda params: (category name, property name, property instance)
*/
inline fun forEachProperty(init: (String, String, ConfigPropertyBase)->Unit) {
reflectFieldsOfType(ConfigPropertyBase::class.java).forEach { property ->
init("global", property.first.split("$")[0], property.second as ConfigPropertyBase)
}
for (category in reflectNestedObjects) {
category.second.reflectFieldsOfType(ConfigPropertyBase::class.java).forEach { property ->
init(category.first, property.first.split("$")[0], property.second as ConfigPropertyBase)
}
}
fun descend(pathName: String) = ConfigBuildContext(langPrefix, path + pathName, builder)
}
open class ConfigCategory(val comment: String? = null) {
}
abstract class ConfigDelegate<T> : ReadOnlyProperty<Any, T> {
lateinit var configValue: ForgeConfigSpec.ConfigValue<T>
var cachedValue: T? = null
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (cachedValue == null) cachedValue = configValue.get()
return cachedValue!!
}
/** Save changes to the [Configuration]. */
fun save() { if (config?.hasChanged() ?: false) config!!.save() }
/**
* Returns true if any of the given configuration elements have changed.
* 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
abstract fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder): ForgeConfigSpec.ConfigValue<T>
fun addToBuilder(ctx: ConfigBuildContext) {
val langKey = ctx.langPrefix + "." + (langPrefixOverride ?: ctx.path.joinToString("."))
ctx.builder.translation(langKey)
configValue = getConfigValue(ctx.path.last(), ctx.builder)
}
/** Called when the configuration for the mod changes. */
abstract fun onChange(event: ConfigChangedEvent.PostConfigChangedEvent)
var langPrefixOverride: String? = null
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")
}
// ============================
// Property delegates
// ============================
/** 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 DelegatingBooleanValue(val defaultValue: Boolean) : ConfigDelegate<Boolean>() {
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.define(name, defaultValue)
}
class ObsoleteConfigProperty : ConfigPropertyBase() {
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
target.getCategory(categoryName)?.remove(propertyName)
}
override val guiProperties = emptyList<Property>()
override val hasChanged: Boolean get() = false
class DelegatingIntValue(
val minValue: Int = 0,
val maxValue: Int = 1,
val defaultValue: Int = 0
) : ConfigDelegate<Int>() {
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
}
/** Delegate for a property backed by a single [Property] instance. */
abstract class ConfigPropertyDelegate<T>() : ConfigPropertyBase() {
/** Cached value of the property. */
var cached: T? = null
/** The [Property] backing this delegate. */
var property: Property? = null
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 ConfigPropertyDouble(val min: Double, val max: Double, val default: Double) :
ConfigPropertyDelegate<Double>() {
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
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)
}
/** [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())
class DelegatingDoubleValue(
val minValue: Double = 0.0,
val maxValue: Double = 1.0,
val defaultValue: Double = 0.0
) : ConfigDelegate<Double>() {
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
}
// ============================
// Delegate factory methods
// ============================
fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = ConfigPropertyDouble(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) = ConfigPropertyInt(min, max, default)
fun intList(defaults: ()->Array<Int>) = ConfigPropertyIntList(defaults)
fun boolean(default: Boolean) = ConfigPropertyBoolean(default)
fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = DelegatingDoubleValue(min, max, default)
fun int(min: Int = 0, max: Int, default: Int) = DelegatingIntValue(min, max, default)
fun boolean(default: Boolean) = DelegatingBooleanValue(default)

View File

@@ -1,8 +1,11 @@
package mods.octarinecore.common.config
import mods.octarinecore.client.resource.getLines
import mods.octarinecore.client.resource.resourceManager
import mods.octarinecore.metaprog.getJavaClass
import net.minecraft.block.Block
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Logger
interface IBlockMatcher {
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) {
override fun convertValue(line: String) = getJavaClass(line)
class ConfigurableBlockMatcher(val logger: Logger, val location: ResourceLocation) : IBlockMatcher {
val blackList = mutableListOf<Class<*>>()
val whiteList = mutableListOf<Class<*>>()
// override fun convertValue(line: String) = getJavaClass(line)
override fun matchesClass(block: Block): Boolean {
val blockClass = block.javaClass
@@ -35,16 +41,34 @@ class ConfigurableBlockMatcher(domain: String, path: String) : IBlockMatcher, Bl
whiteList.forEach { if (it.isAssignableFrom(blockClass)) return it }
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>) {
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) {
override fun convertValue(line: String): ModelTextureList? {
val elements = line.split(",")
if (elements.size < minTextures + 1) return null
return ModelTextureList(ResourceLocation(elements.first()), elements.drop(1))
class ModelTextureListConfiguration(val logger: Logger, val location: ResourceLocation) {
val modelList = mutableListOf<ModelTextureList>()
fun readDefaults() {
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 ->
val elements = line.split(",")
modelList.add(ModelTextureList(ResourceLocation(elements.first()), elements.drop(1)))
}
}
}
}

View File

@@ -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()
}
}

View File

@@ -43,6 +43,14 @@ fun Any.reflectFieldsOfType(vararg types: Class<*>) = this.javaClass.declaredFie
.map { field -> field.name to field.let { it.isAccessible = true; it.get(this) } }
.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 }
abstract class Resolvable<T> {

View File

@@ -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)
))
}

View File

@@ -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

View File

@@ -0,0 +1,6 @@
Manifest-Version: 1.0
Specification-Title: BetterFoliage
Implementation-Title: BetterFoliage
Specification-Vendor: octarine-noise
Implementation-Vendor: octarine-noise
MixinConnector: mods.betterfoliage.MixinConnector

View File

@@ -0,0 +1,20 @@
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace <init>
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178206_b #vertexColorMultiplier
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178207_c #vertexBrightness
public net.minecraft.block.BlockState$Cache
public net.minecraft.client.renderer.chunk.ChunkRenderCache field_212408_i #world
#public net.minecraft.client.renderer.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

View File

@@ -0,0 +1,14 @@
modLoader="kotlinfml"
loaderVersion="[1.4,)"
issueTrackerURL="https://github.com/octarine-noise/BetterFoliage/issues"
[[mods]]
modId="betterfoliage"
version="${version}"
displayName="Better Foliage"
authors="octarine-noise"
description='''
Leafier leaves and grassier grass.
'''
displayURL="https://www.curseforge.com/minecraft/mc-mods/better-foliage"
side="CLIENT"

View File

@@ -1,5 +1,2 @@
// Vanilla
net.minecraft.block.BlockCactus
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Vanilla.BlockCustomCactus
net.minecraft.block.CactusBlock

View File

@@ -1,10 +1,10 @@
// Vanilla
net.minecraft.block.BlockTallGrass
net.minecraft.block.BlockCrops
-net.minecraft.block.BlockReed
-net.minecraft.block.BlockDoublePlant
-net.minecraft.block.BlockCarrot
-net.minecraft.block.BlockPotato
net.minecraft.block.TallGrassBlock
net.minecraft.block.CropsBlock
-net.minecraft.block.ReedBlock
-net.minecraft.block.DoublePlantBlock
-net.minecraft.block.CarrotBlock
-net.minecraft.block.PotatoBlock
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPFlower
@@ -13,24 +13,3 @@ biomesoplenty.common.block.BlockBOPPlant
// Tinkers' Construct
tconstruct.blocks.slime.SlimeTallGrass
// Plant Mega Pack
plantmegapack.block.PMPBlockBerrybush
plantmegapack.block.PMPBlockCrops
plantmegapack.block.PMPBlockDesert
plantmegapack.block.PMPBlockFern
plantmegapack.block.PMPBlockFlowerMulti
plantmegapack.block.PMPBlockFlowerSingle
plantmegapack.block.PMPBlockForest
plantmegapack.block.PMPBlockGrass
plantmegapack.block.PMPBlockJungle
plantmegapack.block.PMPBlockMountain
plantmegapack.block.PMPBlockSavanna
plantmegapack.block.PMPBlockShrub
plantmegapack.block.PMPBlockWetlands
// Pam's HarvestCraft
com.pam.harvestcraft.BlockPamCrop
com.pam.harvestcraft.BlockPamDesertGarden
com.pam.harvestcraft.BlockPamNormalGarden
com.pam.harvestcraft.BlockPamWaterGarden

View File

@@ -1,18 +1,2 @@
// Vanilla
net.minecraft.block.BlockDirt
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPDirt
// Enhanced Biomes
enhancedbiomes.blocks.BlockSoilEB
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Terrain.BlockDirt
// Aether
net.aetherteam.aether.blocks.natural.BlockAetherDirt
com.gildedgames.aether.common.blocks.natural.BlockAetherDirt
// Tinker's Construct
slimeknights.tconstruct.world.block.BlockSlimeDirt
net.minecraft.block.DirtBlock

View File

@@ -1,18 +1,2 @@
// Vanilla
net.minecraft.block.BlockGrass
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPGrass
// Tinker's Construct
tconstruct.blocks.slime.SlimeGrass
// Enhanced Biomes
enhancedbiomes.blocks.BlockGrassEB
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Terrain.BlockGrass
// AbyssalCraft
com.shinoow.abyssalcraft.common.blocks.BlockDreadGrass
com.shinoow.abyssalcraft.common.blocks.BlockDarklandsgrass
net.minecraft.block.GrassBlock

View File

@@ -1,6 +1,3 @@
// Vanilla
block/grass,top
block/grass_block,top
block/cube_bottom_top,top
// Lithos Core
block/soil/grass_master,top

View File

@@ -1,8 +1,2 @@
// Vanilla
net.minecraft.block.BlockLeaves
// Biomes O' Plenty
biomesoplenty.common.block.BlockBOPLeaves
// Aether II
com.gildedgames.aether.common.blocks.natural.BlockAetherLeaves
net.minecraft.block.LeavesBlock

View File

@@ -1,35 +1,2 @@
// Vanilla
net.minecraft.block.BlockLog
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPLog
// Natura
com.progwml6.natura.common.block.BlockEnumLog
// Thaumcraft
thaumcraft.common.blocks.world.plants.BlockLogsTC
// Forestry
forestry.arboriculture.gadgets.BlockLog
// Extra Biomes XL
-extrabiomes.blocks.BlockMiniLog
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Flora.BlockLogVert
com.bioxx.tfc.Blocks.Flora.BlockLogNatural
// The Agricultural Revolution a.k.a. Cooking Plus
CookingPlus.blocks.CookingPlusPalmLog
CookingPlus.blocks.CookingPlusTangleLog
CookingPlus.blocks.CookingPlusTangleHeart
// IC2
ic2.core.block.BlockRubWood
// TechReborn
techreborn.blocks.BlockRubberLog
//Better With Mods
betterwithmods.blocks.BlockStump
net.minecraft.block.LogBlock

View File

@@ -1,5 +1,2 @@
// Vanilla
net.minecraft.block.BlockMycelium
// NetherEx
nex.block.BlockMycelium
net.minecraft.block.MyceliumBlock

View File

@@ -1,5 +1,2 @@
// Vanilla
net.minecraft.block.BlockNetherrack
// NetherEx
nex.block.BlockNetherrack
net.minecraft.block.NetherrackBlock

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

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