Energy cables test

This commit is contained in:
DBotThePony 2023-12-29 16:35:32 +07:00
parent 6de2f14fcd
commit 79aeca5720
Signed by: DBot
GPG Key ID: DCC23B5715498507
12 changed files with 241 additions and 7 deletions

View File

@ -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<StorageNode> STORAGE_NODE = CapabilityManager.get(new CapabilityToken<>() {});
@Nonnull
@NotNull
public static final Capability<EnergyCableBlockEntity.Node> ENERGY_CABLE_NODE = CapabilityManager.get(new CapabilityToken<>() {});
@Nonnull
@NotNull
public static final Capability<ICuriosItemHandler> CURIOS_INVENTORY = CapabilityManager.get(new CapabilityToken<>() {});

View File

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

View File

@ -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,

View File

@ -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<Node, EnergyCableGraph>(::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, CableSide>(RelativeSide::class.java)
val energySides: Map<RelativeSide, CableSide> = 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)
}
}

View File

@ -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<EnergyCableBlockEntity.Node, EnergyCableGraph>() {
val livelyNodes = ObjectOpenHashSet<EnergyCableBlockEntity.Node>()
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
}
}

View File

@ -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<N : GraphNode<N, G>, G : GraphNodeList<N, G>>(val graphFactory: () -> G) {
interface Link {
@ -38,6 +40,11 @@ open class GraphNode<N : GraphNode<N, G>, G : GraphNodeList<N, G>>(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?) {

View File

@ -35,14 +35,32 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : 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<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
private val graphs = ArrayList<WeakReference<GraphNodeList<*, *>>>()
private val queue = ArrayList<WeakReference<GraphNodeList<*, *>>>()
fun tick() {
internal fun tick() {
if (queue.isNotEmpty()) {
graphs.addAll(queue)
queue.clear()

View File

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

View File

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

View File

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

View File

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

View File

@ -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"