From 79aeca57209628097a3b9e5fbeab7e32f92b6a84 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Fri, 29 Dec 2023 16:35:32 +0700 Subject: [PATCH] Energy cables test --- .../mc/otm/capability/MatteryCapability.java | 5 + .../ru/dbotthepony/mc/otm/block/Cables.kt | 16 ++- .../dbotthepony/mc/otm/block/MatteryBlock.kt | 4 +- .../entity/cable/EnergyCableBlockEntity.kt | 133 ++++++++++++++++++ .../block/entity/cable/EnergyCableGraph.kt | 51 +++++++ .../ru/dbotthepony/mc/otm/graph/GraphNode.kt | 7 + .../dbotthepony/mc/otm/graph/GraphNodeList.kt | 24 +++- .../mc/otm/registry/MBlockEntities.kt | 2 + .../ru/dbotthepony/mc/otm/registry/MBlocks.kt | 2 + .../mc/otm/registry/MCreativeTabs.kt | 1 + .../ru/dbotthepony/mc/otm/registry/MItems.kt | 2 + .../ru/dbotthepony/mc/otm/registry/MNames.kt | 1 + 12 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java index 4caae5fed..90c7f3aa5 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java @@ -5,6 +5,7 @@ import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.CapabilityToken; import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; import org.jetbrains.annotations.NotNull; +import ru.dbotthepony.mc.otm.block.entity.cable.EnergyCableBlockEntity; import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive; import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage; import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage; @@ -50,6 +51,10 @@ public class MatteryCapability { @NotNull public static final Capability STORAGE_NODE = CapabilityManager.get(new CapabilityToken<>() {}); + @Nonnull + @NotNull + public static final Capability ENERGY_CABLE_NODE = CapabilityManager.get(new CapabilityToken<>() {}); + @Nonnull @NotNull public static final Capability CURIOS_INVENTORY = CapabilityManager.get(new CapabilityToken<>() {}); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/Cables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/Cables.kt index 9adb59906..e17fe4714 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/Cables.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/Cables.kt @@ -21,10 +21,11 @@ import net.minecraft.world.phys.shapes.Shapes import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.entity.MatterCableBlockEntity import ru.dbotthepony.mc.otm.block.entity.StorageCableBlockEntity +import ru.dbotthepony.mc.otm.block.entity.cable.EnergyCableBlockEntity import java.util.Collections import java.util.EnumMap -abstract class CableBlock(properties: Properties) : Block(properties) { +abstract class CableBlock(properties: Properties) : MatteryBlock(properties) { init { registerDefaultState(defaultBlockState() .setValue(CONNECTION_SOUTH, false) @@ -127,3 +128,16 @@ class StorageCableBlock : CableBlock(Properties.of().mapColor(MapColor.METAL).re return StorageCableBlockEntity(blockPos, blockState) } } + +class EnergyCableBlock : CableBlock(Properties.of().mapColor(MapColor.METAL).requiresCorrectToolForDrops().sound(SoundType.METAL).strength(1.0f, 6.0f)), EntityBlock { + private val shapes = generateShapes(0.125) + + @Suppress("OVERRIDE_DEPRECATION") + override fun getShape(blockState: BlockState, accessor: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape { + return shapes[blockState] ?: Shapes.block() + } + + override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { + return EnergyCableBlockEntity(blockPos, blockState) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt index ac83b83c9..c7806370f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt @@ -70,9 +70,7 @@ fun interface INeighbourChangeListener { ) } -abstract class MatteryBlock @JvmOverloads constructor( - properties: Properties = DEFAULT_PROPERTIES -) : Block(properties), INeighbourChangeListener { +abstract class MatteryBlock(properties: Properties = DEFAULT_PROPERTIES) : Block(properties), INeighbourChangeListener { override fun setPlacedBy( level: Level, blockPos: BlockPos, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt new file mode 100644 index 000000000..20772b3cf --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt @@ -0,0 +1,133 @@ +package ru.dbotthepony.mc.otm.block.entity.cable + +import net.minecraft.core.BlockPos +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.capabilities.ForgeCapabilities +import ru.dbotthepony.mc.otm.SERVER_IS_LIVE +import ru.dbotthepony.mc.otm.block.CableBlock +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.math.BlockRotation +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.graph.GraphNode +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import java.util.Collections +import java.util.EnumMap + +// after some thinking, team decided to settle with IC2's side (techreborn, gregtech, integrated dynamics*, pipez*, p2p tunnels, ...) of implementation, +// where cables have no residue capacitance, and never pull/push energy by themselves + +// this allows simpler implementation and faster code, while also reducing possibility of duplication exploits + +class EnergyCableBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBlockEntity(MBlockEntities.ENERGY_CABLE, blockPos, blockState) { + inner class CableSide(val side: RelativeSide) : IMatteryEnergyStorage { + var isEnabled = true + set(value) { + field = value + + if (value) { + node.graph.livelyNodes.add(node) + } + } + + init { + check(side !in energySidesInternal) + energySidesInternal[side] = this + sides[side]!!.Cap(ForgeCapabilities.ENERGY, this) + } + + val neighbour = sides[side]!!.trackEnergy() + + init { + neighbour.addListener { + if (isEnabled) { + if (it.isPresent) { + if (it.resolve().get() !is CableSide) { + node.graph.livelyNodes.add(node) + } + + ru.dbotthepony.mc.otm.onceServer { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[blockRotation.side2Dir(side)]!!, true) + + if (newState !== blockState) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } + } else { + ru.dbotthepony.mc.otm.onceServer { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[blockRotation.side2Dir(side)]!!, false) + + if (newState !== blockState) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } + } + } + } + } + + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return Decimal.ZERO + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return node.graph.receiveEnergy(howMuch, simulate) + } + + override var batteryLevel: Decimal + get() = Decimal.ZERO + set(value) {} + + override val maxBatteryLevel: Decimal get() = Decimal.ZERO + override var energyFlow: FlowDirection = FlowDirection.BI_DIRECTIONAL + private set + } + + inner class Node : GraphNode(::EnergyCableGraph) { + val sides get() = energySides + + override fun onNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, true) + + if (newState !== blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } + } + + override fun onUnNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, false) + + if (newState !== blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } + } + } + + override val blockRotation: BlockRotation + get() = BlockRotation.NORTH + + private val energySidesInternal = EnumMap(RelativeSide::class.java) + val energySides: Map = Collections.unmodifiableMap(energySidesInternal) + val node = Node() + + override fun setLevel(level: Level) { + super.setLevel(level) + node.discover(this, MatteryCapability.ENERGY_CABLE_NODE) + } + + override fun setRemoved() { + super.setRemoved() + node.isValid = false + } + + init { + sides.keys.forEach { CableSide(it) } + exposeGlobally(MatteryCapability.ENERGY_CABLE_NODE, node) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt new file mode 100644 index 000000000..c5e25be5b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt @@ -0,0 +1,51 @@ +package ru.dbotthepony.mc.otm.block.entity.cable + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import ru.dbotthepony.mc.otm.capability.receiveEnergy +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.graph.GraphNodeList + +class EnergyCableGraph : GraphNodeList() { + val livelyNodes = ObjectOpenHashSet() + + override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) { + livelyNodes.remove(node) + } + + override fun onNodeAdded(node: EnergyCableBlockEntity.Node) { + livelyNodes.add(node) + } + + fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + val itr = livelyNodes.iterator() + var received = Decimal.ZERO + var residue = howMuch + + for (node in itr) { + var hit = false + + for (side in node.sides.values) { + if (side.isEnabled) { + side.neighbour.get().ifPresentK { + if (it !is EnergyCableBlockEntity.CableSide) { + hit = true + + val thisReceived = it.receiveEnergy(residue, simulate) + received += thisReceived + residue -= thisReceived + + if (!residue.isPositive) return received + } + } + } + } + + if (!hit) { + itr.remove() + } + } + + return received + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt index 64d45298b..05899b77f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt @@ -11,6 +11,8 @@ import net.minecraftforge.common.capabilities.Capability import ru.dbotthepony.mc.otm.addTicker import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.core.orNull +import ru.dbotthepony.mc.otm.core.util.IConditionalTickable +import ru.dbotthepony.mc.otm.core.util.ITickable open class GraphNode, G : GraphNodeList>(val graphFactory: () -> G) { interface Link { @@ -38,6 +40,11 @@ open class GraphNode, G : GraphNodeList>(val graphFact private var seen: Int = 0 + fun beginTicking() { + require(this is IConditionalTickable || this is ITickable) { "Node must implement either ITickable or IConditionalTickable to tick" } + graph.beginTicking(this as N) + } + operator fun get(key: Link): N? = neighbours[key] operator fun set(key: Link, node: N?) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt index 763b7beeb..de223337d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt @@ -35,14 +35,32 @@ open class GraphNodeList, G : GraphNodeList> : ICondit return false } + fun beginTicking(node: N) { + require(node in nodesInternal || node in queuedAdd) { "Node $node does not belong to $this" } + if (node in queuedRemove) return + + if (node is IConditionalTickable) { + conditional.add(node) + beginTicking() + } else if (node is ITickable) { + always.add(node) + beginTicking() + } else { + throw ClassCastException("$node does not implement ITickable nor IConditionalTickable") + } + } + private fun addNow(node: N) { node.graph = this as G nodesInternal.add(node) - if (node is IConditionalTickable) + if (node is IConditionalTickable) { conditional.add(node) - else if (node is ITickable) + beginTicking() + } else if (node is ITickable) { always.add(node) + beginTicking() + } onNodeAdded(node) } @@ -178,7 +196,7 @@ open class GraphNodeList, G : GraphNodeList> : ICondit private val graphs = ArrayList>>() private val queue = ArrayList>>() - fun tick() { + internal fun tick() { if (queue.isNotEmpty()) { graphs.addAll(queue) queue.clear() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt index 8172c022a..a0697f9c7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt @@ -15,6 +15,7 @@ import ru.dbotthepony.mc.otm.block.entity.tech.* import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntityExplosionDebugger import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntitySphereDebugger +import ru.dbotthepony.mc.otm.block.entity.cable.EnergyCableBlockEntity import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity import ru.dbotthepony.mc.otm.block.entity.decorative.DevChestBlockEntity import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity @@ -73,6 +74,7 @@ object MBlockEntities { val DEV_CHEST by register(MNames.DEV_CHEST, ::DevChestBlockEntity, MBlocks::DEV_CHEST) val PAINTER by register(MNames.PAINTER, ::PainterBlockEntity, MBlocks::PAINTER) val MATTER_ENTANGLER by register(MNames.MATTER_ENTANGLER, ::MatterEntanglerBlockEntity, MBlocks::MATTER_ENTANGLER) + val ENERGY_CABLE by register(MNames.ENERGY_CABLE, ::EnergyCableBlockEntity, MBlocks::ENERGY_CABLE) val POWERED_FURNACE: BlockEntityType by registry.register(MNames.POWERED_FURNACE) { BlockEntityType.Builder.of({ a, b -> MBlocks.POWERED_FURNACE.newBlockEntity(a, b) }, MBlocks.POWERED_FURNACE).build(null) } val POWERED_BLAST_FURNACE: BlockEntityType by registry.register(MNames.POWERED_BLAST_FURNACE) { BlockEntityType.Builder.of({ a, b -> MBlocks.POWERED_BLAST_FURNACE.newBlockEntity(a, b) }, MBlocks.POWERED_BLAST_FURNACE).build(null) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt index 8e64746f7..266a98416 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt @@ -36,6 +36,7 @@ import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.block.BlackHoleBlock import ru.dbotthepony.mc.otm.block.BlockExplosionDebugger import ru.dbotthepony.mc.otm.block.BlockSphereDebugger +import ru.dbotthepony.mc.otm.block.EnergyCableBlock import ru.dbotthepony.mc.otm.block.MatterCableBlock import ru.dbotthepony.mc.otm.block.StorageCableBlock import ru.dbotthepony.mc.otm.block.decorative.DevChestBlock @@ -114,6 +115,7 @@ object MBlocks { val MATTER_RECONSTRUCTOR: MatterReconstructorBlock by registry.register(MNames.MATTER_RECONSTRUCTOR) { MatterReconstructorBlock() } val PAINTER: PainterBlock by registry.register(MNames.PAINTER) { PainterBlock() } val MATTER_ENTANGLER: MatterEntanglerBlock by registry.register(MNames.MATTER_ENTANGLER) { MatterEntanglerBlock() } + val ENERGY_CABLE: EnergyCableBlock by registry.register(MNames.ENERGY_CABLE) { EnergyCableBlock() } val STORAGE_BUS: Block by registry.register(MNames.STORAGE_BUS) { StorageBusBlock() } val STORAGE_IMPORTER: Block by registry.register(MNames.STORAGE_IMPORTER) { StorageImporterBlock() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt index ef32d75bc..66ef9f405 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt @@ -129,6 +129,7 @@ private fun CreativeModeTab.Output.fluids(value: Item) { private fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) { with(consumer) { + accept(MItems.ENERGY_CABLE) accept(MItems.MACHINES) accept(MItems.MachineUpgrades.Basic.LIST) accept(MItems.MachineUpgrades.Normal.LIST) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt index d9cffe638..4c50272dc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt @@ -46,6 +46,8 @@ object MItems { registry.register(bus) } + val ENERGY_CABLE: BlockItem by registry.register(MNames.ENERGY_CABLE) { BlockItem(MBlocks.ENERGY_CABLE, DEFAULT_PROPERTIES) } + val ANDROID_STATION: BlockItem by registry.register(MNames.ANDROID_STATION) { BlockItem(MBlocks.ANDROID_STATION, DEFAULT_PROPERTIES) } val ANDROID_CHARGER: BlockItem by registry.register(MNames.ANDROID_CHARGER) { BlockItem(MBlocks.ANDROID_CHARGER, DEFAULT_PROPERTIES) } val BATTERY_BANK: BlockItem by registry.register(MNames.BATTERY_BANK) { BlockItem(MBlocks.BATTERY_BANK, DEFAULT_PROPERTIES) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt index 258cc69c5..43316e325 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt @@ -17,6 +17,7 @@ object MNames { const val DEV_CHEST = "dev_chest" const val PAINTER = "painter" const val MATTER_ENTANGLER = "matter_entangler" + const val ENERGY_CABLE = "energy_cable" // blocks const val ANDROID_STATION = "android_station"