merge
This commit is contained in:
@@ -3,9 +3,11 @@ package org.neoflock.neocomputers
|
|||||||
import com.google.common.base.Suppliers
|
import com.google.common.base.Suppliers
|
||||||
import dev.architectury.event.events.client.ClientLifecycleEvent
|
import dev.architectury.event.events.client.ClientLifecycleEvent
|
||||||
import dev.architectury.event.events.common.LifecycleEvent
|
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.client.gui.MenuScreenRegistry
|
||||||
import dev.architectury.registry.registries.RegistrarManager
|
import dev.architectury.registry.registries.RegistrarManager
|
||||||
import net.minecraft.resources.Identifier
|
import net.minecraft.resources.Identifier
|
||||||
|
import net.minecraft.util.profiling.jfr.event.ServerTickTimeEvent
|
||||||
import org.neoflock.neocomputers.block.Blocks
|
import org.neoflock.neocomputers.block.Blocks
|
||||||
import org.neoflock.neocomputers.entity.BlockEntities
|
import org.neoflock.neocomputers.entity.BlockEntities
|
||||||
import org.neoflock.neocomputers.gui.buffer.BufferRenderer
|
import org.neoflock.neocomputers.gui.buffer.BufferRenderer
|
||||||
@@ -39,7 +41,6 @@ object NeoComputers {
|
|||||||
ClientLifecycleEvent.CLIENT_SETUP.register {
|
ClientLifecycleEvent.CLIENT_SETUP.register {
|
||||||
MenuScreenRegistry.registerScreenFactory(Menus.SCREEN_MENU.get(), ::ScreenScreen)
|
MenuScreenRegistry.registerScreenFactory(Menus.SCREEN_MENU.get(), ::ScreenScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientLifecycleEvent.CLIENT_STARTED.register {
|
ClientLifecycleEvent.CLIENT_STARTED.register {
|
||||||
FontProvider.load(Identifier.fromNamespaceAndPath("neocomputers", "font/unscii.hex"))
|
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 logA = Networking.LoggerNode("LogA")
|
||||||
val logB = Networking.LoggerNode("LogB")
|
val logB = Networking.LoggerNode("LogB")
|
||||||
val batteryA = Networking.DebugBatteryNode(0.0, 10000.0)
|
val batteryA = Networking.DebugBatteryNode(0.0, 10000.0)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ object Blocks {
|
|||||||
val BLOCKS: DeferredRegister<Block> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK)
|
val BLOCKS: DeferredRegister<Block> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK)
|
||||||
val TEST_BLOCK: RegistrySupplier<Block> = BaseBlock.register("test") { BaseBlock("test") }
|
val TEST_BLOCK: RegistrySupplier<Block> = BaseBlock.register("test") { BaseBlock("test") }
|
||||||
val SCREEN_BLOCK: RegistrySupplier<Block> = BaseBlock.register("screen") { ScreenBlock() }
|
val SCREEN_BLOCK: RegistrySupplier<Block> = BaseBlock.register("screen") { ScreenBlock() }
|
||||||
|
val CAPACITOR_BLOCK: RegistrySupplier<Block> = BaseBlock.register("capacitor") { CapacitorBlock() }
|
||||||
|
|
||||||
fun registerBlockItems() {
|
fun registerBlockItems() {
|
||||||
BLOCKS.forEach(Consumer { sup: RegistrySupplier<Block> ->
|
BLOCKS.forEach(Consumer { sup: RegistrySupplier<Block> ->
|
||||||
|
|||||||
67
src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt
Normal file
67
src/main/kotlin/org/neoflock/neocomputers/block/Capacitor.kt
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,13 +17,17 @@ import net.minecraft.world.level.block.entity.BlockEntity
|
|||||||
import net.minecraft.world.level.block.state.BlockState
|
import net.minecraft.world.level.block.state.BlockState
|
||||||
import net.minecraft.world.phys.BlockHitResult
|
import net.minecraft.world.phys.BlockHitResult
|
||||||
import org.neoflock.neocomputers.NeoComputers
|
import org.neoflock.neocomputers.NeoComputers
|
||||||
|
import org.neoflock.neocomputers.entity.BlockEntities
|
||||||
import org.neoflock.neocomputers.entity.ScreenEntity
|
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
|
||||||
|
|
||||||
class ScreenBlock() : BaseBlock("screen"), EntityBlock {
|
class ScreenBlock() : BaseBlock("screen"), EntityBlock {
|
||||||
|
|
||||||
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? {
|
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(
|
override fun useWithoutItem(
|
||||||
@@ -34,6 +38,8 @@ class ScreenBlock() : BaseBlock("screen"), EntityBlock {
|
|||||||
blockHitResult: BlockHitResult
|
blockHitResult: BlockHitResult
|
||||||
): InteractionResult {
|
): InteractionResult {
|
||||||
if(!level.isClientSide) {
|
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 {
|
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 {
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import net.minecraft.core.registries.Registries
|
|||||||
import net.minecraft.world.level.block.entity.BlockEntityType
|
import net.minecraft.world.level.block.entity.BlockEntityType
|
||||||
import org.neoflock.neocomputers.NeoComputers
|
import org.neoflock.neocomputers.NeoComputers
|
||||||
import org.neoflock.neocomputers.block.Blocks
|
import org.neoflock.neocomputers.block.Blocks
|
||||||
|
import org.neoflock.neocomputers.block.CapacitorEntity
|
||||||
|
|
||||||
object BlockEntities {
|
object BlockEntities {
|
||||||
val BLOCKENTITIES: DeferredRegister<BlockEntityType<*>> = DeferredRegister.create(NeoComputers.MODID, Registries.BLOCK_ENTITY_TYPE);
|
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 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()))}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,22 @@ import net.minecraft.core.BlockPos
|
|||||||
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.entity.BlockEntityType
|
||||||
import net.minecraft.world.level.block.state.BlockState
|
import net.minecraft.world.level.block.state.BlockState
|
||||||
|
import org.neoflock.neocomputers.network.Networking
|
||||||
|
|
||||||
class ScreenEntity(blockPos: BlockPos, blockState: BlockState) :
|
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
|
// stuff
|
||||||
|
override fun getNode() = scrnod
|
||||||
}
|
}
|
||||||
@@ -36,45 +36,53 @@ object Networking {
|
|||||||
val reachability = Visibility.NETWORK
|
val reachability = Visibility.NETWORK
|
||||||
var reachableCache: Set<Node>? = null
|
var reachableCache: Set<Node>? = null
|
||||||
|
|
||||||
|
open fun isProducer(): Boolean = false
|
||||||
|
open fun isConsumer(): Boolean = false
|
||||||
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
|
||||||
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 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() }
|
||||||
|
|
||||||
// the algorithm for balancing energy levels
|
fun consumeFromNodeAsMuchAsPossible(energy: Double): Double {
|
||||||
fun balanceEnergyLevels() {
|
val consumed = min(energy, getEnergy())
|
||||||
// basic algorithm: ensure equal percentages
|
setEnergy(getEnergy() - consumed)
|
||||||
val cap = this.maxEnergyInConnections();
|
return consumed
|
||||||
val total = this.totalEnergyInConnections();
|
|
||||||
|
|
||||||
val percentage = total / cap;
|
|
||||||
|
|
||||||
getChargerNodes().forEach {
|
|
||||||
it.setEnergy(percentage * it.maxEnergyCapacity());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempts to consume
|
// attempts to consume
|
||||||
fun consumeEnergy(energy: Double): Boolean {
|
fun consumeEnergy(energy: Double): Boolean {
|
||||||
// consumes energy, returns false if not enough
|
// consumes energy, returns false if not enough
|
||||||
val total = this.totalEnergyInConnections()
|
val total = totalEnergyInConnections() + getEnergy()
|
||||||
if(energy > total) return false
|
if(energy > total) return false
|
||||||
|
|
||||||
val percentageConsumed = energy / total
|
var remaining = energy
|
||||||
|
remaining -= consumeFromNodeAsMuchAsPossible(remaining)
|
||||||
|
if(remaining <= 0.0) return true
|
||||||
|
|
||||||
getChargerNodes().forEach {
|
for (charger in getChargerNodes()) {
|
||||||
it.setEnergy(it.getEnergy() * (1.0 - percentageConsumed));
|
if(remaining <= 0.0) break
|
||||||
|
remaining -= charger.consumeFromNodeAsMuchAsPossible(remaining)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
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() {
|
open fun tick() {
|
||||||
// rationale: the other ones can figure it out
|
if(isConsumer()) tryToChargeFully()
|
||||||
if(this.maxEnergyCapacity() > 0) this.balanceEnergyLevels()
|
|
||||||
}
|
}
|
||||||
// processes a received message
|
// processes a received message
|
||||||
open fun received(message: Message) {}
|
open fun received(message: Message) {}
|
||||||
@@ -132,7 +140,7 @@ object Networking {
|
|||||||
other.directConnectTo(this);
|
other.directConnectTo(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disconnectTo(other: Node) {
|
fun disconnectFrom(other: Node) {
|
||||||
this.directDisconnectFrom(other);
|
this.directDisconnectFrom(other);
|
||||||
other.directDisconnectFrom(this);
|
other.directDisconnectFrom(this);
|
||||||
}
|
}
|
||||||
@@ -158,6 +166,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 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 }
|
||||||
@@ -230,6 +239,10 @@ object Networking {
|
|||||||
if(node is WirelessEndpoint) {
|
if(node is WirelessEndpoint) {
|
||||||
wirelessNodes.remove(node);
|
wirelessNodes.remove(node);
|
||||||
}
|
}
|
||||||
|
// toList() in order to copy it
|
||||||
|
node.connections.toList().forEach {
|
||||||
|
node.disconnectFrom(it)
|
||||||
|
}
|
||||||
allNodes.forEach { it.onNodeRemoved(node) }
|
allNodes.forEach { it.onNodeRemoved(node) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user