Compare commits

...

6 Commits

15 changed files with 227 additions and 105 deletions

View File

@ -6,6 +6,7 @@ import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.FurnaceBlockEntity
import net.minecraft.world.level.block.entity.HopperBlockEntity
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.core.multiblock.MultiblockStatus
import ru.dbotthepony.mc.otm.core.multiblock.multiblockEntity
import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
@ -17,7 +18,7 @@ class MultiblockTestBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
override fun tick() {
super.tick()
if (config.update(level!!)) {
if (config.update(level!!) == MultiblockStatus.VALID) {
println("It matches!")
println("hopper block entities: ${config.blockEntities(HOPPERS)}")
}

View File

@ -32,6 +32,7 @@ import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag
import ru.dbotthepony.mc.otm.core.multiblock.MultiblockStatus
import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock
import ru.dbotthepony.mc.otm.core.util.InvalidableLazy
import ru.dbotthepony.mc.otm.menu.tech.BlackHoleGeneratorMenu
@ -169,9 +170,11 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
val multiblock = multiblock
if (multiblock != null) {
if (!multiblock.update(level, blockRotation.back)) {
val status = multiblock.update(level, blockRotation.back)
if (status == MultiblockStatus.INVALID) {
this.lastRange = findBlackHoleRange()
} else {
} else if (status == MultiblockStatus.VALID) {
val blackHole = multiblock.blockEntities(BLACK_HOLE).firstOrNull() ?: return
val matterExtracted = matter.extractMatter(if (mode == Mode.TARGET_MASS && blackHole.mass != targetMass) minOf(injectionRate, (blackHole.mass - targetMass).absoluteValue) else injectionRate, true)

View File

@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.Reference2IntMap
import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos
import net.minecraft.core.Vec3i
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
@ -18,9 +19,11 @@ import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.core.getBlockStateNow
import ru.dbotthepony.mc.otm.core.getChunkNow
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.core.multiblock.MultiblockStatus
import ru.dbotthepony.mc.otm.core.multiblock.ShapedMultiblock
import ru.dbotthepony.mc.otm.core.multiblock.ShapedMultiblockFactory
import ru.dbotthepony.mc.otm.core.multiblock.Strategy
@ -37,7 +40,7 @@ class FlywheelBatteryBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
private var cachedChargeEfficiency = Decimal.ONE
val formed: Boolean
get() = multiblock?.isValid == true
get() = multiblock?.isValid == MultiblockStatus.VALID
// dangerous as Reference2IntMap.Entry reference live data, and its behavior is undefined once flywheel updates again
val currentlyUsedCore: Reference2IntMap.Entry<Block>? get() {
@ -109,36 +112,41 @@ class FlywheelBatteryBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
super.tick()
val level = level!!
var isUnloaded = false
if (multiblock == null || !multiblock!!.update(level, blockRotation.front)) {
var height = 0
val base = blockPos + blockRotation.back.normal * 2
if (multiblock == null || multiblock!!.update(level, blockRotation.front) != MultiblockStatus.VALID) {
val chunk = level.chunkSource.getChunkNow(blockPos)
while (level.getBlockStateNow(base.atY(blockPos.y + height)).block == MBlocks.FLYWHEEL_SHAFT) {
height++
}
if (chunk == null) {
isUnloaded = true
} else {
var height = 0
val base = blockPos + blockRotation.back.normal * 2
if (height <= 1) {
lastHeight = 0
multiblock?.close()
multiblock = null
} else if (multiblock == null || lastHeight != height) {
lastHeight = height
multiblock?.close()
multiblock = getConfiguration(height).create(blockPos)
while (chunk.getBlockState(base.atY(blockPos.y + height)).block == MBlocks.FLYWHEEL_SHAFT) {
height++
}
if (height <= 1) {
lastHeight = 0
multiblock?.close()
multiblock = null
} else if (multiblock == null || lastHeight != height) {
lastHeight = height
multiblock?.close()
multiblock = getConfiguration(height).create(blockPos)
}
}
}
if (multiblock?.isValid == true) {
// energy.parent.batteryLevel *= Decimal("0.99994")
// this way energy loss is recorded in graph
if (multiblock?.isValid == MultiblockStatus.VALID) {
val entry = multiblock!!.blocks(FLYWHEEL_MATERIAL).reference2IntEntrySet().first()
val material = FlywheelMaterials[entry.key]!!
energy.parent.maxBatteryLevel = material.storage * entry.intValue
cachedChargeEfficiency = material.receiveEfficiency
currentLossPerTick = energy.parent.batteryLevel * MachinesConfig.Flywheel.PASSIVE_LOSS * material.momentumLossSpeed
energy.extractEnergy(currentLossPerTick, false)
} else {
} else if (!isUnloaded && multiblock?.isValid != MultiblockStatus.NOT_LOADED) {
energy.parent.maxBatteryLevel = Decimal.ZERO
currentLossPerTick = energy.parent.batteryLevel * MachinesConfig.Flywheel.ACTIVE_LOSS
energy.extractEnergy(currentLossPerTick, false)
@ -241,10 +249,13 @@ class FlywheelBatteryBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
block(MBlocks.FLYWHEEL_HOUSING)
if (y == height - 1) {
block(MBlocks.ENERGY_INPUT_INTERFACE)
block(MBlocks.ENERGY_OUTPUT_INTERFACE)
tag(EnergyInterfaceBlockEntity.INPUT_TAG)
tag(EnergyInterfaceBlockEntity.OUTPUT_TAG)
// wrap in subnode to narrow blockentity lookups
or {
block(MBlocks.ENERGY_INPUT_INTERFACE)
block(MBlocks.ENERGY_OUTPUT_INTERFACE)
tag(EnergyInterfaceBlockEntity.INPUT_TAG)
tag(EnergyInterfaceBlockEntity.OUTPUT_TAG)
}
}
}
}

View File

@ -22,4 +22,13 @@ open class DynamicLabel<out S : Screen>(
super.tickInner()
text = textSupplier.get()
}
// reserve some extra space for text
override val textSizeDeterminationPadding: Float
get() = 14f
override fun sizeToContents() {
text = textSupplier.get()
super.sizeToContents()
}
}

View File

@ -279,6 +279,11 @@ open class FramePanel<out S : Screen>(
protected val tabs: ArrayList<FramePanel<*>.AbstractTab> = ArrayList()
override fun sizeToContents() {
super.sizeToContents()
width = maxOf(width, dockPaddingLeft + dockPaddingRight + (helpButton?.width ?: 0f) + (closeButton?.width ?: 0f) + (title?.let { font.width(it).toFloat() } ?: 0f))
}
override fun performLayout() {
for ((i, tab) in tabs.withIndex()) {
tab.setPos(i * 28f, -28f)

View File

@ -60,11 +60,14 @@ open class Label<out S : Screen>(
}
}
protected open val textSizeDeterminationPadding: Float
get() = 0f
override fun sizeToContents() {
val w = font.width(text)
val w = font.width(text) + textSizeDeterminationPadding
val h = font.lineHeight + 2
width = width.coerceAtLeast(w.toFloat())
width = width.coerceAtLeast(w)
height = height.coerceAtLeast(h.toFloat())
}
}

View File

@ -46,6 +46,7 @@ class BlackHoleGeneratorScreen(menu: BlackHoleGeneratorMenu, inventory: Inventor
}).also {
it.dock = Dock.TOP
it.dockTop = 4f
it.dockBottom = 4f
}
val energy = ProfiledPowerGaugePanel(this, frame, menu.energy)

View File

@ -5,6 +5,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import ru.dbotthepony.mc.otm.client.ShiftPressedCond
import ru.dbotthepony.mc.otm.client.render.RenderGravity
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.Dock
@ -46,7 +47,7 @@ class FlywheelBatteryScreen(menu: FlywheelBatteryMenu, inventory: Inventory, tit
it.dock = Dock.TOP
}
DynamicLabel(this, frame, textSupplier = Supplier { menu.currentLossPerTick.get().formatPower(decimalPlaces = 6) }).also {
DynamicLabel(this, frame, textSupplier = Supplier { menu.currentLossPerTick.get().formatPower(decimalPlaces = 4, formatAsReadable = ShiftPressedCond) }).also {
it.dock = Dock.TOP
}
@ -54,7 +55,7 @@ class FlywheelBatteryScreen(menu: FlywheelBatteryMenu, inventory: Inventory, tit
it.dock = Dock.TOP
}
DynamicLabel(this, frame, textSupplier = Supplier { (menu.currentLossPerTick.get() * 20).formatPower() }).also {
DynamicLabel(this, frame, textSupplier = Supplier { (menu.currentLossPerTick.get() * 20).formatPower(formatAsReadable = ShiftPressedCond) }).also {
it.dock = Dock.TOP
}

View File

@ -36,6 +36,7 @@ import net.minecraft.world.item.component.ItemAttributeModifiers
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeInput
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
@ -44,6 +45,8 @@ import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateHolder
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.level.chunk.ChunkSource
import net.minecraft.world.level.chunk.LevelChunk
import net.minecraft.world.phys.Vec3
import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.items.IItemHandler
@ -127,13 +130,25 @@ operator fun JsonObject.set(s: String, value: Number) = add(s, JsonPrimitive(val
operator fun JsonObject.set(s: String, value: Boolean) = add(s, JsonPrimitive(value))
fun LevelAccessor.getBlockStateNow(pos: BlockPos): BlockState {
return chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))?.getBlockState(pos) ?: Blocks.AIR.defaultBlockState()
return getBlockStateNowOrNull(pos) ?: Blocks.AIR.defaultBlockState()
}
fun LevelAccessor.getBlockStateNowOrNull(pos: BlockPos): BlockState? {
return chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))?.getBlockState(pos)
}
fun LevelAccessor.getBlockEntityNow(pos: BlockPos): BlockEntity? {
return chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))?.getBlockEntity(pos)
}
fun ChunkSource.getChunkNow(pos: BlockPos): LevelChunk? {
return getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))
}
fun ChunkSource.getChunkNow(pos: ChunkPos): LevelChunk? {
return getChunkNow(pos.x, pos.z)
}
inline val FluidStack.isNotEmpty get() = !isEmpty
inline val ItemStack.isNotEmpty get() = !isEmpty

View File

@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.Vec3i
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.phys.Vec3
import org.joml.AxisAngle4f
import org.joml.Matrix3f
@ -420,6 +421,12 @@ operator fun BlockPos.plus(direction: Direction): BlockPos = this.offset(directi
operator fun BlockPos.minus(direction: Vec3i): BlockPos = this.subtract(direction)
operator fun BlockPos.minus(direction: Direction): BlockPos = this.subtract(direction.normal)
operator fun ChunkPos.compareTo(other: ChunkPos): Int {
val cmp = x.compareTo(other.x)
if (cmp != 0) return cmp
return z.compareTo(other.z)
}
operator fun Vec3i.plus(direction: Vec3i): Vec3i = this.offset(direction)
operator fun Vec3i.plus(direction: Direction): Vec3i = this.offset(direction.normal)
operator fun Vec3i.minus(direction: Vec3i): Vec3i = this.subtract(direction)

View File

@ -7,11 +7,10 @@ import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.core.getBlockStateNow
import ru.dbotthepony.mc.otm.core.math.plus
fun interface BlockPredicate {
fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: BlockEntity?): Boolean
fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: Lazy<BlockEntity?>): Boolean
fun rotate(rotation: Rotation): BlockPredicate {
return this
@ -42,7 +41,7 @@ fun interface BlockPredicate {
}
data class Positioned(val offset: BlockPos, val parent: BlockPredicate) : BlockPredicate {
override fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: BlockEntity?): Boolean {
override fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: Lazy<BlockEntity?>): Boolean {
return parent.test(offset + pos, access, blockState, blockEntity)
}
@ -55,7 +54,7 @@ fun interface BlockPredicate {
constructor(vararg nodes: BlockPredicate) : this(ImmutableSet.copyOf(nodes))
constructor(nodes: Set<BlockPredicate>) : this(ImmutableSet.copyOf(nodes))
override fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: BlockEntity?): Boolean {
override fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: Lazy<BlockEntity?>): Boolean {
return nodes.all { it.test(pos, access, blockState, blockEntity) }
}
}
@ -64,19 +63,19 @@ fun interface BlockPredicate {
constructor(vararg nodes: BlockPredicate) : this(ImmutableSet.copyOf(nodes))
constructor(nodes: Set<BlockPredicate>) : this(ImmutableSet.copyOf(nodes))
override fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: BlockEntity?): Boolean {
override fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: Lazy<BlockEntity?>): Boolean {
return nodes.any { it.test(pos, access, blockState, blockEntity) }
}
}
object Air : BlockPredicate {
override fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: BlockEntity?): Boolean {
override fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: Lazy<BlockEntity?>): Boolean {
return blockState.isAir
}
}
object NotAir : BlockPredicate {
override fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: BlockEntity?): Boolean {
override fun test(pos: BlockPos, access: LevelAccessor, blockState: BlockState, blockEntity: Lazy<BlockEntity?>): Boolean {
return !blockState.isAir
}
}

View File

@ -12,7 +12,7 @@ interface IMultiblockAccess {
/**
* Whenever this multiblock is valid (all checks passed)
*/
val isValid: Boolean
val isValid: MultiblockStatus
val currentDirection: Direction?
val currentNodes: Map<BlockPos, IMultiblockNode>

View File

@ -0,0 +1,32 @@
package ru.dbotthepony.mc.otm.core.multiblock
enum class MultiblockStatus {
/**
* Multiblock is functional, configuration is valid
*/
VALID,
/**
* Multiblock status is unknown because it require to check
*/
NOT_LOADED,
/**
* Multiblock is invalid
*/
INVALID;
fun or(other: MultiblockStatus): MultiblockStatus {
if (ordinal <= other.ordinal) {
return this
} else {
return other
}
}
companion object {
fun valid(status: Boolean): MultiblockStatus {
return if (status) VALID else INVALID
}
}
}

View File

@ -7,19 +7,21 @@ import it.unimi.dsi.fastutil.objects.Reference2IntMaps
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.SectionPos
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.chunk.LevelChunk
import net.minecraft.world.phys.AABB
import ru.dbotthepony.kommons.util.Listenable
import ru.dbotthepony.mc.otm.core.collect.collect
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.getBlockEntityNow
import ru.dbotthepony.mc.otm.core.getBlockStateNow
import ru.dbotthepony.mc.otm.core.getBlockStateNowOrNull
import ru.dbotthepony.mc.otm.core.math.Vector
import ru.dbotthepony.mc.otm.core.math.compareTo
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.network.syncher.IRemoteState
@ -35,7 +37,7 @@ import kotlin.collections.HashMap
* [close] MUST be called when multiblock goes out of scope
*/
class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMultiblockAccess, ISynchable, Closeable, GlobalBlockEntityRemovalListener {
override var isValid: Boolean = false
override var isValid: MultiblockStatus = MultiblockStatus.INVALID
private set
private val customChecks = factory.customChecks
@ -68,7 +70,9 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
get() = remotes.isNotEmpty()
private inner class Config(override val currentDirection: Direction, val pos: BlockPos, parts: Collection<ShapedMultiblockFactory.Part>) : IMultiblockAccess, ISynchable, GlobalBlockEntityRemovalListener, Comparable<Config> {
private inner class Part(override val pos: BlockPos, val prototype: ShapedMultiblockFactory.Part) : Comparable<Part>, IMultiblockNode {
private inner class Part private constructor(override val pos: BlockPos, val prototype: ShapedMultiblockFactory.Part, val chunkPos: ChunkPos) : Comparable<Part>, IMultiblockNode {
constructor(pos: BlockPos, prototype: ShapedMultiblockFactory.Part) : this(pos, prototype, ChunkPos(pos))
var index = -1
private var blockEntity: BlockEntity? = null
@ -76,12 +80,10 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
private val assignedBlockEntityLists = ArrayList<BlockEntitySet<*>>(prototype.blockEntityTags.size)
private val assignedBlockStateLists = ArrayList<Reference2IntMap<BlockState>>()
private val assignedBlockLists = ArrayList<Reference2IntMap<Block>>()
private val children: ImmutableList<Part> by lazy(LazyThreadSafetyMode.NONE) { prototype.children.iterator().map { Part(pos, it) }.collect(ImmutableList.toImmutableList()) }
private val children: ImmutableList<Part> by lazy(LazyThreadSafetyMode.NONE) { prototype.children.iterator().map { Part(pos, it, chunkPos) }.collect(ImmutableList.toImmutableList()) }
override fun compareTo(other: Part): Int {
val cmp = SectionPos.blockToSectionCoord(pos.x).compareTo(SectionPos.blockToSectionCoord(other.pos.x))
if (cmp != 0) return cmp
return SectionPos.blockToSectionCoord(pos.z).compareTo(SectionPos.blockToSectionCoord(other.pos.z))
return chunkPos.compareTo(other.chunkPos)
}
init {
@ -114,7 +116,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
stream.writeByte(status.ordinal)
}
private fun orPredicates(levelAccessor: LevelAccessor, blockState: BlockState, blockEntity: BlockEntity?): Boolean {
private fun orPredicates(levelAccessor: LevelAccessor, blockState: BlockState, blockEntity: Lazy<BlockEntity?>): Boolean {
if (prototype.predicate.isNotEmpty()) {
if (lastSuccessfulPathPredicate != -1 && !prototype.predicate[lastSuccessfulPathPredicate].test(pos, levelAccessor, blockState, blockEntity)) {
lastSuccessfulPathPredicate = -1
@ -129,7 +131,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return true
}
private fun orChildren(levelAccessor: LevelAccessor, blockState: BlockState, blockEntity: BlockEntity?): Boolean {
private fun orChildren(levelAccessor: LevelAccessor, blockState: BlockState, blockEntity: Lazy<BlockEntity?>): Boolean {
if (children.isNotEmpty()) {
if (lastSuccessfulPathChildren != -1 && !children[lastSuccessfulPathChildren].test0(levelAccessor, blockState, blockEntity)) {
lastSuccessfulPathChildren = -1
@ -144,7 +146,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return true
}
private fun test0(levelAccessor: LevelAccessor, blockState: BlockState, blockEntity: BlockEntity?): Boolean {
private fun test0(levelAccessor: LevelAccessor, blockState: BlockState, blockEntity: Lazy<BlockEntity?>): Boolean {
val test = when (prototype.strategy) {
Strategy.OR_BOTH -> orPredicates(levelAccessor, blockState, blockEntity) && orChildren(levelAccessor, blockState, blockEntity)
@ -159,7 +161,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
if (test) {
if (assignedBlockEntityLists.isNotEmpty()) {
val be1 = this.blockEntity
val be2 = blockEntity
val be2 = blockEntity.value
if (be1 != be2) {
generation++
@ -219,9 +221,19 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return test
}
fun test(levelAccessor: LevelAccessor): Boolean {
val blockEntity = levelAccessor.getBlockEntityNow(pos)
val blockState = levelAccessor.getBlockStateNow(pos)
fun test(levelAccessor: LevelAccessor): MultiblockStatus {
val blockEntity = lazy(LazyThreadSafetyMode.NONE) { levelAccessor.getBlockEntityNow(pos) }
val blockState = levelAccessor.getBlockStateNowOrNull(pos) ?: return MultiblockStatus.NOT_LOADED
return test(levelAccessor, blockState, blockEntity)
}
fun test(levelAccessor: LevelAccessor, chunk: LevelChunk): MultiblockStatus {
val blockEntity = lazy(LazyThreadSafetyMode.NONE) { chunk.getBlockEntity(pos) }
val blockState = chunk.getBlockState(pos)
return test(levelAccessor, blockState, blockEntity)
}
fun test(levelAccessor: LevelAccessor, blockState: BlockState, blockEntity: Lazy<BlockEntity?>): MultiblockStatus {
val status = test0(levelAccessor, blockState, blockEntity)
val previous = this.status
@ -231,7 +243,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
pushNetworkPartUpdate(this)
}
return status
return if (status) MultiblockStatus.VALID else MultiblockStatus.INVALID
}
private fun clearFull() {
@ -371,6 +383,20 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
.sorted() // group/localize parts by in-world chunks to maximize getChunk() cache hit rate
.collect(ImmutableMap.toImmutableMap({ it.pos }, { it }))
private val partsByChunk: ImmutableMap<ChunkPos, ImmutableList<Part>>
init {
val builder = HashMap<ChunkPos, ArrayList<Part>>()
for (part in this.parts.values) {
builder.computeIfAbsent(part.chunkPos) { ArrayList() }.add(part)
}
partsByChunk = builder.entries.stream()
.map { it.key to ImmutableList.copyOf(it.value) }
.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
}
init {
for ((i, part) in this.parts.values.withIndex()) {
part.index = i
@ -395,7 +421,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
while (networkChangelog.size > parts.size) networkChangelog.removeLast()
}
override var isValid: Boolean = false
override var isValid: MultiblockStatus = MultiblockStatus.INVALID
private set(value) {
if (value != field) {
field = value
@ -403,7 +429,6 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
}
}
private var iterator = this.parts.values.iterator()
private var validParts = 0
override fun compareTo(other: Config): Int {
@ -412,42 +437,46 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return cmp
}
fun updateRemaining(levelAccessor: LevelAccessor) {
fun update(levelAccessor: LevelAccessor, updateEverything: Boolean = remotes.isNotEmpty()): MultiblockStatus {
val networkVersion = networkVersion
if (!isValid) { // update rest
for (part in iterator) {
if (part.test(levelAccessor)) {
validParts++
}
isValid = MultiblockStatus.VALID
validParts = 0
for ((chunkPos, parts) in partsByChunk) {
val chunk = levelAccessor.chunkSource.getChunkNow(chunkPos.x, chunkPos.z)
if (chunk == null) {
isValid = MultiblockStatus.NOT_LOADED
break
}
} else {
for (part in iterator) {
if (part.test(levelAccessor))
validParts++
else {
isValid = false
break
if (updateEverything) {
for (part in parts) {
val status = part.test(levelAccessor, chunk)
if (status === MultiblockStatus.VALID) validParts++
isValid = isValid.or(status)
}
} else {
for (part in parts) {
val status = part.test(levelAccessor, chunk)
if (status === MultiblockStatus.VALID)
validParts++
else {
isValid = status
break
}
}
}
}
if (isValid != MultiblockStatus.VALID) clear()
if (this.networkVersion != networkVersion) {
remotes.forEach { it.listener.run() }
}
}
fun update(levelAccessor: LevelAccessor, updateEverything: Boolean = remotes.isNotEmpty()): Boolean {
isValid = true
iterator = this.parts.values.iterator()
validParts = 0
updateRemaining(levelAccessor)
if (updateEverything && iterator.hasNext()) {
updateRemaining(levelAccessor)
}
if (!isValid) clear()
return isValid
}
@ -565,7 +594,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
override fun read(stream: RegistryFriendlyByteBuf) {
isRemote = true
isValid = stream.readBoolean()
isValid = MultiblockStatus.entries[stream.readByte().toInt()]
activeConfig = configurations[stream.readUnsignedByte().toInt()]
activeConfig.read(stream)
}
@ -580,7 +609,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
}
override fun write(stream: RegistryFriendlyByteBuf) {
stream.writeBoolean(isValid)
stream.writeByte(isValid.ordinal)
stream.writeByte(activeConfig.index)
remotes[activeConfig.index].write(stream)
}
@ -608,36 +637,36 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
}
override val currentDirection: Direction?
get() = if (isValid) activeConfig.currentDirection else null
get() = if (isValid == MultiblockStatus.VALID) activeConfig.currentDirection else null
override val boundingBox: AABB?
get() = if (isValid) activeConfig.boundingBox else null
get() = if (isValid == MultiblockStatus.VALID) activeConfig.boundingBox else null
override val currentNodes: Map<BlockPos, IMultiblockNode>
get() = activeConfig.parts
override fun <T : Any> blockEntities(tag: BlockEntityTag<T>): Set<T> {
if (!isValid) return setOf()
if (isValid != MultiblockStatus.VALID) return setOf()
return activeConfig.blockEntities(tag)
}
override fun blocks(tag: Any): Reference2IntMap<Block> {
if (!isValid) return Reference2IntMaps.emptyMap()
if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blocks(tag)
}
override fun blockStates(tag: Any): Reference2IntMap<BlockState> {
if (!isValid) return Reference2IntMaps.emptyMap()
if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blockStates(tag)
}
override fun blocks(): Reference2IntMap<Block> {
if (!isValid) return Reference2IntMaps.emptyMap()
if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blocks()
}
override fun blockStates(): Reference2IntMap<BlockState> {
if (!isValid) return Reference2IntMaps.emptyMap()
if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blockStates()
}
@ -645,7 +674,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return activeConfig.blockEntityRemoved(blockEntity)
}
fun update(levelAccessor: LevelAccessor): Boolean {
fun update(levelAccessor: LevelAccessor): MultiblockStatus {
isUpdating = true
val thisGeneration = generation
@ -659,25 +688,29 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
configurations.add(max)
}
isValid = false
isValid = MultiblockStatus.INVALID
var hadUnloadedChunks = false
while (configurations.isNotEmpty()) {
val config = configurations.removeFirst()
val status = config.update(levelAccessor)
if (config.update(levelAccessor)) {
if (status == MultiblockStatus.VALID) {
if (customChecks.all { it.test(config) }) {
activeConfig = config
isValid = true
isValid = MultiblockStatus.VALID
remotes.forEach { it.listener.run() }
if (thisGeneration != generation) {
changeListeners.forEach { it.runnable.run() }
}
return true
return status
} else {
config.clear()
}
} else if (status == MultiblockStatus.NOT_LOADED) {
hadUnloadedChunks = true
}
}
@ -685,22 +718,23 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
changeListeners.forEach { it.runnable.run() }
}
return false
isValid = if (hadUnloadedChunks) MultiblockStatus.NOT_LOADED else MultiblockStatus.INVALID
return isValid
} finally {
isUpdating = false
}
}
fun update(levelAccessor: LevelAccessor, direction: Direction): Boolean {
fun update(levelAccessor: LevelAccessor, direction: Direction): MultiblockStatus {
isUpdating = true
val thisGeneration = generation
try {
var changes = false
if (activeConfig.currentDirection != direction && isValid) {
if (activeConfig.currentDirection != direction && isValid === MultiblockStatus.VALID) {
activeConfig.clear()
isValid = false
isValid = MultiblockStatus.INVALID
changes = true
}
@ -716,9 +750,9 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
activeConfig = config
isValid = config.update(levelAccessor)
if (isValid) {
isValid = customChecks.all { it.test(config) }
if (!isValid) config.clear()
if (isValid === MultiblockStatus.VALID) {
isValid = MultiblockStatus.valid(customChecks.all { it.test(config) })
if (isValid !== MultiblockStatus.VALID) config.clear()
}
if (changes) {
@ -739,6 +773,6 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
changeListeners.clear()
remotes.forEach { it.close() }
configurations.forEach { it.clear() }
isValid = false
isValid = MultiblockStatus.INVALID
}
}

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.menu.tech
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleGeneratorBlockEntity
import ru.dbotthepony.mc.otm.core.multiblock.MultiblockStatus
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.DecimalInputWithFeedback
@ -15,7 +16,7 @@ class BlackHoleGeneratorMenu(
inventory: Inventory,
tile: BlackHoleGeneratorBlockEntity? = null,
) : MatteryMenu(MMenus.BLACK_HOLE_GENERATOR, p_38852_, inventory, tile) {
val formed = mSynchronizer.computedBoolean(BooleanSupplier { tile?.multiblock?.isValid ?: false })
val formed = mSynchronizer.computedBoolean(BooleanSupplier { tile?.multiblock?.isValid == MultiblockStatus.VALID })
val drawBuildingGuide = BooleanInputWithFeedback(this, tile?.let { it::drawBuildingGuide })
val energy = CombinedProfiledLevelGaugeWidget(this, tile?.energy)
val matter = CombinedProfiledLevelGaugeWidget(this, tile?.matter)