This commit is contained in:
mewhenthe
2026-04-11 22:45:36 +02:00
8 changed files with 214 additions and 22 deletions

View File

@@ -3,9 +3,11 @@ package org.neoflock.neocomputers
import com.google.common.base.Suppliers
import dev.architectury.event.events.client.ClientLifecycleEvent
import dev.architectury.event.events.common.LifecycleEvent
import dev.architectury.event.events.common.TickEvent
import dev.architectury.registry.client.gui.MenuScreenRegistry
import dev.architectury.registry.registries.RegistrarManager
import net.minecraft.resources.Identifier
import net.minecraft.util.profiling.jfr.event.ServerTickTimeEvent
import org.neoflock.neocomputers.block.Blocks
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.gui.buffer.BufferRenderer
@@ -39,7 +41,6 @@ object NeoComputers {
ClientLifecycleEvent.CLIENT_SETUP.register {
MenuScreenRegistry.registerScreenFactory(Menus.SCREEN_MENU.get(), ::ScreenScreen)
}
ClientLifecycleEvent.CLIENT_STARTED.register {
FontProvider.load(Identifier.fromNamespaceAndPath("neocomputers", "font/unscii.hex"))
@@ -54,6 +55,10 @@ object NeoComputers {
}
TickEvent.SERVER_POST.register {
Networking.tickAllNodes()
}
val logA = Networking.LoggerNode("LogA")
val logB = Networking.LoggerNode("LogB")
val batteryA = Networking.DebugBatteryNode(0.0, 10000.0)

View File

@@ -27,6 +27,7 @@ object Blocks {
val BLOCKS: DeferredRegister<Block> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK)
val TEST_BLOCK: RegistrySupplier<Block> = BaseBlock.register("test") { BaseBlock("test") }
val SCREEN_BLOCK: RegistrySupplier<Block> = BaseBlock.register("screen") { ScreenBlock() }
val CAPACITOR_BLOCK: RegistrySupplier<Block> = BaseBlock.register("capacitor") { CapacitorBlock() }
fun registerBlockItems() {
BLOCKS.forEach(Consumer { sup: RegistrySupplier<Block> ->

View File

@@ -0,0 +1,67 @@
package org.neoflock.neocomputers.block
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.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.phys.BlockHitResult
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.NodeEntity
import org.neoflock.neocomputers.network.Networking
class CapacitorEntity(pos: BlockPos, state: BlockState) : NodeEntity(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 fun getEnergy() = amountStored
override fun maxEnergyCapacity(): Double = capacity
override fun setEnergy(energy: Double) {
amountStored = energy
}
}
override fun getNode() = netNode
}
class CapacitorBlock : BaseBlock("capacitor"), EntityBlock {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? {
val cap = CapacitorEntity(blockPos, blockState)
cap.syncReachable()
Networking.addNode(cap.getNode())
return cap
}
override fun useWithoutItem(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
blockHitResult: BlockHitResult
): InteractionResult {
if(!level.isClientSide()) {
val sp = player as ServerPlayer
val ent = level.getBlockEntity(blockPos, BlockEntities.CAPACITOR_ENTITY.get())
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)")
sp.sendChatMessage(OutgoingChatMessage.create(msg), false, ChatType.bind(ChatType.CHAT, player))
}
}
return InteractionResult.SUCCESS;
}
}

View File

@@ -17,13 +17,17 @@ import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.ScreenEntity
import org.neoflock.neocomputers.gui.menu.Menus
import org.neoflock.neocomputers.network.Networking
class ScreenBlock() : BaseBlock("screen"), EntityBlock {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? {
return ScreenEntity(blockPos, blockState)
val scr = ScreenEntity(blockPos, blockState)
Networking.addNode(scr.getNode())
return scr
}
override fun useWithoutItem(
@@ -34,6 +38,8 @@ class ScreenBlock() : BaseBlock("screen"), EntityBlock {
blockHitResult: BlockHitResult
): InteractionResult {
if(!level.isClientSide) {
val screenState = level.getBlockEntity(blockPos, BlockEntities.SCREEN_ENTITY.get()).get()
if(!screenState.getNode().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

@@ -6,9 +6,11 @@ import net.minecraft.core.registries.Registries
import net.minecraft.world.level.block.entity.BlockEntityType
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.Blocks
import org.neoflock.neocomputers.block.CapacitorEntity
object BlockEntities {
val BLOCKENTITIES: DeferredRegister<BlockEntityType<*>> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK_ENTITY_TYPE);
val SCREEN_ENTITY: RegistrySupplier<BlockEntityType<ScreenEntity>> = BLOCKENTITIES.register("screen_entity") { BlockEntityType(::ScreenEntity, mutableSetOf(Blocks.SCREEN_BLOCK.get()))}
val CAPACITOR_ENTITY: RegistrySupplier<BlockEntityType<CapacitorEntity>> = BLOCKENTITIES.register("capacitor_entity") { BlockEntityType(::CapacitorEntity, mutableSetOf(Blocks.CAPACITOR_BLOCK.get()))}
}

View File

@@ -0,0 +1,85 @@
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 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);
}
}

View File

@@ -4,9 +4,22 @@ 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
class ScreenEntity(blockPos: BlockPos, blockState: BlockState) :
BlockEntity(BlockEntities.SCREEN_ENTITY.get(), blockPos, blockState) {
NodeEntity(BlockEntities.SCREEN_ENTITY.get(), blockPos, blockState) {
var energyStored: Double = 0.0
val scrnod = object : Networking.Node() {
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

@@ -36,45 +36,53 @@ object Networking {
val reachability = Visibility.NETWORK
var reachableCache: Set<Node>? = null
open fun isProducer(): Boolean = false
open fun isConsumer(): Boolean = false
open fun getEnergy(): Double = 0.0
open fun setEnergy(energy: Double) {}
open fun maxEnergyCapacity(): Double = 0.0
open fun getChargerNodes(): Set<Node> = getReachable().plus(this)
fun getChargerNodes(): Set<Node> = getReachable().filter { it.isProducer() }.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() }
// the algorithm for balancing energy levels
fun balanceEnergyLevels() {
// basic algorithm: ensure equal percentages
val cap = this.maxEnergyInConnections();
val total = this.totalEnergyInConnections();
val percentage = total / cap;
getChargerNodes().forEach {
it.setEnergy(percentage * it.maxEnergyCapacity());
}
fun consumeFromNodeAsMuchAsPossible(energy: Double): Double {
val consumed = min(energy, getEnergy())
setEnergy(getEnergy() - consumed)
return consumed
}
// attempts to consume
fun consumeEnergy(energy: Double): Boolean {
// consumes energy, returns false if not enough
val total = this.totalEnergyInConnections()
val total = totalEnergyInConnections() + getEnergy()
if(energy > total) return false
val percentageConsumed = energy / total
var remaining = energy
remaining -= consumeFromNodeAsMuchAsPossible(remaining)
if(remaining <= 0.0) return true
getChargerNodes().forEach {
it.setEnergy(it.getEnergy() * (1.0 - percentageConsumed));
for (charger in getChargerNodes()) {
if(remaining <= 0.0) break
remaining -= charger.consumeFromNodeAsMuchAsPossible(remaining)
}
return true
}
fun tryToChargeFully() {
var remaining = maxEnergyCapacity() - getEnergy()
if(remaining <= 0.0) return
for (charger in getChargerNodes()) {
if(remaining <= 0.0) break
val amount = charger.consumeFromNodeAsMuchAsPossible(remaining)
setEnergy(getEnergy() + amount)
remaining -= amount
}
}
open fun tick() {
// rationale: the other ones can figure it out
if(this.maxEnergyCapacity() > 0) this.balanceEnergyLevels()
if(isConsumer()) tryToChargeFully()
}
// processes a received message
open fun received(message: Message) {}
@@ -132,7 +140,7 @@ object Networking {
other.directConnectTo(this);
}
fun disconnectTo(other: Node) {
fun disconnectFrom(other: Node) {
this.directDisconnectFrom(other);
other.directDisconnectFrom(this);
}
@@ -158,6 +166,7 @@ object Networking {
}
class DebugBatteryNode(var power: Double, val capacity: Double): Node() {
override fun isProducer() = true
override fun maxEnergyCapacity() = capacity
override fun getEnergy() = power
override fun setEnergy(energy: Double) { power = energy }
@@ -230,6 +239,10 @@ object Networking {
if(node is WirelessEndpoint) {
wirelessNodes.remove(node);
}
// toList() in order to copy it
node.connections.toList().forEach {
node.disconnectFrom(it)
}
allNodes.forEach { it.onNodeRemoved(node) }
}