From db2cbbbe8ddfc29fceb4e11e91df2f30ebbc9427 Mon Sep 17 00:00:00 2001 From: IonutParau Date: Sun, 12 Apr 2026 15:15:16 +0200 Subject: [PATCH] networking bullshit --- build.gradle.kts | 19 +++ .../neoflock/neocomputers/block/Capacitor.kt | 21 ++- .../neoflock/neocomputers/block/NodeBlock.kt | 123 ++++++++++++++++++ .../neocomputers/block/ScreenBlock.kt | 4 +- .../neocomputers/entity/NodeEntity.kt | 95 -------------- .../neocomputers/entity/ScreenEntity.kt | 13 +- .../neocomputers/network/Networking.kt | 30 +++-- 7 files changed, 179 insertions(+), 126 deletions(-) create mode 100644 src/main/kotlin/org/neoflock/neocomputers/block/NodeBlock.kt delete mode 100644 src/main/kotlin/org/neoflock/neocomputers/entity/NodeEntity.kt diff --git a/build.gradle.kts b/build.gradle.kts index 473c803..33d0f73 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,6 +11,15 @@ plugins { val minecraft = stonecutter.current.version val loader = loom.platform.get().name.lowercase() +fun minecraftVersionToNum(ver: String): Int { + val parts = ver.split(".") + if(parts.size == 2) { + // 26.1 and above + return parts[0].toInt() * 10000 + parts[1].toInt() + } + return parts[1].toInt() * 100 + parts[2].toInt() +} + version = "${mod.version}+$minecraft" group = mod.group base { @@ -59,6 +68,15 @@ dependencies { modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${mod.dep("fabric_version")}") modImplementation("net.fabricmc:fabric-language-kotlin:1.13.10+kotlin.2.3.20") modApi("dev.architectury:architectury-fabric:${archversion}") + fun getTeamRebornEnergy(): String { + val curVer = minecraftVersionToNum(minecraft) + if (curVer < minecraftVersionToNum("1.20.5")) { return "3.0.0" } + if (curVer < minecraftVersionToNum("1.21.0")) { return "4.0.0" } + if (curVer < minecraftVersionToNum("1.21.5")) { return "4.1.0" } + if (curVer < minecraftVersionToNum("26.1")) { return "4.2.0" } + return "5.0.0" + } + modApi("teamreborn:energy:${getTeamRebornEnergy()}") } if (loader == "forge") { "forge"("net.minecraftforge:forge:${minecraft}-${mod.dep("forge_loader")}") @@ -83,6 +101,7 @@ dependencies { if (minecraft=="1.21.9" || minecraft=="1.21.11") modApi("dev.architectury:architectury-neoforge:${archversion}") else modApi("dev.architectury:architectury-forge:${archversion}") // NOTE: this could be wrong implementation("thedarkcolour:kotlinforforge-neoforge:6.0.0") + } } buildscript { diff --git a/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt b/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt index 50bdf8c..ea51ccd 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt @@ -4,40 +4,35 @@ import net.minecraft.core.BlockPos 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.util.RandomSource -import net.minecraft.world.InteractionHand import net.minecraft.world.InteractionResult import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.ItemStack 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.entity.BlockEntity -import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.redstone.Orientation import net.minecraft.world.phys.BlockHitResult import org.neoflock.neocomputers.entity.BlockEntities -import org.neoflock.neocomputers.entity.NodeEntity import org.neoflock.neocomputers.network.Networking +import org.neoflock.neocomputers.network.PowerRole -class CapacitorEntity(pos: BlockPos, state: BlockState) : NodeEntity(BlockEntities.CAPACITOR_ENTITY.get(), pos, state) { +class CapacitorEntity(pos: BlockPos, state: BlockState) : NodeBlockEntity(BlockEntities.CAPACITOR_ENTITY.get(), pos, state) { var amountStored: Double = 0.0 val capacity = 20000.0 - val netNode = object : Networking.Node() { - override fun isProducer() = true + override val node = object : Networking.Node() { + override fun getPowerRole() = PowerRole.PRODUCER override fun getEnergy() = amountStored override fun maxEnergyCapacity(): Double = capacity override fun setEnergy(energy: Double) { amountStored = energy } } - - override fun getNode() = netNode } -class CapacitorBlock : BaseBlock("capacitor"), EntityBlock { +class CapacitorBlock : NodeBlock("capacitor") { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? { val cap = CapacitorEntity(blockPos, blockState) cap.initNetworking() @@ -57,7 +52,7 @@ class CapacitorBlock : BaseBlock("capacitor"), EntityBlock { if(ent.isPresent()) { val cap = ent.get() if(sp.isCrouching()) cap.amountStored++ - val msg = PlayerChatMessage.system("energy: ${cap.amountStored} / ${cap.capacity} (${cap.getReachableNodes().size} reachable, ${cap.getNode().getReachable().size} connected)") + val msg = PlayerChatMessage.system("energy: ${cap.amountStored} / ${cap.capacity} (${cap.computeEdges().size} edges, ${cap.node.getReachable().size} connected)") sp.sendChatMessage(OutgoingChatMessage.create(msg), false, ChatType.bind(ChatType.CHAT, player)) } } diff --git a/src/main/kotlin/org/neoflock/neocomputers/block/NodeBlock.kt b/src/main/kotlin/org/neoflock/neocomputers/block/NodeBlock.kt new file mode 100644 index 0000000..bd4105d --- /dev/null +++ b/src/main/kotlin/org/neoflock/neocomputers/block/NodeBlock.kt @@ -0,0 +1,123 @@ +package org.neoflock.neocomputers.block + +import net.minecraft.core.BlockPos + +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.item.ItemStack +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.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.redstone.Orientation +import org.neoflock.neocomputers.network.Networking + +abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : BlockEntity(blockEntityType, blockPos, blockState) { + abstract val node: Networking.Node + + fun initNetworking() { + Networking.addNode(node) + } + + private var stateIsDirty = true + + open fun getNeighbourEntities(): List { + val subpos = listOf( + blockPos.offset(0, 0, 1), + blockPos.offset(0, 0, -1), + blockPos.offset(0, 1, 0), + blockPos.offset(0, -1, 0), + blockPos.offset(1, 0, 0), + blockPos.offset(-1, 0, 0), + ) + + return subpos.mapNotNull { pos -> level?.getBlockEntity(pos) } + } + + fun computeEdges(): Set { + val s = mutableSetOf() + val neighbours = getNeighbourEntities() + for(neighbour in neighbours) { + if(neighbour is NodeBlockEntity) s.add(neighbour); + // TODO: handle cable entities + } + s.remove(this) + return s + } + + fun invalidateNodeState() { + stateIsDirty = true + } + + fun needsSynchronization() = stateIsDirty + + fun ensureSynchronized() { + if(!stateIsDirty) return + stateIsDirty = false + computeEdges().forEach { + node.connectTo(it.node) + } + } + + override fun setChanged() { + invalidateNodeState() + computeEdges().forEach { it.invalidateNodeState() } + super.setChanged() + } + + override fun setRemoved() { + super.setRemoved() + Networking.removeNode(node) + } +} + +abstract class NodeBlock(name: String): BaseBlock(name), EntityBlock { + override fun getTicker( + level: Level, + blockState: BlockState, + blockEntityType: BlockEntityType + ): BlockEntityTicker? { + return object : BlockEntityTicker { + override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T) { + if(blockEntity !is NodeBlockEntity) return; + blockEntity.ensureSynchronized() + } + } + } + + override fun setPlacedBy( + level: Level, + blockPos: BlockPos, + blockState: BlockState, + livingEntity: LivingEntity?, + itemStack: ItemStack + ) { + if(!level.isClientSide) { + val ent = level.getBlockEntity(blockPos) + if(ent is NodeBlockEntity) { + ent.invalidateNodeState() + } + } + super.setPlacedBy(level, blockPos, blockState, livingEntity, itemStack) + } + + override fun neighborChanged( + blockState: BlockState, + level: Level, + blockPos: BlockPos, + block: Block, + orientation: Orientation?, + bl: Boolean + ) { + if(!level.isClientSide) { + val ent = level.getBlockEntity(blockPos) + if(ent is NodeBlockEntity) { + ent.invalidateNodeState() + } + + } + super.neighborChanged(blockState, level, blockPos, block, orientation, bl) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/neoflock/neocomputers/block/ScreenBlock.kt b/src/main/kotlin/org/neoflock/neocomputers/block/ScreenBlock.kt index b111c0b..50407cd 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/block/ScreenBlock.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/block/ScreenBlock.kt @@ -22,7 +22,7 @@ import org.neoflock.neocomputers.entity.ScreenEntity import org.neoflock.neocomputers.gui.menu.Menus import org.neoflock.neocomputers.network.Networking -class ScreenBlock() : BaseBlock("screen"), EntityBlock { +class ScreenBlock() : NodeBlock("screen") { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? { val scr = ScreenEntity(blockPos, blockState) @@ -39,7 +39,7 @@ class ScreenBlock() : BaseBlock("screen"), EntityBlock { ): InteractionResult { if(!level.isClientSide) { val screenState = level.getBlockEntity(blockPos, BlockEntities.SCREEN_ENTITY.get()).get() - if(!screenState.getNode().consumeEnergy(5.0)) return InteractionResult.SUCCESS; + if(!screenState.node.consumeEnergy(5.0)) return InteractionResult.SUCCESS; MenuRegistry.openMenu(player as ServerPlayer, object : MenuProvider { override fun getDisplayName(): Component = Component.literal("SCREEEEEN!") override fun createMenu(i: Int, inventory: Inventory, player: Player): AbstractContainerMenu { diff --git a/src/main/kotlin/org/neoflock/neocomputers/entity/NodeEntity.kt b/src/main/kotlin/org/neoflock/neocomputers/entity/NodeEntity.kt deleted file mode 100644 index ed2356c..0000000 --- a/src/main/kotlin/org/neoflock/neocomputers/entity/NodeEntity.kt +++ /dev/null @@ -1,95 +0,0 @@ -package org.neoflock.neocomputers.entity; - -import net.minecraft.core.BlockPos -import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraft.world.level.block.entity.BlockEntityType -import net.minecraft.world.level.block.state.BlockState -import org.neoflock.neocomputers.network.Networking - -open class NodeEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : - BlockEntity(blockEntityType, blockPos, blockState) { - - // stuff - open fun getNode(): Networking.Node? = null - - open fun getSubnodes(): List = listOf() - - fun initNetworking() { - val node = getNode() - if(node != null) Networking.addNode(node) - getSubnodes().forEach { Networking.addNode(it) } - syncReachable() - } - - open fun getDirectConnections(): List { - if(level == null) return listOf(); - val offs = listOf( - BlockPos(0, 1, 0), - BlockPos(0, -1, 0), - BlockPos(1, 0, 0), - BlockPos(-1, 0, 0), - BlockPos(0, 0, 1), - BlockPos(0, 0, -1), - ) - val entities = mutableListOf() - offs.forEach { - val ent = level?.getBlockEntity(blockPos.offset(it.x, it.y, it.z)) - if(ent is NodeEntity) { - entities.add(ent) - } - } - return entities - } - - // may include itself - fun getReachableNodes(): Set { - val visited = mutableSetOf() - val working = mutableListOf(this) - val nodes = mutableSetOf() - - while(working.isNotEmpty()) { - val cur = working.removeFirst() - if(cur in visited) continue - visited.add(cur) - val n = cur.getNode() - if(n != null) { - // rely on the defined direct connections of the node - nodes.add(n) - if(n != this.getNode()) continue - } - working.addAll(cur.getDirectConnections()); - } - - return nodes - } - - fun syncReachable() { - val reachable = getReachableNodes().toList() - val node = getNode() - // nothing to sync - if(node == null) return - - reachable.filter { - it !in node.connections - }.forEach { - node.connectTo(it) - } - - node.connections.filter { it !in reachable }.forEach { - node.disconnectFrom(it) - } - } - - override fun setChanged() { - super.setChanged() - syncReachable() - } - - override fun setRemoved() { - super.setRemoved() - syncReachable() - val n = getNode() - if(n != null) Networking.removeNode(n) - getSubnodes().forEach { Networking.removeNode(it) } - } -} \ No newline at end of file diff --git a/src/main/kotlin/org/neoflock/neocomputers/entity/ScreenEntity.kt b/src/main/kotlin/org/neoflock/neocomputers/entity/ScreenEntity.kt index fc93b8c..dc1e578 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/entity/ScreenEntity.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/entity/ScreenEntity.kt @@ -1,25 +1,22 @@ package org.neoflock.neocomputers.entity; import net.minecraft.core.BlockPos -import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraft.world.level.block.entity.BlockEntityType 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 class ScreenEntity(blockPos: BlockPos, blockState: BlockState) : - NodeEntity(BlockEntities.SCREEN_ENTITY.get(), blockPos, blockState) { + NodeBlockEntity(BlockEntities.SCREEN_ENTITY.get(), blockPos, blockState) { var energyStored: Double = 0.0 - val scrnod = object : Networking.Node() { + override val node = object : Networking.Node() { + override fun getPowerRole() = PowerRole.CONSUMER override fun getEnergy() = energyStored override fun setEnergy(energy: Double) { energyStored = energy } override fun maxEnergyCapacity(): Double = 10.0 - override fun isConsumer() = true } - - // stuff - override fun getNode() = scrnod } \ 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 c0356fc..12c559f 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/network/Networking.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/network/Networking.kt @@ -7,6 +7,15 @@ import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt +enum class PowerRole { + // consumes energy, wants to be fully charged + // does not give energy to network nodes + CONSUMER, + // produces/stores energy, will not care to charge itself + // will happily give energy to network nodes + PRODUCER, +} + object Networking { // maximum amount of hops between nodes var maxHopCount = 32 @@ -21,6 +30,7 @@ object Networking { NETWORK, } + abstract class Message(val sender: Node) class ClassicPacket(sender: Node, val src: String, val dst: String, val port: Int, val data: List, val hopCount: Int) : Message(sender) { @@ -33,16 +43,15 @@ object Networking { open class Node { val connections = mutableSetOf() - val reachability = Visibility.NETWORK - var reachableCache: Set? = null + private var reachableCache: Set? = null - open fun isProducer(): Boolean = false - open fun isConsumer(): Boolean = false + open fun getReachability() = Visibility.NETWORK + open fun getPowerRole() = PowerRole.CONSUMER open fun getEnergy(): Double = 0.0 open fun setEnergy(energy: Double) {} open fun maxEnergyCapacity(): Double = 0.0 - fun getChargerNodes(): Set = getReachable().filter { it.isProducer() }.toSet() + fun getChargerNodes(): Set = getReachable().filter { it.getPowerRole() == PowerRole.PRODUCER }.toSet() fun totalEnergyInConnections(): Double = getChargerNodes().fold(0.0) { acc, node -> acc + node.getEnergy() } fun maxEnergyInConnections(): Double = getChargerNodes().fold(0.0) { acc, node -> acc + node.maxEnergyCapacity() } @@ -82,7 +91,7 @@ object Networking { } open fun tick() { - if(isConsumer()) tryToChargeFully() + if(getPowerRole() == PowerRole.CONSUMER) tryToChargeFully() } // processes a received message open fun received(message: Message) {} @@ -109,12 +118,17 @@ object Networking { return reachableCache!!; } + fun invalidateReachableCache() { + reachableCache = null + } + fun computeReachable(): Set { + val reachability = getReachability() if(reachability == Visibility.NONE) { return setOf(); } if(reachability == Visibility.DIRECT) { - return connections; + return connections.minus(this); } if(reachability == Visibility.NETWORK) { // absolute cinema @@ -167,7 +181,7 @@ object Networking { } class DebugBatteryNode(var power: Double, val capacity: Double): Node() { - override fun isProducer() = true + override fun getPowerRole() = PowerRole.PRODUCER override fun maxEnergyCapacity() = capacity override fun getEnergy() = power override fun setEnergy(energy: Double) { power = energy }