networking bullshit

This commit is contained in:
2026-04-12 15:15:16 +02:00
parent 59358e6b08
commit db2cbbbe8d
7 changed files with 179 additions and 126 deletions

View File

@@ -11,6 +11,15 @@ plugins {
val minecraft = stonecutter.current.version val minecraft = stonecutter.current.version
val loader = loom.platform.get().name.lowercase() 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" version = "${mod.version}+$minecraft"
group = mod.group group = mod.group
base { base {
@@ -59,6 +68,15 @@ dependencies {
modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${mod.dep("fabric_version")}") modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${mod.dep("fabric_version")}")
modImplementation("net.fabricmc:fabric-language-kotlin:1.13.10+kotlin.2.3.20") modImplementation("net.fabricmc:fabric-language-kotlin:1.13.10+kotlin.2.3.20")
modApi("dev.architectury:architectury-fabric:${archversion}") 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") { if (loader == "forge") {
"forge"("net.minecraftforge:forge:${minecraft}-${mod.dep("forge_loader")}") "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}") 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 else modApi("dev.architectury:architectury-forge:${archversion}") // NOTE: this could be wrong
implementation("thedarkcolour:kotlinforforge-neoforge:6.0.0") implementation("thedarkcolour:kotlinforforge-neoforge:6.0.0")
} }
} }
buildscript { buildscript {

View File

@@ -4,40 +4,35 @@ import net.minecraft.core.BlockPos
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.util.RandomSource
import net.minecraft.world.InteractionHand
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.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.entity.BlockEntity 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.block.state.BlockState
import net.minecraft.world.level.redstone.Orientation
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.NodeEntity
import org.neoflock.neocomputers.network.Networking 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 var amountStored: Double = 0.0
val capacity = 20000.0 val capacity = 20000.0
val netNode = object : Networking.Node() { override val node = object : Networking.Node() {
override fun isProducer() = true override fun getPowerRole() = PowerRole.PRODUCER
override fun getEnergy() = amountStored override fun getEnergy() = amountStored
override fun maxEnergyCapacity(): Double = capacity override fun maxEnergyCapacity(): Double = capacity
override fun setEnergy(energy: Double) { override fun setEnergy(energy: Double) {
amountStored = energy amountStored = energy
} }
} }
override fun getNode() = netNode
} }
class CapacitorBlock : BaseBlock("capacitor"), EntityBlock { class CapacitorBlock : NodeBlock("capacitor") {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? {
val cap = CapacitorEntity(blockPos, blockState) val cap = CapacitorEntity(blockPos, blockState)
cap.initNetworking() cap.initNetworking()
@@ -57,7 +52,7 @@ class CapacitorBlock : BaseBlock("capacitor"), EntityBlock {
if(ent.isPresent()) { if(ent.isPresent()) {
val cap = ent.get() val cap = ent.get()
if(sp.isCrouching()) cap.amountStored++ 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)) sp.sendChatMessage(OutgoingChatMessage.create(msg), false, ChatType.bind(ChatType.CHAT, player))
} }
} }

View File

@@ -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<BlockEntity> {
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<NodeBlockEntity> {
val s = mutableSetOf<NodeBlockEntity>()
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 <T : BlockEntity> getTicker(
level: Level,
blockState: BlockState,
blockEntityType: BlockEntityType<T>
): BlockEntityTicker<T>? {
return object : BlockEntityTicker<T> {
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)
}
}

View File

@@ -22,7 +22,7 @@ import org.neoflock.neocomputers.entity.ScreenEntity
import org.neoflock.neocomputers.gui.menu.Menus import org.neoflock.neocomputers.gui.menu.Menus
import org.neoflock.neocomputers.network.Networking import org.neoflock.neocomputers.network.Networking
class ScreenBlock() : BaseBlock("screen"), EntityBlock { class ScreenBlock() : NodeBlock("screen") {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? {
val scr = ScreenEntity(blockPos, blockState) val scr = ScreenEntity(blockPos, blockState)
@@ -39,7 +39,7 @@ class ScreenBlock() : BaseBlock("screen"), EntityBlock {
): InteractionResult { ): InteractionResult {
if(!level.isClientSide) { if(!level.isClientSide) {
val screenState = level.getBlockEntity(blockPos, BlockEntities.SCREEN_ENTITY.get()).get() 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 { MenuRegistry.openMenu(player as ServerPlayer, object : MenuProvider {
override fun getDisplayName(): Component = Component.literal("SCREEEEEN!") override fun getDisplayName(): Component = Component.literal("SCREEEEEN!")
override fun createMenu(i: Int, inventory: Inventory, player: Player): AbstractContainerMenu { override fun createMenu(i: Int, inventory: Inventory, player: Player): AbstractContainerMenu {

View File

@@ -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<Networking.Node> = listOf()
fun initNetworking() {
val node = getNode()
if(node != null) Networking.addNode(node)
getSubnodes().forEach { Networking.addNode(it) }
syncReachable()
}
open fun getDirectConnections(): List<NodeEntity> {
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<NodeEntity>()
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<Networking.Node> {
val visited = mutableSetOf<NodeEntity>()
val working = mutableListOf<NodeEntity>(this)
val nodes = mutableSetOf<Networking.Node>()
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) }
}
}

View File

@@ -1,25 +1,22 @@
package org.neoflock.neocomputers.entity; package org.neoflock.neocomputers.entity;
import net.minecraft.core.BlockPos 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 net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.network.Networking import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole
class ScreenEntity(blockPos: BlockPos, blockState: BlockState) : 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 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 getEnergy() = energyStored
override fun setEnergy(energy: Double) { override fun setEnergy(energy: Double) {
energyStored = energy energyStored = energy
} }
override fun maxEnergyCapacity(): Double = 10.0 override fun maxEnergyCapacity(): Double = 10.0
override fun isConsumer() = true
} }
// stuff
override fun getNode() = scrnod
} }

View File

@@ -7,6 +7,15 @@ import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt 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 { object Networking {
// maximum amount of hops between nodes // maximum amount of hops between nodes
var maxHopCount = 32 var maxHopCount = 32
@@ -21,6 +30,7 @@ object Networking {
NETWORK, NETWORK,
} }
abstract class Message(val sender: Node) abstract class Message(val sender: Node)
class ClassicPacket(sender: Node, val src: String, val dst: String, val port: Int, val data: List<Any>, val hopCount: Int) : Message(sender) { class ClassicPacket(sender: Node, val src: String, val dst: String, val port: Int, val data: List<Any>, val hopCount: Int) : Message(sender) {
@@ -33,16 +43,15 @@ object Networking {
open class Node { open class Node {
val connections = mutableSetOf<Node>() val connections = mutableSetOf<Node>()
val reachability = Visibility.NETWORK private var reachableCache: Set<Node>? = null
var reachableCache: Set<Node>? = null
open fun isProducer(): Boolean = false open fun getReachability() = Visibility.NETWORK
open fun isConsumer(): Boolean = false open fun getPowerRole() = PowerRole.CONSUMER
open fun getEnergy(): Double = 0.0 open fun getEnergy(): Double = 0.0
open fun setEnergy(energy: Double) {} open fun setEnergy(energy: Double) {}
open fun maxEnergyCapacity(): Double = 0.0 open fun maxEnergyCapacity(): Double = 0.0
fun getChargerNodes(): Set<Node> = getReachable().filter { it.isProducer() }.toSet() fun getChargerNodes(): Set<Node> = getReachable().filter { it.getPowerRole() == PowerRole.PRODUCER }.toSet()
fun totalEnergyInConnections(): Double = getChargerNodes().fold(0.0) { acc, node -> acc + node.getEnergy() } fun totalEnergyInConnections(): Double = getChargerNodes().fold(0.0) { acc, node -> acc + node.getEnergy() }
fun maxEnergyInConnections(): Double = getChargerNodes().fold(0.0) { acc, node -> acc + node.maxEnergyCapacity() } fun maxEnergyInConnections(): Double = getChargerNodes().fold(0.0) { acc, node -> acc + node.maxEnergyCapacity() }
@@ -82,7 +91,7 @@ object Networking {
} }
open fun tick() { open fun tick() {
if(isConsumer()) tryToChargeFully() if(getPowerRole() == PowerRole.CONSUMER) tryToChargeFully()
} }
// processes a received message // processes a received message
open fun received(message: Message) {} open fun received(message: Message) {}
@@ -109,12 +118,17 @@ object Networking {
return reachableCache!!; return reachableCache!!;
} }
fun invalidateReachableCache() {
reachableCache = null
}
fun computeReachable(): Set<Node> { fun computeReachable(): Set<Node> {
val reachability = getReachability()
if(reachability == Visibility.NONE) { if(reachability == Visibility.NONE) {
return setOf(); return setOf();
} }
if(reachability == Visibility.DIRECT) { if(reachability == Visibility.DIRECT) {
return connections; return connections.minus(this);
} }
if(reachability == Visibility.NETWORK) { if(reachability == Visibility.NETWORK) {
// absolute cinema // absolute cinema
@@ -167,7 +181,7 @@ object Networking {
} }
class DebugBatteryNode(var power: Double, val capacity: Double): Node() { class DebugBatteryNode(var power: Double, val capacity: Double): Node() {
override fun isProducer() = true override fun getPowerRole() = PowerRole.PRODUCER
override fun maxEnergyCapacity() = capacity override fun maxEnergyCapacity() = capacity
override fun getEnergy() = power override fun getEnergy() = power
override fun setEnergy(energy: Double) { power = energy } override fun setEnergy(energy: Double) { power = energy }