diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MultiblockTestBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MultiblockTestBlockEntity.kt index ec63a6d87..696ce3ba3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MultiblockTestBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MultiblockTestBlockEntity.kt @@ -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)}") } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleGeneratorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleGeneratorBlockEntity.kt index c9528e519..04827ffa3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleGeneratorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleGeneratorBlockEntity.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/FlywheelBatteryBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/FlywheelBatteryBlockEntity.kt index 6e6084a5b..1ec935423 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/FlywheelBatteryBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/FlywheelBatteryBlockEntity.kt @@ -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? 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) + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index d27971e20..04120682f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt index 15c0ac5fe..77d910401 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/IMultiblockAccess.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/IMultiblockAccess.kt index 5f0bcdd9b..553c01a21 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/IMultiblockAccess.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/IMultiblockAccess.kt @@ -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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/MultiblockStatus.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/MultiblockStatus.kt new file mode 100644 index 000000000..c5967765c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/MultiblockStatus.kt @@ -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 + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblock.kt index abd67ade9..ab94191d1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblock.kt @@ -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) : IMultiblockAccess, ISynchable, GlobalBlockEntityRemovalListener, Comparable { - private inner class Part(override val pos: BlockPos, val prototype: ShapedMultiblockFactory.Part) : Comparable, IMultiblockNode { + private inner class Part private constructor(override val pos: BlockPos, val prototype: ShapedMultiblockFactory.Part, val chunkPos: ChunkPos) : Comparable, 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>(prototype.blockEntityTags.size) private val assignedBlockStateLists = ArrayList>() private val assignedBlockLists = ArrayList>() - private val children: ImmutableList by lazy(LazyThreadSafetyMode.NONE) { prototype.children.iterator().map { Part(pos, it) }.collect(ImmutableList.toImmutableList()) } + private val children: ImmutableList 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 { @@ -219,9 +221,19 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti return test } - fun test(levelAccessor: LevelAccessor): Boolean { + fun test(levelAccessor: LevelAccessor): MultiblockStatus { val blockEntity = levelAccessor.getBlockEntityNow(pos) - val blockState = levelAccessor.getBlockStateNow(pos) + val blockState = levelAccessor.getBlockStateNowOrNull(pos) ?: return MultiblockStatus.NOT_LOADED + return test(levelAccessor, blockState, blockEntity) + } + + fun test(levelAccessor: LevelAccessor, chunk: LevelChunk): MultiblockStatus { + val blockEntity = chunk.getBlockEntity(pos) + val blockState = chunk.getBlockState(pos) + return test(levelAccessor, blockState, blockEntity) + } + + fun test(levelAccessor: LevelAccessor, blockState: BlockState, blockEntity: 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> + + init { + val builder = HashMap>() + + 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 get() = activeConfig.parts override fun blockEntities(tag: BlockEntityTag): Set { - if (!isValid) return setOf() + if (isValid != MultiblockStatus.VALID) return setOf() return activeConfig.blockEntities(tag) } override fun blocks(tag: Any): Reference2IntMap { - if (!isValid) return Reference2IntMaps.emptyMap() + if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap() return activeConfig.blocks(tag) } override fun blockStates(tag: Any): Reference2IntMap { - if (!isValid) return Reference2IntMaps.emptyMap() + if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap() return activeConfig.blockStates(tag) } override fun blocks(): Reference2IntMap { - if (!isValid) return Reference2IntMaps.emptyMap() + if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap() return activeConfig.blocks() } override fun blockStates(): Reference2IntMap { - 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 } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BlackHoleGeneratorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BlackHoleGeneratorMenu.kt index 49b9039ed..56e4f2a6e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BlackHoleGeneratorMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BlackHoleGeneratorMenu.kt @@ -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)