synchronization and persistant state

This commit is contained in:
2026-04-18 11:53:18 +02:00
parent 834a9c7ed8
commit a4533e03bb
12 changed files with 320 additions and 98 deletions

View File

@@ -1,7 +1,9 @@
package org.neoflock.neocomputers package org.neoflock.neocomputers
import dev.architectury.event.events.client.ClientLifecycleEvent import dev.architectury.event.events.client.ClientLifecycleEvent
import dev.architectury.event.events.common.PlayerEvent
import dev.architectury.event.events.common.TickEvent import dev.architectury.event.events.common.TickEvent
import dev.architectury.networking.NetworkManager
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.block.Blocks import org.neoflock.neocomputers.block.Blocks
import org.neoflock.neocomputers.entity.BlockEntities 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 org.neoflock.neocomputers.gui.screen.ScreenScreen
import dev.architectury.registry.menu.MenuRegistry import dev.architectury.registry.menu.MenuRegistry
import net.minecraft.client.Minecraft 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.Items
import org.neoflock.neocomputers.item.Tabs import org.neoflock.neocomputers.item.Tabs
import org.neoflock.neocomputers.network.Networking import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.utils.FontProvider import org.neoflock.neocomputers.utils.FontProvider
import org.neoflock.neocomputers.utils.GenericContainerScreen
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -54,12 +62,40 @@ object NeoComputers {
TickEvent.SERVER_POST.register { TickEvent.SERVER_POST.register {
Networking.tickAllNodes() 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("Registered!")
//LOGGER.info("Started mod in %s loader".formatted(NeoComputersInit.PLATFORM.getModloader())) //LOGGER.info("Started mod in %s loader".formatted(NeoComputersInit.PLATFORM.getModloader()))
//LOGGER.info("Kotlin: %s".formatted(NeoComputers.hello())) //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!") LOGGER.info("Hello from kotlin!")
} }
} }

View File

@@ -10,7 +10,7 @@ import org.neoflock.neocomputers.NeoComputers
import java.util.function.Supplier import java.util.function.Supplier
import com.google.common.base.Suppliers 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 // val tier: Int
companion object Registry { companion object Registry {

View File

@@ -1,6 +1,9 @@
package org.neoflock.neocomputers.block package org.neoflock.neocomputers.block
import net.minecraft.client.player.LocalPlayer
import net.minecraft.core.BlockPos 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.ChatType
import net.minecraft.network.chat.OutgoingChatMessage import net.minecraft.network.chat.OutgoingChatMessage
import net.minecraft.network.chat.PlayerChatMessage import net.minecraft.network.chat.PlayerChatMessage
@@ -19,23 +22,18 @@ import org.neoflock.neocomputers.network.PowerRole
import kotlin.math.min import kotlin.math.min
open class CapacitorEntity(val capacity: Long, type: BlockEntityType<*>, pos: BlockPos, state: BlockState) : NodeBlockEntity(type, pos, state) { 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 val node = object : Networking.Node() {
override fun getPowerRole() = PowerRole.STORAGE override var powerRole = PowerRole.STORAGE
override fun getEnergy() = amountStored override var energyCapacity: Long = capacity
override fun getEnergyCapacity() = capacity }
override fun giveEnergy(amount: Long): Long {
val given = min(amount, capacity - amountStored)
amountStored += given
return given
}
override fun withdrawEnergy(amount: Long): Long { override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
val taken = min(amount, amountStored) node.energy = min(compoundTag.getLong("energy"), node.energyCapacity)
amountStored -= taken }
return taken
} 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, player: Player,
blockHitResult: BlockHitResult blockHitResult: BlockHitResult
): InteractionResult { ): InteractionResult {
if(!level.isClientSide()) { if(level.isClientSide()) {
val sp = player as ServerPlayer val p = player as LocalPlayer
val ent = level.getBlockEntity(blockPos) val ent = level.getBlockEntity(blockPos)
if(ent is CapacitorEntity) { if(ent is CapacitorEntity) {
if(sp.isCrouching) ent.amountStored++ if(p.isCrouching) ent.node.giveEnergy(1)
val msg = PlayerChatMessage.system("energy: ${ent.amountStored} / ${ent.capacity} (${ent.computeEdges().size} edges, ${ent.node.getReachable().size} connected)") val msg = PlayerChatMessage.system("energy: ${ent.node.energy} / ${ent.capacity} (${ent.computeEdges().size} edges, ${ent.node.getReachable().size} connected)")
sp.sendChatMessage(OutgoingChatMessage.create(msg), false, ChatType.bind(ChatType.CHAT, player)) p.sendSystemMessage(OutgoingChatMessage.create(msg).content())
} }
} }
return InteractionResult.SUCCESS; return InteractionResult.SUCCESS
} }
} }

View File

@@ -2,16 +2,30 @@ package org.neoflock.neocomputers.block
import dev.architectury.registry.menu.MenuRegistry import dev.architectury.registry.menu.MenuRegistry
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.network.chat.ChatType import net.minecraft.network.chat.ChatType
import net.minecraft.network.chat.OutgoingChatMessage import net.minecraft.network.chat.OutgoingChatMessage
import net.minecraft.network.chat.PlayerChatMessage import net.minecraft.network.chat.PlayerChatMessage
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer 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.InteractionResult
import net.minecraft.world.entity.player.Player 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.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock 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.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState 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 net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.entity.BlockEntities import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.SolarGeneratorBlockEntity import org.neoflock.neocomputers.entity.SolarGeneratorBlockEntity
@@ -22,7 +36,19 @@ class SolarGeneratorBlock : NodeBlock(), EntityBlock {
} }
// TODO: make it glow when burning // 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 newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = CombustionGeneratorBlockEntity(blockPos, blockState).initNetworking()
override fun useWithoutItem( override fun useWithoutItem(
@@ -35,8 +61,25 @@ class CombustionGeneratorBlock : NodeBlock(), EntityBlock {
if(!level.isClientSide()) { if(!level.isClientSide()) {
val sp = player as ServerPlayer val sp = player as ServerPlayer
val ent = level.getBlockEntity(blockPos, BlockEntities.COMBUSTGEN_ENTITY.get()).get() val ent = level.getBlockEntity(blockPos, BlockEntities.COMBUSTGEN_ENTITY.get()).get()
NodeSynchronizer.registerPlayerScreen(sp, ent)
MenuRegistry.openMenu(sp, ent) MenuRegistry.openMenu(sp, ent)
} }
return InteractionResult.SUCCESS return InteractionResult.SUCCESS
} }
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
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)
}
}
} }

View File

@@ -1,6 +1,15 @@
package org.neoflock.neocomputers.block package org.neoflock.neocomputers.block
import dev.architectury.networking.NetworkManager
import io.netty.buffer.Unpooled
import net.minecraft.core.BlockPos 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.entity.LivingEntity
import net.minecraft.world.item.ItemStack 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.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.network.Networking 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<StatePayload>(NODE_SYNC_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, StatePayload> {
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<ScreenPayload>(SCREEN_SYNC_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, ScreenPayload> {
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<ServerPlayer, NodeBlockEntity>()
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 class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : BlockEntity(blockEntityType, blockPos, blockState) {
abstract val node: Networking.Node abstract val node: Networking.Node
@@ -21,6 +95,27 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
return this 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 private var stateIsDirty = true
open fun getNeighbourEntities(): List<BlockEntity> { open fun getNeighbourEntities(): List<BlockEntity> {
@@ -36,7 +131,7 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
return subpos.mapNotNull { pos -> level?.getBlockEntity(pos) } return subpos.mapNotNull { pos -> level?.getBlockEntity(pos) }
} }
fun computeEdges(): Set<NodeBlockEntity> { open fun computeEdges(): Set<NodeBlockEntity> {
val s = mutableSetOf<NodeBlockEntity>() val s = mutableSetOf<NodeBlockEntity>()
val neighbours = getNeighbourEntities() val neighbours = getNeighbourEntities()
for(neighbour in neighbours) { for(neighbour in neighbours) {
@@ -47,13 +142,21 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
return s return s
} }
fun invalidateNodeState() { open fun invalidateNodeState() {
stateIsDirty = true stateIsDirty = true
} }
fun needsSynchronization() = stateIsDirty 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 if(!stateIsDirty) return
stateIsDirty = false stateIsDirty = false
computeEdges().forEach { 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 <T : BlockEntity> getTicker( override fun <T : BlockEntity> getTicker(
level: Level, level: Level,
blockState: BlockState, blockState: BlockState,
@@ -82,7 +185,7 @@ abstract class NodeBlock: BaseBlock(), EntityBlock {
return object : BlockEntityTicker<T> { return object : BlockEntityTicker<T> {
override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T) { override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T) {
if(blockEntity !is NodeBlockEntity) return; if(blockEntity !is NodeBlockEntity) return;
blockEntity.tickNode() blockEntity.tickNode(level)
} }
} }
} }

View File

@@ -40,32 +40,32 @@ class BullshitFix: DataFixType<Unit>() {
object BlockEntities { object BlockEntities {
val BLOCKENTITIES: DeferredRegister<BlockEntityType<*>> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK_ENTITY_TYPE); val BLOCKENTITIES: DeferredRegister<BlockEntityType<*>> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK_ENTITY_TYPE);
val SCREEN_ENTITY: RegistrySupplier<BlockEntityType<ScreenEntity>> = BLOCKENTITIES.register("screen_entity") { val SCREEN_ENTITY: RegistrySupplier<BlockEntityType<ScreenEntity>> = BLOCKENTITIES.register("screen") {
BlockEntityType( BlockEntityType(
::ScreenEntity, mutableSetOf(Blocks.SCREEN_BLOCK.get()), BullshitFix() ::ScreenEntity, mutableSetOf(Blocks.SCREEN_BLOCK.get()), BullshitFix()
) )
} }
val CAPACITOR_ENTITY: RegistrySupplier<BlockEntityType<CapacitorEntityTier1>> = BLOCKENTITIES.register("capacitor_entity") { val CAPACITOR_ENTITY: RegistrySupplier<BlockEntityType<CapacitorEntityTier1>> = BLOCKENTITIES.register("capacitor") {
BlockEntityType( BlockEntityType(
::CapacitorEntityTier1, mutableSetOf(Blocks.CAPACITOR_BLOCK.get()), BullshitFix() ::CapacitorEntityTier1, mutableSetOf(Blocks.CAPACITOR_BLOCK.get()), BullshitFix()
) )
} }
val CAPACITOR2_ENTITY: RegistrySupplier<BlockEntityType<CapacitorEntityTier2>> = BLOCKENTITIES.register("capacitor_entity2") { val CAPACITOR2_ENTITY: RegistrySupplier<BlockEntityType<CapacitorEntityTier2>> = BLOCKENTITIES.register("capacitor2") {
BlockEntityType( BlockEntityType(
::CapacitorEntityTier2, mutableSetOf(Blocks.CAPACITOR_BLOCK2.get()), BullshitFix() ::CapacitorEntityTier2, mutableSetOf(Blocks.CAPACITOR_BLOCK2.get()), BullshitFix()
) )
} }
val CAPACITOR3_ENTITY: RegistrySupplier<BlockEntityType<CapacitorEntityTier3>> = BLOCKENTITIES.register("capacitor_entity3") { val CAPACITOR3_ENTITY: RegistrySupplier<BlockEntityType<CapacitorEntityTier3>> = BLOCKENTITIES.register("capacitor3") {
BlockEntityType( BlockEntityType(
::CapacitorEntityTier3, mutableSetOf(Blocks.CAPACITOR_BLOCK3.get()), BullshitFix() ::CapacitorEntityTier3, mutableSetOf(Blocks.CAPACITOR_BLOCK3.get()), BullshitFix()
) )
} }
val SOLARGEN_ENTITY: RegistrySupplier<BlockEntityType<SolarGeneratorBlockEntity>> = BLOCKENTITIES.register("solargen_entity") { val SOLARGEN_ENTITY: RegistrySupplier<BlockEntityType<SolarGeneratorBlockEntity>> = BLOCKENTITIES.register("solargen") {
BlockEntityType( BlockEntityType(
::SolarGeneratorBlockEntity, mutableSetOf(Blocks.SOLARGEN_BLOCK.get()), BullshitFix() ::SolarGeneratorBlockEntity, mutableSetOf(Blocks.SOLARGEN_BLOCK.get()), BullshitFix()
) )
} }
val COMBUSTGEN_ENTITY: RegistrySupplier<BlockEntityType<CombustionGeneratorBlockEntity>> = BLOCKENTITIES.register("combustgen_entity") { val COMBUSTGEN_ENTITY: RegistrySupplier<BlockEntityType<CombustionGeneratorBlockEntity>> = BLOCKENTITIES.register("combustgen") {
BlockEntityType( BlockEntityType(
::CombustionGeneratorBlockEntity, mutableSetOf(Blocks.COMBUSTGEN_BLOCK.get()), BullshitFix() ::CombustionGeneratorBlockEntity, mutableSetOf(Blocks.COMBUSTGEN_BLOCK.get()), BullshitFix()
) )

View File

@@ -1,14 +1,24 @@
package org.neoflock.neocomputers.entity package org.neoflock.neocomputers.entity
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList 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.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.MenuProvider
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack 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 net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.CombustionGeneratorBlock
import org.neoflock.neocomputers.block.NodeBlockEntity import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.gui.menu.CombustionGeneratorMenu import org.neoflock.neocomputers.gui.menu.CombustionGeneratorMenu
import org.neoflock.neocomputers.network.Networking 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 { class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : NodeBlockEntity(BlockEntities.COMBUSTGEN_ENTITY.get(), blockPos, blockState), GenericContainer, MenuProvider {
val energyPerTick: Long = 50 val energyPerTick: Long = 50
var energy: Long = 0
val maxEnergy: Long = 100000
var burningTimeRemaining: Int = 0 var burningTimeRemaining: Int = 0
override val node = object : Networking.Node() { override val node = object : Networking.Node() {
override fun getPowerRole() = PowerRole.GENERATOR override var powerRole = PowerRole.GENERATOR
override fun getEnergy() = energy override var energyCapacity: Long = 100000
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
}
} }
val stacks: NonNullList<ItemStack> = NonNullList<ItemStack>.withSize(1, ItemStack.EMPTY) val stacks: NonNullList<ItemStack> = NonNullList<ItemStack>.withSize(1, ItemStack.EMPTY)
@@ -53,8 +49,8 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
return !this.isRemoved return !this.isRemoved
} }
override fun tickNode() { override fun tickNode(level: Level) {
super.tickNode() super.tickNode(level)
// TODO: give us a block state tag for active // TODO: give us a block state tag for active
// keep combusting and shi // keep combusting and shi
@@ -66,17 +62,38 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
} }
// no point // no point
if(node.getEnergy() >= node.getEnergyCapacity()) return; if(node.energy >= node.energyCapacity) return;
// :fire: // :fire:
val fuel = stacks[0] val fuel = stacks[0]
if(fuel.isEmpty) return if(fuel.isEmpty) return
burningTimeRemaining = ContainerUtils.getBurningTime(fuel) ?: 0 burningTimeRemaining = ContainerUtils.getBurningTime(fuel) ?: 0
setChanged()
fuel.count-- fuel.count--
} }
override fun getDisplayName(): Component? = Component.translatable("block.neocomputers.combustgen") override fun getDisplayName(): Component? = Component.translatable("block.neocomputers.combustgen")
override fun createMenu(i: Int, inventory: Inventory, player: Player) = CombustionGeneratorMenu(i, inventory, this) 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)
}
} }

View File

@@ -1,38 +1,35 @@
package org.neoflock.neocomputers.entity package org.neoflock.neocomputers.entity
import net.minecraft.core.BlockPos 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 net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.NodeBlockEntity import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.network.Networking import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole import org.neoflock.neocomputers.network.PowerRole
import kotlin.math.min
class SolarGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : NodeBlockEntity(BlockEntities.SOLARGEN_ENTITY.get(), blockPos, blockState) { class SolarGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : NodeBlockEntity(BlockEntities.SOLARGEN_ENTITY.get(), blockPos, blockState) {
val energyPerTick: Long = 50 val energyPerTick: Long = 50
var energyStored: Long = 0
val capacity: Long = 50000
override val node = object : Networking.Node() { override val node = object : Networking.Node() {
override fun getPowerRole(): PowerRole = PowerRole.GENERATOR override var powerRole: PowerRole = PowerRole.GENERATOR
override fun getEnergy(): Long = energyStored override var energyCapacity: Long = 50000
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 fun tickNode() { override fun tickNode(level: Level) {
super.tickNode() super.tickNode(level)
val l = level ?: return val l = level ?: return
if(l.isDay) { if(l.isDay) {
node.giveEnergy(energyPerTick) 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)
}
} }

View File

@@ -1,16 +1,21 @@
package org.neoflock.neocomputers.gui.screen package org.neoflock.neocomputers.gui.screen
import net.minecraft.client.gui.GuiGraphics 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.network.chat.Component
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory 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.gui.menu.CombustionGeneratorMenu
import org.neoflock.neocomputers.utils.GenericContainerScreen import org.neoflock.neocomputers.utils.GenericContainerScreen
class CombustionGeneratorScreen(abstractContainerMenu: CombustionGeneratorMenu, inventory: Inventory, component: Component) : GenericContainerScreen<CombustionGeneratorMenu>(abstractContainerMenu, inventory, component) { class CombustionGeneratorScreen(abstractContainerMenu: CombustionGeneratorMenu, inventory: Inventory, component: Component) : GenericContainerScreen<CombustionGeneratorMenu>(abstractContainerMenu, inventory, component) {
override fun findMenuTexture(): ResourceLocation = ResourceLocation.withDefaultNamespace("textures/gui/container/dispenser.png") 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) { override fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, something: Float) {
super.render(graphics, mouseX, mouseY, something) super.render(graphics, mouseX, mouseY, something)
@@ -21,9 +26,16 @@ class CombustionGeneratorScreen(abstractContainerMenu: CombustionGeneratorMenu,
val lineY = imageY + 6 val lineY = imageY + 6
val lineHeight = 60 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, lineFg)
graphics.fill(lineX, lineY, lineX + 2, lineY + (lineHeight * (1.0 - power)).toInt(), lineBg) 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()
}
} }

View File

@@ -2,6 +2,7 @@ package org.neoflock.neocomputers.network
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import org.neoflock.neocomputers.NeoComputers import org.neoflock.neocomputers.NeoComputers
import java.util.UUID
import kotlin.math.min import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt import kotlin.math.sqrt
@@ -46,26 +47,35 @@ object Networking {
open class Node { open class Node {
val connections = mutableSetOf<Node>() val connections = mutableSetOf<Node>()
private var reachableCache: Set<Node>? = null private var reachableCache: Set<Node>? = null
open var address = UUID.randomUUID()
open fun getReachability() = Visibility.NETWORK open var reachability = Visibility.NETWORK
open fun getPowerRole() = PowerRole.CONSUMER open var powerRole = PowerRole.CONSUMER
open fun getEnergy(): Long = 0 open var energy: Long = 0
open var energyCapacity: Long = 0
// give energy, returns how much was actually given // give energy, returns how much was actually given
// cannot exceed amount specified // 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 // take energy out, returns how much was actually taken
// cannot exceed amount specified // 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<Node> = getReachable().filter { it.powerRole != PowerRole.CONSUMER }.toSet()
fun getChargerNodes(): Set<Node> = getReachable().filter { it.getPowerRole() != PowerRole.CONSUMER }.toSet() fun totalEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.energy }
fun totalEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.getEnergy() } fun maxEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.energyCapacity }
fun maxEnergyInConnections(): Long = getChargerNodes().fold(0) { acc, node -> acc + node.getEnergyCapacity() }
// attempts to consume // attempts to consume
fun consumeEnergy(energy: Long): Boolean { fun consumeEnergy(energy: Long): Boolean {
// consumes energy, returns false if not enough // consumes energy, returns false if not enough
val total = totalEnergyInConnections() + getEnergy() val total = totalEnergyInConnections() + this.energy
if(energy > total) return false if(energy > total) return false
var remaining = energy var remaining = energy
@@ -82,7 +92,7 @@ object Networking {
// PLEASE only call if consumer, in the name of all that is holy // PLEASE only call if consumer, in the name of all that is holy
fun tryToChargeFully() { fun tryToChargeFully() {
var remaining = getEnergyCapacity() - getEnergy() var remaining = energyCapacity - energy
if(remaining <= 0) return if(remaining <= 0) return
for (charger in getChargerNodes()) { for (charger in getChargerNodes()) {
if(remaining <= 0) break if(remaining <= 0) break
@@ -101,13 +111,13 @@ object Networking {
// only call if storage // only call if storage
fun balanceStorage() { fun balanceStorage() {
for(battery in getReachable()) { 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 // 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 // we don't try to even the energy between them since that's just bad
// and might pointless delete energy over time // 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) { if(meaningfulSurplus <= 0) {
// WE'RE greedy (or negligible surplus)? Do nothing // WE'RE greedy (or negligible surplus)? Do nothing
@@ -127,10 +137,10 @@ object Networking {
// rob the generators // rob the generators
fun stealGeneratorPower() { fun stealGeneratorPower() {
var remaining = getEnergyCapacity() - getEnergy() var remaining = energyCapacity - energy
for(generator in getReachable()) { for(generator in getReachable()) {
if(generator.getPowerRole() != PowerRole.GENERATOR) continue if(generator.powerRole != PowerRole.GENERATOR) continue
// rob this mf // rob this mf
val robbed = generator.withdrawEnergy(remaining) val robbed = generator.withdrawEnergy(remaining)
val taken = giveEnergy(robbed) val taken = giveEnergy(robbed)
@@ -142,8 +152,8 @@ object Networking {
} }
open fun tick() { open fun tick() {
if(getPowerRole() == PowerRole.CONSUMER) tryToChargeFully() if(powerRole == PowerRole.CONSUMER) tryToChargeFully()
if(getPowerRole() == PowerRole.STORAGE) { if(powerRole == PowerRole.STORAGE) {
stealGeneratorPower() stealGeneratorPower()
balanceStorage() balanceStorage()
} }
@@ -178,7 +188,6 @@ object Networking {
} }
fun computeReachable(): Set<Node> { fun computeReachable(): Set<Node> {
val reachability = getReachability()
if(reachability == Visibility.NONE) { if(reachability == Visibility.NONE) {
return setOf(); return setOf();
} }

View File

@@ -15,12 +15,12 @@ object PowerManager {
//? if fabric { //? if fabric {
EnergyStorage.SIDED.registerForBlockEntity({ EnergyStorage.SIDED.registerForBlockEntity({
entity, dir -> object : EnergyStorage { entity, dir -> object : EnergyStorage {
override fun getAmount() = entity.node.getEnergy() override fun getAmount() = entity.node.energy
override fun getCapacity() = entity.node.getEnergyCapacity() override fun getCapacity() = entity.node.energyCapacity
override fun supportsExtraction() = entity.node.getPowerRole() != PowerRole.CONSUMER override fun supportsExtraction() = entity.node.powerRole != PowerRole.CONSUMER
override fun supportsInsertion() = entity.node.getPowerRole() != PowerRole.GENERATOR override fun supportsInsertion() = entity.node.powerRole != PowerRole.GENERATOR
override fun extract(maxAmount: Long, transaction: TransactionContext?): Long { 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) val taken = entity.node.withdrawEnergy(maxAmount)
transaction?.addCloseCallback { transaction?.addCloseCallback {
ctx, res -> if(res.wasAborted() || !res.wasCommitted()) entity.node.giveEnergy(taken) ctx, res -> if(res.wasAborted() || !res.wasCommitted()) entity.node.giveEnergy(taken)
@@ -28,7 +28,7 @@ object PowerManager {
return taken return taken
} }
override fun insert(maxAmount: Long, transaction: TransactionContext?): Long { 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) val given = entity.node.giveEnergy(maxAmount)
transaction?.addCloseCallback { ctx, res -> transaction?.addCloseCallback { ctx, res ->
if (res.wasAborted() || !res.wasCommitted()) entity.node.withdrawEnergy(given) if (res.wasAborted() || !res.wasCommitted()) entity.node.withdrawEnergy(given)

View File

@@ -5,6 +5,8 @@ import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.world.Container; import net.minecraft.world.Container;
import net.minecraft.core.NonNullList; import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.Resource 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.MenuType
import net.minecraft.world.inventory.Slot import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack 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 // Common container interface, assumes the entire purpose is purely raw item storage
interface GenericContainer : Container { interface GenericContainer : Container {
@@ -112,6 +115,10 @@ abstract class GenericContainerScreen<T: GenericContainerMenu>(menu: T, inventor
open fun shouldRenderTooltip() = true open fun shouldRenderTooltip() = true
open fun findMenuTexture(): ResourceLocation? = null open fun findMenuTexture(): ResourceLocation? = null
open fun getBoundBlockEntityType(): Set<BlockEntityType<*>> = setOf()
open fun processScreenStatePacket(buf: FriendlyByteBuf) {}
val imageX: Int val imageX: Int
get() = (width - imageWidth) / 2 get() = (width - imageWidth) / 2