Compare commits
4 Commits
c0b8488afd
...
43afa73df7
Author | SHA1 | Date | |
---|---|---|---|
43afa73df7 | |||
0673fbf352 | |||
3f75e40e9c | |||
5f6da754f9 |
@ -222,7 +222,7 @@ fun tickServer(ticker: IConditionalTickable) {
|
|||||||
postServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping")
|
postServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Level.timer(time: Int, ticker: Runnable): TickList.Timer? {
|
fun Level.once(time: Int, ticker: Runnable): TickList.Timer? {
|
||||||
if (this.isClientSide) return null
|
if (this.isClientSide) return null
|
||||||
|
|
||||||
if (!SERVER_IS_LIVE) {
|
if (!SERVER_IS_LIVE) {
|
||||||
|
@ -15,9 +15,13 @@ import ru.dbotthepony.mc.otm.capability.FlowDirection
|
|||||||
import ru.dbotthepony.mc.otm.capability.MatteryCapability
|
import ru.dbotthepony.mc.otm.capability.MatteryCapability
|
||||||
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
|
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
|
||||||
import ru.dbotthepony.mc.otm.config.CablesConfig
|
import ru.dbotthepony.mc.otm.config.CablesConfig
|
||||||
|
import ru.dbotthepony.mc.otm.core.get
|
||||||
import ru.dbotthepony.mc.otm.core.math.BlockRotation
|
import ru.dbotthepony.mc.otm.core.math.BlockRotation
|
||||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||||
import ru.dbotthepony.mc.otm.core.math.RelativeSide
|
import ru.dbotthepony.mc.otm.core.math.RelativeSide
|
||||||
|
import ru.dbotthepony.mc.otm.core.set
|
||||||
|
import ru.dbotthepony.mc.otm.core.util.TickList
|
||||||
|
import ru.dbotthepony.mc.otm.core.util.countingLazy
|
||||||
import ru.dbotthepony.mc.otm.graph.GraphNode
|
import ru.dbotthepony.mc.otm.graph.GraphNode
|
||||||
import ru.dbotthepony.mc.otm.once
|
import ru.dbotthepony.mc.otm.once
|
||||||
import ru.dbotthepony.mc.otm.onceServer
|
import ru.dbotthepony.mc.otm.onceServer
|
||||||
@ -37,7 +41,7 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
|
|||||||
field = value
|
field = value
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
node.graph.livelyNodes.add(node)
|
node.graph.addLivelyNode(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +59,7 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
|
|||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
if (neighbour.isPresent) {
|
if (neighbour.isPresent) {
|
||||||
if (neighbour.get() !is CableSide) {
|
if (neighbour.get() !is CableSide) {
|
||||||
node.graph.livelyNodes.add(node)
|
node.graph.addLivelyNode(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +94,27 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
|
|||||||
get() = Decimal.POSITIVE_INFINITY
|
get() = Decimal.POSITIVE_INFINITY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val isPoweredState by countingLazy(blockStateChangesCounter) {
|
||||||
|
this.blockState[BlockStateProperties.POWERED]
|
||||||
|
}
|
||||||
|
|
||||||
inner class Node : GraphNode<Node, EnergyCableGraph>(::EnergyCableGraph) {
|
inner class Node : GraphNode<Node, EnergyCableGraph>(::EnergyCableGraph) {
|
||||||
|
private var _segment = EnergyCableGraph.Segment(this)
|
||||||
|
|
||||||
|
var segment: EnergyCableGraph.Segment
|
||||||
|
get() { return _segment }
|
||||||
|
set(value) {
|
||||||
|
if (_segment === value) return
|
||||||
|
_segment.remove(this)
|
||||||
|
value.add(this)
|
||||||
|
_segment = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onInvalidate() {
|
||||||
|
_segment = EnergyCableGraph.Segment(this)
|
||||||
|
updatePoweredState(false)
|
||||||
|
}
|
||||||
|
|
||||||
val sides get() = energySides
|
val sides get() = energySides
|
||||||
|
|
||||||
override fun onNeighbour(link: Link) {
|
override fun onNeighbour(link: Link) {
|
||||||
@ -105,6 +129,35 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var updateTimer: TickList.Timer? = null
|
||||||
|
private var ongoingNewState: Boolean? = null
|
||||||
|
|
||||||
|
fun updatePoweredState(newState: Boolean) {
|
||||||
|
if (isRemoved) return
|
||||||
|
val level = level ?: return
|
||||||
|
|
||||||
|
if (isPoweredState != newState || ongoingNewState != newState) {
|
||||||
|
updateTimer?.cancel()
|
||||||
|
|
||||||
|
if (isPoweredState == newState) {
|
||||||
|
ongoingNewState = null
|
||||||
|
updateTimer?.cancel()
|
||||||
|
} else {
|
||||||
|
ongoingNewState = newState
|
||||||
|
|
||||||
|
// reduces flickering and block updates pressure
|
||||||
|
updateTimer = level.once(4) {
|
||||||
|
ongoingNewState = null
|
||||||
|
updateTimer = null
|
||||||
|
|
||||||
|
if (!isRemoved) {
|
||||||
|
level.setBlock(blockPos, blockState.set(BlockStateProperties.POWERED, newState), Block.UPDATE_CLIENTS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val blockEntity get() = this@EnergyCableBlockEntity
|
val blockEntity get() = this@EnergyCableBlockEntity
|
||||||
val canTraverse get() = energyThroughput > Decimal.ZERO
|
val canTraverse get() = energyThroughput > Decimal.ZERO
|
||||||
val energyThroughput get() = this@EnergyCableBlockEntity.energyThroughput
|
val energyThroughput get() = this@EnergyCableBlockEntity.energyThroughput
|
||||||
@ -115,30 +168,14 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
|
|||||||
if (!SERVER_IS_LIVE) return
|
if (!SERVER_IS_LIVE) return
|
||||||
val level = level
|
val level = level
|
||||||
|
|
||||||
val powered = node.graph.livelyNodes.filter {
|
|
||||||
it.blockEntity.energySides.filter {
|
|
||||||
side -> side.value.neighbour.isPresent && side.value.neighbour.get() !is CableSide
|
|
||||||
}.isNotEmpty()
|
|
||||||
}.size >= 2
|
|
||||||
|
|
||||||
level?.once {
|
level?.once {
|
||||||
if (!node.isValid) return@once
|
if (!node.isValid) return@once
|
||||||
|
|
||||||
val newState = blockState
|
val newState = blockState
|
||||||
.setValue(CableBlock.MAPPING_CONNECTION_PROP[side]!!, status)
|
.setValue(CableBlock.MAPPING_CONNECTION_PROP[side]!!, status)
|
||||||
.setValue(BlockStateProperties.POWERED, powered)
|
|
||||||
|
|
||||||
if (newState !== blockState && SERVER_IS_LIVE)
|
if (newState !== blockState && SERVER_IS_LIVE)
|
||||||
level.setBlock(blockPos, newState, Block.UPDATE_CLIENTS)
|
level.setBlock(blockPos, newState, Block.UPDATE_CLIENTS)
|
||||||
|
|
||||||
node.graph.livelyNodes.forEach {
|
|
||||||
if (it.isValid) {
|
|
||||||
val newState = it.blockEntity.blockState.setValue(BlockStateProperties.POWERED, powered)
|
|
||||||
|
|
||||||
if (newState !== it.blockEntity.blockState && SERVER_IS_LIVE)
|
|
||||||
level.setBlock(it.blockEntity.blockPos, newState, Block.UPDATE_CLIENTS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +191,9 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
|
|||||||
|
|
||||||
override fun setLevel(level: Level) {
|
override fun setLevel(level: Level) {
|
||||||
super.setLevel(level)
|
super.setLevel(level)
|
||||||
node.discover(this, MatteryCapability.ENERGY_CABLE_NODE)
|
|
||||||
|
if (!level.isClientSide)
|
||||||
|
node.discover(this, MatteryCapability.ENERGY_CABLE_NODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setRemoved() {
|
override fun setRemoved() {
|
||||||
|
@ -1,37 +1,245 @@
|
|||||||
package ru.dbotthepony.mc.otm.block.entity.cable
|
package ru.dbotthepony.mc.otm.block.entity.cable
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
||||||
|
import net.minecraft.core.BlockPos
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.mc.otm.UNIVERSE_TICKS
|
||||||
import ru.dbotthepony.mc.otm.capability.receiveEnergy
|
import ru.dbotthepony.mc.otm.capability.receiveEnergy
|
||||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||||
import ru.dbotthepony.mc.otm.core.math.RelativeSide
|
import ru.dbotthepony.mc.otm.core.math.RelativeSide
|
||||||
|
import ru.dbotthepony.mc.otm.core.shuffle
|
||||||
import ru.dbotthepony.mc.otm.graph.GraphNodeList
|
import ru.dbotthepony.mc.otm.graph.GraphNodeList
|
||||||
|
import ru.dbotthepony.mc.otm.onceServer
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.collections.HashSet
|
||||||
import kotlin.math.ln
|
import kotlin.math.ln
|
||||||
|
|
||||||
class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableGraph>() {
|
class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableGraph>() {
|
||||||
val livelyNodes = HashSet<EnergyCableBlockEntity.Node>()
|
private val livelyNodes = HashSet<EnergyCableBlockEntity.Node>()
|
||||||
|
private val livelyNodesList = ArrayList<EnergyCableBlockEntity.Node>()
|
||||||
|
|
||||||
private val pathCache = HashMap<Pair<EnergyCableBlockEntity.Node, EnergyCableBlockEntity.Node>, Decimal?>()
|
fun addLivelyNode(node: EnergyCableBlockEntity.Node) {
|
||||||
|
require(node in nodesSet) { "$node does not belong to $this" }
|
||||||
|
|
||||||
|
if (livelyNodes.add(node)) {
|
||||||
|
livelyNodesList.add(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: LRU cache?
|
||||||
|
private val pathCache = HashMap<Pair<EnergyCableBlockEntity.Node, EnergyCableBlockEntity.Node>, ArrayList<SegmentPath?>>()
|
||||||
|
|
||||||
private class SearchNode(val node: EnergyCableBlockEntity.Node, target: EnergyCableBlockEntity.Node, var parent: SearchNode? = null) : Comparable<SearchNode> {
|
private class SearchNode(val node: EnergyCableBlockEntity.Node, target: EnergyCableBlockEntity.Node, var parent: SearchNode? = null) : Comparable<SearchNode> {
|
||||||
val heuristics: Double = node.position.distSqr(target.position) * 0.0001 - ln(node.energyThroughput.coerceAtMost(Decimal.LONG_MAX_VALUE).toDouble())
|
val heuristics: Double = node.position.distSqr(target.position) * 0.0001 - ln(node.segment.availableThroughput.coerceAtMost(Decimal.LONG_MAX_VALUE).toDouble())
|
||||||
|
|
||||||
override fun compareTo(other: SearchNode): Int {
|
override fun compareTo(other: SearchNode): Int {
|
||||||
return heuristics.compareTo(other.heuristics)
|
return heuristics.compareTo(other.heuristics)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invalidatePathCache() {
|
class SegmentPath(private val a: BlockPos, private val b: BlockPos) {
|
||||||
pathCache.clear()
|
val segments = HashSet<Segment>()
|
||||||
|
private var shortCircuit = false
|
||||||
|
private var lastTickTransfers = 0
|
||||||
|
private var lastTick = 0
|
||||||
|
|
||||||
|
val availableThroughput: Decimal
|
||||||
|
get() = segments.minOfOrNull { it.availableThroughput } ?: Decimal.ZERO
|
||||||
|
|
||||||
|
fun transfer(amount: Decimal, simulate: Boolean, instantSnapshot: MutableMap<Segment, Decimal>): Decimal {
|
||||||
|
if (!amount.isPositive || shortCircuit) {
|
||||||
|
return Decimal.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastTickTransfers++ >= 40000) {
|
||||||
|
if (lastTick == UNIVERSE_TICKS) {
|
||||||
|
shortCircuit = true
|
||||||
|
LOGGER.warn("Cable path from $a to $b appears to be a short circuit, disabling.")
|
||||||
|
} else {
|
||||||
|
lastTick = UNIVERSE_TICKS
|
||||||
|
lastTickTransfers = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var min = amount
|
||||||
|
|
||||||
|
for (segment in segments) {
|
||||||
|
min = minOf(segment.transfer(amount, instantSnapshot), min)
|
||||||
|
if (!min.isPositive) return Decimal.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!simulate)
|
||||||
|
segments.forEach { it.transfer(min) }
|
||||||
|
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove() {
|
||||||
|
segments.toTypedArray().forEach { it.remove(this) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPath(a: EnergyCableBlockEntity.Node, b: EnergyCableBlockEntity.Node): Decimal? {
|
// TODO: Actual segment logic (merging segments)
|
||||||
|
class Segment() {
|
||||||
|
constructor(node: EnergyCableBlockEntity.Node) : this() {
|
||||||
|
nodes.add(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val nodes = HashSet<EnergyCableBlockEntity.Node>()
|
||||||
|
private val paths = HashSet<SegmentPath>()
|
||||||
|
|
||||||
|
var throughput = Decimal.ZERO
|
||||||
|
private set
|
||||||
|
|
||||||
|
var transferredLastTick = Decimal.ZERO
|
||||||
|
private set
|
||||||
|
|
||||||
|
val availableThroughput: Decimal
|
||||||
|
get() = throughput - transferredLastTick
|
||||||
|
|
||||||
|
private var throughputKnown = false
|
||||||
|
private var lastTick = 0
|
||||||
|
private var lastPoweredStatus = 0
|
||||||
|
|
||||||
|
private fun updateBlockstates(): Boolean {
|
||||||
|
if (lastTick + 2 < UNIVERSE_TICKS) {
|
||||||
|
transferredLastTick = Decimal.ZERO
|
||||||
|
lastTick = UNIVERSE_TICKS
|
||||||
|
}
|
||||||
|
|
||||||
|
val newState = transferredLastTick.isPositive
|
||||||
|
val numState = if (newState) 1 else -1
|
||||||
|
|
||||||
|
if (numState != lastPoweredStatus) {
|
||||||
|
lastPoweredStatus = numState
|
||||||
|
nodes.forEach { if (it.segment === this) it.updatePoweredState(newState) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return newState
|
||||||
|
}
|
||||||
|
|
||||||
|
private var hasBlockstateTimer = false
|
||||||
|
|
||||||
|
private fun blockstateUpdateStep() {
|
||||||
|
hasBlockstateTimer = updateBlockstates()
|
||||||
|
|
||||||
|
if (hasBlockstateTimer) {
|
||||||
|
onceServer(20) {
|
||||||
|
blockstateUpdateStep()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun transfer(amount: Decimal, instantSnapshot: MutableMap<Segment, Decimal>): Decimal {
|
||||||
|
if (lastTick != UNIVERSE_TICKS) {
|
||||||
|
transferredLastTick = Decimal.ZERO
|
||||||
|
lastTick = UNIVERSE_TICKS
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentTransferred = instantSnapshot[this] ?: transferredLastTick
|
||||||
|
|
||||||
|
if (currentTransferred >= throughput || !amount.isPositive) {
|
||||||
|
return Decimal.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
val new = minOf(currentTransferred + amount, throughput)
|
||||||
|
val diff = new - currentTransferred
|
||||||
|
instantSnapshot[this] = new
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
fun transfer(amount: Decimal): Decimal {
|
||||||
|
if (lastTick != UNIVERSE_TICKS) {
|
||||||
|
transferredLastTick = Decimal.ZERO
|
||||||
|
lastTick = UNIVERSE_TICKS
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transferredLastTick >= throughput || !amount.isPositive) {
|
||||||
|
return Decimal.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
val new = minOf(transferredLastTick + amount, throughput)
|
||||||
|
val diff = new - transferredLastTick
|
||||||
|
transferredLastTick = new
|
||||||
|
|
||||||
|
if (!hasBlockstateTimer) {
|
||||||
|
blockstateUpdateStep()
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(node: EnergyCableBlockEntity.Node) {
|
||||||
|
check(nodes.remove(node)) { "Tried to remove node $node from segment $this, but that node does not belong to this segment" }
|
||||||
|
|
||||||
|
if (nodes.isEmpty())
|
||||||
|
throughput = Decimal.ZERO
|
||||||
|
else
|
||||||
|
throughput = nodes.maxOf { it.energyThroughput }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(node: EnergyCableBlockEntity.Node) {
|
||||||
|
check(nodes.add(node)) { "Tried to add node $node to segment $this, but we already have that node added" }
|
||||||
|
throughput = maxOf(throughput, node.energyThroughput)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(path: SegmentPath) {
|
||||||
|
check(paths.add(path)) { "Path $path should already contain $this" }
|
||||||
|
check(path.segments.add(this)) { "Path set and Segment disagree whenever $this is absent from $path" }
|
||||||
|
|
||||||
|
if (!throughputKnown) {
|
||||||
|
throughput = nodes.maxOf { it.energyThroughput }
|
||||||
|
throughputKnown = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(path: SegmentPath) {
|
||||||
|
check(paths.remove(path)) { "Path $path shouldn't contain $this" }
|
||||||
|
check(path.segments.remove(this)) { "Path set and Segment disagree whenever $this is present in $path" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// breaks "instant snapshots" of segments atm
|
||||||
|
// shouldn't cause major gameplay issues though
|
||||||
|
fun split(): List<Segment> {
|
||||||
|
if (nodes.isEmpty()) {
|
||||||
|
throw IllegalStateException("Empty segment somehow?")
|
||||||
|
} else if (nodes.size == 1) {
|
||||||
|
return listOf(this)
|
||||||
|
} else {
|
||||||
|
lastPoweredStatus = 0
|
||||||
|
val list = ArrayList(nodes)
|
||||||
|
val itr = list.iterator()
|
||||||
|
itr.next()
|
||||||
|
val result = ArrayList<Segment>()
|
||||||
|
result.add(this)
|
||||||
|
|
||||||
|
for (v in itr) {
|
||||||
|
v.segment = Segment()
|
||||||
|
v.segment.paths.addAll(paths)
|
||||||
|
paths.forEach { it.segments.add(v.segment) }
|
||||||
|
result.add(v.segment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invalidatePathCache() {
|
||||||
|
pathCache.clear()
|
||||||
|
nodes.forEach { it.onInvalidate() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Multiple paths, so energy can be delivered to receiver through different paths if previously found path is congested
|
||||||
|
private fun getPath(a: EnergyCableBlockEntity.Node, b: EnergyCableBlockEntity.Node, energyToTransfer: Decimal): SegmentPath? {
|
||||||
if (!a.canTraverse || !b.canTraverse)
|
if (!a.canTraverse || !b.canTraverse)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val key = a to b
|
val list = pathCache.computeIfAbsent(a to b) { ArrayList(1) }
|
||||||
|
|
||||||
if (key in pathCache)
|
if (list.isNotEmpty())
|
||||||
return pathCache[key]
|
return list.first
|
||||||
|
|
||||||
// no free paths available, try to find extra one
|
// no free paths available, try to find extra one
|
||||||
// while this use A* algorithm, this is done purely for biasing search towards end point (to speed up search),
|
// while this use A* algorithm, this is done purely for biasing search towards end point (to speed up search),
|
||||||
@ -57,9 +265,13 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
|
|||||||
last = last.parent
|
last = last.parent
|
||||||
}
|
}
|
||||||
|
|
||||||
val calc = solution.minOf { it.energyThroughput }
|
val touchedSegments = ArrayList<Segment>()
|
||||||
pathCache[key] = calc
|
solution.forEach { touchedSegments.addAll(it.segment.split()) }
|
||||||
return calc
|
val path = SegmentPath(a.blockEntity.blockPos, b.blockEntity.blockPos)
|
||||||
|
touchedSegments.forEach { it.add(path) }
|
||||||
|
|
||||||
|
list.add(path)
|
||||||
|
return path
|
||||||
} else {
|
} else {
|
||||||
for (neighbour in first.node.neighboursView.values) {
|
for (neighbour in first.node.neighboursView.values) {
|
||||||
if (!seenNodes.add(neighbour) || !neighbour.canTraverse) continue
|
if (!seenNodes.add(neighbour) || !neighbour.canTraverse) continue
|
||||||
@ -69,24 +281,31 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
|
|||||||
}
|
}
|
||||||
|
|
||||||
// solution does not exist
|
// solution does not exist
|
||||||
pathCache[key] = null
|
list.add(null)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) {
|
override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) {
|
||||||
livelyNodes.remove(node)
|
if (livelyNodes.remove(node)) {
|
||||||
|
check(livelyNodesList.remove(node))
|
||||||
|
}
|
||||||
|
|
||||||
invalidatePathCache()
|
invalidatePathCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNodeAdded(node: EnergyCableBlockEntity.Node) {
|
override fun onNodeAdded(node: EnergyCableBlockEntity.Node) {
|
||||||
livelyNodes.add(node)
|
check(livelyNodes.add(node))
|
||||||
|
livelyNodesList.add(node)
|
||||||
invalidatePathCache()
|
invalidatePathCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun receiveEnergy(howMuch: Decimal, simulate: Boolean, fromNode: EnergyCableBlockEntity.Node, fromSide: RelativeSide): Decimal {
|
fun receiveEnergy(howMuch: Decimal, simulate: Boolean, fromNode: EnergyCableBlockEntity.Node, fromSide: RelativeSide): Decimal {
|
||||||
val itr = livelyNodes.iterator()
|
livelyNodesList.shuffle(fromNode.blockEntity.level!!.random)
|
||||||
|
|
||||||
|
val itr = livelyNodesList.iterator()
|
||||||
var received = Decimal.ZERO
|
var received = Decimal.ZERO
|
||||||
var residue = howMuch.coerceAtMost(fromNode.energyThroughput)
|
var residue = howMuch.coerceAtMost(fromNode.energyThroughput)
|
||||||
|
val snapshot = Reference2ObjectOpenHashMap<Segment, Decimal>()
|
||||||
|
|
||||||
for (node in itr) {
|
for (node in itr) {
|
||||||
var hit = false
|
var hit = false
|
||||||
@ -100,11 +319,11 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
|
|||||||
|
|
||||||
side.neighbour.get()?.let {
|
side.neighbour.get()?.let {
|
||||||
if (it !is EnergyCableBlockEntity.CableSide) {
|
if (it !is EnergyCableBlockEntity.CableSide) {
|
||||||
val limit = getPath(fromNode, node)
|
val path = getPath(fromNode, node, residue)
|
||||||
hit = true
|
hit = true
|
||||||
|
|
||||||
if (limit != null) {
|
if (path != null) {
|
||||||
val thisReceived = it.receiveEnergy(residue.coerceAtMost(limit), simulate)
|
val thisReceived = it.receiveEnergy(path.transfer(residue, simulate, snapshot), simulate)
|
||||||
received += thisReceived
|
received += thisReceived
|
||||||
residue -= thisReceived
|
residue -= thisReceived
|
||||||
if (!residue.isPositive) return received
|
if (!residue.isPositive) return received
|
||||||
@ -116,9 +335,14 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
|
|||||||
|
|
||||||
if (!hit) {
|
if (!hit) {
|
||||||
itr.remove()
|
itr.remove()
|
||||||
|
check(livelyNodes.remove(node))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return received
|
return received
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,22 @@ fun IMultiblockAccess.render(
|
|||||||
val nodes = currentNodes
|
val nodes = currentNodes
|
||||||
|
|
||||||
for (node in nodes.values) {
|
for (node in nodes.values) {
|
||||||
|
if (node.displayBlocks.isNotEmpty() && node.status == NodeStatus.VALID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: rendering
|
||||||
|
// TODO: rendering
|
||||||
|
// TODO: rendering
|
||||||
|
// TODO: Rendering
|
||||||
|
// TODO: Rendering...
|
||||||
|
// TODO: Rendering!..
|
||||||
|
// TODO: Rendering!..
|
||||||
|
// TODO: Rendering!
|
||||||
|
// TODO: Rendering!!
|
||||||
|
// TODO: RENDERING!!
|
||||||
|
// TODO: RENDERING!!!
|
||||||
|
|
||||||
val color = when (node.status) {
|
val color = when (node.status) {
|
||||||
NodeStatus.UNKNOWN -> UNKNOWN
|
NodeStatus.UNKNOWN -> UNKNOWN
|
||||||
NodeStatus.VALID -> VALID
|
NodeStatus.VALID -> VALID
|
||||||
|
@ -74,7 +74,7 @@ enum class BlockRotation(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
val crossproduct = front.normal.cross(top.normal)
|
val crossproduct = front.normal.cross(top.normal)
|
||||||
right = Direction.values().first { it.normal == crossproduct }
|
right = Direction.entries.first { it.normal == crossproduct }
|
||||||
}
|
}
|
||||||
|
|
||||||
val left: Direction = right.opposite
|
val left: Direction = right.opposite
|
||||||
@ -165,7 +165,7 @@ enum class BlockRotation(
|
|||||||
private val mapped = EnumMap<Direction, EnumMap<Direction, BlockRotation>>(Direction::class.java)
|
private val mapped = EnumMap<Direction, EnumMap<Direction, BlockRotation>>(Direction::class.java)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
for (value in values()) {
|
for (value in entries) {
|
||||||
val (front, top) = value
|
val (front, top) = value
|
||||||
mapped.computeIfAbsent(front) { EnumMap(Direction::class.java) }.put(top, value)
|
mapped.computeIfAbsent(front) { EnumMap(Direction::class.java) }.put(top, value)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package ru.dbotthepony.mc.otm.core.multiblock
|
package ru.dbotthepony.mc.otm.core.multiblock
|
||||||
|
|
||||||
import net.minecraft.core.BlockPos
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.world.level.block.state.BlockState
|
||||||
|
|
||||||
interface IMultiblockNode {
|
interface IMultiblockNode {
|
||||||
val pos: BlockPos
|
val pos: BlockPos
|
||||||
val status: NodeStatus
|
val status: NodeStatus
|
||||||
|
|
||||||
|
val displayBlocks: List<BlockState>
|
||||||
|
get() = listOf()
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,8 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
|
|||||||
private var lastSuccessfulPathPredicate = -1
|
private var lastSuccessfulPathPredicate = -1
|
||||||
private var lastSuccessfulPathChildren = -1
|
private var lastSuccessfulPathChildren = -1
|
||||||
|
|
||||||
|
override val displayBlocks: List<BlockState> = prototype.displayBlocks.asList()
|
||||||
|
|
||||||
override var status: NodeStatus = NodeStatus.UNKNOWN
|
override var status: NodeStatus = NodeStatus.UNKNOWN
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ class ShapedMultiblockBuilder {
|
|||||||
val blockStateTags = ObjectArraySet<Any>()
|
val blockStateTags = ObjectArraySet<Any>()
|
||||||
val blockTags = ObjectArraySet<Any>()
|
val blockTags = ObjectArraySet<Any>()
|
||||||
val blockEntityTags = ObjectArraySet<BlockEntityTag<*>>()
|
val blockEntityTags = ObjectArraySet<BlockEntityTag<*>>()
|
||||||
|
val displayBlocks = HashSet<BlockState>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks this node report its block in [ShapedMultiblock.blockStates] method when called with specified [value]
|
* Marks this node report its block in [ShapedMultiblock.blockStates] method when called with specified [value]
|
||||||
@ -192,6 +193,7 @@ class ShapedMultiblockBuilder {
|
|||||||
ImmutableSet.copyOf(blockStateTags),
|
ImmutableSet.copyOf(blockStateTags),
|
||||||
ImmutableSet.copyOf(blockTags),
|
ImmutableSet.copyOf(blockTags),
|
||||||
ImmutableSet.copyOf(blockEntityTags),
|
ImmutableSet.copyOf(blockEntityTags),
|
||||||
|
ImmutableSet.copyOf(displayBlocks)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
|
|||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
import net.minecraft.core.BlockPos
|
import net.minecraft.core.BlockPos
|
||||||
import net.minecraft.world.level.block.Rotation
|
import net.minecraft.world.level.block.Rotation
|
||||||
|
import net.minecraft.world.level.block.state.BlockState
|
||||||
import ru.dbotthepony.mc.otm.core.collect.collect
|
import ru.dbotthepony.mc.otm.core.collect.collect
|
||||||
import ru.dbotthepony.mc.otm.core.collect.map
|
import ru.dbotthepony.mc.otm.core.collect.map
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
@ -17,6 +18,7 @@ class ShapedMultiblockFactory(val north: ImmutableSet<Part>, val customChecks: I
|
|||||||
val blockStateTags: ImmutableSet<Any>,
|
val blockStateTags: ImmutableSet<Any>,
|
||||||
val blockTags: ImmutableSet<Any>,
|
val blockTags: ImmutableSet<Any>,
|
||||||
val blockEntityTags: ImmutableSet<BlockEntityTag<*>>,
|
val blockEntityTags: ImmutableSet<BlockEntityTag<*>>,
|
||||||
|
val displayBlocks: ImmutableSet<BlockState>,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6,18 +6,20 @@ import java.lang.ref.WeakReference
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayDeque
|
import kotlin.collections.ArrayDeque
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.collections.HashSet
|
||||||
|
|
||||||
open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : IConditionalTickable {
|
open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : IConditionalTickable {
|
||||||
private val queuedAdd = ArrayDeque<N>()
|
private val queuedAdd = ArrayDeque<N>()
|
||||||
private val queuedRemove = ArrayDeque<N>()
|
private val queuedRemove = ArrayDeque<N>()
|
||||||
private val nodesInternal = ArrayList<N>()
|
private val nodesInternal = ArrayList<N>()
|
||||||
|
private val nodesSetInternal = HashSet<N>()
|
||||||
private val conditional = ArrayList<IConditionalTickable>()
|
private val conditional = ArrayList<IConditionalTickable>()
|
||||||
private val always = ArrayList<ITickable>()
|
private val always = ArrayList<ITickable>()
|
||||||
private var isTicking = false
|
private var isTicking = false
|
||||||
private var shouldQueueChanges = false
|
private var shouldQueueChanges = false
|
||||||
|
|
||||||
protected val nodes: List<N> = Collections.unmodifiableList(nodesInternal)
|
val nodes: List<N> = Collections.unmodifiableList(nodesInternal)
|
||||||
|
val nodesSet: Set<N> = Collections.unmodifiableSet(nodesSetInternal)
|
||||||
val size get() = nodesInternal.size
|
val size get() = nodesInternal.size
|
||||||
|
|
||||||
var isValid = true
|
var isValid = true
|
||||||
@ -36,7 +38,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun beginTicking(node: N) {
|
fun beginTicking(node: N) {
|
||||||
require(node in nodesInternal || node in queuedAdd) { "Node $node does not belong to $this" }
|
require(node in nodesSetInternal || node in queuedAdd) { "Node $node does not belong to $this" }
|
||||||
if (node in queuedRemove) return
|
if (node in queuedRemove) return
|
||||||
|
|
||||||
if (node is IConditionalTickable) {
|
if (node is IConditionalTickable) {
|
||||||
@ -53,6 +55,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
|
|||||||
private fun addNow(node: N) {
|
private fun addNow(node: N) {
|
||||||
node.graph = this as G
|
node.graph = this as G
|
||||||
nodesInternal.add(node)
|
nodesInternal.add(node)
|
||||||
|
nodesSetInternal.add(node)
|
||||||
|
|
||||||
if (node is IConditionalTickable) {
|
if (node is IConditionalTickable) {
|
||||||
conditional.add(node)
|
conditional.add(node)
|
||||||
@ -69,6 +72,8 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
|
|||||||
if (removeFromList)
|
if (removeFromList)
|
||||||
nodesInternal.remove(node)
|
nodesInternal.remove(node)
|
||||||
|
|
||||||
|
nodesSetInternal.remove(node)
|
||||||
|
|
||||||
if (node is IConditionalTickable)
|
if (node is IConditionalTickable)
|
||||||
conditional.remove(node)
|
conditional.remove(node)
|
||||||
else if (node is ITickable)
|
else if (node is ITickable)
|
||||||
@ -108,7 +113,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
|
|||||||
fun addNodeQueued(node: N): Boolean {
|
fun addNodeQueued(node: N): Boolean {
|
||||||
check(isValid) { "$this is no longer valid" }
|
check(isValid) { "$this is no longer valid" }
|
||||||
|
|
||||||
if (node in nodesInternal || node in queuedAdd)
|
if (node in nodesSetInternal || node in queuedAdd)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
queuedAdd.add(node)
|
queuedAdd.add(node)
|
||||||
@ -119,7 +124,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
|
|||||||
fun addNode(node: N): Boolean {
|
fun addNode(node: N): Boolean {
|
||||||
check(isValid) { "$this is no longer valid" }
|
check(isValid) { "$this is no longer valid" }
|
||||||
|
|
||||||
if (node in nodesInternal)
|
if (node in nodesSetInternal)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if (shouldQueueChanges) {
|
if (shouldQueueChanges) {
|
||||||
@ -137,7 +142,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
|
|||||||
fun removeNode(node: N): Boolean {
|
fun removeNode(node: N): Boolean {
|
||||||
check(isValid) { "$this is no longer valid" }
|
check(isValid) { "$this is no longer valid" }
|
||||||
|
|
||||||
if (node !in nodesInternal)
|
if (node !in nodesSetInternal)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if (shouldQueueChanges) {
|
if (shouldQueueChanges) {
|
||||||
@ -155,6 +160,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
|
|||||||
fun retain(nodes: Set<N>) {
|
fun retain(nodes: Set<N>) {
|
||||||
check(isValid) { "$this is no longer valid" }
|
check(isValid) { "$this is no longer valid" }
|
||||||
queuedAdd.retainAll(nodes)
|
queuedAdd.retainAll(nodes)
|
||||||
|
|
||||||
nodesInternal.removeIf {
|
nodesInternal.removeIf {
|
||||||
if (it !in nodes) {
|
if (it !in nodes) {
|
||||||
removeNow(it, false)
|
removeNow(it, false)
|
||||||
|
@ -26,7 +26,7 @@ import ru.dbotthepony.mc.otm.core.math.plus
|
|||||||
import ru.dbotthepony.mc.otm.core.util.TickList
|
import ru.dbotthepony.mc.otm.core.util.TickList
|
||||||
import ru.dbotthepony.mc.otm.item.MatteryItem
|
import ru.dbotthepony.mc.otm.item.MatteryItem
|
||||||
import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
|
import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
|
||||||
import ru.dbotthepony.mc.otm.timer
|
import ru.dbotthepony.mc.otm.once
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class RedstoneInteractorItem : MatteryItem(Properties().stacksTo(1)) {
|
class RedstoneInteractorItem : MatteryItem(Properties().stacksTo(1)) {
|
||||||
@ -79,7 +79,7 @@ class RedstoneInteractorItem : MatteryItem(Properties().stacksTo(1)) {
|
|||||||
for (pos in positions) {
|
for (pos in positions) {
|
||||||
val shouldSendUpdate = pos !in map
|
val shouldSendUpdate = pos !in map
|
||||||
|
|
||||||
val timer = context.level.timer(context.itemInHand.getOrDefault(MDataComponentTypes.TICK_TIMER, TickTimer.TICK_30).ticks) {
|
val timer = context.level.once(context.itemInHand.getOrDefault(MDataComponentTypes.TICK_TIMER, TickTimer.TICK_30).ticks) {
|
||||||
map.remove(pos)
|
map.remove(pos)
|
||||||
context.level.updateNeighborsAt(pos, context.level.getBlockState(pos).block)
|
context.level.updateNeighborsAt(pos, context.level.getBlockState(pos).block)
|
||||||
} ?: throw RuntimeException("Timer unexpectedly ended up being null")
|
} ?: throw RuntimeException("Timer unexpectedly ended up being null")
|
||||||
|
Loading…
Reference in New Issue
Block a user