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

View File

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

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

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

View File

@@ -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<Any>, val hopCount: Int) : Message(sender) {
@@ -33,16 +43,15 @@ object Networking {
open class Node {
val connections = mutableSetOf<Node>()
val reachability = Visibility.NETWORK
var reachableCache: Set<Node>? = null
private var reachableCache: Set<Node>? = 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<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 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<Node> {
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 }