From a4533e03bbb6cf06dcb86c7c39b0ad424dc96fd8 Mon Sep 17 00:00:00 2001 From: IonutParau Date: Sat, 18 Apr 2026 11:53:18 +0200 Subject: [PATCH] synchronization and persistant state --- .../org/neoflock/neocomputers/NeoComputers.kt | 38 +++++- .../neoflock/neocomputers/block/BaseBlock.kt | 2 +- .../neoflock/neocomputers/block/Capacitor.kt | 38 +++--- .../neoflock/neocomputers/block/Generators.kt | 45 ++++++- .../neoflock/neocomputers/block/NodeBlock.kt | 113 +++++++++++++++++- .../neocomputers/entity/BlockEntities.kt | 12 +- .../entity/CombustionGeneratorBlockEntity.kt | 55 ++++++--- .../entity/SolarGeneratorBlockEntity.kt | 33 +++-- .../gui/screen/CombustionGeneratorScreen.kt | 16 ++- .../neocomputers/network/Networking.kt | 47 +++++--- .../neocomputers/network/PowerManager.kt | 12 +- .../neocomputers/utils/GenericContainer.kt | 7 ++ 12 files changed, 320 insertions(+), 98 deletions(-) diff --git a/src/main/kotlin/org/neoflock/neocomputers/NeoComputers.kt b/src/main/kotlin/org/neoflock/neocomputers/NeoComputers.kt index 30a0a83..cfbd9b2 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/NeoComputers.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/NeoComputers.kt @@ -1,7 +1,9 @@ package org.neoflock.neocomputers import dev.architectury.event.events.client.ClientLifecycleEvent +import dev.architectury.event.events.common.PlayerEvent import dev.architectury.event.events.common.TickEvent +import dev.architectury.networking.NetworkManager import net.minecraft.resources.ResourceLocation import org.neoflock.neocomputers.block.Blocks import org.neoflock.neocomputers.entity.BlockEntities @@ -10,10 +12,16 @@ import org.neoflock.neocomputers.gui.menu.Menus import org.neoflock.neocomputers.gui.screen.ScreenScreen import dev.architectury.registry.menu.MenuRegistry import net.minecraft.client.Minecraft +import net.minecraft.client.player.LocalPlayer +import net.minecraft.server.level.ServerPlayer +import org.neoflock.neocomputers.block.NodeBlock +import org.neoflock.neocomputers.block.NodeBlockEntity +import org.neoflock.neocomputers.block.NodeSynchronizer import org.neoflock.neocomputers.item.Items import org.neoflock.neocomputers.item.Tabs import org.neoflock.neocomputers.network.Networking import org.neoflock.neocomputers.utils.FontProvider +import org.neoflock.neocomputers.utils.GenericContainerScreen import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -54,12 +62,40 @@ object NeoComputers { TickEvent.SERVER_POST.register { Networking.tickAllNodes() + NodeSynchronizer.syncScreens() } + + PlayerEvent.CLOSE_MENU.register { + player, menu -> + if(player is ServerPlayer) NodeSynchronizer.playerScreenClosed(player) + } + + PlayerEvent.PLAYER_QUIT.register { + player -> + NodeSynchronizer.playerScreenClosed(player) + } + + NetworkManager.registerReceiver(NetworkManager.s2c(),NodeSynchronizer.StatePayload.TYPE, NodeSynchronizer.StatePayload.CODEC, { + packet, ctx -> + val level = ctx.player.level() + val ent = level.getBlockEntity(packet.blockPos) + if(ent is NodeBlockEntity) { + ent.syncWithUpstream(packet.buffer) + } + }) + + NetworkManager.registerReceiver(NetworkManager.s2c(),NodeSynchronizer.ScreenPayload.TYPE, NodeSynchronizer.ScreenPayload.CODEC, { + packet, ctx -> + val scr = Minecraft.getInstance().screen + if(scr is GenericContainerScreen<*>) { + scr.processScreenStatePacket(packet.buffer) + } + }) LOGGER.info("Registered!") //LOGGER.info("Started mod in %s loader".formatted(NeoComputersInit.PLATFORM.getModloader())) //LOGGER.info("Kotlin: %s".formatted(NeoComputers.hello())) - LOGGER.info("Started mod in ${NeoComputers.PLATFORM?.modloader} loader") + LOGGER.info("Started mod in ${PLATFORM?.modloader} loader") LOGGER.info("Hello from kotlin!") } } \ No newline at end of file diff --git a/src/main/kotlin/org/neoflock/neocomputers/block/BaseBlock.kt b/src/main/kotlin/org/neoflock/neocomputers/block/BaseBlock.kt index 18bd2b0..0863fff 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/block/BaseBlock.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/block/BaseBlock.kt @@ -10,7 +10,7 @@ import org.neoflock.neocomputers.NeoComputers import java.util.function.Supplier import com.google.common.base.Suppliers -open class BaseBlock : Block(BlockBehaviour.Properties.of()) { // TODO: create a TieredBaseBlock class that extends this or something +open class BaseBlock(properties: Properties = Properties.of()) : Block(properties) { // TODO: create a TieredBaseBlock class that extends this or something // val tier: Int companion object Registry { diff --git a/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt b/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt index 1384636..34c3ee8 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt @@ -1,6 +1,9 @@ package org.neoflock.neocomputers.block +import net.minecraft.client.player.LocalPlayer import net.minecraft.core.BlockPos +import net.minecraft.core.HolderLookup +import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.ChatType import net.minecraft.network.chat.OutgoingChatMessage import net.minecraft.network.chat.PlayerChatMessage @@ -19,23 +22,18 @@ import org.neoflock.neocomputers.network.PowerRole import kotlin.math.min open class CapacitorEntity(val capacity: Long, type: BlockEntityType<*>, pos: BlockPos, state: BlockState) : NodeBlockEntity(type, pos, state) { - var amountStored: Long = 0 override val node = object : Networking.Node() { - override fun getPowerRole() = PowerRole.STORAGE - override fun getEnergy() = amountStored - override fun getEnergyCapacity() = capacity - override fun giveEnergy(amount: Long): Long { - val given = min(amount, capacity - amountStored) - amountStored += given - return given - } + override var powerRole = PowerRole.STORAGE + override var energyCapacity: Long = capacity + } - override fun withdrawEnergy(amount: Long): Long { - val taken = min(amount, amountStored) - amountStored -= taken - return taken - } + override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) { + node.energy = min(compoundTag.getLong("energy"), node.energyCapacity) + } + + override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) { + compoundTag.putLong("energy", node.energy) } } @@ -61,15 +59,15 @@ class CapacitorBlock(val tier: Int) : NodeBlock() { player: Player, blockHitResult: BlockHitResult ): InteractionResult { - if(!level.isClientSide()) { - val sp = player as ServerPlayer + if(level.isClientSide()) { + val p = player as LocalPlayer val ent = level.getBlockEntity(blockPos) if(ent is CapacitorEntity) { - if(sp.isCrouching) ent.amountStored++ - val msg = PlayerChatMessage.system("energy: ${ent.amountStored} / ${ent.capacity} (${ent.computeEdges().size} edges, ${ent.node.getReachable().size} connected)") - sp.sendChatMessage(OutgoingChatMessage.create(msg), false, ChatType.bind(ChatType.CHAT, player)) + if(p.isCrouching) ent.node.giveEnergy(1) + val msg = PlayerChatMessage.system("energy: ${ent.node.energy} / ${ent.capacity} (${ent.computeEdges().size} edges, ${ent.node.getReachable().size} connected)") + p.sendSystemMessage(OutgoingChatMessage.create(msg).content()) } } - return InteractionResult.SUCCESS; + return InteractionResult.SUCCESS } } \ No newline at end of file diff --git a/src/main/kotlin/org/neoflock/neocomputers/block/Generators.kt b/src/main/kotlin/org/neoflock/neocomputers/block/Generators.kt index 09371a3..7b7495c 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/block/Generators.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/block/Generators.kt @@ -2,16 +2,30 @@ package org.neoflock.neocomputers.block import dev.architectury.registry.menu.MenuRegistry import net.minecraft.core.BlockPos +import net.minecraft.core.particles.ParticleTypes import net.minecraft.network.chat.ChatType import net.minecraft.network.chat.OutgoingChatMessage import net.minecraft.network.chat.PlayerChatMessage +import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer +import net.minecraft.sounds.SoundEvent +import net.minecraft.sounds.SoundEvents +import net.minecraft.sounds.SoundSource +import net.minecraft.util.RandomSource import net.minecraft.world.InteractionResult import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.FurnaceBlock +import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.block.state.StateDefinition +import net.minecraft.world.level.block.state.properties.BooleanProperty +import net.minecraft.world.level.storage.loot.LootParams import net.minecraft.world.phys.BlockHitResult import org.neoflock.neocomputers.entity.BlockEntities import org.neoflock.neocomputers.entity.SolarGeneratorBlockEntity @@ -22,7 +36,19 @@ class SolarGeneratorBlock : NodeBlock(), EntityBlock { } // TODO: make it glow when burning -class CombustionGeneratorBlock : NodeBlock(), EntityBlock { +class CombustionGeneratorBlock : NodeBlock, EntityBlock { + companion object { + val ACTIVE = BooleanProperty.create("active") + + fun getLuminance(blockState: BlockState): Int { + return if(blockState.getValue(ACTIVE)) 5 else 0 + } + } + + constructor(): super(Properties.of().sound(SoundType.STONE).lightLevel(CombustionGeneratorBlock::getLuminance)) { + registerDefaultState(defaultBlockState().setValue(ACTIVE, false)) + } + override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = CombustionGeneratorBlockEntity(blockPos, blockState).initNetworking() override fun useWithoutItem( @@ -35,8 +61,25 @@ class CombustionGeneratorBlock : NodeBlock(), EntityBlock { if(!level.isClientSide()) { val sp = player as ServerPlayer val ent = level.getBlockEntity(blockPos, BlockEntities.COMBUSTGEN_ENTITY.get()).get() + NodeSynchronizer.registerPlayerScreen(sp, ent) MenuRegistry.openMenu(sp, ent) } return InteractionResult.SUCCESS } + + override fun createBlockStateDefinition(builder: StateDefinition.Builder) { + builder.add(ACTIVE) + } + + override fun animateTick(blockState: BlockState, level: Level, blockPos: BlockPos, randomSource: RandomSource) { + if(blockState.getValue(ACTIVE)) { + if(randomSource.nextDouble() < 0.1) level.playSound(null, blockPos, SoundEvents.FURNACE_FIRE_CRACKLE, SoundSource.AMBIENT) + + val x = blockPos.x.toDouble() + val y = blockPos.y.toDouble() + val z = blockPos.z.toDouble() + + level.addParticle(ParticleTypes.SMOKE, x+0.5, y+0.5, z+0.5, 0.0, 0.0, 0.0) + } + } } diff --git a/src/main/kotlin/org/neoflock/neocomputers/block/NodeBlock.kt b/src/main/kotlin/org/neoflock/neocomputers/block/NodeBlock.kt index 4b6d8a0..7d561af 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/block/NodeBlock.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/block/NodeBlock.kt @@ -1,6 +1,15 @@ package org.neoflock.neocomputers.block +import dev.architectury.networking.NetworkManager +import io.netty.buffer.Unpooled import net.minecraft.core.BlockPos +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.network.RegistryFriendlyByteBuf +import net.minecraft.network.codec.StreamCodec +import net.minecraft.network.protocol.common.custom.CustomPacketPayload +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.LivingEntity import net.minecraft.world.item.ItemStack @@ -11,8 +20,73 @@ 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 org.neoflock.neocomputers.NeoComputers import org.neoflock.neocomputers.network.Networking +object NodeSynchronizer { + class StatePayload(var blockPos: BlockPos, var buffer: FriendlyByteBuf): CustomPacketPayload { + companion object { + val NODE_SYNC_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "node_sync") + val TYPE = CustomPacketPayload.Type(NODE_SYNC_ID) + val CODEC = object : StreamCodec { + override fun decode(buf: RegistryFriendlyByteBuf): StatePayload { + val blockPos = buf.readBlockPos() + val buffer = FriendlyByteBuf(buf.copy(buf.readerIndex(), buf.readableBytes())) + return StatePayload(blockPos, buffer) + } + + override fun encode(buf: RegistryFriendlyByteBuf, payload: StatePayload) { + buf.writeBlockPos(payload.blockPos) + buf.writeBytes(payload.buffer) + } + } + } + + override fun type() = TYPE + } + + class ScreenPayload(var entityTypeWireID: String, var buffer: FriendlyByteBuf): CustomPacketPayload { + companion object { + val SCREEN_SYNC_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen_sync") + val TYPE = CustomPacketPayload.Type(SCREEN_SYNC_ID) + val CODEC = object : StreamCodec { + override fun decode(buf: RegistryFriendlyByteBuf): ScreenPayload { + val id = buf.readByteArray().decodeToString() + val buffer = FriendlyByteBuf(buf.copy(buf.readerIndex(), buf.readableBytes())) + return ScreenPayload(id, buffer) + } + + override fun encode(buf: RegistryFriendlyByteBuf, payload: ScreenPayload) { + buf.writeByteArray(payload.entityTypeWireID.encodeToByteArray()) + buf.writeBytes(payload.buffer) + } + } + } + + override fun type() = TYPE + } + + val screenMap = mutableMapOf() + + fun playerScreenClosed(player: ServerPlayer) { + screenMap.remove(player) + } + + fun nodeTypeToWireID(nodeType: BlockEntityType<*>): String = nodeType.javaClass.canonicalName + + fun registerPlayerScreen(player: ServerPlayer, entity: NodeBlockEntity) { + screenMap[player] = entity + } + + fun syncScreens() { + for((player, ent) in screenMap) { + val buf = FriendlyByteBuf(Unpooled.buffer()) + ent.encodeScreenData(player, buf) + NetworkManager.sendToPlayer(player, ScreenPayload(nodeTypeToWireID(ent.type), buf)) + } + } +} + abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : BlockEntity(blockEntityType, blockPos, blockState) { abstract val node: Networking.Node @@ -21,6 +95,27 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl return this } + // runs on the server, meant to encode state to send to all players + open fun encodeDownstreamData(packet: FriendlyByteBuf) { + packet.writeUUID(node.address) + packet.writeLong(node.energy) + packet.writeLong(node.energyCapacity) + packet.writeEnum(node.reachability) + packet.writeEnum(node.powerRole) + } + + // runs on the client, meant to decode server state packets to synchronize client state + open fun syncWithUpstream(packet: FriendlyByteBuf) { + node.address = packet.readUUID() + node.energy = packet.readLong() + node.energyCapacity = packet.readLong() + node.reachability = packet.readEnum(node.reachability.javaClass) + node.powerRole = packet.readEnum(node.powerRole.javaClass) + } + + // Encodes data meant for the associated screen of a player + open fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) {} + private var stateIsDirty = true open fun getNeighbourEntities(): List { @@ -36,7 +131,7 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl return subpos.mapNotNull { pos -> level?.getBlockEntity(pos) } } - fun computeEdges(): Set { + open fun computeEdges(): Set { val s = mutableSetOf() val neighbours = getNeighbourEntities() for(neighbour in neighbours) { @@ -47,13 +142,21 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl return s } - fun invalidateNodeState() { + open fun invalidateNodeState() { stateIsDirty = true } fun needsSynchronization() = stateIsDirty - open fun tickNode() { + open fun tickNode(level: Level) { + if(!level.isClientSide) { + val l = level as ServerLevel + val packetBuf = FriendlyByteBuf(Unpooled.buffer()) + encodeDownstreamData(packetBuf) + l.players().forEach { + if(it.level().isLoaded(blockPos)) NetworkManager.sendToPlayer(it, NodeSynchronizer.StatePayload(blockPos, packetBuf)) + } + } if(!stateIsDirty) return stateIsDirty = false computeEdges().forEach { @@ -73,7 +176,7 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl } } -abstract class NodeBlock: BaseBlock(), EntityBlock { +abstract class NodeBlock(properties: Properties = Properties.of()): BaseBlock(properties), EntityBlock { override fun getTicker( level: Level, blockState: BlockState, @@ -82,7 +185,7 @@ abstract class NodeBlock: BaseBlock(), EntityBlock { return object : BlockEntityTicker { override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T) { if(blockEntity !is NodeBlockEntity) return; - blockEntity.tickNode() + blockEntity.tickNode(level) } } } diff --git a/src/main/kotlin/org/neoflock/neocomputers/entity/BlockEntities.kt b/src/main/kotlin/org/neoflock/neocomputers/entity/BlockEntities.kt index fcc5f09..b7563dd 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/entity/BlockEntities.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/entity/BlockEntities.kt @@ -40,32 +40,32 @@ class BullshitFix: DataFixType() { object BlockEntities { val BLOCKENTITIES: DeferredRegister> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK_ENTITY_TYPE); - val SCREEN_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("screen_entity") { + val SCREEN_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("screen") { BlockEntityType( ::ScreenEntity, mutableSetOf(Blocks.SCREEN_BLOCK.get()), BullshitFix() ) } - val CAPACITOR_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("capacitor_entity") { + val CAPACITOR_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("capacitor") { BlockEntityType( ::CapacitorEntityTier1, mutableSetOf(Blocks.CAPACITOR_BLOCK.get()), BullshitFix() ) } - val CAPACITOR2_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("capacitor_entity2") { + val CAPACITOR2_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("capacitor2") { BlockEntityType( ::CapacitorEntityTier2, mutableSetOf(Blocks.CAPACITOR_BLOCK2.get()), BullshitFix() ) } - val CAPACITOR3_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("capacitor_entity3") { + val CAPACITOR3_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("capacitor3") { BlockEntityType( ::CapacitorEntityTier3, mutableSetOf(Blocks.CAPACITOR_BLOCK3.get()), BullshitFix() ) } - val SOLARGEN_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("solargen_entity") { + val SOLARGEN_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("solargen") { BlockEntityType( ::SolarGeneratorBlockEntity, mutableSetOf(Blocks.SOLARGEN_BLOCK.get()), BullshitFix() ) } - val COMBUSTGEN_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("combustgen_entity") { + val COMBUSTGEN_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("combustgen") { BlockEntityType( ::CombustionGeneratorBlockEntity, mutableSetOf(Blocks.COMBUSTGEN_BLOCK.get()), BullshitFix() ) diff --git a/src/main/kotlin/org/neoflock/neocomputers/entity/CombustionGeneratorBlockEntity.kt b/src/main/kotlin/org/neoflock/neocomputers/entity/CombustionGeneratorBlockEntity.kt index f2c0655..09b7495 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/entity/CombustionGeneratorBlockEntity.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/entity/CombustionGeneratorBlockEntity.kt @@ -1,14 +1,24 @@ package org.neoflock.neocomputers.entity import net.minecraft.core.BlockPos +import net.minecraft.core.HolderLookup import net.minecraft.core.NonNullList +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.network.chat.Component +import net.minecraft.server.level.ServerPlayer +import net.minecraft.sounds.SoundEvents +import net.minecraft.sounds.SoundSource import net.minecraft.world.MenuProvider import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.FurnaceBlock import net.minecraft.world.level.block.state.BlockState +import org.neoflock.neocomputers.block.CombustionGeneratorBlock import org.neoflock.neocomputers.block.NodeBlockEntity import org.neoflock.neocomputers.gui.menu.CombustionGeneratorMenu import org.neoflock.neocomputers.network.Networking @@ -20,25 +30,11 @@ import kotlin.math.min class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : NodeBlockEntity(BlockEntities.COMBUSTGEN_ENTITY.get(), blockPos, blockState), GenericContainer, MenuProvider { val energyPerTick: Long = 50 - var energy: Long = 0 - val maxEnergy: Long = 100000 var burningTimeRemaining: Int = 0 override val node = object : Networking.Node() { - override fun getPowerRole() = PowerRole.GENERATOR - override fun getEnergy() = energy - override fun getEnergyCapacity() = maxEnergy - override fun withdrawEnergy(amount: Long): Long { - val taken = min(amount, energy) - energy -= taken - return taken - } - - override fun giveEnergy(amount: Long): Long { - val given = min(amount, maxEnergy - energy) - energy += given - return given - } + override var powerRole = PowerRole.GENERATOR + override var energyCapacity: Long = 100000 } val stacks: NonNullList = NonNullList.withSize(1, ItemStack.EMPTY) @@ -53,8 +49,8 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) return !this.isRemoved } - override fun tickNode() { - super.tickNode() + override fun tickNode(level: Level) { + super.tickNode(level) // TODO: give us a block state tag for active // keep combusting and shi @@ -66,17 +62,38 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) } // no point - if(node.getEnergy() >= node.getEnergyCapacity()) return; + if(node.energy >= node.energyCapacity) return; // :fire: val fuel = stacks[0] if(fuel.isEmpty) return burningTimeRemaining = ContainerUtils.getBurningTime(fuel) ?: 0 + setChanged() fuel.count-- } override fun getDisplayName(): Component? = Component.translatable("block.neocomputers.combustgen") override fun createMenu(i: Int, inventory: Inventory, player: Player) = CombustionGeneratorMenu(i, inventory, this) + + override fun setChanged() { + super.setChanged() + level?.setBlockAndUpdate(blockPos, blockState.setValue(CombustionGeneratorBlock.ACTIVE, burningTimeRemaining > 0)) + } + + override fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) { + packet.writeLong(node.energy) + packet.writeLong(node.energyCapacity) + } + + override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) { + node.energy = min(node.energyCapacity, compoundTag.getLong("energy")) + burningTimeRemaining = compoundTag.getInt("burningTimeRemaining") + } + + override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) { + compoundTag.putLong("energy", node.energy) + compoundTag.putInt("burningTimeRemaining", burningTimeRemaining) + } } \ No newline at end of file diff --git a/src/main/kotlin/org/neoflock/neocomputers/entity/SolarGeneratorBlockEntity.kt b/src/main/kotlin/org/neoflock/neocomputers/entity/SolarGeneratorBlockEntity.kt index 3f1d9e9..bf7e2d8 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/entity/SolarGeneratorBlockEntity.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/entity/SolarGeneratorBlockEntity.kt @@ -1,38 +1,35 @@ package org.neoflock.neocomputers.entity import net.minecraft.core.BlockPos +import net.minecraft.core.HolderLookup +import net.minecraft.nbt.CompoundTag +import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState import org.neoflock.neocomputers.block.NodeBlockEntity import org.neoflock.neocomputers.network.Networking import org.neoflock.neocomputers.network.PowerRole -import kotlin.math.min class SolarGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : NodeBlockEntity(BlockEntities.SOLARGEN_ENTITY.get(), blockPos, blockState) { val energyPerTick: Long = 50 - var energyStored: Long = 0 - val capacity: Long = 50000 override val node = object : Networking.Node() { - override fun getPowerRole(): PowerRole = PowerRole.GENERATOR - override fun getEnergy(): Long = energyStored - override fun getEnergyCapacity(): Long = capacity - override fun giveEnergy(amount: Long): Long { - val taken = min(amount, capacity - energyStored) - energyStored += taken - return taken - } - override fun withdrawEnergy(amount: Long): Long { - val taken = min(amount, energyStored) - energyStored -= taken - return taken - } + override var powerRole: PowerRole = PowerRole.GENERATOR + override var energyCapacity: Long = 50000 } - override fun tickNode() { - super.tickNode() + override fun tickNode(level: Level) { + super.tickNode(level) val l = level ?: return if(l.isDay) { node.giveEnergy(energyPerTick) } } + + override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) { + node.energy = compoundTag.getLong("energy") + } + + override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) { + compoundTag.putLong("energy", node.energy) + } } \ No newline at end of file diff --git a/src/main/kotlin/org/neoflock/neocomputers/gui/screen/CombustionGeneratorScreen.kt b/src/main/kotlin/org/neoflock/neocomputers/gui/screen/CombustionGeneratorScreen.kt index 63c2dce..3323d7a 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/gui/screen/CombustionGeneratorScreen.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/gui/screen/CombustionGeneratorScreen.kt @@ -1,16 +1,21 @@ package org.neoflock.neocomputers.gui.screen import net.minecraft.client.gui.GuiGraphics +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.world.entity.player.Inventory -import org.neoflock.neocomputers.NeoComputers +import org.neoflock.neocomputers.entity.BlockEntities import org.neoflock.neocomputers.gui.menu.CombustionGeneratorMenu import org.neoflock.neocomputers.utils.GenericContainerScreen class CombustionGeneratorScreen(abstractContainerMenu: CombustionGeneratorMenu, inventory: Inventory, component: Component) : GenericContainerScreen(abstractContainerMenu, inventory, component) { override fun findMenuTexture(): ResourceLocation = ResourceLocation.withDefaultNamespace("textures/gui/container/dispenser.png") + var energy: Long = 0 + var energyCapacity: Long = 1 + override fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, something: Float) { super.render(graphics, mouseX, mouseY, something) @@ -21,9 +26,16 @@ class CombustionGeneratorScreen(abstractContainerMenu: CombustionGeneratorMenu, val lineY = imageY + 6 val lineHeight = 60 - val power = 0.2 + val power = energy.toDouble() / energyCapacity graphics.fill(lineX, lineY, lineX + 2, lineY + lineHeight, lineFg) graphics.fill(lineX, lineY, lineX + 2, lineY + (lineHeight * (1.0 - power)).toInt(), lineBg) } + + override fun getBoundBlockEntityType() = setOf(BlockEntities.COMBUSTGEN_ENTITY.get()) + + override fun processScreenStatePacket(buf: FriendlyByteBuf) { + energy = buf.readLong() + energyCapacity = buf.readLong() + } } \ No newline at end of file diff --git a/src/main/kotlin/org/neoflock/neocomputers/network/Networking.kt b/src/main/kotlin/org/neoflock/neocomputers/network/Networking.kt index 2743026..15cfaed 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/network/Networking.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/network/Networking.kt @@ -2,6 +2,7 @@ package org.neoflock.neocomputers.network import net.minecraft.core.BlockPos import org.neoflock.neocomputers.NeoComputers +import java.util.UUID import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt @@ -46,26 +47,35 @@ object Networking { open class Node { val connections = mutableSetOf() private var reachableCache: Set? = null + open var address = UUID.randomUUID() - open fun getReachability() = Visibility.NETWORK - open fun getPowerRole() = PowerRole.CONSUMER - open fun getEnergy(): Long = 0 + open var reachability = Visibility.NETWORK + open var powerRole = PowerRole.CONSUMER + open var energy: Long = 0 + open var energyCapacity: Long = 0 // give energy, returns how much was actually given // cannot exceed amount specified - open fun giveEnergy(amount: Long): Long = 0 + open fun giveEnergy(amount: Long): Long { + val maximum = min(amount, energyCapacity - energy) + energy += maximum + return maximum + } // take energy out, returns how much was actually taken // cannot exceed amount specified - open fun withdrawEnergy(amount: Long): Long = 0 + open fun withdrawEnergy(amount: Long): Long { + val maximum = min(amount, energy) + energy -= maximum + return maximum + } - open fun getEnergyCapacity(): Long = 0 - fun getChargerNodes(): Set = getReachable().filter { it.getPowerRole() != PowerRole.CONSUMER }.toSet() - fun totalEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.getEnergy() } - fun maxEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.getEnergyCapacity() } + fun getChargerNodes(): Set = getReachable().filter { it.powerRole != PowerRole.CONSUMER }.toSet() + fun totalEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.energy } + fun maxEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.energyCapacity } // attempts to consume fun consumeEnergy(energy: Long): Boolean { // consumes energy, returns false if not enough - val total = totalEnergyInConnections() + getEnergy() + val total = totalEnergyInConnections() + this.energy if(energy > total) return false var remaining = energy @@ -82,7 +92,7 @@ object Networking { // PLEASE only call if consumer, in the name of all that is holy fun tryToChargeFully() { - var remaining = getEnergyCapacity() - getEnergy() + var remaining = energyCapacity - energy if(remaining <= 0) return for (charger in getChargerNodes()) { if(remaining <= 0) break @@ -101,13 +111,13 @@ object Networking { // only call if storage fun balanceStorage() { for(battery in getReachable()) { - if(battery.getPowerRole() != PowerRole.STORAGE) continue + if(battery.powerRole != PowerRole.STORAGE) continue // its so if for example we have a battery with 2x the capacity // we don't try to even the energy between them since that's just bad // and might pointless delete energy over time - val capacityRatio = getEnergyCapacity().toDouble() / battery.getEnergyCapacity() + val capacityRatio = energyCapacity.toDouble() / battery.energyCapacity - val meaningfulSurplus = (battery.getEnergy() * capacityRatio - getEnergy()).toLong() + val meaningfulSurplus = (battery.energy * capacityRatio - energy).toLong() if(meaningfulSurplus <= 0) { // WE'RE greedy (or negligible surplus)? Do nothing @@ -127,10 +137,10 @@ object Networking { // rob the generators fun stealGeneratorPower() { - var remaining = getEnergyCapacity() - getEnergy() + var remaining = energyCapacity - energy for(generator in getReachable()) { - if(generator.getPowerRole() != PowerRole.GENERATOR) continue + if(generator.powerRole != PowerRole.GENERATOR) continue // rob this mf val robbed = generator.withdrawEnergy(remaining) val taken = giveEnergy(robbed) @@ -142,8 +152,8 @@ object Networking { } open fun tick() { - if(getPowerRole() == PowerRole.CONSUMER) tryToChargeFully() - if(getPowerRole() == PowerRole.STORAGE) { + if(powerRole == PowerRole.CONSUMER) tryToChargeFully() + if(powerRole == PowerRole.STORAGE) { stealGeneratorPower() balanceStorage() } @@ -178,7 +188,6 @@ object Networking { } fun computeReachable(): Set { - val reachability = getReachability() if(reachability == Visibility.NONE) { return setOf(); } diff --git a/src/main/kotlin/org/neoflock/neocomputers/network/PowerManager.kt b/src/main/kotlin/org/neoflock/neocomputers/network/PowerManager.kt index 99baa6b..4f61238 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/network/PowerManager.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/network/PowerManager.kt @@ -15,12 +15,12 @@ object PowerManager { //? if fabric { EnergyStorage.SIDED.registerForBlockEntity({ entity, dir -> object : EnergyStorage { - override fun getAmount() = entity.node.getEnergy() - override fun getCapacity() = entity.node.getEnergyCapacity() - override fun supportsExtraction() = entity.node.getPowerRole() != PowerRole.CONSUMER - override fun supportsInsertion() = entity.node.getPowerRole() != PowerRole.GENERATOR + override fun getAmount() = entity.node.energy + override fun getCapacity() = entity.node.energyCapacity + override fun supportsExtraction() = entity.node.powerRole != PowerRole.CONSUMER + override fun supportsInsertion() = entity.node.powerRole != PowerRole.GENERATOR override fun extract(maxAmount: Long, transaction: TransactionContext?): Long { - if(entity.node.getPowerRole() == PowerRole.CONSUMER) return 0 + if(entity.node.powerRole == PowerRole.CONSUMER) return 0 val taken = entity.node.withdrawEnergy(maxAmount) transaction?.addCloseCallback { ctx, res -> if(res.wasAborted() || !res.wasCommitted()) entity.node.giveEnergy(taken) @@ -28,7 +28,7 @@ object PowerManager { return taken } override fun insert(maxAmount: Long, transaction: TransactionContext?): Long { - if(entity.node.getPowerRole() == PowerRole.GENERATOR) return 0 + if(entity.node.powerRole == PowerRole.GENERATOR) return 0 val given = entity.node.giveEnergy(maxAmount) transaction?.addCloseCallback { ctx, res -> if (res.wasAborted() || !res.wasCommitted()) entity.node.withdrawEnergy(given) diff --git a/src/main/kotlin/org/neoflock/neocomputers/utils/GenericContainer.kt b/src/main/kotlin/org/neoflock/neocomputers/utils/GenericContainer.kt index 44998b7..ef8319f 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/utils/GenericContainer.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/utils/GenericContainer.kt @@ -5,6 +5,8 @@ import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.world.Container; import net.minecraft.core.NonNullList; +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.server.packs.resources.Resource @@ -15,6 +17,7 @@ import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.MenuType import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.block.entity.BlockEntityType // Common container interface, assumes the entire purpose is purely raw item storage interface GenericContainer : Container { @@ -112,6 +115,10 @@ abstract class GenericContainerScreen(menu: T, inventor open fun shouldRenderTooltip() = true open fun findMenuTexture(): ResourceLocation? = null + open fun getBoundBlockEntityType(): Set> = setOf() + + open fun processScreenStatePacket(buf: FriendlyByteBuf) {} + val imageX: Int get() = (width - imageWidth) / 2