[WIP] initial Fabric port
major package refactoring
This commit is contained in:
55
src/main/kotlin/mods/betterfoliage/chunk/BlockContext.kt
Normal file
55
src/main/kotlin/mods/betterfoliage/chunk/BlockContext.kt
Normal file
@@ -0,0 +1,55 @@
|
||||
package mods.betterfoliage.chunk
|
||||
|
||||
import mods.betterfoliage.util.Int3
|
||||
import mods.betterfoliage.util.allDirections
|
||||
import mods.betterfoliage.util.offset
|
||||
import mods.betterfoliage.util.plus
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.world.ExtendedBlockView
|
||||
import net.minecraft.world.biome.Biome
|
||||
|
||||
/**
|
||||
* Represents the block being rendered. Has properties and methods to query the neighborhood of the block in
|
||||
* block-relative coordinates.
|
||||
*/
|
||||
interface BlockCtx {
|
||||
val world: ExtendedBlockView
|
||||
val pos: BlockPos
|
||||
|
||||
fun offset(dir: Direction) = offset(dir.offset)
|
||||
fun offset(offset: Int3): BlockCtx
|
||||
|
||||
val state: BlockState get() = world.getBlockState(pos)
|
||||
fun state(dir: Direction) = world.getBlockState(pos + dir.offset)
|
||||
fun state(offset: Int3) = world.getBlockState(pos + offset)
|
||||
|
||||
val biome: Biome get() = world.getBiome(pos)
|
||||
|
||||
val isNormalCube: Boolean get() = state.isSimpleFullBlock(world, pos)
|
||||
|
||||
fun shouldSideBeRendered(side: Direction) = Block.shouldDrawSide(state, world, pos, side)
|
||||
|
||||
fun isNeighborSolid(dir: Direction) = offset(dir).let { it.state.isSideSolidFullSquare(it.world, it.pos, dir.opposite) }
|
||||
|
||||
fun model(dir: Direction) = state(dir).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! }
|
||||
fun model(offset: Int3) = state(offset).let { MinecraftClient.getInstance().blockRenderManager.getModel(it)!! }
|
||||
}
|
||||
|
||||
open class BasicBlockCtx(
|
||||
override val world: ExtendedBlockView,
|
||||
override val pos: BlockPos
|
||||
) : BlockCtx {
|
||||
override val state = world.getBlockState(pos)
|
||||
override fun offset(offset: Int3) = BasicBlockCtx(world, pos + offset)
|
||||
fun cache() = CachedBlockCtx(world, pos)
|
||||
}
|
||||
|
||||
open class CachedBlockCtx(world: ExtendedBlockView, pos: BlockPos) : BasicBlockCtx(world, pos) {
|
||||
var neighbors = Array<BlockState>(6) { world.getBlockState(pos + allDirections[it].offset) }
|
||||
override var biome: Biome = world.getBiome(pos)
|
||||
override fun state(dir: Direction) = neighbors[dir.ordinal]
|
||||
}
|
||||
50
src/main/kotlin/mods/betterfoliage/chunk/OffsetBlockView.kt
Normal file
50
src/main/kotlin/mods/betterfoliage/chunk/OffsetBlockView.kt
Normal file
@@ -0,0 +1,50 @@
|
||||
package mods.betterfoliage.chunk
|
||||
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.ExtendedBlockView
|
||||
import net.minecraft.world.LightType
|
||||
|
||||
/**
|
||||
* Delegating [IBlockAccess] that fakes a _modified_ location to return values from a _target_ location.
|
||||
* All other locations are handled normally.
|
||||
*
|
||||
* @param[original] the [IBlockAccess] that is delegated to
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
|
||||
open class OffsetBlockView(open val original: BlockView, val modded: BlockPos, val target: BlockPos) : BlockView {
|
||||
inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos
|
||||
|
||||
override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
|
||||
override fun getBlockEntity(pos: BlockPos) = original.getBlockEntity(actualPos(pos))
|
||||
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
|
||||
class OffsetExtBlockView(val original: ExtendedBlockView, val modded: BlockPos, val target: BlockPos) : ExtendedBlockView by original {
|
||||
inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos
|
||||
|
||||
override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
|
||||
override fun getBlockEntity(pos: BlockPos) = original.getBlockEntity(actualPos(pos))
|
||||
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
|
||||
|
||||
override fun getLightLevel(type: LightType, pos: BlockPos) = original.getLightLevel(type, actualPos(pos))
|
||||
override fun getLightmapIndex(pos: BlockPos, light: Int) = original.getLightmapIndex(actualPos(pos), light)
|
||||
override fun getBiome(pos: BlockPos) = original.getBiome(actualPos(pos))
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily replaces the [IBlockReader] used by this [BlockContext] and the corresponding [ExtendedRenderBlocks]
|
||||
* to use an [OffsetEnvBlockReader] while executing this lambda.
|
||||
*
|
||||
* @param[modded] the _modified_ location
|
||||
* @param[target] the _target_ location
|
||||
* @param[func] the lambda to execute
|
||||
*/
|
||||
//inline fun <reified T> BlockContext.withOffset(modded: Int3, target: Int3, func: () -> T): T {
|
||||
// val original = reader!!
|
||||
// reader = OffsetEnvBlockReader(original, pos + modded, pos + target)
|
||||
// val result = func()
|
||||
// reader = original
|
||||
// return result
|
||||
//}
|
||||
124
src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt
Normal file
124
src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt
Normal file
@@ -0,0 +1,124 @@
|
||||
package mods.betterfoliage.chunk
|
||||
|
||||
import mods.betterfoliage.*
|
||||
import mods.betterfoliage.util.YarnHelper
|
||||
import mods.betterfoliage.util.get
|
||||
import net.minecraft.client.render.chunk.ChunkRendererRegion
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.ChunkPos
|
||||
import net.minecraft.world.ExtendedBlockView
|
||||
import net.minecraft.world.ViewableWorld
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.chunk.WorldChunk
|
||||
import net.minecraft.world.dimension.DimensionType
|
||||
import java.util.*
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.MutableMap
|
||||
import kotlin.collections.associateWith
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.mutableListOf
|
||||
import kotlin.collections.mutableMapOf
|
||||
import kotlin.collections.set
|
||||
|
||||
// net.minecraft.world.chunk.WorldChunk.world
|
||||
val WorldChunk_world = YarnHelper.requiredField<World>("net.minecraft.class_2818", "field_12858", "Lnet/minecraft/class_1937;")
|
||||
// net.minecraft.client.render.chunk.ChunkRendererRegion.world
|
||||
val ChunkRendererRegion_world = YarnHelper.requiredField<World>("net.minecraft.class_853", "field_4490", "Lnet/minecraft/class_1937;")
|
||||
|
||||
val ExtendedBlockView.dimType: DimensionType get() = when {
|
||||
this is ViewableWorld -> dimension.type
|
||||
this is ChunkRendererRegion -> this[ChunkRendererRegion_world]!!.dimension.type
|
||||
// this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache]!!.dimType
|
||||
else -> throw IllegalArgumentException("DimensionType of world with class ${this::class.qualifiedName} cannot be determined!")
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position
|
||||
*/
|
||||
interface ChunkOverlayLayer<T> {
|
||||
fun calculate(ctx: BlockCtx): T
|
||||
fun onBlockUpdate(world: ExtendedBlockView, pos: BlockPos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
|
||||
*/
|
||||
object ChunkOverlayManager : ClientChunkLoadCallback, ClientWorldLoadCallback {
|
||||
|
||||
init {
|
||||
ClientWorldLoadCallback.EVENT.register(this)
|
||||
ClientChunkLoadCallback.EVENT.register(this)
|
||||
}
|
||||
|
||||
val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>()
|
||||
val layers = mutableListOf<ChunkOverlayLayer<*>>()
|
||||
|
||||
/**
|
||||
* Get the overlay data for a given layer and position
|
||||
*
|
||||
* @param layer Overlay layer to query
|
||||
* @param reader World to use if calculation of overlay value is necessary
|
||||
* @param pos Block position
|
||||
*/
|
||||
fun <T> get(layer: ChunkOverlayLayer<T>, ctx: BlockCtx): T? {
|
||||
val data = chunkData[ctx.world.dimType]?.get(ChunkPos(ctx.pos)) ?: return null
|
||||
data.get(layer, ctx.pos).let { value ->
|
||||
if (value !== ChunkOverlayData.UNCALCULATED) return value
|
||||
val newValue = layer.calculate(ctx)
|
||||
data.set(layer, ctx.pos, newValue)
|
||||
return newValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the overlay data for a given layer and position
|
||||
*
|
||||
* @param layer Overlay layer to clear
|
||||
* @param pos Block position
|
||||
*/
|
||||
fun <T> clear(dimension: DimensionType, layer: ChunkOverlayLayer<T>, pos: BlockPos) {
|
||||
chunkData[dimension]?.get(ChunkPos(pos))?.clear(layer, pos)
|
||||
}
|
||||
|
||||
fun onBlockChange(world: ClientWorld, pos: BlockPos) {
|
||||
if (chunkData[world.dimType]?.containsKey(ChunkPos(pos)) == true) {
|
||||
layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadChunk(chunk: WorldChunk) {
|
||||
chunk[WorldChunk_world]!!.dimType.let { dim ->
|
||||
val data = chunkData[dim] ?: mutableMapOf<ChunkPos, ChunkOverlayData>().apply { chunkData[dim] = this }
|
||||
data.let { chunks ->
|
||||
// check for existence first because Optifine fires a TON of these
|
||||
if (chunk.pos !in chunks.keys) chunks[chunk.pos] = ChunkOverlayData(layers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun unloadChunk(chunk: WorldChunk) {
|
||||
chunk[WorldChunk_world]!!.dimType.let { dim ->
|
||||
chunkData[dim]?.remove(chunk.pos)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadWorld(world: ClientWorld) {
|
||||
val dim = world.dimType
|
||||
// chunkData.keys.forEach { if (it == dim) chunkData[dim] = mutableMapOf() else chunkData.remove(dim)}
|
||||
}
|
||||
}
|
||||
|
||||
class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
|
||||
val BlockPos.isValid: Boolean get() = y in validYRange
|
||||
val rawData = layers.associateWith { emptyOverlay() }
|
||||
fun <T> get(layer: ChunkOverlayLayer<T>, pos: BlockPos): T? = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T? else null
|
||||
fun <T> set(layer: ChunkOverlayLayer<T>, pos: BlockPos, data: T) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data) else null
|
||||
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, UNCALCULATED) else null
|
||||
|
||||
companion object {
|
||||
val UNCALCULATED = object {}
|
||||
fun emptyOverlay() = Array(16) { Array(16) { Array<Any?>(256) { UNCALCULATED }}}
|
||||
val validYRange = 0 until 256
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user