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
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!")
}
}

View File

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

View File

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

View File

@@ -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<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
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<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 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<BlockEntity> {
@@ -36,7 +131,7 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
return subpos.mapNotNull { pos -> level?.getBlockEntity(pos) }
}
fun computeEdges(): Set<NodeBlockEntity> {
open fun computeEdges(): Set<NodeBlockEntity> {
val s = mutableSetOf<NodeBlockEntity>()
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 <T : BlockEntity> getTicker(
level: Level,
blockState: BlockState,
@@ -82,7 +185,7 @@ abstract class NodeBlock: BaseBlock(), EntityBlock {
return object : BlockEntityTicker<T> {
override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T) {
if(blockEntity !is NodeBlockEntity) return;
blockEntity.tickNode()
blockEntity.tickNode(level)
}
}
}

View File

@@ -40,32 +40,32 @@ class BullshitFix: DataFixType<Unit>() {
object BlockEntities {
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(
::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(
::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(
::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(
::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(
::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(
::CombustionGeneratorBlockEntity, mutableSetOf(Blocks.COMBUSTGEN_BLOCK.get()), BullshitFix()
)

View File

@@ -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<ItemStack> = NonNullList<ItemStack>.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)
}
}

View File

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

View File

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

View File

@@ -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<Node>()
private var reachableCache: Set<Node>? = 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<Node> = 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<Node> = 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<Node> {
val reachability = getReachability()
if(reachability == Visibility.NONE) {
return setOf();
}

View File

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

View File

@@ -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<T: GenericContainerMenu>(menu: T, inventor
open fun shouldRenderTooltip() = true
open fun findMenuTexture(): ResourceLocation? = null
open fun getBoundBlockEntityType(): Set<BlockEntityType<*>> = setOf()
open fun processScreenStatePacket(buf: FriendlyByteBuf) {}
val imageX: Int
get() = (width - imageWidth) / 2