From 3a2283d415b06f71212c45c6de7c9bc0bbf6e3df Mon Sep 17 00:00:00 2001 From: alex_s168 <63254202+alex-s168@users.noreply.github.com> Date: Sat, 3 Aug 2024 09:13:28 +0000 Subject: [PATCH] better fuel tank rendering --- .../mixin/client/MixinBlockClient.java | 33 +++++++ .../util/block/WithExRenderInfo.java | 16 ++++ .../tournament/TournamentModels.kt | 19 ++-- .../tournament/TournamentNetworking.kt | 36 ++++++-- .../blockentity/FuelTankBlockEntity.kt | 32 +++++-- .../TransparentFuelTankBlockEntityRender.kt | 31 ++++++- .../tournament/blocks/FuelTankBlock.kt | 88 ++++++++++++++++++- .../tournament/blocks/ThrusterBlock.kt | 10 ++- .../tournament/ship/TournamentShips.kt | 16 +++- .../vs_tournament-common.mixins.json | 3 +- 10 files changed, 250 insertions(+), 34 deletions(-) create mode 100644 common/src/main/java/org/valkyrienskies/tournament/mixin/client/MixinBlockClient.java create mode 100644 common/src/main/java/org/valkyrienskies/tournament/util/block/WithExRenderInfo.java diff --git a/common/src/main/java/org/valkyrienskies/tournament/mixin/client/MixinBlockClient.java b/common/src/main/java/org/valkyrienskies/tournament/mixin/client/MixinBlockClient.java new file mode 100644 index 0000000..83da267 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/tournament/mixin/client/MixinBlockClient.java @@ -0,0 +1,33 @@ +package org.valkyrienskies.tournament.mixin.client; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.valkyrienskies.tournament.util.block.WithExRenderInfo; + +@Mixin(Block.class) +public class MixinBlockClient { + @Inject(at = @At("HEAD"), method = "shouldRenderFace", cancellable = true) + private static void shouldRenderFace(BlockState state, BlockGetter level, BlockPos offset, Direction face, BlockPos pos, CallbackInfoReturnable cir) { + var blockState = level.getBlockState(pos); + if (blockState.getBlock() instanceof WithExRenderInfo blockEx) { + var info = blockEx.getFaceRenderType(blockState, level, pos, face); + + if (info == WithExRenderInfo.FaceRenderType.FORCE_RENDER) { + cir.setReturnValue(true); + return; + } + + if (info == WithExRenderInfo.FaceRenderType.FORCE_NOT_RENDER) { + cir.setReturnValue(false); + return; + } + } + } +} diff --git a/common/src/main/java/org/valkyrienskies/tournament/util/block/WithExRenderInfo.java b/common/src/main/java/org/valkyrienskies/tournament/util/block/WithExRenderInfo.java new file mode 100644 index 0000000..ee8f40f --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/tournament/util/block/WithExRenderInfo.java @@ -0,0 +1,16 @@ +package org.valkyrienskies.tournament.util.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; + +public interface WithExRenderInfo { + FaceRenderType getFaceRenderType(BlockState state, BlockGetter level, BlockPos pos, Direction face); + + enum FaceRenderType { + NORMAL, + FORCE_RENDER, + FORCE_NOT_RENDER, + } +} diff --git a/common/src/main/kotlin/org/valkyrienskies/tournament/TournamentModels.kt b/common/src/main/kotlin/org/valkyrienskies/tournament/TournamentModels.kt index 13d53cc..2903977 100644 --- a/common/src/main/kotlin/org/valkyrienskies/tournament/TournamentModels.kt +++ b/common/src/main/kotlin/org/valkyrienskies/tournament/TournamentModels.kt @@ -37,7 +37,9 @@ object TournamentModels { } data class Model( - val resourceLocation: ResourceLocation + val resourceLocation: ResourceLocation, + val checkSides: Boolean = true, + val useAO: Boolean = false, ) { val bakedModel: BakedModel by lazy { getModel(resourceLocation) @@ -53,14 +55,16 @@ object TournamentModels { ) { val level = blockEntity.level ?: return - Minecraft.getInstance().blockRenderer.modelRenderer.tesselateWithoutAO( + val modRend = Minecraft.getInstance().blockRenderer.modelRenderer + val fn = if (useAO) modRend::tesselateWithAO else modRend::tesselateWithoutAO + fn( level, bakedModel, blockEntity.blockState, blockEntity.blockPos, matrixStack, bufferSource.getBuffer(RenderType.cutout()), - true, + checkSides, level.random, 42L, // Used in ModelBlockRenderer.class in renderModel, not sure what the right number is but this seems to work packedOverlay @@ -69,18 +73,21 @@ object TournamentModels { } } - private fun model(name: String): Model { + private fun model(name: String, checkSides: Boolean = true, useAO: Boolean = false): Model { val rl = ResourceLocation(TournamentMod.MOD_ID, name) MODELS += rl - return Model(rl) + return Model(rl, checkSides, useAO) } val PROP_BIG = model("block/prop_big_prop") val PROP_SMALL = model("block/prop_small_prop") val SOLID_FUEL = model("block/solid_fuel") val ROTATOR_ROTARY = model("block/rotator_rotary") - val FUEL_TANK_FULL_TRANSPARENT = model("block/fuel_tank_full_transparent") + val FUEL_TANK_FULL_TRANSPARENT = model( + "block/fuel_tank_full_transparent", + useAO = true + ) } \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/tournament/TournamentNetworking.kt b/common/src/main/kotlin/org/valkyrienskies/tournament/TournamentNetworking.kt index 7629e7a..94d53c7 100644 --- a/common/src/main/kotlin/org/valkyrienskies/tournament/TournamentNetworking.kt +++ b/common/src/main/kotlin/org/valkyrienskies/tournament/TournamentNetworking.kt @@ -4,13 +4,29 @@ import blitz.collections.remove import net.minecraft.core.BlockPos import net.minecraft.resources.ResourceLocation import org.valkyrienskies.core.api.ships.properties.ShipId +import org.valkyrienskies.core.impl.game.ships.ShipObjectServerWorld import org.valkyrienskies.core.impl.networking.simple.SimplePacket import org.valkyrienskies.core.impl.networking.simple.register import org.valkyrienskies.core.impl.networking.simple.registerClientHandler import org.valkyrienskies.core.impl.networking.simple.sendToAllClients +import org.valkyrienskies.mod.common.vsCore import org.valkyrienskies.tournament.ship.TournamentShips +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract object TournamentNetworking { + @OptIn(ExperimentalContracts::class) + private fun runIfServer(fn: () -> R): R? { + contract { + callsInPlace(fn, InvocationKind.AT_MOST_ONCE) + } + + return if (vsCore.dummyShipWorldServer is ShipObjectServerWorld) { + fn() + } else null + } + data class ShipFuelTypeChange( val ship: ShipId, val fuel: String?, @@ -27,10 +43,12 @@ object TournamentNetworking { fuelKey()?.let(TournamentFuelManager.fuels::get) fun send() { - // TODO after vs update - // with(vsCore.simplePacketNetworking) { - this.sendToAllClients() - // } + runIfServer { + // TODO after vs update + // with(vsCore.simplePacketNetworking) { + this.sendToAllClients() + // } + } } } @@ -45,10 +63,12 @@ object TournamentNetworking { throttle < 0.0f fun send() { - // TODO after vs update - // with(vsCore.simplePacketNetworking) { - this.sendToAllClients() - // } + runIfServer { + // TODO after vs update + // with(vsCore.simplePacketNetworking) { + this.sendToAllClients() + // } + } } } diff --git a/common/src/main/kotlin/org/valkyrienskies/tournament/blockentity/FuelTankBlockEntity.kt b/common/src/main/kotlin/org/valkyrienskies/tournament/blockentity/FuelTankBlockEntity.kt index 70ff714..c103e8c 100644 --- a/common/src/main/kotlin/org/valkyrienskies/tournament/blockentity/FuelTankBlockEntity.kt +++ b/common/src/main/kotlin/org/valkyrienskies/tournament/blockentity/FuelTankBlockEntity.kt @@ -1,5 +1,6 @@ package org.valkyrienskies.tournament.blockentity +import blitz.caching import net.minecraft.core.BlockPos import net.minecraft.core.Direction import net.minecraft.nbt.CompoundTag @@ -17,6 +18,7 @@ import org.valkyrienskies.tournament.TournamentConfig import org.valkyrienskies.tournament.ship.TournamentShips import org.valkyrienskies.tournament.tournamentFuel import org.valkyrienskies.tournament.util.extension.void +import java.util.BitSet import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -27,7 +29,7 @@ import kotlin.math.min class FuelTankBlockEntity( pos: BlockPos, state: BlockState, - val capf: Float, + var capf: Float, src: () -> BlockEntityType ): BlockEntity( src(), @@ -35,8 +37,6 @@ class FuelTankBlockEntity( state ) { - // TODO: do using custom networking instead - @Volatile var wholeShipFillLevelSynced = 0.0f private set @@ -51,15 +51,21 @@ class FuelTankBlockEntity( override fun saveAdditional(tag: CompoundTag) { tag.putFloat("ship_fill_synced", wholeShipFillLevelSynced) + tag.putByteArray("neighbors", neighborsTransparent.toByteArray()) super.saveAdditional(tag) } override fun load(tag: CompoundTag) { wholeShipFillLevelSynced = tag.getFloat("ship_fill_synced") + + neighborsTransparent = if (tag.contains("neighbors")) + BitSet.valueOf(tag.getByteArray("neighbors")) + else BitSet(6) + super.load(tag) } - private fun update() { + fun update() { level?.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_ALL_IMMEDIATE) } @@ -70,7 +76,9 @@ class FuelTankBlockEntity( } } - val cap = ceil(TournamentConfig.SERVER.fuelContainerCap * capf).toInt() + val cap by caching(::capf) { + ceil(TournamentConfig.SERVER.fuelContainerCap * capf).toInt() + } @OptIn(ExperimentalContracts::class) fun ship(fn: (TournamentShips) -> R): R? { @@ -92,6 +100,15 @@ class FuelTankBlockEntity( } } + fun updateCapf(new: Float) { + val old = cap + capf = new + val diff = cap - old + ship { + it.fuelCap += diff + } + } + fun getContainer(): CustomContainer { return CustomContainer(this) } @@ -111,6 +128,11 @@ class FuelTankBlockEntity( it.fuelCount += count }.void() + var neighborsTransparent = BitSet(6) + + fun isNeighborTransparent(direction: Direction) = + neighborsTransparent[direction.ordinal] + class CustomContainer( val be: FuelTankBlockEntity ): WorldlyContainer { diff --git a/common/src/main/kotlin/org/valkyrienskies/tournament/blockentity/render/TransparentFuelTankBlockEntityRender.kt b/common/src/main/kotlin/org/valkyrienskies/tournament/blockentity/render/TransparentFuelTankBlockEntityRender.kt index cfd90d4..cf7c279 100644 --- a/common/src/main/kotlin/org/valkyrienskies/tournament/blockentity/render/TransparentFuelTankBlockEntityRender.kt +++ b/common/src/main/kotlin/org/valkyrienskies/tournament/blockentity/render/TransparentFuelTankBlockEntityRender.kt @@ -4,6 +4,7 @@ import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.renderer.MultiBufferSource import net.minecraft.client.renderer.blockentity.BlockEntityRenderer +import net.minecraft.core.Direction import org.valkyrienskies.tournament.TournamentModels import org.valkyrienskies.tournament.blockentity.FuelTankBlockEntity import org.valkyrienskies.tournament.util.extension.pose @@ -20,12 +21,34 @@ class TransparentFuelTankBlockEntityRender: packedLight: Int, packedOverlay: Int ) { - // TODO: back faces of fuel tank not visible + RenderSystem.disableCull() if (be.wholeShipFillLevelSynced > 0.05f) { + val byDir = List(Direction.entries.size) { index -> + if (be.neighborsTransparent[index]) { + 0.0 + } else { + 0.01 + } + } + + val x = byDir[Direction.WEST.ordinal] + val y = byDir[Direction.DOWN.ordinal] + val z = byDir[Direction.NORTH.ordinal] + + val nx = byDir[Direction.EAST.ordinal] + val ny = byDir[Direction.UP.ordinal] + val nz = byDir[Direction.SOUTH.ordinal] + pose.pose { - translate(0.1, 0.1, 0.1) - scale(0.8f, 0.8f * be.wholeShipFillLevelSynced, 0.8f) + translate(x, y, z) + + scale( + 1.0f - (x + nx).toFloat(), + (1.0f - (y + ny).toFloat()) * be.wholeShipFillLevelSynced, + 1.0f - (z + nz).toFloat() + ) + TournamentModels.SOLID_FUEL.renderer.render( pose, be, @@ -43,6 +66,8 @@ class TransparentFuelTankBlockEntityRender: packedLight, packedOverlay ) + + RenderSystem.enableCull() } } \ No newline at end of file diff --git a/common/src/main/kotlin/org/valkyrienskies/tournament/blocks/FuelTankBlock.kt b/common/src/main/kotlin/org/valkyrienskies/tournament/blocks/FuelTankBlock.kt index e14fd15..4a8642b 100644 --- a/common/src/main/kotlin/org/valkyrienskies/tournament/blocks/FuelTankBlock.kt +++ b/common/src/main/kotlin/org/valkyrienskies/tournament/blocks/FuelTankBlock.kt @@ -1,6 +1,7 @@ package org.valkyrienskies.tournament.blocks import net.minecraft.core.BlockPos +import net.minecraft.core.Direction import net.minecraft.network.chat.TranslatableComponent import net.minecraft.server.level.ServerLevel import net.minecraft.world.InteractionHand @@ -8,21 +9,26 @@ import net.minecraft.world.InteractionResult import net.minecraft.world.WorldlyContainer import net.minecraft.world.WorldlyContainerHolder import net.minecraft.world.entity.player.Player +import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level import net.minecraft.world.level.LevelAccessor import net.minecraft.world.level.block.BaseEntityBlock +import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.RenderShape import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntityTicker import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.block.state.properties.SlabType import net.minecraft.world.level.material.Material import net.minecraft.world.phys.BlockHitResult import org.valkyrienskies.tournament.TournamentBlockEntities import org.valkyrienskies.tournament.blockentity.FuelTankBlockEntity import org.valkyrienskies.tournament.util.TitleType import org.valkyrienskies.tournament.util.block.SlabBaseEntityBlock +import org.valkyrienskies.tournament.util.block.WithExRenderInfo import org.valkyrienskies.tournament.util.sendTitle +import java.util.BitSet private fun useCommon( state: BlockState, @@ -68,8 +74,10 @@ private fun useCommon( class FuelTankBlockFull( val transparent: Boolean ): BaseEntityBlock( - Properties.of(Material.METAL) -), WorldlyContainerHolder { + Properties + .of(Material.METAL) + .noOcclusion() +), WorldlyContainerHolder, WithExRenderInfo { override fun onPlace(state: BlockState, level: Level, pos: BlockPos, oldState: BlockState, isMoving: Boolean) { super.onPlace(state, level, pos, oldState, isMoving) @@ -77,6 +85,8 @@ class FuelTankBlockFull( val be = level.getBlockEntity(pos) as? FuelTankBlockEntity? ?: return be.onAdded() + + updateNeighborTransparent(level, pos) } override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, isMoving: Boolean) { @@ -110,6 +120,73 @@ class FuelTankBlockFull( override fun getRenderShape(blockState: BlockState) = if (transparent) RenderShape.INVISIBLE else RenderShape.MODEL + override fun skipRendering(state: BlockState, adjacentBlockState: BlockState, direction: Direction): Boolean { + if (transparent) return false + return super.skipRendering(state, adjacentBlockState, direction) + } + + override fun getShadeBrightness(state: BlockState, level: BlockGetter, pos: BlockPos): Float { + if (transparent) return 1.0f + return super.getShadeBrightness(state, level, pos) + } + + override fun propagatesSkylightDown(state: BlockState, level: BlockGetter, pos: BlockPos): Boolean { + if (transparent) return true + return super.propagatesSkylightDown(state, level, pos) + } + + fun updateNeighborTransparent(level: Level, pos: BlockPos) { + val be = level.getBlockEntity(pos) as? FuelTankBlockEntity ?: return + + if (transparent) { + val bitSet = BitSet(6) + + Direction.entries.forEachIndexed { index, direction -> + val p = pos.relative(direction) + val bs = level.getBlockState(p).block + if (bs is FuelTankBlockFull && bs.transparent) { + bitSet[index] = true + } + } + + be.neighborsTransparent = bitSet + be.update() + } + } + + override fun neighborChanged( + state: BlockState, + level: Level, + pos: BlockPos, + block: Block, + fromPos: BlockPos, + isMoving: Boolean + ) { + super.neighborChanged(state, level, pos, block, fromPos, isMoving) + + updateNeighborTransparent(level, pos) + } + + override fun getFaceRenderType( + state: BlockState, + level: BlockGetter, + pos: BlockPos, + face: Direction + ): WithExRenderInfo.FaceRenderType { + if (!transparent) { + return WithExRenderInfo.FaceRenderType.NORMAL + } + + val be = level.getBlockEntity(pos) as? FuelTankBlockEntity + ?: return WithExRenderInfo.FaceRenderType.NORMAL + + if (be.isNeighborTransparent(face.opposite)) { + return WithExRenderInfo.FaceRenderType.FORCE_NOT_RENDER + } + + return WithExRenderInfo.FaceRenderType.FORCE_RENDER + } + } class FuelTankBlockHalf: SlabBaseEntityBlock( @@ -136,10 +213,13 @@ class FuelTankBlockHalf: SlabBaseEntityBlock( override fun use(state: BlockState, level: Level, pos: BlockPos, player: Player, hand: InteractionHand, hit: BlockHitResult) = useCommon(state, level, pos, player, hand, hit) - override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity = - FuelTankBlockEntity(pos, state, 1.0f) { + override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity { + val isDouble = state.getValue(TYPE) == SlabType.DOUBLE + val capF = if (isDouble) 1.0f else 0.5f + return FuelTankBlockEntity(pos, state, capF) { TournamentBlockEntities.FUEL_TANK_HALF_SOLID.get() } + } override fun getContainer(state: BlockState, level: LevelAccessor, pos: BlockPos): WorldlyContainer = (level.getBlockEntity(pos) as FuelTankBlockEntity).getContainer() diff --git a/common/src/main/kotlin/org/valkyrienskies/tournament/blocks/ThrusterBlock.kt b/common/src/main/kotlin/org/valkyrienskies/tournament/blocks/ThrusterBlock.kt index f31119a..b599b09 100644 --- a/common/src/main/kotlin/org/valkyrienskies/tournament/blocks/ThrusterBlock.kt +++ b/common/src/main/kotlin/org/valkyrienskies/tournament/blocks/ThrusterBlock.kt @@ -159,10 +159,14 @@ class ThrusterBlock( val ships = TournamentShips.get(level, pos) - ships?.thrusterV2(pos) - ?.throttle = getThrottle(state, signal) + ships?.thrusterV2(pos)?.let { + val new = getThrottle(state, signal) - ships?.updateThrusterV2(pos) + if (it.throttle != new) { + it.throttle = new + ships.updateThrusterV2(pos) + } + } } override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState { diff --git a/common/src/main/kotlin/org/valkyrienskies/tournament/ship/TournamentShips.kt b/common/src/main/kotlin/org/valkyrienskies/tournament/ship/TournamentShips.kt index 52e8ce5..cbe023e 100644 --- a/common/src/main/kotlin/org/valkyrienskies/tournament/ship/TournamentShips.kt +++ b/common/src/main/kotlin/org/valkyrienskies/tournament/ship/TournamentShips.kt @@ -68,10 +68,10 @@ class TournamentShips: ShipForcesInducer { ) @JsonIgnore - private val toUpdateV2 = ConcurrentLinkedQueue() + private val toUpdateV2 = ConcurrentLinkedQueue() fun updateThrusterV2(pos: BlockPos) { - toUpdateV2.add(pos) + toUpdateV2.add(pos.asLong()) } fun allThrusters() = @@ -182,17 +182,25 @@ class TournamentShips: ShipForcesInducer { @JsonIgnore private var lastFuelType = fuelType + @JsonIgnore + private val filteredUpdates = mutableSetOf() // TODO: replace with faster long set override fun applyForces(physShip: PhysShip) { physShip as PhysShipImpl toUpdateV2.pollUntilEmpty { - val throttle = thrusterV2(it) + filteredUpdates.add(it) + } + + filteredUpdates.forEach { packed -> + val pos = BlockPos.of(packed) + + val throttle = thrusterV2(pos) ?.throttle ?: -1.0f // remove TournamentNetworking.ShipThrusterChange( physShip.id, - it.x, it.y, it.z, + pos.x, pos.y, pos.z, throttle ).send() } diff --git a/common/src/main/resources/vs_tournament-common.mixins.json b/common/src/main/resources/vs_tournament-common.mixins.json index 131a876..e1c0124 100644 --- a/common/src/main/resources/vs_tournament-common.mixins.json +++ b/common/src/main/resources/vs_tournament-common.mixins.json @@ -4,7 +4,8 @@ "compatibilityLevel": "JAVA_17", "client": [ "client.MixinDebugRenderer", - "client.MixinItemTooltips" + "client.MixinItemTooltips", + "client.MixinBlockClient" ], "injectors": { "defaultRequire": 1