diff --git a/src/main/kotlin/org/neoflock/neocomputers/NeoComputers.kt b/src/main/kotlin/org/neoflock/neocomputers/NeoComputers.kt index 85ab7de..88f09e0 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/NeoComputers.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/NeoComputers.kt @@ -3,8 +3,10 @@ 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.util.profiling.jfr.event.ServerTickTimeEvent import org.neoflock.neocomputers.block.Blocks import org.neoflock.neocomputers.entity.BlockEntities import org.neoflock.neocomputers.gui.menu.Menus @@ -37,24 +39,9 @@ object NeoComputers { MenuScreenRegistry.registerScreenFactory(Menus.SCREEN_MENU.get(), ::ScreenScreen) } - val logA = Networking.LoggerNode("LogA") - val logB = Networking.LoggerNode("LogB") - val batteryA = Networking.DebugBatteryNode(0.0, 10000.0) - val batteryB = Networking.DebugBatteryNode(15000.0, 20000.0) - logA.connectTo(logB) - logA.connectTo(batteryA) - logB.connectTo(batteryB) - - Networking.addNodes(logA, logB, batteryA, batteryB) - - Networking.emitMessage(logA, Networking.ClassicPacket(logA, "a", "b", 0, listOf(), 0)) - LOGGER.info("A: ${batteryA.getEnergy()} / ${batteryA.maxEnergyCapacity()}, B: ${batteryB.getEnergy()} / ${batteryB.maxEnergyCapacity()}") - Networking.tickAllNodes(); - LOGGER.info("A: ${batteryA.getEnergy()} / ${batteryA.maxEnergyCapacity()}, B: ${batteryB.getEnergy()} / ${batteryB.maxEnergyCapacity()}") - LOGGER.info("Had enough: ${if(logA.consumeEnergy(600.0)) 'Y' else 'N'}") - LOGGER.info("A: ${batteryA.getEnergy()} / ${batteryA.maxEnergyCapacity()}, B: ${batteryB.getEnergy()} / ${batteryB.maxEnergyCapacity()}") - - Networking.removeNodes(logA, logB, batteryA, batteryB) + TickEvent.SERVER_POST.register { + Networking.tickAllNodes() + } LOGGER.info("Registered!") //LOGGER.info("Started mod in %s loader".formatted(NeoComputersInit.PLATFORM.getModloader())) diff --git a/src/main/kotlin/org/neoflock/neocomputers/block/Blocks.kt b/src/main/kotlin/org/neoflock/neocomputers/block/Blocks.kt index 9c3a6a6..11dfc45 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/block/Blocks.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/block/Blocks.kt @@ -27,6 +27,7 @@ object Blocks { val BLOCKS: DeferredRegister = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK) val TEST_BLOCK: RegistrySupplier = BaseBlock.register("test") { BaseBlock("test") } val SCREEN_BLOCK: RegistrySupplier = BaseBlock.register("screen") { ScreenBlock() } + val CAPACITOR_BLOCK: RegistrySupplier = BaseBlock.register("capacitor") { CapacitorBlock() } fun registerBlockItems() { BLOCKS.forEach(Consumer { sup: RegistrySupplier -> diff --git a/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt b/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt new file mode 100644 index 0000000..d8979ae --- /dev/null +++ b/src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt @@ -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; + } +} \ 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 61151a4..cae61c2 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/block/ScreenBlock.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/block/ScreenBlock.kt @@ -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 { diff --git a/src/main/kotlin/org/neoflock/neocomputers/entity/BlockEntities.kt b/src/main/kotlin/org/neoflock/neocomputers/entity/BlockEntities.kt index 4ee11d1..6ff0c28 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/entity/BlockEntities.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/entity/BlockEntities.kt @@ -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> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK_ENTITY_TYPE); val SCREEN_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("screen_entity") { BlockEntityType(::ScreenEntity, mutableSetOf(Blocks.SCREEN_BLOCK.get()))} + val CAPACITOR_ENTITY: RegistrySupplier> = BLOCKENTITIES.register("capacitor_entity") { BlockEntityType(::CapacitorEntity, mutableSetOf(Blocks.CAPACITOR_BLOCK.get()))} } \ No newline at end of file diff --git a/src/main/kotlin/org/neoflock/neocomputers/entity/NodeEntity.kt b/src/main/kotlin/org/neoflock/neocomputers/entity/NodeEntity.kt new file mode 100644 index 0000000..8f04ece --- /dev/null +++ b/src/main/kotlin/org/neoflock/neocomputers/entity/NodeEntity.kt @@ -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 { + 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); + } +} \ 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 df94c12..fc93b8c 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/entity/ScreenEntity.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/entity/ScreenEntity.kt @@ -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 } \ 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 62b2c7e..915dcc0 100644 --- a/src/main/kotlin/org/neoflock/neocomputers/network/Networking.kt +++ b/src/main/kotlin/org/neoflock/neocomputers/network/Networking.kt @@ -36,45 +36,53 @@ object Networking { val reachability = Visibility.NETWORK var reachableCache: Set? = 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 = getReachable().plus(this) + fun getChargerNodes(): Set = 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) } }