diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index 763d8ccff..df3082d0a 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -510,6 +510,10 @@ private fun blocks(provider: MatteryLanguageProvider) { add(MBlocks.ITEM_OUTPUT_HATCH, "Item Output Hatch") add(MBlocks.MATTER_OUTPUT_HATCH, "Matter Output Hatch") + add(MBlocks.MATTER_INJECTOR, "Matter Injector") + add(MBlocks.ANTIMATTER_INJECTOR, "Antimatter Injector") + add(MBlocks.HIGH_ENERGY_PARTICLE_COLLECTOR, "High Energy Particle Collector") + addBlock(MBlocks.COBBLESTONE_GENERATOR.values, "Cobblestone Generator") add(MBlocks.INFINITE_WATER_SOURCE, "Infinite Water Source") add(MBlocks.INFINITE_WATER_SOURCE, "desc", "Pushes water into all neighbour blocks") diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt index f8575df20..672541b8c 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt @@ -520,6 +520,10 @@ private fun blocks(provider: MatteryLanguageProvider) { add(MBlocks.ITEM_OUTPUT_HATCH, "Выходной предметный клапан") add(MBlocks.MATTER_OUTPUT_HATCH, "Выходной клапан материи") + add(MBlocks.MATTER_INJECTOR, "Инжектор материи") + add(MBlocks.ANTIMATTER_INJECTOR, "Инжектор анти-материи") + add(MBlocks.HIGH_ENERGY_PARTICLE_COLLECTOR, "Коллектор высокоэнергичных частиц") + add(MBlocks.DEV_CHEST, "Сундук разработчика") add(MBlocks.DEV_CHEST, "desc", "Хранит все предметы, которые есть в игре") add(MBlocks.PAINTER, "Стол маляра") diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt index ed4fb3df2..9ba29f098 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt @@ -41,6 +41,18 @@ fun addTags(tagsProvider: TagsProvider) { tagsProvider.storageBlocksAsBlock.add("tritanium", MBlocks.TRITANIUM_INGOT_BLOCK) tagsProvider.blocks.Appender(BlockTags.BEACON_BASE_BLOCKS).add(MBlocks.TRITANIUM_INGOT_BLOCK) + tagsProvider.blocks.Appender(MBlockTags.MULTIBLOCK_STRUCTURE) + .add(MBlockTags.MULTIBLOCK_HARD_STRUCTURE, MBlockTags.MULTIBLOCK_SOFT_STRUCTURE) + + tagsProvider.blocks.Appender(MBlockTags.MULTIBLOCK_SOFT_STRUCTURE) + .add(MBlockTags.HARDENED_GLASS) + + tagsProvider.blocks.Appender(MBlockTags.MULTIBLOCK_HARD_STRUCTURE) + .add(MRegistry.VENT.allBlocks.values) + .add(MRegistry.VENT_ALTERNATIVE.allBlocks.values) + .add(MRegistry.TRITANIUM_BLOCK.allBlocks.values) + .add(MRegistry.TRITANIUM_STRIPED_BLOCK.blocks.values.stream().flatMap { it.values.stream() }) + tagsProvider.stoneOre("tritanium", MBlocks.TRITANIUM_ORE) tagsProvider.deepslateOre("tritanium", MBlocks.DEEPSLATE_TRITANIUM_ORE) tagsProvider.singleDropOre( diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/BlockEntityMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/BlockEntityMixin.java index eabcb4ed1..baeaf78c9 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/mixin/BlockEntityMixin.java +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/BlockEntityMixin.java @@ -5,7 +5,7 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import ru.dbotthepony.mc.otm.core.MultiblockKt; +import ru.dbotthepony.mc.otm.core.multiblock.GlobalBlockEntityRemovalListener; // because i know // someone @@ -20,6 +20,6 @@ public abstract class BlockEntityMixin { remap = false ) public void setRemovedListener(CallbackInfo ci) { - MultiblockKt.onBlockEntityInvalidated((BlockEntity) (Object) this); + GlobalBlockEntityRemovalListener.Companion.onBlockEntityInvalidated((BlockEntity) (Object) this); } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt index b143da612..c781b64a4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt @@ -4,7 +4,7 @@ import net.minecraft.core.HolderLookup import net.minecraft.nbt.CompoundTag import net.neoforged.neoforge.common.util.INBTSerializable import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.mc.otm.capability.MatteryPlayer @@ -12,7 +12,7 @@ import ru.dbotthepony.mc.otm.core.nbt.set abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: MatteryPlayer) : INBTSerializable { val ply get() = android.ply - val syncher = Syncher() + val syncher = SynchableGroup() val syncherRemote = syncher.Remote() open var level by syncher.int(setter = setter@{ field, value -> diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt index b1f3a113f..003fad620 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.android -import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import net.minecraft.ChatFormatting import net.minecraft.core.HolderLookup import net.minecraft.nbt.CompoundTag @@ -10,7 +9,7 @@ import net.minecraft.world.entity.player.Player import net.neoforged.bus.api.Event import net.neoforged.neoforge.common.NeoForge import net.neoforged.neoforge.common.util.INBTSerializable -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.mc.otm.OverdriveThatMatters @@ -22,7 +21,6 @@ import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.milliTime import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger -import java.io.InputStream import kotlin.math.absoluteValue class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlayer) : INBTSerializable { @@ -48,7 +46,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay val ply: Player get() = capability.ply - val syncher = Syncher() + val syncher = SynchableGroup() val syncherRemote = syncher.Remote() var isResearched by syncher.boolean() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt index 3a901d945..b34b3701c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt @@ -53,7 +53,7 @@ import ru.dbotthepony.mc.otm.core.util.Savetables import ru.dbotthepony.mc.otm.core.util.TickList import ru.dbotthepony.mc.otm.core.util.countingLazy import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup import ru.dbotthepony.mc.otm.onceServer import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.sometimeServer @@ -369,8 +369,8 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc } } - val syncher = Syncher() - private val synchers = Object2ObjectArrayMap() + val syncher = SynchableGroup() + private val synchers = Object2ObjectArrayMap() override fun setLevel(level: Level) { val old = this.level 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 a5bdd18f7..37fd54bd5 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 @@ -3,12 +3,11 @@ package ru.dbotthepony.mc.otm.block.entity import net.minecraft.core.BlockPos import net.minecraft.tags.BlockTags import net.minecraft.world.level.block.Blocks -import net.minecraft.world.level.block.entity.BlockEntity 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.multiblockConfiguration -import ru.dbotthepony.mc.otm.core.multiblockEntity +import ru.dbotthepony.mc.otm.core.multiblock.multiblockEntity +import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlocks @@ -28,7 +27,7 @@ class MultiblockTestBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma private val HOPPERS = multiblockEntity() private val FURNACES = multiblockEntity() - val CONFIG = multiblockConfiguration { + val CONFIG = shapedMultiblock { block(MBlocks.MULTIBLOCK_TEST) and { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt index 179294c93..18d47149f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt @@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.block.entity import net.minecraft.core.HolderLookup import net.minecraft.nbt.CompoundTag import net.neoforged.neoforge.common.util.INBTSerializable -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup import ru.dbotthepony.kommons.util.Listenable import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue @@ -79,7 +79,7 @@ class RedstoneControl(private val valueChanges: (new: Boolean, old: Boolean) -> } class SynchronizedRedstoneControl( - synchronizer: Syncher, + synchronizer: SynchableGroup, private val valueChanges: (new: Boolean, old: Boolean) -> Unit, ) : AbstractRedstoneControl() { override var redstoneSetting: RedstoneSetting by synchronizer.enum(RedstoneSetting.LOW, setter = { access, value -> diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt index cf1a69ca3..20a701545 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt @@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArraySet import net.minecraft.client.Minecraft import net.minecraft.core.BlockPos import net.minecraft.core.HolderLookup +import net.minecraft.core.SectionPos import net.minecraft.nbt.CompoundTag import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer @@ -26,6 +27,8 @@ import ru.dbotthepony.mc.otm.block.BlackHoleBlock import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity import ru.dbotthepony.mc.otm.config.ServerConfig +import ru.dbotthepony.mc.otm.core.addAll +import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.damageType import ru.dbotthepony.mc.otm.core.getExplosionResistance import ru.dbotthepony.mc.otm.core.gracefulBlockBreak @@ -182,6 +185,9 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery } } + private val sphericalPositionsCache = ArrayList() + private var lastRadii = -1 + override fun tick() { super.tick() if (--sleepTicks > 0) return @@ -245,7 +251,30 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery var sphereIterator = sphereIterator if (sphereIterator == null || !sphereIterator.hasNext()) { - sphereIterator = getSphericalBlockPositions((gravitationStrength * 18.0).roundToInt()) + val radii = (gravitationStrength * 18.0).roundToInt() + + if (lastRadii != radii) { + sphericalPositionsCache.clear() + lastRadii = radii + + if (radii <= 24) { + sphericalPositionsCache.addAll(getSphericalBlockPositions(radii).map { it + blockPos }) + + // Sort to maximize chunk cache + sphericalPositionsCache.sortWith { o1, o2 -> + val cmp = SectionPos.blockToSectionCoord(o1.x).compareTo(SectionPos.blockToSectionCoord(o2.x)) + if (cmp != 0) return@sortWith cmp + return@sortWith SectionPos.blockToSectionCoord(o1.z).compareTo(SectionPos.blockToSectionCoord(o2.z)) + } + } + } + + if (sphericalPositionsCache.isNotEmpty()) { + sphereIterator = sphericalPositionsCache.iterator() + } else { + sphereIterator = getSphericalBlockPositions(radii).map { it + blockPos } + } + this.sphereIterator = sphereIterator } @@ -254,7 +283,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery while (iterations < ITERATIONS && sphereIterator.hasNext()) { iterations++ - val pos = sphereIterator.next() + blockPos + val pos = sphereIterator.next() val getBlock = level.getBlockState(pos) if (!getBlock.isAir && getBlock.block !is BlackHoleBlock) { 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 01702455d..e909a8497 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 @@ -2,29 +2,83 @@ package ru.dbotthepony.mc.otm.block.entity.blackhole import it.unimi.dsi.fastutil.objects.ObjectArrayList import it.unimi.dsi.fastutil.objects.ObjectArraySet +import it.unimi.dsi.fastutil.objects.ObjectIterators import net.minecraft.core.BlockPos import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.level.block.state.BlockState +import net.neoforged.neoforge.capabilities.Capabilities +import ru.dbotthepony.kommons.util.DelegateSetter +import ru.dbotthepony.kommons.util.getValue +import ru.dbotthepony.kommons.util.setValue +import ru.dbotthepony.kommons.util.value import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.tech.EnergyHatchBlockEntity import ru.dbotthepony.mc.otm.block.entity.tech.MatterHatchBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.energy.CombinedProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.receiveEnergy +import ru.dbotthepony.mc.otm.capability.matter.CombinedProfiledMatterStorage import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.core.Multiblock -import ru.dbotthepony.mc.otm.core.MultiblockBuilder +import ru.dbotthepony.mc.otm.core.RandomSource2Generator +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.multiblock.ShapedMultiblock import ru.dbotthepony.mc.otm.core.getBlockStateNow import ru.dbotthepony.mc.otm.core.immutableList 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.multiblockConfiguration +import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag +import ru.dbotthepony.mc.otm.core.multiblock.Strategy +import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock +import ru.dbotthepony.mc.otm.menu.tech.BlackHoleGeneratorMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.registry.MBlockTags import ru.dbotthepony.mc.otm.registry.MBlocks +import java.io.Closeable class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.BLACK_HOLE_GENERATOR, blockPos, blockState) { - private var multiblock: Multiblock? = null - private var lastRange = -1 + var multiblock: ShapedMultiblock? = null + private set + private var multiblockSyncher: Closeable? = null + + var lastRange by syncher.int(-1, setter = DelegateSetter { field, value -> + if (value == -1) { + multiblockSyncher?.close() + multiblockSyncher = null + multiblock?.close() + multiblock = null + field.accept(value) + } else if (value != field.value) { + multiblockSyncher?.close() + multiblock = CONFIGURATIONS[value - 5].value.create(blockPos) + multiblockSyncher = syncher.add0(multiblock!!) + field.accept(value) + } + }) + private set + + var drawBuildingGuide by syncher.boolean() + + val energy = CombinedProfiledEnergyStorage( + FlowDirection.NONE, + { multiblock?.blockEntities(EnergyHatchBlockEntity.OUTPUT_TAG)?.iterator()?.map { it.energy } ?: ObjectIterators.emptyIterator() }, + { level?.random?.let { RandomSource2Generator(it) } } + ) + + val matter = CombinedProfiledMatterStorage( + FlowDirection.NONE, + { multiblock?.blockEntities(MatterHatchBlockEntity.INPUT_TAG)?.iterator()?.map { it.matter } ?: ObjectIterators.emptyIterator() }, + { level?.random?.let { RandomSource2Generator(it) } } + ) + + init { + exposeSideless(MatteryCapability.MATTER_BLOCK, matter) + exposeSideless(MatteryCapability.BLOCK_ENERGY, energy) + exposeSideless(Capabilities.EnergyStorage.BLOCK, energy) + } private fun findBlackHoleRange(): Int { val normal = blockRotation.normal @@ -52,69 +106,29 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) override fun tick() { super.tick() + energy.tick() + matter.tick() + val level = level!! if (lastRange == -1) { - val found = findBlackHoleRange() - - if (found != -1) { - lastRange = found - multiblock = CONFIGURATIONS[found - 5].value.create(blockPos) - } else { - multiblock = null - } + this.lastRange = findBlackHoleRange() } val multiblock = multiblock if (multiblock != null) { if (!multiblock.update(level, blockRotation.front)) { - val found = findBlackHoleRange() - - if (found == -1) { - this.multiblock = null - } else if (found != lastRange) { - lastRange = found - this.multiblock = CONFIGURATIONS[found - 5].value.create(blockPos) - } + this.lastRange = findBlackHoleRange() } else { val blackHole = multiblock.blockEntities(BLACK_HOLE).first() - val matterHatches = multiblock.blockEntities(MatterHatchBlockEntity.INPUT_TAG) - val energyHatches = multiblock.blockEntities(EnergyHatchBlockEntity.OUTPUT_TAG) - if (matterHatches.isEmpty() || energyHatches.isEmpty()) return + if (matter.extractMatter(MachinesConfig.BlackHoleGenerator.MATTER_RATE, true) != MachinesConfig.BlackHoleGenerator.MATTER_RATE) return + val energyToStore = blackHole.mass / MachinesConfig.BlackHoleGenerator.MASS_DIVISOR + if (energy.receiveEnergy(energyToStore, true) != energyToStore) return - var required = MachinesConfig.BlackHoleGenerator.MATTER_RATE - - for (hatch in matterHatches) { - required -= hatch.matter.extractMatter(required, true) - if (required <= Decimal.ZERO) break - } - - if (required > Decimal.ZERO) return - - var energyLeftover = blackHole.mass / MachinesConfig.BlackHoleGenerator.MASS_DIVISOR - - for (hatch in energyHatches) { - energyLeftover -= hatch.energy.receiveEnergy(energyLeftover, true) - if (energyLeftover <= Decimal.ZERO) break - } - - if (energyLeftover > Decimal.ZERO) return - - required = MachinesConfig.BlackHoleGenerator.MATTER_RATE - - for (hatch in matterHatches) { - required -= hatch.matter.extractMatter(required, false) - if (required <= Decimal.ZERO) break - } - - energyLeftover = blackHole.mass / MachinesConfig.BlackHoleGenerator.MASS_DIVISOR - - for (hatch in energyHatches) { - energyLeftover -= hatch.energy.receiveEnergy(energyLeftover, false) - if (energyLeftover <= Decimal.ZERO) break - } + matter.extractMatter(MachinesConfig.BlackHoleGenerator.MATTER_RATE, false) + energy.receiveEnergy(energyToStore, false) blackHole.mass += MachinesConfig.BlackHoleGenerator.MASS_FEEDING_RATIO * MachinesConfig.BlackHoleGenerator.MATTER_RATE } @@ -122,11 +136,11 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) } override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? { - return null + return BlackHoleGeneratorMenu(containerID, inventory, this) } companion object { - val BLACK_HOLE = MultiblockBuilder.EntityTag(BlackHoleBlockEntity::class) + val BLACK_HOLE = BlockEntityTag(BlackHoleBlockEntity::class) private fun points(x: Int, z: Int, result: ObjectArraySet) { if (x == 0) { @@ -187,7 +201,7 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) val i = i2 + 5 accept(lazy { - multiblockConfiguration { + shapedMultiblock { builder.customCheck { val blocks = it.blocks() @@ -226,9 +240,9 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) tag(MatterHatchBlockEntity.INPUT_TAG) } - node.block(MBlocks.MULTIBLOCK_STRUCTURE) + node.block(MBlockTags.MULTIBLOCK_STRUCTURE) } else { - node.block(MBlocks.MULTIBLOCK_STRUCTURE) + node.block(MBlockTags.MULTIBLOCK_HARD_STRUCTURE) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt index ae24ab448..053281143 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt @@ -18,6 +18,7 @@ import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.capability.* import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart import ru.dbotthepony.mc.otm.core.math.BlockRotation import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.RelativeSide diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt index b42dfc868..91b39476c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt @@ -17,7 +17,7 @@ import ru.dbotthepony.mc.otm.config.EnergyBalanceValues import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.core.MultiblockBuilder +import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag import ru.dbotthepony.mc.otm.menu.tech.EnergyHatchMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities @@ -70,8 +70,8 @@ class EnergyHatchBlockEntity( companion object { const val CAPACITY = 1 - val INPUT_TAG = MultiblockBuilder.EntityTag(EnergyHatchBlockEntity::class) { it.isInput } - val OUTPUT_TAG = MultiblockBuilder.EntityTag(EnergyHatchBlockEntity::class) { !it.isInput } + val INPUT_TAG = BlockEntityTag(EnergyHatchBlockEntity::class) { it.isInput } + val OUTPUT_TAG = BlockEntityTag(EnergyHatchBlockEntity::class) { !it.isInput } fun input(blockPos: BlockPos, blockState: BlockState): EnergyHatchBlockEntity { return EnergyHatchBlockEntity(true, MachinesConfig.ENERGY_HATCH, MBlockEntities.ENERGY_INPUT_HATCH, blockPos, blockState) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt index 1fea51d5e..73e4b35b1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt @@ -11,7 +11,7 @@ import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.core.MultiblockBuilder +import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag import ru.dbotthepony.mc.otm.menu.tech.ItemHatchMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities @@ -36,8 +36,8 @@ class ItemHatchBlockEntity( companion object { const val CAPACITY = CargoCrateBlockEntity.CAPACITY - val INPUT_TAG = MultiblockBuilder.EntityTag(ItemHatchBlockEntity::class) { it.isInput } - val OUTPUT_TAG = MultiblockBuilder.EntityTag(ItemHatchBlockEntity::class) { !it.isInput } + val INPUT_TAG = BlockEntityTag(ItemHatchBlockEntity::class) { it.isInput } + val OUTPUT_TAG = BlockEntityTag(ItemHatchBlockEntity::class) { !it.isInput } fun input(blockPos: BlockPos, blockState: BlockState): ItemHatchBlockEntity { return ItemHatchBlockEntity(true, MBlockEntities.ITEM_INPUT_HATCH, blockPos, blockState) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt index 55c635639..6bd878e77 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt @@ -17,8 +17,8 @@ import ru.dbotthepony.mc.otm.capability.moveMatter import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.core.MultiblockBuilder import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities @@ -74,8 +74,8 @@ class MatterHatchBlockEntity( companion object { const val CAPACITY = 1 - val INPUT_TAG = MultiblockBuilder.EntityTag(MatterHatchBlockEntity::class) { it.isInput } - val OUTPUT_TAG = MultiblockBuilder.EntityTag(MatterHatchBlockEntity::class) { !it.isInput } + val INPUT_TAG = BlockEntityTag(MatterHatchBlockEntity::class) { it.isInput } + val OUTPUT_TAG = BlockEntityTag(MatterHatchBlockEntity::class) { !it.isInput } fun input(blockPos: BlockPos, blockState: BlockState): MatterHatchBlockEntity { return MatterHatchBlockEntity(true, MBlockEntities.MATTER_INPUT_HATCH, blockPos, blockState) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt index f03086785..9e93d3166 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt @@ -4,18 +4,18 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList import net.minecraft.core.HolderLookup import net.minecraft.nbt.CompoundTag import net.neoforged.neoforge.common.util.INBTSerializable -import ru.dbotthepony.mc.otm.core.DecimalHistoryChart +import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.set -abstract class AbstractProfiledStorage(val parent: P) : INBTSerializable { - val received = DecimalHistoryChart(ticks = HISTORY_SIZE) - val transferred = DecimalHistoryChart(ticks = HISTORY_SIZE) +abstract class AbstractProfiledStorage(val parent: P) : IProfiledStorage, INBTSerializable { + final override val received = DecimalHistoryChart(ticks = HISTORY_SIZE) + final override val transferred = DecimalHistoryChart(ticks = HISTORY_SIZE) - var receivedThisTick = Decimal.ZERO + final override var receivedThisTick = Decimal.ZERO private set - var transferredThisTick = Decimal.ZERO + final override var transferredThisTick = Decimal.ZERO private set private var isTicking = false diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt index d2ac47101..a6c43e04c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.mc.otm.capability import com.google.common.collect.Streams +import it.unimi.dsi.fastutil.objects.ObjectArrayList import net.minecraft.ChatFormatting import net.minecraft.network.chat.Component import net.minecraft.world.entity.LivingEntity @@ -33,7 +34,10 @@ import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.shuffle import ru.dbotthepony.mc.otm.core.util.formatFluidLevel +import java.util.function.Supplier +import java.util.random.RandomGenerator import java.util.stream.Stream private val LOGGER = LogManager.getLogger() @@ -41,6 +45,37 @@ private val LOGGER = LogManager.getLogger() val Player.matteryPlayer: MatteryPlayer get() = (this as IMatteryPlayer).otmPlayer val LivingEntity.matteryPlayer: MatteryPlayer? get() = (this as? IMatteryPlayer)?.otmPlayer +fun Supplier>.iterateProviders(random: RandomGenerator?, value: Decimal, simulate: Boolean, walker: T.(Decimal, Boolean) -> Decimal): Decimal { + val providers = get() + val iteratorProvider: () -> Iterator + + if (random == null) { + iteratorProvider = providers::iterator + } else { + val providerList = ObjectArrayList(providers) + providerList.shuffle(random) + iteratorProvider = providerList::iterator + } + + var leftover = value + + for (provider in iteratorProvider()) { + leftover -= walker(provider, leftover, true) + if (leftover.signum() <= 0) break + } + + if (simulate) return value - leftover + + leftover = value + + for (provider in iteratorProvider()) { + leftover -= walker(provider, leftover, false) + if (leftover.signum() <= 0) break + } + + return value - leftover +} + /** * Does a checked energy receive, calls [IMatteryEnergyStorage.receiveEnergyChecked] if possible */ diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IProfiledStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IProfiledStorage.kt new file mode 100644 index 000000000..0d2109e66 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IProfiledStorage.kt @@ -0,0 +1,21 @@ +package ru.dbotthepony.mc.otm.capability + +import ru.dbotthepony.mc.otm.core.chart.CombinedDecimalHistoryChart +import ru.dbotthepony.mc.otm.core.chart.IHistoryChart +import ru.dbotthepony.mc.otm.core.math.Decimal + +interface IProfiledStorage { + val received: IHistoryChart + val transferred: IHistoryChart + + val receivedThisTick: Decimal + val transferredThisTick: Decimal + + /** + * Just a middleware interface for [CombinedDecimalHistoryChart] carriers + */ + interface Combined : IProfiledStorage { + override val received: CombinedDecimalHistoryChart + override val transferred: CombinedDecimalHistoryChart + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayer.kt index 52f63fc06..a33f45d94 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayer.kt @@ -13,10 +13,8 @@ import net.minecraft.commands.arguments.EntityArgument import net.minecraft.core.HolderLookup import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.IntTag import net.minecraft.nbt.ListTag import net.minecraft.nbt.StringTag -import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.network.chat.Component import net.minecraft.network.protocol.common.custom.CustomPacketPayload import net.minecraft.resources.ResourceLocation @@ -59,7 +57,7 @@ import org.apache.logging.log4j.LogManager import org.joml.Vector4f import ru.dbotthepony.kommons.collect.ListenableMap import ru.dbotthepony.kommons.collect.ListenableSet -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup import ru.dbotthepony.kommons.util.ListenableDelegate import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue @@ -80,7 +78,6 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact import ru.dbotthepony.mc.otm.capability.energy.receiveEnergyExact import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.client.onceClient import ru.dbotthepony.mc.otm.config.AndroidConfig import ru.dbotthepony.mc.otm.config.ExopackConfig import ru.dbotthepony.mc.otm.container.CombinedContainer @@ -184,7 +181,7 @@ class MatteryPlayer(val ply: Player) { * any differences in field order/types/etc between client and server * will break *everything* */ - val syncher = Syncher() + val syncher = SynchableGroup() /** * Remote for "this" player @@ -198,10 +195,10 @@ class MatteryPlayer(val ply: Player) { * any differences in field order/types/etc between client and server * will break *everything* */ - val publicSyncher = Syncher() + val publicSyncher = SynchableGroup() val publicSyncherRemote = publicSyncher.Remote() - val remoteSynchers = Object2ObjectArrayMap() + val remoteSynchers = Object2ObjectArrayMap() /** * For data to be stored to and loaded from NBT automatically diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt index 58f409500..256a66c80 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt @@ -8,7 +8,7 @@ import net.minecraft.world.item.ItemStack import net.minecraft.world.ticks.ContainerSingleItem import net.neoforged.neoforge.capabilities.Capabilities import net.neoforged.neoforge.common.util.INBTSerializable -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.mc.otm.capability.FlowDirection @@ -23,7 +23,7 @@ import ru.dbotthepony.mc.otm.triggers.ExopackBatterySlotTrigger class BatteryBackedEnergyStorage( private val ply: Player, - synchronizer: Syncher, + synchronizer: SynchableGroup, initialCharge: Decimal, maxCharge: Decimal, val isAndroid: Boolean, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/CombinedEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/CombinedEnergyStorage.kt new file mode 100644 index 000000000..7cece4a06 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/CombinedEnergyStorage.kt @@ -0,0 +1,56 @@ +package ru.dbotthepony.mc.otm.capability.energy + +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.iterateProviders +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.reduce +import ru.dbotthepony.mc.otm.core.math.Decimal +import java.util.function.Supplier +import java.util.random.RandomGenerator + +open class CombinedEnergyStorage( + final override val energyFlow: FlowDirection, + val provider: Supplier>, + val random: Supplier = Supplier { null } +) : IMatteryEnergyStorage { + final override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return provider.iterateProviders(random.get(), howMuch, simulate, IMatteryEnergyStorage::extractEnergy) + } + + final override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return provider.iterateProviders(random.get(), howMuch, simulate, IMatteryEnergyStorage::receiveEnergy) + } + + /* + final override fun extractEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return provider.iterateProviders(random.get(), howMuch, simulate, IMatteryEnergyStorage::extractEnergyChecked) + } + + final override fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return provider.iterateProviders(random.get(), howMuch, simulate, IMatteryEnergyStorage::receiveEnergyChecked) + } + */ + + final override var batteryLevel: Decimal + get() = provider.get().iterator().map { it.batteryLevel }.reduce(Decimal.ZERO, Decimal::plus) + set(value) { throw UnsupportedOperationException() } + final override val maxBatteryLevel: Decimal + get() = provider.get().iterator().map { it.maxBatteryLevel }.reduce(Decimal.ZERO, Decimal::plus) + + final override val canSetBatteryLevel: Boolean + get() = false + final override val missingPower: Decimal + get() = provider.get().iterator().map { it.missingPower }.reduce(Decimal.ZERO, Decimal::plus) + + final override fun drainBattery(): Boolean { + var any = false + provider.get().forEach { any = it.drainBattery() || any } + return any + } + + final override fun fillBattery(): Boolean { + var any = false + provider.get().forEach { any = it.fillBattery() || any } + return any + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/CombinedProfiledEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/CombinedProfiledEnergyStorage.kt new file mode 100644 index 000000000..7001566b6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/CombinedProfiledEnergyStorage.kt @@ -0,0 +1,30 @@ +package ru.dbotthepony.mc.otm.capability.energy + +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.IProfiledStorage +import ru.dbotthepony.mc.otm.core.chart.CombinedDecimalHistoryChart +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.reduce +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.ITickable +import java.util.function.Supplier +import java.util.random.RandomGenerator + +class CombinedProfiledEnergyStorage( + energyFlow: FlowDirection, + provider: Supplier>>, + random: Supplier = Supplier { null } +) : CombinedEnergyStorage(energyFlow, provider, random), IProfiledMatteryEnergyStorage, IProfiledStorage.Combined, ITickable { + override val received = CombinedDecimalHistoryChart(Supplier { this.provider.get().map { (it as ProfiledEnergyStorage<*>).received } }, ticks = AbstractProfiledStorage.HISTORY_SIZE) + override val transferred = CombinedDecimalHistoryChart(Supplier { this.provider.get().map { (it as ProfiledEnergyStorage<*>).transferred } }, ticks = AbstractProfiledStorage.HISTORY_SIZE) + override val receivedThisTick: Decimal + get() = provider.get().map { (it as ProfiledEnergyStorage<*>).receivedThisTick }.reduce(Decimal.ZERO, Decimal::plus) + override val transferredThisTick: Decimal + get() = provider.get().map { (it as ProfiledEnergyStorage<*>).transferredThisTick }.reduce(Decimal.ZERO, Decimal::plus) + + override fun tick() { + received.tick() + transferred.tick() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt index 2ab6a1cd0..2fad6fb07 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt @@ -4,6 +4,7 @@ import net.neoforged.neoforge.energy.IEnergyStorage import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.kommons.math.RGBAColor +import ru.dbotthepony.mc.otm.capability.IProfiledStorage import java.math.BigInteger import kotlin.math.roundToInt @@ -72,7 +73,7 @@ interface IMatteryEnergyStorage : IEnergyStorage { * * Returned value CAN NOT be bigger than requested [howMuch], implementations failing to obey this contract will cause undefined behavior in upstream code. * Upstream code SHOULD NOT check for this contract. * - * @return energy extracted + * @return energy received */ fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal @@ -81,7 +82,7 @@ interface IMatteryEnergyStorage : IEnergyStorage { * * Interface implementations generally do not need to override this. * - * @return energy extracted + * @return energy received */ fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { if (howMuch.isNegative || !energyFlow.input) @@ -122,12 +123,16 @@ interface IMatteryEnergyStorage : IEnergyStorage { * Empties power of this energy storage * * @see batteryLevel - * @return whenever operation was successful + * @return whenever operation was successful, including partial completion */ fun drainBattery(): Boolean { try { - batteryLevel = Decimal.ZERO + if (canSetBatteryLevel) + batteryLevel = Decimal.ZERO + else + return false } catch(err: UnsupportedOperationException) { + // anti-stupid return false } @@ -138,12 +143,16 @@ interface IMatteryEnergyStorage : IEnergyStorage { * Fully fills power in this energy storage * * @see batteryLevel - * @return whenever operation was successful + * @return whenever operation was successful, including partial completion */ fun fillBattery(): Boolean { try { - batteryLevel = maxBatteryLevel + if (canSetBatteryLevel) + batteryLevel = maxBatteryLevel + else + return false } catch(err: UnsupportedOperationException) { + // anti-stupid return false } @@ -374,3 +383,5 @@ fun IMatteryEnergyStorage.getBarColor(): Int { return RGBAColor.LOW_POWER.linearInterpolation((batteryLevel / maxBatteryLevel).toFloat(), RGBAColor.FULL_POWER).toBGR() } + +interface IProfiledMatteryEnergyStorage : IProfiledStorage, IMatteryEnergyStorage diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProfiledEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProfiledEnergyStorage.kt index 3cb14c888..1bd03806c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProfiledEnergyStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProfiledEnergyStorage.kt @@ -4,7 +4,7 @@ import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.core.math.Decimal -class ProfiledEnergyStorage(parent: E) : AbstractProfiledStorage(parent), IMatteryEnergyStorage { +class ProfiledEnergyStorage(parent: E) : AbstractProfiledStorage(parent), IProfiledMatteryEnergyStorage { override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { return recordTransfer(parent.extractEnergy(howMuch, simulate), simulate) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProxiedEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProxiedEnergyStorage.kt index 4aff62fc1..e6bad2d75 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProxiedEnergyStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProxiedEnergyStorage.kt @@ -40,4 +40,28 @@ class ProxiedEnergyStorage(var parent: T? = null) : I override fun fillBattery(): Boolean { return parent?.fillBattery() ?: false } + + override fun receiveEnergy(maxReceive: Int, simulate: Boolean): Int { + return parent?.receiveEnergy(maxReceive, simulate) ?: 0 + } + + override fun extractEnergy(maxReceive: Int, simulate: Boolean): Int { + return parent?.extractEnergy(maxReceive, simulate) ?: 0 + } + + override fun getEnergyStored(): Int { + return parent?.getEnergyStored() ?: 0 + } + + override fun getMaxEnergyStored(): Int { + return parent?.getMaxEnergyStored() ?: 0 + } + + override fun canExtract(): Boolean { + return parent?.canExtract() == true + } + + override fun canReceive(): Boolean { + return parent?.canReceive() == true + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/CombinedMatterStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/CombinedMatterStorage.kt new file mode 100644 index 000000000..cd0c3d7c7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/CombinedMatterStorage.kt @@ -0,0 +1,59 @@ +package ru.dbotthepony.mc.otm.capability.matter + +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.iterateProviders +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.reduce +import ru.dbotthepony.mc.otm.core.math.Decimal +import java.util.function.Supplier +import java.util.random.RandomGenerator + +open class CombinedMatterStorage( + final override val matterFlow: FlowDirection, + val provider: Supplier>, + val random: Supplier = Supplier { null } +) : IMatterStorage { + final override var storedMatter: Decimal + get() = provider.get().map { it.storedMatter }.reduce(Decimal.ZERO, Decimal::plus) + set(value) { + throw UnsupportedOperationException() + } + final override val maxStoredMatter: Decimal + get() = provider.get().map { it.maxStoredMatter }.reduce(Decimal.ZERO, Decimal::plus) + + final override fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal { + return provider.iterateProviders(random.get(), howMuch, simulate, IMatterStorage::receiveMatter) + } + + final override fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal { + return provider.iterateProviders(random.get(), howMuch, simulate, IMatterStorage::extractMatter) + } + + final override val canSetMatterLevel: Boolean + get() = false + + final override fun drainMatter(): Boolean { + var any = false + provider.get().forEach { any = it.drainMatter() || any } + return any + } + + final override fun fillMatter(): Boolean { + var any = false + provider.get().forEach { any = it.fillMatter() || any } + return any + } + + /* + final override fun receiveMatterChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return provider.iterateProviders(random.get(), howMuch, simulate, IMatterStorage::receiveMatterChecked) + } + + final override fun extractMatterChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return provider.iterateProviders(random.get(), howMuch, simulate, IMatterStorage::extractMatterChecked) + } + */ + + final override val missingMatter: Decimal + get() = provider.get().iterator().map { it.missingMatter }.reduce(Decimal.ZERO, Decimal::plus) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/CombinedProfiledMatterStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/CombinedProfiledMatterStorage.kt new file mode 100644 index 000000000..e870ca7b4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/CombinedProfiledMatterStorage.kt @@ -0,0 +1,30 @@ +package ru.dbotthepony.mc.otm.capability.matter + +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.IProfiledStorage +import ru.dbotthepony.mc.otm.core.chart.CombinedDecimalHistoryChart +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.reduce +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.ITickable +import java.util.function.Supplier +import java.util.random.RandomGenerator + +class CombinedProfiledMatterStorage( + matterFlow: FlowDirection, + provider: Supplier>>, + random: Supplier = Supplier { null } +) : CombinedMatterStorage(matterFlow, provider, random), IProfiledMatterStorage, IProfiledStorage.Combined, ITickable { + override val received = CombinedDecimalHistoryChart(Supplier { this.provider.get().map { (it as ProfiledMatterStorage<*>).received } }, ticks = AbstractProfiledStorage.HISTORY_SIZE) + override val transferred = CombinedDecimalHistoryChart(Supplier { this.provider.get().map { (it as ProfiledMatterStorage<*>).transferred } }, ticks = AbstractProfiledStorage.HISTORY_SIZE) + override val receivedThisTick: Decimal + get() = provider.get().map { (it as ProfiledMatterStorage<*>).receivedThisTick }.reduce(Decimal.ZERO, Decimal::plus) + override val transferredThisTick: Decimal + get() = provider.get().map { (it as ProfiledMatterStorage<*>).transferredThisTick }.reduce(Decimal.ZERO, Decimal::plus) + + override fun tick() { + received.tick() + transferred.tick() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt index dfaa1e3ec..6fddd7aa3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.capability.matter import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.kommons.math.RGBAColor +import ru.dbotthepony.mc.otm.capability.IProfiledStorage import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import kotlin.math.roundToInt @@ -39,21 +40,41 @@ interface IMatterStorage { /** * Empties matter stored of this matter storage, if possible * - * @throws [UnsupportedOperationException] * @see storedMatter + * @return whenever operation was successful, including partial completion */ - fun drainMatter() { - storedMatter = Decimal.ZERO + fun drainMatter(): Boolean { + try { + if (canSetMatterLevel) + storedMatter = Decimal.ZERO + else + return false + } catch (err: UnsupportedOperationException) { + // anti-stupid + return false + } + + return true } /** * Fully fills matter stored, if possible * - * @throws [UnsupportedOperationException] * @see storedMatter + * @return whenever operation was successful, including partial completion */ - fun fillMatter() { - storedMatter = maxStoredMatter + fun fillMatter(): Boolean { + try { + if (canSetMatterLevel) + storedMatter = maxStoredMatter + else + return false + } catch (err: UnsupportedOperationException) { + // anti-stupid + return false + } + + return true } /** @@ -122,3 +143,5 @@ fun IMatterStorage.getBarWidth(): Int { fun IMatterStorage.getBarColor(): Int { return RGBAColor.LOW_MATTER.linearInterpolation((storedMatter / maxStoredMatter).toFloat(), RGBAColor.FULL_MATTER).toBGR() } + +interface IProfiledMatterStorage : IMatterStorage, IProfiledStorage diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ProfiledMatterStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ProfiledMatterStorage.kt index b3ebe61f0..f00cf356d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ProfiledMatterStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ProfiledMatterStorage.kt @@ -32,12 +32,12 @@ class ProfiledMatterStorage(parent: M) : AbstractProfile override val canSetMatterLevel: Boolean get() = parent.canSetMatterLevel - override fun drainMatter() { - parent.drainMatter() + override fun drainMatter(): Boolean { + return parent.drainMatter() } - override fun fillMatter() { - parent.fillMatter() + override fun fillMatter(): Boolean { + return parent.fillMatter() } override val missingMatter: Decimal diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/DynamicBufferSource.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/DynamicBufferSource.kt index 9ed298079..3cfa3b5a7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/DynamicBufferSource.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/DynamicBufferSource.kt @@ -13,6 +13,7 @@ import net.minecraft.client.renderer.Sheets import net.minecraft.client.resources.model.ModelBakery import net.neoforged.fml.ModLoader import net.neoforged.neoforge.client.event.RegisterRenderBuffersEvent +import ru.dbotthepony.mc.otm.client.render.blockentity.BlackHoleGeneratorRenderer import java.io.Closeable private fun equals(existing: ImmutableList?, types: Array): Boolean { @@ -179,6 +180,7 @@ class DynamicBufferSource( next = State(RenderType.entityGlintDirect(), ImmutableList.of(next.type), immutableAfter = true, chained = false) next = State(RenderType.waterMask(), ImmutableList.of(next.type), immutableAfter = true, chained = false) + next = State(MULTIBLOCK_STATUS_RENDER_TYPE, ImmutableList.of(next.type), immutableAfter = true, chained = true) next = State(rectRenderType(true, true, true), ImmutableList.of(next.type), immutableAfter = true, chained = true) next = State(rectRenderType(true, true, false), ImmutableList.of(next.type), immutableAfter = true, chained = true) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/MultiblockRendering.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/MultiblockRendering.kt new file mode 100644 index 000000000..d80ce6f76 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/MultiblockRendering.kt @@ -0,0 +1,113 @@ +package ru.dbotthepony.mc.otm.client.render + +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.DefaultVertexFormat +import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.blaze3d.vertex.VertexFormat +import net.minecraft.client.renderer.GameRenderer +import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.RenderStateShard +import net.minecraft.client.renderer.RenderType +import net.minecraft.core.BlockPos +import org.lwjgl.opengl.GL11.GL_LESS +import ru.dbotthepony.kommons.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.component1 +import ru.dbotthepony.mc.otm.core.math.component2 +import ru.dbotthepony.mc.otm.core.math.component3 +import ru.dbotthepony.mc.otm.core.math.minus +import ru.dbotthepony.mc.otm.core.multiblock.IMultiblockAccess +import ru.dbotthepony.mc.otm.core.multiblock.NodeStatus + +private val UNKNOWN = RGBAColor.YELLOW.copy(alpha = 0.5f) +private val VALID = RGBAColor.DARK_GREEN.copy(alpha = 0.5f) +private val INVALID = RGBAColor.DARK_RED.copy(alpha = 0.5f) + +val MULTIBLOCK_STATUS_RENDER_TYPE = run { + val builder = RenderType.CompositeState.builder() + + builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorShader)) + builder.setDepthTestState(RenderStateShard.DepthTestStateShard("less", GL_LESS)) + + builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", { + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + }, { RenderSystem.disableBlend() })) + + @Suppress("INACCESSIBLE_TYPE") + RenderType.create( + "otm_multiblock_status", + DefaultVertexFormat.POSITION_COLOR, + VertexFormat.Mode.QUADS, + 256_000, + false, + false, + builder.createCompositeState(false) + ) as RenderType +} + +fun IMultiblockAccess.render( + poseStack: PoseStack, + buffers: MultiBufferSource, + relativePos: BlockPos, +) { + val buffer = buffers.getBuffer(MULTIBLOCK_STATUS_RENDER_TYPE) + val nodes = currentNodes + + for (node in nodes.values) { + val color = when (node.status) { + NodeStatus.UNKNOWN -> UNKNOWN + NodeStatus.VALID -> VALID + NodeStatus.INVALID -> INVALID + } + + val (x, y, z) = node.pos - relativePos + + if (nodes[node.pos.south()]?.status != node.status) { + // south + buffer.vertex(poseStack, x.toFloat(), y.toFloat(), z.toFloat() + 1f).color(color) + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat(), z.toFloat() + 1f).color(color) + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat() + 1f, z.toFloat() + 1f).color(color) + buffer.vertex(poseStack, x.toFloat(), y.toFloat() + 1f, z.toFloat() + 1f).color(color) + } + + if (nodes[node.pos.north()]?.status != node.status) { + // north + buffer.vertex(poseStack, x.toFloat(), y.toFloat(), z.toFloat()).color(color) + buffer.vertex(poseStack, x.toFloat(), y.toFloat() + 1f, z.toFloat()).color(color) + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat() + 1f, z.toFloat()).color(color) + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat(), z.toFloat()).color(color) + } + + if (nodes[node.pos.above()]?.status != node.status) { + // up + buffer.vertex(poseStack, x.toFloat(), y.toFloat() + 1f, z.toFloat()).color(color) + buffer.vertex(poseStack, x.toFloat(), y.toFloat() + 1f, z.toFloat() + 1f).color(color) + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat() + 1f, z.toFloat() + 1f).color(color) + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat() + 1f, z.toFloat()).color(color) + } + + if (nodes[node.pos.below()]?.status != node.status) { + // down + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat(), z.toFloat() + 1f).color(color) + buffer.vertex(poseStack, x.toFloat(), y.toFloat(), z.toFloat() + 1f).color(color) + buffer.vertex(poseStack, x.toFloat(), y.toFloat(), z.toFloat()).color(color) + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat(), z.toFloat()).color(color) + } + + if (nodes[node.pos.east()]?.status != node.status) { + // east + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat(), z.toFloat()).color(color) + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat() + 1f, z.toFloat()).color(color) + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat() + 1f, z.toFloat() + 1f).color(color) + buffer.vertex(poseStack, x.toFloat() + 1f, y.toFloat(), z.toFloat() + 1f).color(color) + } + + if (nodes[node.pos.west()]?.status != node.status) { + // west + buffer.vertex(poseStack, x.toFloat(), y.toFloat() + 1f, z.toFloat() + 1f).color(color) + buffer.vertex(poseStack, x.toFloat(), y.toFloat() + 1f, z.toFloat()).color(color) + buffer.vertex(poseStack, x.toFloat(), y.toFloat(), z.toFloat()).color(color) + buffer.vertex(poseStack, x.toFloat(), y.toFloat(), z.toFloat() + 1f).color(color) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleGeneratorRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleGeneratorRenderer.kt new file mode 100644 index 000000000..250ec9077 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleGeneratorRenderer.kt @@ -0,0 +1,24 @@ +package ru.dbotthepony.mc.otm.client.render.blockentity + +import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider +import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleGeneratorBlockEntity +import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource +import ru.dbotthepony.mc.otm.client.render.render + +class BlackHoleGeneratorRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { + override fun render( + tile: BlackHoleGeneratorBlockEntity, + partialtick: Float, + poseStack: PoseStack, + buffers: MultiBufferSource, + packedLight: Int, + packedOverlay: Int + ) { + //if (!tile.drawBuildingGuide) return + val multiblock = tile.multiblock ?: return + multiblock.render(poseStack, DynamicBufferSource.WORLD, tile.blockPos) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AbstractHistoryGraphPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AbstractHistoryGraphPanel.kt index fa21f2a03..45a1ac75b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AbstractHistoryGraphPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AbstractHistoryGraphPanel.kt @@ -1,7 +1,7 @@ package ru.dbotthepony.mc.otm.client.screen.panels import ru.dbotthepony.mc.otm.client.screen.MatteryScreen -import ru.dbotthepony.mc.otm.core.AbstractHistoryChart +import ru.dbotthepony.mc.otm.core.chart.AbstractHistoryChart abstract class AbstractHistoryGraphPanel, G : AbstractHistoryChart, V : Any>( screen: S, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DecimalHistoryChartPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DecimalHistoryChartPanel.kt index 3198a2cdd..9c187fe74 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DecimalHistoryChartPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DecimalHistoryChartPanel.kt @@ -8,7 +8,7 @@ import ru.dbotthepony.mc.otm.client.render.ChartMouseLabels import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.renderChart import ru.dbotthepony.mc.otm.client.screen.MatteryScreen -import ru.dbotthepony.mc.otm.core.DecimalHistoryChart +import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.math.Decimal diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt index c17dac55c..bd78347a9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt @@ -494,7 +494,7 @@ private enum class PreviewScrollers( ) } -class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_97743_: Component) : +class AndroidStationScreen(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_97743_: Component) : MatteryScreen(p_97741_, p_97742_, p_97743_) { private fun makeCanvas(isPreview: Boolean): DraggableCanvasPanel { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/BlackHoleGeneratorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/BlackHoleGeneratorScreen.kt new file mode 100644 index 000000000..2694b7cff --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/BlackHoleGeneratorScreen.kt @@ -0,0 +1,28 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledMatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.TallHorizontalProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.menu.tech.BlackHoleGeneratorMenu +import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu + +class BlackHoleGeneratorScreen(menu: BlackHoleGeneratorMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + val energy = TallHorizontalProfiledPowerGaugePanel(this, frame, menu.energy) + //val matter = ProfiledMatterGaugePanel + + energy.dock = Dock.TOP + energy.dockTop = 4f + energy.dockResize = DockResizeMode.NONE + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyHatchScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyHatchScreen.kt index 18cdb016e..f5af14ae3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyHatchScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyHatchScreen.kt @@ -10,27 +10,26 @@ import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel import ru.dbotthepony.mc.otm.client.screen.panels.SpritePanel import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.slot.MatterCapacitorSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.HorizontalProfiledPowerGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel import ru.dbotthepony.mc.otm.menu.tech.EnergyHatchMenu -import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu class EnergyHatchScreen(menu: EnergyHatchMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { override fun makeMainFrame(): FramePanel> { - val frame = FramePanel(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, AbstractSlotPanel.SIZE, getTitle()) + val frame = FramePanel.padded(this, null, INVENTORY_FRAME_WIDTH, AbstractSlotPanel.SIZE, getTitle()) frame.makeCloseButton() frame.onClose { onClose() } for (slot in menu.inputSlots) { - val panel = MatterCapacitorSlotPanel(this, frame, slot) + val panel = BatterySlotPanel(this, frame, slot) panel.dock = Dock.LEFT panel.dockResize = DockResizeMode.NONE } val arrow = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND) - arrow.dockLeft = 2f + arrow.dockLeft = 20f arrow.dockRight = 2f arrow.dock = Dock.LEFT arrow.dockResize = DockResizeMode.NONE diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/MatterHatchScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/MatterHatchScreen.kt index bb0287fe9..9811a2e03 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/MatterHatchScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/MatterHatchScreen.kt @@ -16,7 +16,7 @@ import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu class MatterHatchScreen(menu: MatterHatchMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { override fun makeMainFrame(): FramePanel> { - val frame = FramePanel(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, AbstractSlotPanel.SIZE, getTitle()) + val frame = FramePanel.padded(this, null, INVENTORY_FRAME_WIDTH, AbstractSlotPanel.SIZE, getTitle()) frame.makeCloseButton() frame.onClose { onClose() } @@ -28,7 +28,7 @@ class MatterHatchScreen(menu: MatterHatchMenu, inventory: Inventory, title: Comp } val arrow = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND) - arrow.dockLeft = 2f + arrow.dockLeft = 20f arrow.dockRight = 2f arrow.dock = Dock.LEFT arrow.dockResize = DockResizeMode.NONE diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/HorizontalPowerGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/HorizontalPowerGaugePanel.kt index f119d707b..cc5919e01 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/HorizontalPowerGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/HorizontalPowerGaugePanel.kt @@ -5,6 +5,7 @@ import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.WidgetLocation import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.menu.widget.IProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget @@ -73,7 +74,7 @@ fun TallHorizontalPowerGaugePanel( open class HorizontalProfiledPowerGaugePanel( screen: S, parent: EditablePanel<*>? = null, - widget: ProfiledLevelGaugeWidget<*>, + widget: IProfiledLevelGaugeWidget, x: Float = 0f, y: Float = 0f, width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND.width, @@ -93,7 +94,7 @@ open class HorizontalProfiledPowerGaugePanel( fun TallHorizontalProfiledPowerGaugePanel( screen: S, parent: EditablePanel<*>? = null, - widget: ProfiledLevelGaugeWidget<*>, + widget: IProfiledLevelGaugeWidget, x: Float = 0f, y: Float = 0f, width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt index 672c8a8af..cdaa08d57 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt @@ -5,30 +5,20 @@ import com.mojang.blaze3d.vertex.BufferUploader import com.mojang.blaze3d.vertex.DefaultVertexFormat import com.mojang.blaze3d.vertex.VertexFormat import com.mojang.datafixers.util.Either -import it.unimi.dsi.fastutil.ints.IntArrayList -import net.minecraft.ChatFormatting import net.minecraft.client.gui.screens.Screen import net.minecraft.client.renderer.GameRenderer -import net.minecraft.network.chat.Component import net.minecraft.network.chat.FormattedText import net.minecraft.world.inventory.tooltip.TooltipComponent import org.lwjgl.opengl.GL11 -import ru.dbotthepony.kommons.util.value -import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage -import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.ShiftPressedCond -import ru.dbotthepony.mc.otm.client.isShiftDown -import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.WidgetLocation import ru.dbotthepony.mc.otm.client.render.tesselator import ru.dbotthepony.mc.otm.client.render.uv import ru.dbotthepony.mc.otm.client.render.vertex import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.util.formatHistory -import ru.dbotthepony.mc.otm.core.util.formatMatter import ru.dbotthepony.mc.otm.core.util.formatMatterLevel import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt index e0a4fbdb6..817e2a8d0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt @@ -12,6 +12,7 @@ import ru.dbotthepony.mc.otm.client.render.* import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.core.util.formatHistory import ru.dbotthepony.mc.otm.core.util.formatPowerLevel +import ru.dbotthepony.mc.otm.menu.widget.IProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget @@ -92,7 +93,7 @@ fun WidePowerGaugePanel( open class ProfiledPowerGaugePanel( screen: S, parent: EditablePanel<*>? = null, - val profiledWidget: ProfiledLevelGaugeWidget<*>, + val profiledWidget: IProfiledLevelGaugeWidget, x: Float = 0f, y: Float = 0f, width: Float = GAUGE_BACKGROUND.width, @@ -117,7 +118,7 @@ open class ProfiledPowerGaugePanel( fun WideProfiledPowerGaugePanel( screen: S, parent: EditablePanel<*>? = null, - widget: ProfiledLevelGaugeWidget<*>, + widget: IProfiledLevelGaugeWidget, x: Float = 0f, y: Float = 0f, width: Float = 18f, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt index 97f6859d6..3c3b15906 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt @@ -4,18 +4,15 @@ import net.minecraft.ChatFormatting import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation -import net.neoforged.neoforge.capabilities.BlockCapability -import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.kommons.math.RGBAColor +import ru.dbotthepony.mc.otm.capability.IProfiledStorage import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.compat.jade.JadeColors import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys import ru.dbotthepony.mc.otm.compat.jade.JadeUids import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.kommons.math.RGBAColor -import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity -import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage import ru.dbotthepony.mc.otm.core.getCapability +import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.getDecimal import ru.dbotthepony.mc.otm.core.math.putDecimal import ru.dbotthepony.mc.otm.core.util.formatMatter @@ -37,14 +34,14 @@ object MatterStorageProvider : IBlockComponentProvider, IServerDataProvider) { + if (it is IProfiledStorage) { val profiledData = CompoundTag() // profiledData.putDecimal("lastTickReceive", it.lastTickReceive) // profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer) - profiledData.putDecimal("weightedReceive", it.received.calcWeightedAverage()) - profiledData.putDecimal("weightedTransfer", it.transferred.calcWeightedAverage()) + profiledData.putDecimal("weightedReceive", it.received.calculateAverage()) + profiledData.putDecimal("weightedTransfer", it.transferred.calculateAverage()) matterData.put("profiledData", profiledData) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt index 2c6a0d46d..56727d58a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt @@ -4,7 +4,6 @@ import net.minecraft.ChatFormatting import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.compat.jade.JadeColors import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys @@ -12,6 +11,7 @@ import ru.dbotthepony.mc.otm.compat.jade.JadeUids import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.kommons.math.RGBAColor +import ru.dbotthepony.mc.otm.capability.IProfiledStorage import ru.dbotthepony.mc.otm.core.getCapability import ru.dbotthepony.mc.otm.core.math.getDecimal import ru.dbotthepony.mc.otm.core.math.putDecimal @@ -31,14 +31,14 @@ object MatteryEnergyProvider : IBlockComponentProvider, IServerDataProvider) { + if (it is IProfiledStorage) { val profiledData = CompoundTag() // profiledData.putDecimal("lastTickReceive", it.lastTickReceive) // profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer) - profiledData.putDecimal("weightedReceive", it.received.calcWeightedAverage()) - profiledData.putDecimal("weightedTransfer", it.transferred.calcWeightedAverage()) + profiledData.putDecimal("weightedReceive", it.received.calculateAverage()) + profiledData.putDecimal("weightedTransfer", it.transferred.calculateAverage()) energyData.put("profiledData", profiledData) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt index a3c0adbd6..a52001fe6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt @@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.container import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.ints.IntAVLTreeSet import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.ints.IntComparators @@ -12,10 +11,8 @@ import net.minecraft.core.HolderLookup import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.world.item.ItemStack import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.ListTag import net.minecraft.nbt.NbtOps import net.minecraft.nbt.Tag -import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.world.Container import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.StackedContents @@ -24,9 +21,7 @@ import net.minecraft.world.item.Item import net.minecraft.world.item.Items import net.neoforged.neoforge.common.util.INBTSerializable import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kommons.collect.ListenableMap import ru.dbotthepony.kommons.util.Delegate -import ru.dbotthepony.mc.otm.network.syncher.Syncher import ru.dbotthepony.mc.otm.container.util.IContainerSlot import ru.dbotthepony.mc.otm.core.addSorted import ru.dbotthepony.mc.otm.core.collect.any @@ -37,11 +32,8 @@ import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.collect.toList import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.isNotEmpty -import ru.dbotthepony.mc.otm.core.nbt.set -import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.data.minRange import ru.dbotthepony.mc.otm.network.StreamCodecs -import ru.dbotthepony.mc.otm.network.syncher.IRemoteState import ru.dbotthepony.mc.otm.network.syncher.ISynchable import ru.dbotthepony.mc.otm.network.syncher.SynchableObservedDelegate import java.util.* 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 6b7da446c..7a2b5525b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -59,6 +59,7 @@ import java.util.concurrent.Callable import java.util.concurrent.Future import java.util.function.Consumer import java.util.function.Supplier +import java.util.random.RandomGenerator import java.util.stream.Stream import java.util.stream.StreamSupport import kotlin.jvm.optionals.getOrNull @@ -154,6 +155,24 @@ fun > T.prev(values: Array): T { return values[next] } +fun IntArray.shuffle(random: RandomGenerator) { + for (i in lastIndex downTo 1) { + val j = random.nextInt(i + 1) + val copy = this[i] + this[i] = this[j] + this[j] = copy + } +} + +fun MutableList.shuffle(random: RandomGenerator) { + for (i in lastIndex downTo 1) { + val j = random.nextInt(i + 1) + val copy = this[i] + this[i] = this[j] + this[j] = copy + } +} + inline fun immutableList(size: Int, initializer: (index: Int) -> T): ImmutableList { require(size >= 0) { "Invalid list size $size" } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/IDAllocator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/IDAllocator.kt new file mode 100644 index 000000000..c66de7b2f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/IDAllocator.kt @@ -0,0 +1,37 @@ +package ru.dbotthepony.mc.otm.core + +import it.unimi.dsi.fastutil.ints.IntAVLTreeSet + +class IDAllocator { + private var highestID = 0 + private val gaps = IntAVLTreeSet() + + fun allocate(): Int { + if (gaps.isEmpty()) { + return highestID++ + } else { + return gaps.removeFirst() + } + } + + fun release(id: Int) { + if (id >= 0) { + throw IllegalArgumentException("Invalid ID: $id") + } else if (id >= highestID) { + throw IllegalArgumentException("Not tracking ID: $id (highest known ID is ${highestID - 1})") + } else if (id in gaps) { + throw IllegalArgumentException("ID is already free: $id") + } + + if (id + 1 == highestID) { + highestID-- + } else { + gaps.add(id) + } + } + + fun reset() { + highestID = 0 + gaps.clear() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Multiblock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Multiblock.kt deleted file mode 100644 index a69d566dc..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Multiblock.kt +++ /dev/null @@ -1,988 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import com.google.common.collect.ImmutableList -import com.google.common.collect.ImmutableSet -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap -import it.unimi.dsi.fastutil.objects.ObjectArraySet -import it.unimi.dsi.fastutil.objects.ObjectSets -import it.unimi.dsi.fastutil.objects.Reference2IntMap -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.core.Vec3i -import net.minecraft.tags.TagKey -import net.minecraft.world.level.Level -import net.minecraft.world.level.LevelAccessor -import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.Rotation -import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraft.world.level.block.state.BlockState -import net.minecraft.world.level.chunk.status.ChunkStatus -import ru.dbotthepony.mc.otm.core.collect.SupplierList -import ru.dbotthepony.mc.otm.core.collect.WeakHashSet -import ru.dbotthepony.mc.otm.core.collect.collect -import ru.dbotthepony.mc.otm.core.collect.map -import ru.dbotthepony.mc.otm.core.math.RelativeSide -import ru.dbotthepony.mc.otm.core.math.plus -import java.util.* -import java.util.function.Predicate -import kotlin.collections.HashMap -import kotlin.reflect.KClass - -private val blockEntityListeners = WeakHashMap>>() - -fun onBlockEntityInvalidated(blockEntity: BlockEntity) { - blockEntityListeners[blockEntity.level]?.get(blockEntity)?.forEach { it.blockEntityRemoved(blockEntity) } - blockEntityListeners[blockEntity.level]?.remove(blockEntity) -} - -fun interface BlockPredicate { - fun test(pos: BlockPos, access: LevelAccessor): Boolean - - fun rotate(rotation: Rotation): BlockPredicate { - return this - } - - fun and(other: BlockPredicate): BlockPredicate { - return BlockPredicate { pos, access -> test(pos, access) && other.test(pos, access) } - } - - fun or(other: BlockPredicate): BlockPredicate { - return BlockPredicate { pos, access -> test(pos, access) || other.test(pos, access) } - } - - fun and(other: BlockPredicate, vararg others: BlockPredicate): BlockPredicate { - return And(ImmutableSet.copyOf(listOf(other, *others))) - } - - fun or(other: BlockPredicate, vararg others: BlockPredicate): BlockPredicate { - return Or(ImmutableSet.copyOf(listOf(other, *others))) - } - - fun offset(offset: BlockPos): Positioned { - return Positioned(offset, this) - } - - fun offset(offset: Vec3i): Positioned { - return offset(BlockPos(offset)) - } - - data class Positioned(val offset: BlockPos, val parent: BlockPredicate) : BlockPredicate { - override fun test(pos: BlockPos, access: LevelAccessor): Boolean { - return parent.test(offset + pos, access) - } - - override fun offset(offset: BlockPos): Positioned { - return Positioned(this.offset + offset, parent) - } - } - - data class And(val nodes: ImmutableSet) : BlockPredicate { - constructor(vararg nodes: BlockPredicate) : this(ImmutableSet.copyOf(nodes)) - constructor(nodes: Set) : this(ImmutableSet.copyOf(nodes)) - - override fun test(pos: BlockPos, access: LevelAccessor): Boolean { - return nodes.all { it.test(pos, access) } - } - } - - data class Or(val nodes: ImmutableSet) : BlockPredicate { - constructor(vararg nodes: BlockPredicate) : this(ImmutableSet.copyOf(nodes)) - constructor(nodes: Set) : this(ImmutableSet.copyOf(nodes)) - - override fun test(pos: BlockPos, access: LevelAccessor): Boolean { - return nodes.any { it.test(pos, access) } - } - } - - object Air : BlockPredicate { - override fun test(pos: BlockPos, access: LevelAccessor): Boolean { - return access.getBlockStateNow(pos).isAir - } - } - - object NotAir : BlockPredicate { - override fun test(pos: BlockPos, access: LevelAccessor): Boolean { - return !access.getBlockStateNow(pos).isAir - } - } -} - -inline fun multiblockConfiguration(configurator: MultiblockBuilder.Node.() -> Unit): MultiblockFactory { - val builder = MultiblockBuilder() - configurator.invoke(builder.root()) - return builder.build() -} - -inline fun multiblockEntity(): MultiblockBuilder.EntityTag { - return MultiblockBuilder.EntityTag(T::class) -} - -private val GLOBAL_BLOCK_TAG = Any() - -class MultiblockBuilder { - private val nodes = Object2ObjectOpenHashMap() - private val customChecks = ArrayList>() - - /** - * Tags specific [T] block entities to be exposed through [Multiblock.blockEntities] when specific [Part]s are tagged using [Part.tag] - */ - class EntityTag(val clazz: KClass, val predicate: Predicate = Predicate { true }) : Predicate { - override fun test(t: BlockEntity): Boolean { - return clazz.isInstance(t) && predicate.test(t as T) - } - } - - enum class Strategy { - OR, AND - } - - /** - * Adds custom [predicate] which determines whenever selected multiblock configuration is valid - * - * Code inside predicate can safely call [Multiblock.blocks], [Multiblock.blockEntities], etc to determine whenever - * all demands are met - */ - fun customCheck(predicate: Predicate): MultiblockBuilder { - customChecks.add(predicate) - return this - } - - @Suppress("unchecked_cast") - abstract inner class Part> { - val predicates = ObjectArraySet() - val children = ObjectArraySet>() - - val builder: MultiblockBuilder - get() = this@MultiblockBuilder - - val blockStateTags = ObjectArraySet() - val blockTags = ObjectArraySet() - val blockEntityTags = ObjectArraySet>() - - /** - * Marks this node report its block in [Multiblock.blockStates] method when called with specified [value] - * - * [value] is searched using "value" semantics (`==`) - */ - fun tagBlockState(value: Any): T { - blockStateTags.add(value) - return this as T - } - - /** - * Marks this node report its block in [Multiblock.blockStates] method when called without arguments - */ - fun tagBlockState(): T { - blockStateTags.add(GLOBAL_BLOCK_TAG) - return this as T - } - - /** - * Marks this node report its block in [Multiblock.blocks] method when called with specified [value] - * - * [value] is searched for using "value" semantics (`==`) - */ - fun tagBlock(value: Any): T { - blockTags.add(value) - return this as T - } - - /** - * Marks this node report its block in [Multiblock.blocks] method when called without arguments - */ - fun tagBlock(): T { - blockTags.add(GLOBAL_BLOCK_TAG) - return this as T - } - - /** - * Marks this node to report block entities put at its position when called [Multiblock.blockEntities] with specified [value] - * - * [value] is searched for using "identity" semantics (`===`) - */ - fun tag(value: EntityTag<*>): T { - blockEntityTags.add(value) - return this as T - } - - /** - * Removes all predicates, custom and prebuilt alike - */ - fun clear(): T { - predicates.clear() - return this as T - } - - /** - * Adds a custom predicate on this node's position - */ - fun predicate(predicate: BlockPredicate): T { - predicates.add(predicate) - return this as T - } - - /** - * Adds a block predicate, matching any valid blockstate of [block] on this node's position - */ - fun block(block: Block): T { - predicates.add { pos, access -> - // use getChunk directly because we don't want to chunkload - access.getBlockStateNow(pos).block == block - } - - return this as T - } - - /** - * Adds a block tag predicate on this node's position - */ - fun block(block: TagKey): T { - predicates.add { pos, access -> - // use getChunk directly because we don't want to chunkload - access.getBlockStateNow(pos).`is`(block) - } - - return this as T - } - - /** - * Adds "nothing" predicate (no block should be present) on this node's position - */ - fun air(): T { - predicate(BlockPredicate.Air) - return this as T - } - - /** - * Adds a blockstate predicate, matching exactly provided [state] on this node's position - */ - fun block(state: BlockState): T { - predicates.add { pos, access -> - // use getChunk directly because we don't want to chunkload - access.getChunk(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z), ChunkStatus.FULL, false)?.getBlockState(pos) == state - } - - return this as T - } - - /** - * Creates "and" subnode, which requires all its children to test true - * - * @see And - */ - fun and(): And { - return And(this as T) - } - - /** - * Creates "or" subnode, which requires at least one of its children to test true - * - * @see or - */ - fun or(): Or { - return Or(this as T) - } - - /** - * Creates "and" subnode, which requires all its children to test true - * - * @see And - */ - fun and(configurator: And.() -> Unit): And { - return And(this as T).also(configurator) - } - - /** - * Creates "or" subnode, which requires at least one of its children to test true - * - * @see or - */ - fun or(configurator: Or.() -> Unit): Or { - return Or(this as T).also(configurator) - } - - protected fun build(pos: BlockPos): MultiblockFactory.Part { - return MultiblockFactory.Part( - pos, strategy, ImmutableList.copyOf(predicates), - children.stream().map { it.build(pos) }.collect(ImmutableList.toImmutableList()), - ImmutableSet.copyOf(blockStateTags), - ImmutableSet.copyOf(blockTags), - ImmutableSet.copyOf(blockEntityTags), - ) - } - - abstract val strategy: Strategy - } - - /** - * Logical AND node, which tests `true` if all of its children test `true` - */ - inner class And

>(val parent: P) : Part>() { - init { - parent.children.add(this) - } - - fun end() = parent - - override val strategy: Strategy - get() = Strategy.AND - } - - /** - * Logical OR node, which tests `true` if at least one of its children test `true` - * - * This is default behavior of newly created nodes, including the multiblock root node - */ - inner class Or

>(val parent: P) : Part>() { - init { - parent.children.add(this) - } - - fun end() = parent - - override val strategy: Strategy - get() = Strategy.OR - } - - /** - * Behaves as if being [Or] node - */ - inner class Node(val pos: BlockPos) : Part() { - init { - check(nodes.put(pos, this) == null) { "Trying to create new node at $pos while already having one" } - } - - /** - * Creates new node relative to this node at [diff] - */ - fun relative(diff: Vec3i): Node { - return node(pos + diff) - } - - /** - * Creates new node relative to this node at [dir] side - */ - fun relative(dir: Direction): Node { - return relative(dir.normal) - } - - /** - * Creates new node relative to this node at [dir] side - */ - fun relative(dir: RelativeSide): Node { - return relative(dir.default) - } - - /** - * Creates new node relative to this node at [diff], and configures it in-place using provided [configurator] expression - */ - fun relative(diff: Vec3i, configurator: Node.() -> Unit): Node { - return node(pos + diff).also(configurator) - } - - /** - * Creates new node relative to this node at [dir] side, and configures it in-place using provided [configurator] expression - */ - fun relative(dir: Direction, configurator: Node.() -> Unit): Node { - return relative(dir.normal).also(configurator) - } - - /** - * Creates new node relative to this node at [dir] side, and configures it in-place using provided [configurator] expression - */ - fun relative(dir: RelativeSide, configurator: Node.() -> Unit): Node { - return relative(dir.default).also(configurator) - } - - fun front() = relative(RelativeSide.FRONT) - fun back() = relative(RelativeSide.BACK) - fun left() = relative(RelativeSide.LEFT) - fun right() = relative(RelativeSide.RIGHT) - fun top() = relative(RelativeSide.TOP) - fun bottom() = relative(RelativeSide.BOTTOM) - - fun front(block: Block) = relative(RelativeSide.FRONT).also { it.block(block) } - fun back(block: Block) = relative(RelativeSide.BACK).also { it.block(block) } - fun left(block: Block) = relative(RelativeSide.LEFT).also { it.block(block) } - fun right(block: Block) = relative(RelativeSide.RIGHT).also { it.block(block) } - fun top(block: Block) = relative(RelativeSide.TOP).also { it.block(block) } - fun bottom(block: Block) = relative(RelativeSide.BOTTOM).also { it.block(block) } - - fun front(block: BlockState) = relative(RelativeSide.FRONT).also { it.block(block) } - fun back(block: BlockState) = relative(RelativeSide.BACK).also { it.block(block) } - fun left(block: BlockState) = relative(RelativeSide.LEFT).also { it.block(block) } - fun right(block: BlockState) = relative(RelativeSide.RIGHT).also { it.block(block) } - fun top(block: BlockState) = relative(RelativeSide.TOP).also { it.block(block) } - fun bottom(block: BlockState) = relative(RelativeSide.BOTTOM).also { it.block(block) } - - fun front(predicate: BlockPredicate) = relative(RelativeSide.FRONT).also { it.predicate(predicate) } - fun back(predicate: BlockPredicate) = relative(RelativeSide.BACK).also { it.predicate(predicate) } - fun left(predicate: BlockPredicate) = relative(RelativeSide.LEFT).also { it.predicate(predicate) } - fun right(predicate: BlockPredicate) = relative(RelativeSide.RIGHT).also { it.predicate(predicate) } - fun top(predicate: BlockPredicate) = relative(RelativeSide.TOP).also { it.predicate(predicate) } - fun bottom(predicate: BlockPredicate) = relative(RelativeSide.BOTTOM).also { it.predicate(predicate) } - - fun front(configurator: Node.() -> Unit) = relative(RelativeSide.FRONT).also(configurator) - fun back(configurator: Node.() -> Unit) = relative(RelativeSide.BACK).also(configurator) - fun left(configurator: Node.() -> Unit) = relative(RelativeSide.LEFT).also(configurator) - fun right(configurator: Node.() -> Unit) = relative(RelativeSide.RIGHT).also(configurator) - fun top(configurator: Node.() -> Unit) = relative(RelativeSide.TOP).also(configurator) - fun bottom(configurator: Node.() -> Unit) = relative(RelativeSide.BOTTOM).also(configurator) - - fun front(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.FRONT).also { it.block(block) }.also(configurator) - fun back(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.BACK).also { it.block(block) }.also(configurator) - fun left(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.LEFT).also { it.block(block) }.also(configurator) - fun right(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.RIGHT).also { it.block(block) }.also(configurator) - fun top(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.TOP).also { it.block(block) }.also(configurator) - fun bottom(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.BOTTOM).also { it.block(block) }.also(configurator) - - fun front(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.FRONT).also { it.block(block) }.also(configurator) - fun back(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.BACK).also { it.block(block) }.also(configurator) - fun left(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.LEFT).also { it.block(block) }.also(configurator) - fun right(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.RIGHT).also { it.block(block) }.also(configurator) - fun top(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.TOP).also { it.block(block) }.also(configurator) - fun bottom(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.BOTTOM).also { it.block(block) }.also(configurator) - - fun front(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.FRONT).also { it.predicate(predicate) }.also(configurator) - fun back(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.BACK).also { it.predicate(predicate) }.also(configurator) - fun left(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.LEFT).also { it.predicate(predicate) }.also(configurator) - fun right(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.RIGHT).also { it.predicate(predicate) }.also(configurator) - fun top(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.TOP).also { it.predicate(predicate) }.also(configurator) - fun bottom(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.BOTTOM).also { it.predicate(predicate) }.also(configurator) - - fun build(): MultiblockFactory.Part { - return build(pos) - } - - override var strategy: Strategy = Strategy.OR - } - - /** - * Creates new (or returns existing one) node at specified position and returns it - */ - fun node(at: BlockPos): Node { - return nodes[at] ?: Node(at) - } - - /** - * Returns node which represents multiblock "root" position (relative position of 0 0 0) - */ - fun root() = node(BlockPos.ZERO) - - /** - * Returns node which represents multiblock "root" position (relative position of 0 0 0), and configures it using provided [configurator] expression - */ - fun root(configurator: Node.() -> Unit) = node(BlockPos.ZERO).also(configurator) - - /** - * Makes a deep copy of this [MultiblockBuilder], which can be modified independently of this builder - */ - fun copy(): MultiblockBuilder { - val copied = MultiblockBuilder() - - for ((k, v) in nodes) { - val node = copied.Node(k) - copied.nodes[k] = node - node.predicates.addAll(v.predicates) - } - - return copied - } - - /** - * Creates [MultiblockFactory] out of this [MultiblockBuilder]. - * - * Created factory does not share reference(s) to this builder, and this builder can be mutated further without consequences. - */ - fun build(): MultiblockFactory { - return MultiblockFactory(nodes.values.iterator().map { it.build() }.collect(ImmutableSet.toImmutableSet()), ImmutableList.copyOf(customChecks)) - } -} - -class MultiblockFactory(val north: ImmutableSet, val customChecks: ImmutableList>) { - data class Part( - val pos: BlockPos, - val strategy: MultiblockBuilder.Strategy, - val predicate: ImmutableList, - val children: ImmutableList, - val blockStateTags: ImmutableSet, - val blockTags: ImmutableSet, - val blockEntityTags: ImmutableSet>, - ) - - /** - * Bakes multiblock configuration with specified [pos] as multiblock's root position - */ - fun create(pos: BlockPos): Multiblock { - return Multiblock(pos, this) - } - - val south: ImmutableSet = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.CLOCKWISE_180)) }.collect(ImmutableSet.toImmutableSet()) - val west: ImmutableSet = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.COUNTERCLOCKWISE_90)) }.collect(ImmutableSet.toImmutableSet()) - val east: ImmutableSet = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.CLOCKWISE_90)) }.collect(ImmutableSet.toImmutableSet()) -} - -class Multiblock(pos: BlockPos, factory: MultiblockFactory) { - var isValid = false - private set - - private val customChecks = factory.customChecks - private var collectFailedParts = false - private val north by lazy(LazyThreadSafetyMode.NONE) { Config(Direction.NORTH, pos, factory.north) } - private val south by lazy(LazyThreadSafetyMode.NONE) { Config(Direction.SOUTH, pos, factory.south) } - private val west by lazy(LazyThreadSafetyMode.NONE) { Config(Direction.WEST, pos, factory.west) } - private val east by lazy(LazyThreadSafetyMode.NONE) { Config(Direction.EAST, pos, factory.east) } - private val configurations = SupplierList(::north, ::south, ::west, ::east) - - private inner class BEList(val tag: MultiblockBuilder.EntityTag) { - val list = ArrayList() - val set = ObjectArraySet() - val setView: Set = ObjectSets.unmodifiable(set) - - fun add(blockEntity: BlockEntity) { - if (tag.test(blockEntity)) { - if (set.add(blockEntity as T)) { - blockEntityListeners - .computeIfAbsent(blockEntity.level) { WeakHashMap() } - .computeIfAbsent(blockEntity) { WeakHashSet() } - .add(this@Multiblock) - } - - list.add(blockEntity) - } - } - - fun remove(blockEntity: BlockEntity) { - if (tag.test(blockEntity)) { - check(list.remove(blockEntity as T)) { "Unable to remove $blockEntity from tag $tag" } - - if (blockEntity !in list) { - set.remove(blockEntity) - - val getSet = blockEntityListeners[blockEntity.level]?.get(blockEntity) - - if (getSet != null) { - getSet.remove(this@Multiblock) - - if (getSet.isEmpty()) { - blockEntityListeners[blockEntity.level]?.remove(blockEntity) - } - } - } - } - } - - fun blockEntityRemoved(blockEntity: BlockEntity): Boolean { - if (blockEntity in list) { - while (list.remove(blockEntity)) {} - set.remove(blockEntity) - - return true - } - - return false - } - - fun clear() { - set.forEach { - val getSet = checkNotNull(blockEntityListeners[it.level]) { "Consistency check failed: No subscriber lists for level ${it.level}" }.get(it) - checkNotNull(getSet) { "Consistency check failed: No subscriber list for $it" } - check(getSet.remove(this@Multiblock)) { "Consistency check failed: Can't remove ${this@Multiblock} from $it subscriber list" } - - if (getSet.isEmpty()) { - blockEntityListeners[it.level]?.remove(it) - } - } - - list.clear() - set.clear() - } - } - - private inner class Config(val direction: Direction, val pos: BlockPos, parts: Collection) { - private inner class Part(val pos: BlockPos, val prototype: MultiblockFactory.Part) : Comparable { - private var blockEntity: BlockEntity? = null - private var blockState: BlockState? = null - 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.stream().map { Part(pos, it) }.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)) - } - - init { - prototype.blockEntityTags.forEach { - assignedBlockEntityLists.add(getBlockEntityList(it)) - } - - prototype.blockStateTags.forEach { - assignedBlockStateLists.add(getBlockStateList(it)) - } - - prototype.blockTags.forEach { - assignedBlockLists.add(getBlockList(it)) - } - } - - private var lastSuccessfulPathPredicate = -1 - private var lastSuccessfulPathChildren = -1 - - fun test(levelAccessor: LevelAccessor): Boolean { - val test = when (prototype.strategy) { - MultiblockBuilder.Strategy.OR -> { - var status = true - - if (prototype.predicate.isNotEmpty()) { - if (lastSuccessfulPathPredicate != -1 && !prototype.predicate[lastSuccessfulPathPredicate].test(pos, levelAccessor)) { - lastSuccessfulPathPredicate = -1 - } - - if (lastSuccessfulPathPredicate == -1) { - lastSuccessfulPathPredicate = prototype.predicate.indexOfFirst { it.test(pos, levelAccessor) } - status = lastSuccessfulPathPredicate != -1 - } - } - - if (status && children.isNotEmpty()) { - if (lastSuccessfulPathChildren != -1 && !children[lastSuccessfulPathChildren].test(levelAccessor)) { - lastSuccessfulPathChildren = -1 - } - - if (lastSuccessfulPathChildren == -1) { - lastSuccessfulPathChildren = children.indexOfFirst { it.test(levelAccessor) } - status = lastSuccessfulPathChildren != -1 - } - } - - status - } - - MultiblockBuilder.Strategy.AND -> prototype.predicate.all { it.test(pos, levelAccessor) } && children.all { it.test(levelAccessor) } - } - - if (test) { - if (assignedBlockEntityLists.isNotEmpty()) { - val be1 = blockEntity - val be2 = levelAccessor.getBlockEntity(pos) - - if (be1 != be2) { - if (be1 != null) { - assignedBlockEntityLists.forEach { it.remove(be1) } - } - - if (be2 != null) { - assignedBlockEntityLists.forEach { it.add(be2) } - } - - blockEntity = be2 - } - } - - if (assignedBlockStateLists.isNotEmpty() || assignedBlockLists.isNotEmpty()) { - val state = levelAccessor.getBlockState(pos) - val old = blockState - - if (state != old) { - if (old != null) { - assignedBlockStateLists.forEach { - it[old] = it.getInt(old) - 1 - check(it.getInt(old) >= 0) { "Consistency check failed: Counter for block state $old turned negative" } - } - - assignedBlockLists.forEach { - it[old.block] = it.getInt(old.block) - 1 - check(it.getInt(old.block) >= 0) { "Consistency check failed: Counter for block ${old.block.registryName} turned negative" } - } - } - - assignedBlockStateLists.forEach { - it[state] = it.getInt(state) + 1 - } - - assignedBlockLists.forEach { - it[state.block] = it.getInt(state.block) + 1 - } - - blockState = state - } - } - } else { - clearFull() - } - - return test - } - - private fun clearFull() { - val blockEntity = blockEntity - - if (blockEntity != null) { - assignedBlockEntityLists.forEach { it.remove(blockEntity) } - } - - val blockState = blockState - - if (blockState != null) { - assignedBlockStateLists.forEach { - it[blockState] = it.getInt(blockState) - 1 - check(it.getInt(blockState) >= 0) { "Consistency check failed: Counter for block state $blockState turned negative" } - } - - assignedBlockLists.forEach { - it[blockState.block] = it.getInt(blockState.block) - 1 - check(it.getInt(blockState.block) >= 0) { "Consistency check failed: Counter for block ${blockState.block.registryName} turned negative" } - } - } - - this.blockEntity = null - this.blockState = null - - lastSuccessfulPathPredicate = -1 - lastSuccessfulPathChildren = -1 - - // avoid allocating iterator when empty - if (children.isNotEmpty()) - children.forEach { it.clearFull() } - } - - fun clear() { - blockEntity = null - blockState = null - - lastSuccessfulPathPredicate = -1 - lastSuccessfulPathChildren = -1 - - children.forEach { it.clear() } - } - } - - private fun getBlockEntityList(tag: MultiblockBuilder.EntityTag): BEList { - val existing = tag2BlockEntity[tag] - - if (existing != null) - return existing as BEList - - val new = BEList(tag) - blockEntityLists.add(new) - tag2BlockEntity[tag] = new - return new - } - - private fun getBlockList(tag: Any): Reference2IntMap { - val existing = tag2Block[tag] - - if (existing != null) - return existing - - val new = Reference2IntOpenHashMap() - tag2Block[tag] = new - tag2BlockViews[tag] = Reference2IntMaps.unmodifiable(new) - blockLists.add(new) - return new - } - - private fun getBlockStateList(tag: Any): Reference2IntMap { - val existing = tag2BlockState[tag] - - if (existing != null) - return existing - - val new = Reference2IntOpenHashMap() - tag2BlockState[tag] = new - tag2BlockStateViews[tag] = Reference2IntMaps.unmodifiable(new) - blockStateLists.add(new) - return new - } - - private val tag2BlockEntity = HashMap, BEList<*>>() - private val tag2BlockState = HashMap>() - private val tag2Block = HashMap>() - - private val tag2BlockStateViews = HashMap>() - private val tag2BlockViews = HashMap>() - - private val blockEntityLists = ArrayList>() - private val blockStateLists = ArrayList>() - private val blockLists = ArrayList>() - - private val globalBlocks = getBlockList(GLOBAL_BLOCK_TAG) - private val globalBlockStates = getBlockStateList(GLOBAL_BLOCK_TAG) - private val globalBlocksView = tag2BlockViews[GLOBAL_BLOCK_TAG]!! - private val globalBlockStatesView = tag2BlockStateViews[GLOBAL_BLOCK_TAG]!! - - private val parts: ImmutableList = parts.stream() - .map { Part(it.pos + pos, it) } - .sorted() // group/localize parts by in-world chunks to maximize getChunk() cache hit rate - .collect(ImmutableList.toImmutableList()) - - fun clear() { - blockEntityLists.forEach { it.clear() } - blockStateLists.forEach { it.clear() } - blockLists.forEach { it.clear() } - parts.forEach { it.clear() } - } - - fun update(levelAccessor: LevelAccessor): Boolean { - var valid = true - - for (part in parts) { - valid = part.test(levelAccessor) - if (!valid) break - } - - if (!valid) - clear() - - return valid - } - - fun blockEntities(tag: MultiblockBuilder.EntityTag): Set { - return (tag2BlockEntity[tag]?.setView ?: setOf()) as Set - } - - fun blocks(tag: Any): Reference2IntMap { - return tag2BlockViews[tag] ?: Reference2IntMaps.emptyMap() - } - - fun blockStates(tag: Any): Reference2IntMap { - return tag2BlockStateViews[tag] ?: Reference2IntMaps.emptyMap() - } - - fun blocks(): Reference2IntMap { - return globalBlocksView - } - - fun blockStates(): Reference2IntMap { - return globalBlockStatesView - } - - fun blockEntityRemoved(blockEntity: BlockEntity): Boolean { - var any = false - - blockEntityLists.forEach { - any = it.blockEntityRemoved(blockEntity) || any - } - - return any - } - } - - private var activeConfig: Config? = null - - val currentDirection: Direction? - get() = activeConfig?.direction - - fun blockEntities(tag: MultiblockBuilder.EntityTag): Set { - if (!isValid) return emptySet() - return activeConfig?.blockEntities(tag) ?: setOf() - } - - /** - * Returns block counts present on nodes tagged by [tag] - */ - fun blocks(tag: Any): Reference2IntMap { - if (!isValid) return Reference2IntMaps.emptyMap() - return activeConfig?.blocks(tag) ?: Reference2IntMaps.emptyMap() - } - - /** - * Returns blockstate counts present on nodes tagged by [tag] - */ - fun blockStates(tag: Any): Reference2IntMap { - if (!isValid) return Reference2IntMaps.emptyMap() - return activeConfig?.blockStates(tag) ?: Reference2IntMaps.emptyMap() - } - - /** - * Returns block counts present on nodes tagged by [MultiblockBuilder.Part.tagBlock] without arguments - */ - fun blocks(): Reference2IntMap { - if (!isValid) return Reference2IntMaps.emptyMap() - return activeConfig?.blocks() ?: Reference2IntMaps.emptyMap() - } - - /** - * Returns blockstate counts present on nodes tagged by [MultiblockBuilder.Part.tagBlock] without arguments - */ - fun blockStates(): Reference2IntMap { - if (!isValid) return Reference2IntMaps.emptyMap() - return activeConfig?.blockStates() ?: Reference2IntMaps.emptyMap() - } - - fun blockEntityRemoved(blockEntity: BlockEntity) { - activeConfig?.blockEntityRemoved(blockEntity) - } - - fun update(levelAccessor: LevelAccessor): Boolean { - val activeConfig = activeConfig - - if (activeConfig != null && activeConfig.update(levelAccessor) && customChecks.all { it.test(this) }) { - return true - } else if (activeConfig != null) { - for (config in configurations) { - if (config !== activeConfig && config.update(levelAccessor)) { - this.activeConfig = config - - if (customChecks.all { it.test(this) }) - return true - else { - config.clear() - this.activeConfig = null - } - } - } - - this.activeConfig = null - this.isValid = false - return false - } else { - for (config in configurations) { - if (config.update(levelAccessor)) { - this.activeConfig = config - - if (customChecks.all { it.test(this) }) { - this.isValid = true - return true - } else { - config.clear() - this.activeConfig = null - } - } - } - - return false - } - } - - fun update(levelAccessor: LevelAccessor, direction: Direction): Boolean { - if (activeConfig?.direction != direction) { - activeConfig?.clear() - activeConfig = null - } - - val config = when (direction) { - Direction.NORTH -> north - Direction.SOUTH -> south - Direction.WEST -> west - Direction.EAST -> east - else -> throw IllegalArgumentException(direction.name) - } - - activeConfig = config - isValid = config.update(levelAccessor) && customChecks.all { it.test(this) } - - if (!isValid) { - config.clear() - activeConfig = null - } - - return isValid - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/AbstractCombinedHistoryChart.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/AbstractCombinedHistoryChart.kt new file mode 100644 index 000000000..75039e9bf --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/AbstractCombinedHistoryChart.kt @@ -0,0 +1,93 @@ +package ru.dbotthepony.mc.otm.core.chart + +import it.unimi.dsi.fastutil.objects.ObjectIterators +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import net.minecraft.network.RegistryFriendlyByteBuf +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.toList +import ru.dbotthepony.mc.otm.core.util.ITickable +import ru.dbotthepony.mc.otm.network.syncher.DynamicSynchableGroup +import ru.dbotthepony.mc.otm.network.syncher.IRemoteState +import ru.dbotthepony.mc.otm.network.syncher.ISynchable +import java.util.function.Supplier + +abstract class AbstractCombinedHistoryChart>( + protected val provider: Supplier>, + final override val width: Int, + final override val resolution: Int +) : IHistoryChart, ISynchable, ITickable { + protected abstract fun factory(stream: RegistryFriendlyByteBuf): CHART + protected abstract fun networkMetadata(stream: RegistryFriendlyByteBuf, chart: CHART) + private val group = DynamicSynchableGroup(::factory) { networkMetadata(it, this) } + protected abstract fun sum(values: List): V + + override val isRemote: Boolean + get() = group.isRemote + override val hasRemotes: Boolean + get() = group.hasRemotes + + override fun tick() { + if (group.hasRemotes) { + val current = ObjectOpenHashSet(provider.get()) + group.retainAll(current) + + for (v in current) { + if (v !in group) { + group.add(v) + } + } + } else { + group.clear() + } + } + + final override fun iterator(): Iterator { + val iterators = if (group.isRemote) + group.iterator().map { it.iterator() }.toList() + else + provider.get() + .map { it.iterator() } + .filter { it.hasNext() } + .toList() + + if (iterators.isEmpty()) { + return ObjectIterators.emptyIterator() + } else { + val buffer = ArrayList(iterators.size) + + return object : Iterator { + override fun hasNext(): Boolean { + return iterators.isNotEmpty() + } + + override fun next(): V { + buffer.clear() + + iterators.removeIf { + buffer.add(it.next()) + !it.hasNext() + } + + return sum(buffer) + } + } + } + } + + final override fun get(index: Int): V { + if (group.isRemote) { + return sum(group.map { it[index] }) + } else { + return sum(provider.get().map { it[index] }.toList()) + } + } + + final override fun read(stream: RegistryFriendlyByteBuf) { + group.read(stream) + } + + final override fun createRemoteState(listener: Runnable): IRemoteState { + return group.createRemoteState(listener) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/AbstractHistoryChart.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/AbstractHistoryChart.kt similarity index 86% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/AbstractHistoryChart.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/AbstractHistoryChart.kt index ad7547c0d..65a515e12 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/AbstractHistoryChart.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/AbstractHistoryChart.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.chart import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder @@ -14,16 +14,9 @@ import ru.dbotthepony.mc.otm.network.syncher.ISynchable import java.util.concurrent.CopyOnWriteArrayList abstract class AbstractHistoryChart( - /** - * How many measurements one graph value contains - */ - val resolution: Int, - - /** - * Limit on how many graph values to store - */ - val width: Int, -) : Iterable, INBTSerializable, ISynchable { + final override val resolution: Int, + final override val width: Int, +) : IHistoryChart, ISynchable, INBTSerializable { constructor(ticks: Int) : this(1, ticks) init { @@ -39,13 +32,13 @@ abstract class AbstractHistoryChart( protected abstract fun sum(input: List): V protected abstract fun identity(): V - fun calculateAverage(width: Int = this.width): V { + fun calculateAverage(width: Int): V { require(width >= 0) { "Invalid time frame: $width" } if (width == 0 || this.values.isEmpty()) return identity() - return calculateAverage(this.values.subList(0, width.coerceAtMost(this.values.size))) + return calculateAverage(values.subList(0, width.coerceAtMost(values.size))) } fun calculateSum(width: Int = this.width): V { @@ -61,6 +54,11 @@ abstract class AbstractHistoryChart( abstract val streamCodec: MatteryStreamCodec private val remotes = CopyOnWriteArrayList() + final override val hasRemotes: Boolean + get() = remotes.isNotEmpty() + + final override var isRemote: Boolean = false + private set private inner class RemoteState(val listener: Runnable) : IRemoteState { init { @@ -98,7 +96,9 @@ abstract class AbstractHistoryChart( } } - override fun read(stream: RegistryFriendlyByteBuf) { + final override fun read(stream: RegistryFriendlyByteBuf) { + isRemote = true + if (stream.readBoolean()) { values.clear() } @@ -110,11 +110,11 @@ abstract class AbstractHistoryChart( while (values.size > width) values.removeLast() } - override fun createRemoteState(listener: Runnable): IRemoteState { + final override fun createRemoteState(listener: Runnable): IRemoteState { return RemoteState(listener) } - fun calculateAverage(): V { + final override fun calculateAverage(): V { if (values.isEmpty()) { return identity() } @@ -143,7 +143,7 @@ abstract class AbstractHistoryChart( val size: Int get() = values.size - operator fun get(index: Int): V { + final override operator fun get(index: Int): V { return values.getOrNull(index) ?: identity() } @@ -158,7 +158,7 @@ abstract class AbstractHistoryChart( it.group( list.fieldOf("accumulator").forGetter { it.accumulated }, list.fieldOf("values").forGetter { it.values }, - ).apply(it, ::SData) + ).apply(it) { a, b -> SData(a, b) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/CombinedDecimalHistoryChart.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/CombinedDecimalHistoryChart.kt new file mode 100644 index 000000000..e1f21760e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/CombinedDecimalHistoryChart.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.core.chart + +import net.minecraft.network.RegistryFriendlyByteBuf +import ru.dbotthepony.mc.otm.core.collect.reduce +import ru.dbotthepony.mc.otm.core.math.Decimal +import java.util.function.Supplier + +class CombinedDecimalHistoryChart( + provider: Supplier>, + resolution: Int, + width: Int, +) : AbstractCombinedHistoryChart(provider, width, resolution) { + constructor(provider: Supplier>, ticks: Int) : this(provider, 1, ticks) + + override fun sum(values: List): Decimal { + return values.iterator().reduce(Decimal.ZERO, Decimal::plus) + } + + override fun calculateAverage(): Decimal { + return this.iterator().reduce(Decimal.ZERO, Decimal::plus) / Decimal(width) + } + + override fun factory(stream: RegistryFriendlyByteBuf): DecimalHistoryChart { + return DecimalHistoryChart(stream.readVarInt(), stream.readVarInt()) + } + + override fun networkMetadata(stream: RegistryFriendlyByteBuf, chart: DecimalHistoryChart) { + stream.writeVarInt(chart.resolution) + stream.writeVarInt(chart.width) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/DecimalHistoryChart.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/DecimalHistoryChart.kt similarity index 92% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/DecimalHistoryChart.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/DecimalHistoryChart.kt index 371b232fb..afa628c2c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/DecimalHistoryChart.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/DecimalHistoryChart.kt @@ -1,9 +1,10 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.chart import com.google.common.collect.ImmutableList import com.mojang.serialization.Codec import net.minecraft.network.RegistryFriendlyByteBuf import ru.dbotthepony.mc.otm.core.collect.reduce +import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.data.DecimalCodec import ru.dbotthepony.mc.otm.network.MatteryStreamCodec @@ -33,10 +34,6 @@ class DecimalHistoryChart : AbstractHistoryChart { override val streamCodec: MatteryStreamCodec get() = DecimalCodec.NETWORK - fun calcWeightedAverage(): Decimal { - return calcWeightedAverage(this::get) - } - companion object { private val HISTORY_WEIGHTERS: ImmutableList = immutableList { /*for (i in 0 until 20) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/IHistoryChart.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/IHistoryChart.kt new file mode 100644 index 000000000..3a97ca45e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/chart/IHistoryChart.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.mc.otm.core.chart + +/** + * Common interface for reading chart values + */ +interface IHistoryChart : Iterable { + /** + * Limit on how many graph values to store + */ + val width: Int + + /** + * How many measurements one graph value contains + */ + val resolution: Int + + operator fun get(index: Int): V + + fun calculateAverage(): V +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/BlockEntitySet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/BlockEntitySet.kt new file mode 100644 index 000000000..fb203ad2c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/BlockEntitySet.kt @@ -0,0 +1,46 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap +import net.minecraft.world.level.block.entity.BlockEntity +import java.util.Collections + +class BlockEntitySet(private val listener: GlobalBlockEntityRemovalListener, val tag: BlockEntityTag) { + private val items = Reference2IntOpenHashMap() + val set: Set = Collections.unmodifiableSet(items.keys) + + fun add(blockEntity: BlockEntity) { + if (tag.test(blockEntity)) { + val new = items.getInt(blockEntity) + 1 + + if (new == 1) + GlobalBlockEntityRemovalListener.listen(blockEntity, listener) + + items.put(blockEntity as T, new) + } + } + + fun remove(blockEntity: BlockEntity) { + if (tag.test(blockEntity)) { + val existing = items.getInt(blockEntity) + + if (existing == 1) { + GlobalBlockEntityRemovalListener.stopListening(blockEntity, listener) + items.removeInt(blockEntity) + } else if (existing > 1) { + items.put(blockEntity as T, existing - 1) + } + } + } + + fun blockEntityRemoved(blockEntity: BlockEntity): Boolean { + return items.removeInt(blockEntity as T) != 0 + } + + fun clear() { + items.keys.forEach { + GlobalBlockEntityRemovalListener.stopListening(it, listener) + } + + items.clear() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/BlockEntityTag.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/BlockEntityTag.kt new file mode 100644 index 000000000..39f1a0f47 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/BlockEntityTag.kt @@ -0,0 +1,21 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +import net.minecraft.world.level.block.entity.BlockEntity +import java.util.function.Predicate +import kotlin.reflect.KClass + +inline fun multiblockEntity(): BlockEntityTag { + return BlockEntityTag(T::class) +} + +/** + * Tags specific [T] block (or any subclassed one) entities to be exposed through [ShapedMultiblock.blockEntities] when specific [Part]s are tagged using [Part.tag] + * + * Optionally, can have [predicate] specified, which narrows which block entities can be accepted by this tag + */ +class BlockEntityTag(val clazz: KClass, val predicate: Predicate = Predicate { true }) : + Predicate { + override fun test(t: BlockEntity): Boolean { + return clazz.isInstance(t) && predicate.test(t as T) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/BlockPredicate.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/BlockPredicate.kt new file mode 100644 index 000000000..5aa8268a0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/BlockPredicate.kt @@ -0,0 +1,83 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +import com.google.common.collect.ImmutableSet +import net.minecraft.core.BlockPos +import net.minecraft.core.Vec3i +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: Lazy, blockEntity: Lazy): Boolean + + fun rotate(rotation: Rotation): BlockPredicate { + return this + } + + fun and(other: BlockPredicate): BlockPredicate { + return BlockPredicate { pos, access, blockState, blockEntity -> test(pos, access, blockState, blockEntity) && other.test(pos, access, blockState, blockEntity) } + } + + fun or(other: BlockPredicate): BlockPredicate { + return BlockPredicate { pos, access, blockState, blockEntity -> test(pos, access, blockState, blockEntity) || other.test(pos, access, blockState, blockEntity) } + } + + fun and(other: BlockPredicate, vararg others: BlockPredicate): BlockPredicate { + return And(ImmutableSet.copyOf(listOf(other, *others))) + } + + fun or(other: BlockPredicate, vararg others: BlockPredicate): BlockPredicate { + return Or(ImmutableSet.copyOf(listOf(other, *others))) + } + + fun offset(offset: BlockPos): Positioned { + return Positioned(offset, this) + } + + fun offset(offset: Vec3i): Positioned { + return offset(BlockPos(offset)) + } + + data class Positioned(val offset: BlockPos, val parent: BlockPredicate) : BlockPredicate { + override fun test(pos: BlockPos, access: LevelAccessor, blockState: Lazy, blockEntity: Lazy): Boolean { + return parent.test(offset + pos, access, blockState, blockEntity) + } + + override fun offset(offset: BlockPos): Positioned { + return Positioned(this.offset + offset, parent) + } + } + + data class And(val nodes: ImmutableSet) : BlockPredicate { + constructor(vararg nodes: BlockPredicate) : this(ImmutableSet.copyOf(nodes)) + constructor(nodes: Set) : this(ImmutableSet.copyOf(nodes)) + + override fun test(pos: BlockPos, access: LevelAccessor, blockState: Lazy, blockEntity: Lazy): Boolean { + return nodes.all { it.test(pos, access, blockState, blockEntity) } + } + } + + data class Or(val nodes: ImmutableSet) : BlockPredicate { + constructor(vararg nodes: BlockPredicate) : this(ImmutableSet.copyOf(nodes)) + constructor(nodes: Set) : this(ImmutableSet.copyOf(nodes)) + + override fun test(pos: BlockPos, access: LevelAccessor, blockState: Lazy, blockEntity: Lazy): Boolean { + return nodes.any { it.test(pos, access, blockState, blockEntity) } + } + } + + object Air : BlockPredicate { + override fun test(pos: BlockPos, access: LevelAccessor, blockState: Lazy, blockEntity: Lazy): Boolean { + return blockState.value.isAir + } + } + + object NotAir : BlockPredicate { + override fun test(pos: BlockPos, access: LevelAccessor, blockState: Lazy, blockEntity: Lazy): Boolean { + return !blockState.value.isAir + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/GlobalBlockEntityRemovalListener.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/GlobalBlockEntityRemovalListener.kt new file mode 100644 index 000000000..4d4b2af79 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/GlobalBlockEntityRemovalListener.kt @@ -0,0 +1,39 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.entity.BlockEntity +import ru.dbotthepony.mc.otm.core.collect.WeakHashSet +import java.util.* + +interface GlobalBlockEntityRemovalListener { + fun blockEntityRemoved(blockEntity: BlockEntity): Boolean + + companion object { + private val BLOCK_ENTITY_LISTENERS = WeakHashMap>>() + + fun onBlockEntityInvalidated(blockEntity: BlockEntity) { + BLOCK_ENTITY_LISTENERS[blockEntity.level]?.get(blockEntity)?.forEach { it.blockEntityRemoved(blockEntity) } + BLOCK_ENTITY_LISTENERS[blockEntity.level]?.remove(blockEntity) + } + + fun listen(blockEntity: BlockEntity, listener: GlobalBlockEntityRemovalListener) { + BLOCK_ENTITY_LISTENERS + .computeIfAbsent(blockEntity.level) { WeakHashMap() } + .computeIfAbsent(blockEntity) { WeakHashSet() } + .add(listener) + } + + fun stopListening(blockEntity: BlockEntity, listener: GlobalBlockEntityRemovalListener) { + val levelMap = BLOCK_ENTITY_LISTENERS[blockEntity.level] ?: return + val getSet = levelMap[blockEntity] + + if (getSet != null) { + getSet.remove(listener) + + if (getSet.isEmpty()) { + levelMap.remove(blockEntity) + } + } + } + } +} 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 new file mode 100644 index 000000000..3a96f6ecf --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/IMultiblockAccess.kt @@ -0,0 +1,50 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +import it.unimi.dsi.fastutil.objects.Reference2IntMap +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.state.BlockState +import ru.dbotthepony.mc.otm.core.collect.WeakHashSet +import java.util.* + +interface IMultiblockAccess { + /** + * Whenever this multiblock is valid (all checks passed) + */ + val isValid: Boolean + val currentDirection: Direction? + val currentNodes: Map + + /** + * Returns block counts present on nodes tagged by [tag] + */ + fun blocks(tag: Any): Reference2IntMap + + /** + * Returns blockstate counts present on nodes tagged by [tag] + */ + fun blockStates(tag: Any): Reference2IntMap + + /** + * Returns block counts present on nodes tagged by [GLOBAL_BLOCK_TAG] + */ + fun blocks(): Reference2IntMap { + return blocks(GLOBAL_BLOCK_TAG) + } + + /** + * Returns blockstate counts present on nodes tagged by [GLOBAL_BLOCK_TAG] + */ + fun blockStates(): Reference2IntMap { + return blockStates(GLOBAL_BLOCK_TAG) + } + + fun blockEntities(tag: BlockEntityTag): Set + + companion object { + val GLOBAL_BLOCK_TAG = Any() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/IMultiblockNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/IMultiblockNode.kt new file mode 100644 index 000000000..68931119e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/IMultiblockNode.kt @@ -0,0 +1,8 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +import net.minecraft.core.BlockPos + +interface IMultiblockNode { + val pos: BlockPos + val status: NodeStatus +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/NodeStatus.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/NodeStatus.kt new file mode 100644 index 000000000..0634d57c6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/NodeStatus.kt @@ -0,0 +1,7 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +enum class NodeStatus { + UNKNOWN, + VALID, + 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 new file mode 100644 index 000000000..36cd2f668 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblock.kt @@ -0,0 +1,627 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.objects.Reference2IntMap +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.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 ru.dbotthepony.mc.otm.core.addAll +import ru.dbotthepony.mc.otm.core.collect.collect +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.getBlockStateNow +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.network.syncher.IRemoteState +import ru.dbotthepony.mc.otm.network.syncher.ISynchable +import java.io.Closeable +import java.util.LinkedList +import java.util.concurrent.CopyOnWriteArrayList +import java.util.function.BooleanSupplier +import kotlin.collections.ArrayDeque +import kotlin.collections.ArrayList +import kotlin.collections.HashMap + +/** + * [close] is not required to be explicitly called, but it will help in freeing allocated memory faster + */ +class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMultiblockAccess, ISynchable, Closeable, GlobalBlockEntityRemovalListener { + override var isValid: Boolean = false + private set + + private val customChecks = factory.customChecks + private val north = Config(Direction.NORTH, pos, factory.north) + private val south = Config(Direction.SOUTH, pos, factory.south) + private val west = Config(Direction.WEST, pos, factory.west) + private val east = Config(Direction.EAST, pos, factory.east) + private val configurations = ImmutableList.of(north, south, west, east) + + override val hasRemotes: Boolean + 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 { + var index = -1 + + private var blockEntity: BlockEntity? = null + private var blockState: BlockState? = null + 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()) } + + 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)) + } + + init { + prototype.blockEntityTags.forEach { + assignedBlockEntityLists.add(getBlockEntityList(it)) + } + + prototype.blockStateTags.forEach { + assignedBlockStateLists.add(getBlockStateList(it)) + } + + prototype.blockTags.forEach { + assignedBlockLists.add(getBlockList(it)) + } + } + + private var lastSuccessfulPathPredicate = -1 + private var lastSuccessfulPathChildren = -1 + + override var status: NodeStatus = NodeStatus.UNKNOWN + private set + + fun read(stream: RegistryFriendlyByteBuf) { + status = NodeStatus.entries[stream.readUnsignedByte().toInt()] + } + + fun write(stream: RegistryFriendlyByteBuf) { + stream.writeByte(status.ordinal) + } + + private fun orPredicates(levelAccessor: LevelAccessor, blockState: Lazy, blockEntity: Lazy): Boolean { + if (prototype.predicate.isNotEmpty()) { + if (lastSuccessfulPathPredicate != -1 && !prototype.predicate[lastSuccessfulPathPredicate].test(pos, levelAccessor, blockState, blockEntity)) { + lastSuccessfulPathPredicate = -1 + } + + if (lastSuccessfulPathPredicate == -1) { + lastSuccessfulPathPredicate = prototype.predicate.indexOfFirst { it.test(pos, levelAccessor, blockState, blockEntity) } + return lastSuccessfulPathPredicate != -1 + } + } + + return true + } + + private fun orChildren(levelAccessor: LevelAccessor, blockState: Lazy, blockEntity: Lazy): Boolean { + if (children.isNotEmpty()) { + if (lastSuccessfulPathChildren != -1 && !children[lastSuccessfulPathChildren].test0(levelAccessor, blockState, blockEntity)) { + lastSuccessfulPathChildren = -1 + } + + if (lastSuccessfulPathChildren == -1) { + lastSuccessfulPathChildren = children.indexOfFirst { it.test0(levelAccessor, blockState, blockEntity) } + return lastSuccessfulPathChildren != -1 + } + } + + return true + } + + private fun test0(levelAccessor: LevelAccessor, blockState: Lazy, blockEntity: Lazy): Boolean { + val test = when (prototype.strategy) { + Strategy.OR_BOTH -> orPredicates(levelAccessor, blockState, blockEntity) && orChildren(levelAccessor, blockState, blockEntity) + + Strategy.OR_EITHER -> + prototype.predicate.isNotEmpty() && orPredicates(levelAccessor, blockState, blockEntity) || + children.isNotEmpty() && orChildren(levelAccessor, blockState, blockEntity) || + children.isEmpty() && prototype.predicate.isEmpty() + + Strategy.AND -> prototype.predicate.all { it.test(pos, levelAccessor, blockState, blockEntity) } && children.all { it.test0(levelAccessor, blockState, blockEntity) } + } + + if (test) { + if (assignedBlockEntityLists.isNotEmpty()) { + val be1 = this.blockEntity + val be2 = blockEntity.value + + if (be1 != be2) { + if (be1 != null) { + assignedBlockEntityLists.forEach { it.remove(be1) } + } + + if (be2 != null) { + assignedBlockEntityLists.forEach { it.add(be2) } + } + + this.blockEntity = be2 + } + } + + if (assignedBlockStateLists.isNotEmpty() || assignedBlockLists.isNotEmpty()) { + val state = blockState.value + val old = this.blockState + + if (state != old) { + if (old != null) { + assignedBlockStateLists.forEach { + it[old] = it.getInt(old) - 1 + check(it.getInt(old) >= 0) { "Consistency check failed: Counter for block state $old turned negative" } + } + + assignedBlockLists.forEach { + it[old.block] = it.getInt(old.block) - 1 + check(it.getInt(old.block) >= 0) { "Consistency check failed: Counter for block ${old.block.registryName} turned negative" } + } + } + + assignedBlockStateLists.forEach { + it[state] = it.getInt(state) + 1 + } + + assignedBlockLists.forEach { + it[state.block] = it.getInt(state.block) + 1 + } + + this.blockState = state + } + } + } else { + clearFull() + } + + return test + } + + fun test(levelAccessor: LevelAccessor): Boolean { + val blockEntity = lazy(LazyThreadSafetyMode.NONE) { levelAccessor.getBlockEntity(pos) } + val blockState = lazy(LazyThreadSafetyMode.NONE) { levelAccessor.getBlockStateNow(pos) } + val status = test0(levelAccessor, blockState, blockEntity) + + val previous = this.status + this.status = if (status) NodeStatus.VALID else NodeStatus.INVALID + + if (previous !== this.status && index != -1) { + pushNetworkPartUpdate(this) + } + + return status + } + + private fun clearFull() { + val blockEntity = blockEntity + + if (blockEntity != null) { + assignedBlockEntityLists.forEach { it.remove(blockEntity) } + } + + val blockState = blockState + + if (blockState != null) { + assignedBlockStateLists.forEach { + it[blockState] = it.getInt(blockState) - 1 + check(it.getInt(blockState) >= 0) { "Consistency check failed: Counter for block state $blockState turned negative" } + } + + assignedBlockLists.forEach { + it[blockState.block] = it.getInt(blockState.block) - 1 + check(it.getInt(blockState.block) >= 0) { "Consistency check failed: Counter for block ${blockState.block.registryName} turned negative" } + } + } + + this.blockEntity = null + this.blockState = null + + lastSuccessfulPathPredicate = -1 + lastSuccessfulPathChildren = -1 + + // avoid allocating iterator when empty + if (children.isNotEmpty()) + children.forEach { it.clearFull() } + } + + fun clear() { + blockEntity = null + blockState = null + + lastSuccessfulPathPredicate = -1 + lastSuccessfulPathChildren = -1 + + children.forEach { it.clear() } + } + } + + val index = when (currentDirection) { + Direction.NORTH -> 0 + Direction.SOUTH -> 1 + Direction.WEST -> 2 + Direction.EAST -> 3 + else -> throw RuntimeException() + } + + override val currentNodes: Map + get() = parts + + private val tag2BlockEntity = HashMap, BlockEntitySet<*>>() + private val tag2BlockState = HashMap>() + private val tag2Block = HashMap>() + + private val tag2BlockStateViews = HashMap>() + private val tag2BlockViews = HashMap>() + + init { + getBlockList(IMultiblockAccess.GLOBAL_BLOCK_TAG) + getBlockStateList(IMultiblockAccess.GLOBAL_BLOCK_TAG) + } + + private val globalBlocksView = tag2BlockViews[IMultiblockAccess.GLOBAL_BLOCK_TAG]!! + private val globalBlockStatesView = tag2BlockStateViews[IMultiblockAccess.GLOBAL_BLOCK_TAG]!! + + private fun getBlockEntityList(tag: BlockEntityTag): BlockEntitySet { + val existing = tag2BlockEntity[tag] + + if (existing != null) + return existing as BlockEntitySet + + val new = BlockEntitySet(this, tag) + tag2BlockEntity[tag] = new + return new + } + + private fun getBlockList(tag: Any): Reference2IntMap { + val existing = tag2Block[tag] + + if (existing != null) + return existing + + val new = Reference2IntOpenHashMap() + tag2Block[tag] = new + tag2BlockViews[tag] = Reference2IntMaps.unmodifiable(new) + return new + } + + private fun getBlockStateList(tag: Any): Reference2IntMap { + val existing = tag2BlockState[tag] + + if (existing != null) + return existing + + val new = Reference2IntOpenHashMap() + tag2BlockState[tag] = new + tag2BlockStateViews[tag] = Reference2IntMaps.unmodifiable(new) + return new + } + + val parts: ImmutableMap = parts.stream() + .map { Part(it.pos + pos, it) } + .sorted() // group/localize parts by in-world chunks to maximize getChunk() cache hit rate + .collect(ImmutableMap.toImmutableMap({ it.pos }, { it })) + + init { + for ((i, part) in this.parts.values.withIndex()) { + part.index = i + } + } + + fun clear() { + tag2BlockEntity.values.forEach { it.clear() } + tag2BlockState.values.forEach { it.clear() } + tag2Block.values.forEach { it.clear() } + parts.values.forEach { it.clear() } + } + + private val networkChangelog = ArrayDeque() + private var networkVersion = 0 + + private fun pushNetworkPartUpdate(part: Part) { + networkChangelog.addFirst(part) + networkVersion++ + while (networkChangelog.size > parts.size) networkChangelog.removeLast() + } + + override var isValid: Boolean = false + private set + + private var iterator = this.parts.values.iterator() + private var validParts = 0 + + override fun compareTo(other: Config): Int { + var cmp = validParts.compareTo(other.validParts) + if (cmp == 0) cmp = currentDirection.compareTo(other.currentDirection) + return cmp + } + + fun updateRemaining(levelAccessor: LevelAccessor) { + val networkVersion = networkVersion + + if (!isValid) { // update rest + for (part in iterator) { + if (part.test(levelAccessor)) { + validParts++ + } + } + } else { + for (part in iterator) { + if (part.test(levelAccessor)) + validParts++ + else { + isValid = false + break + } + } + } + + 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 + } + + override fun blockEntities(tag: BlockEntityTag): Set { + return (tag2BlockEntity[tag]?.set ?: setOf()) as Set + } + + override fun blocks(tag: Any): Reference2IntMap { + return tag2BlockViews[tag] ?: Reference2IntMaps.emptyMap() + } + + override fun blockStates(tag: Any): Reference2IntMap { + return tag2BlockStateViews[tag] ?: Reference2IntMaps.emptyMap() + } + + override fun blocks(): Reference2IntMap { + return globalBlocksView + } + + override fun blockStates(): Reference2IntMap { + return globalBlockStatesView + } + + override fun blockEntityRemoved(blockEntity: BlockEntity): Boolean { + var any = false + tag2BlockEntity.values.forEach { any = it.blockEntityRemoved(blockEntity) || any } + return any + } + + private val remotes = CopyOnWriteArrayList() + + override val hasRemotes: Boolean + get() = remotes.isNotEmpty() + + override var isRemote: Boolean = false + private set + + override fun read(stream: RegistryFriendlyByteBuf) { + isRemote = true + + if (stream.readBoolean()) { + for (part in parts.values) { + part.read(stream) + } + } else { + var index = stream.readVarInt() + val list = parts.values.asList() + + while (index != 0) { + list[index - 1].read(stream) + index = stream.readVarInt() + } + } + } + + private inner class RemoteState(val listener: Runnable) : IRemoteState { + init { + remotes.add(this) + } + + private var version = -1 + + override fun write(stream: RegistryFriendlyByteBuf) { + if (version == -1 || networkVersion - version >= networkChangelog.size) { + stream.writeBoolean(true) + + for (part in parts.values) { + part.write(stream) + } + } else { + stream.writeBoolean(false) + val itr = networkChangelog.listIterator(networkVersion - version) + + while (itr.hasPrevious()) { + val part = itr.previous() + stream.writeVarInt(part.index + 1) + part.write(stream) + } + + stream.writeByte(0) + } + + version = networkVersion + } + + override fun invalidate() { + version = -1 + } + + override fun close() { + remotes.remove(this) + } + } + + override fun createRemoteState(listener: Runnable): IRemoteState { + return RemoteState(listener) + } + } + + override var isRemote: Boolean = false + private set + + override fun read(stream: RegistryFriendlyByteBuf) { + isRemote = true + isValid = stream.readBoolean() + activeConfig = configurations[stream.readUnsignedByte().toInt()] + activeConfig.read(stream) + } + + private val remotes = CopyOnWriteArrayList() + + private inner class RemoteState(val listener: Runnable) : IRemoteState { + private val remotes = configurations.map { it.createRemoteState(listener) } + + init { + this@ShapedMultiblock.remotes.add(this) + } + + override fun write(stream: RegistryFriendlyByteBuf) { + stream.writeBoolean(isValid) + stream.writeByte(activeConfig.index) + remotes[activeConfig.index].write(stream) + } + + override fun invalidate() { + remotes.forEach { it.invalidate() } + } + + override fun close() { + remotes.forEach { it.close() } + this@ShapedMultiblock.remotes.remove(this) + } + } + + override fun createRemoteState(listener: Runnable): IRemoteState { + return RemoteState(listener) + } + + private var activeConfig: Config = north + + override val currentDirection: Direction? + get() = if (isValid) activeConfig.currentDirection else null + + override val currentNodes: Map + get() = activeConfig.parts + + override fun blockEntities(tag: BlockEntityTag): Set { + if (!isValid) return setOf() + return activeConfig.blockEntities(tag) + } + + override fun blocks(tag: Any): Reference2IntMap { + if (!isValid) return Reference2IntMaps.emptyMap() + return activeConfig.blocks(tag) + } + + override fun blockStates(tag: Any): Reference2IntMap { + if (!isValid) return Reference2IntMaps.emptyMap() + return activeConfig.blockStates(tag) + } + + override fun blocks(): Reference2IntMap { + if (!isValid) return Reference2IntMaps.emptyMap() + return activeConfig.blocks() + } + + override fun blockStates(): Reference2IntMap { + if (!isValid) return Reference2IntMaps.emptyMap() + return activeConfig.blockStates() + } + + override fun blockEntityRemoved(blockEntity: BlockEntity): Boolean { + return activeConfig.blockEntityRemoved(blockEntity) + } + + fun update(levelAccessor: LevelAccessor): Boolean { + val configurations = LinkedList() + val configurations0 = ArrayList(this.configurations) + + while (configurations0.isNotEmpty()) { + val max = configurations0.max() + configurations0.remove(max) + configurations.add(max) + } + + isValid = false + + while (configurations.isNotEmpty()) { + val config = configurations.removeFirst() + + if (config.update(levelAccessor)) { + if (customChecks.all { it.test(config) }) { + activeConfig = config + isValid = true + remotes.forEach { it.listener.run() } + return true + } else { + config.clear() + } + } + } + + return false + } + + fun update(levelAccessor: LevelAccessor, direction: Direction): Boolean { + var changes = false + + if (activeConfig.currentDirection != direction && isValid) { + activeConfig.clear() + isValid = false + changes = true + } + + val config = when (direction) { + Direction.NORTH -> north + Direction.SOUTH -> south + Direction.WEST -> west + Direction.EAST -> east + else -> throw IllegalArgumentException(direction.name) + } + + changes = changes || activeConfig != config + activeConfig = config + isValid = config.update(levelAccessor) + + if (isValid) { + isValid = customChecks.all { it.test(config) } + if (!isValid) config.clear() + } + + if (changes) { + remotes.forEach { it.listener.run() } + } + + return isValid + } + + override fun close() { + remotes.forEach { it.close() } + configurations.forEach { it.clear() } + isValid = false + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblockBuilder.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblockBuilder.kt new file mode 100644 index 000000000..a281824b1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblockBuilder.kt @@ -0,0 +1,365 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSet +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectArraySet +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.core.SectionPos +import net.minecraft.core.Vec3i +import net.minecraft.tags.TagKey +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.chunk.status.ChunkStatus +import ru.dbotthepony.mc.otm.core.collect.collect +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.getBlockStateNow +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.core.math.plus +import java.util.function.Predicate + +inline fun shapedMultiblock(configurator: ShapedMultiblockBuilder.Node.() -> Unit): ShapedMultiblockFactory { + val builder = ShapedMultiblockBuilder() + configurator.invoke(builder.root()) + return builder.build() +} + +class ShapedMultiblockBuilder { + private val nodes = Object2ObjectOpenHashMap() + private val customChecks = ArrayList>() + + /** + * Adds custom [predicate] which determines whenever selected multiblock configuration is valid + * + * Code inside predicate can safely call [ShapedMultiblock.blocks], [ShapedMultiblock.blockEntities], etc to determine whenever + * all demands are met + */ + fun customCheck(predicate: Predicate): ShapedMultiblockBuilder { + customChecks.add(predicate) + return this + } + + @Suppress("unchecked_cast") + abstract inner class Part> { + val predicates = ObjectArraySet() + val children = ObjectArraySet>() + + val builder: ShapedMultiblockBuilder + get() = this@ShapedMultiblockBuilder + + val blockStateTags = ObjectArraySet() + val blockTags = ObjectArraySet() + val blockEntityTags = ObjectArraySet>() + + /** + * Marks this node report its block in [ShapedMultiblock.blockStates] method when called with specified [value] + * + * [value] is searched using "value" semantics (`==`) + */ + fun tagBlockState(value: Any): T { + blockStateTags.add(value) + return this as T + } + + /** + * Marks this node report its block in [ShapedMultiblock.blockStates] method when called without arguments + */ + fun tagBlockState(): T { + blockStateTags.add(IMultiblockAccess.GLOBAL_BLOCK_TAG) + return this as T + } + + /** + * Marks this node report its block in [ShapedMultiblock.blocks] method when called with specified [value] + * + * [value] is searched for using "value" semantics (`==`) + */ + fun tagBlock(value: Any): T { + blockTags.add(value) + return this as T + } + + /** + * Marks this node report its block in [ShapedMultiblock.blocks] method when called without arguments + */ + fun tagBlock(): T { + blockTags.add(IMultiblockAccess.GLOBAL_BLOCK_TAG) + return this as T + } + + /** + * Marks this node to report block entities put at its position when called [ShapedMultiblock.blockEntities] with specified [value] + * + * [value] is searched for using "identity" semantics (`===`) + */ + fun tag(value: BlockEntityTag<*>): T { + blockEntityTags.add(value) + return this as T + } + + /** + * Removes all predicates, custom and prebuilt alike + */ + fun clear(): T { + predicates.clear() + return this as T + } + + /** + * Adds a custom predicate on this node's position + */ + fun predicate(predicate: BlockPredicate): T { + predicates.add(predicate) + return this as T + } + + /** + * Adds a block predicate, matching any valid blockstate of [block] on this node's position + */ + fun block(block: Block): T { + predicates.add { pos, access, blockState, blockEntity -> + blockState.value.block === block + } + + return this as T + } + + /** + * Adds a block tag predicate on this node's position + */ + fun block(block: TagKey): T { + predicates.add { pos, access, blockState, blockEntity -> + blockState.value.`is`(block) + } + + return this as T + } + + /** + * Adds "nothing" predicate (no block should be present) on this node's position + */ + fun air(): T { + predicate(BlockPredicate.Air) + return this as T + } + + /** + * Adds a blockstate predicate, matching exactly provided [state] on this node's position + */ + fun block(state: BlockState): T { + predicates.add { pos, access, blockState, blockEntity -> + blockState.value === state + } + + return this as T + } + + /** + * Creates "and" subnode, which requires all its children to test true + * + * @see And + */ + fun and(): SubNode { + return SubNode(this as T, Strategy.AND) + } + + /** + * Creates "or" subnode, which requires at least one of its children to test true + */ + fun or(): SubNode { + return SubNode(this as T, Strategy.OR_EITHER) + } + + /** + * Creates "and" subnode, which requires all its children to test true + */ + fun and(configurator: SubNode.() -> Unit): SubNode { + return SubNode(this as T, Strategy.AND).also(configurator) + } + + /** + * Creates "or" subnode, which requires at least one of its children to test true + */ + fun or(configurator: SubNode.() -> Unit): SubNode { + return SubNode(this as T, Strategy.OR_EITHER).also(configurator) + } + + protected fun build(pos: BlockPos): ShapedMultiblockFactory.Part { + return ShapedMultiblockFactory.Part( + pos, strategy, ImmutableList.copyOf(predicates), + children.stream().map { it.build(pos) }.collect(ImmutableList.toImmutableList()), + ImmutableSet.copyOf(blockStateTags), + ImmutableSet.copyOf(blockTags), + ImmutableSet.copyOf(blockEntityTags), + ) + } + + abstract val strategy: Strategy + } + + /** + * Logical subnode, which combines all children using [strategy] + */ + inner class SubNode

>(val parent: P, override val strategy: Strategy) : Part>() { + init { + parent.children.add(this) + } + + fun end() = parent + } + + /** + * By default, tests children using [Strategy.OR_BOTH] strategy + */ + inner class Node(val pos: BlockPos) : Part() { + init { + check(nodes.put(pos, this) == null) { "Trying to create new node at $pos while already having one" } + } + + /** + * Creates new node relative to this node at [diff], which behaves as if being [Or] node + */ + fun relative(diff: Vec3i): Node { + return node(pos + diff) + } + + /** + * Creates new node relative to this node at [dir] side, which behaves as if being [Or] node + */ + fun relative(dir: Direction): Node { + return relative(dir.normal) + } + + /** + * Creates new node relative to this node at [dir] side, which behaves as if being [Or] node + */ + fun relative(dir: RelativeSide): Node { + return relative(dir.default) + } + + /** + * Creates new node relative to this node at [diff], and configures it in-place using provided [configurator] expression, which behaves as if being [Or] node + */ + fun relative(diff: Vec3i, configurator: Node.() -> Unit): Node { + return node(pos + diff).also(configurator) + } + + /** + * Creates new node relative to this node at [dir] side, and configures it in-place using provided [configurator] expression, which behaves as if being [Or] node + */ + fun relative(dir: Direction, configurator: Node.() -> Unit): Node { + return relative(dir.normal).also(configurator) + } + + /** + * Creates new node relative to this node at [dir] side, and configures it in-place using provided [configurator] expression, which behaves as if being [Or] node + */ + fun relative(dir: RelativeSide, configurator: Node.() -> Unit): Node { + return relative(dir.default).also(configurator) + } + + fun front() = relative(RelativeSide.FRONT) + fun back() = relative(RelativeSide.BACK) + fun left() = relative(RelativeSide.LEFT) + fun right() = relative(RelativeSide.RIGHT) + fun top() = relative(RelativeSide.TOP) + fun bottom() = relative(RelativeSide.BOTTOM) + + fun front(block: Block) = relative(RelativeSide.FRONT).also { it.block(block) } + fun back(block: Block) = relative(RelativeSide.BACK).also { it.block(block) } + fun left(block: Block) = relative(RelativeSide.LEFT).also { it.block(block) } + fun right(block: Block) = relative(RelativeSide.RIGHT).also { it.block(block) } + fun top(block: Block) = relative(RelativeSide.TOP).also { it.block(block) } + fun bottom(block: Block) = relative(RelativeSide.BOTTOM).also { it.block(block) } + + fun front(block: BlockState) = relative(RelativeSide.FRONT).also { it.block(block) } + fun back(block: BlockState) = relative(RelativeSide.BACK).also { it.block(block) } + fun left(block: BlockState) = relative(RelativeSide.LEFT).also { it.block(block) } + fun right(block: BlockState) = relative(RelativeSide.RIGHT).also { it.block(block) } + fun top(block: BlockState) = relative(RelativeSide.TOP).also { it.block(block) } + fun bottom(block: BlockState) = relative(RelativeSide.BOTTOM).also { it.block(block) } + + fun front(predicate: BlockPredicate) = relative(RelativeSide.FRONT).also { it.predicate(predicate) } + fun back(predicate: BlockPredicate) = relative(RelativeSide.BACK).also { it.predicate(predicate) } + fun left(predicate: BlockPredicate) = relative(RelativeSide.LEFT).also { it.predicate(predicate) } + fun right(predicate: BlockPredicate) = relative(RelativeSide.RIGHT).also { it.predicate(predicate) } + fun top(predicate: BlockPredicate) = relative(RelativeSide.TOP).also { it.predicate(predicate) } + fun bottom(predicate: BlockPredicate) = relative(RelativeSide.BOTTOM).also { it.predicate(predicate) } + + fun front(configurator: Node.() -> Unit) = relative(RelativeSide.FRONT).also(configurator) + fun back(configurator: Node.() -> Unit) = relative(RelativeSide.BACK).also(configurator) + fun left(configurator: Node.() -> Unit) = relative(RelativeSide.LEFT).also(configurator) + fun right(configurator: Node.() -> Unit) = relative(RelativeSide.RIGHT).also(configurator) + fun top(configurator: Node.() -> Unit) = relative(RelativeSide.TOP).also(configurator) + fun bottom(configurator: Node.() -> Unit) = relative(RelativeSide.BOTTOM).also(configurator) + + fun front(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.FRONT).also { it.block(block) }.also(configurator) + fun back(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.BACK).also { it.block(block) }.also(configurator) + fun left(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.LEFT).also { it.block(block) }.also(configurator) + fun right(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.RIGHT).also { it.block(block) }.also(configurator) + fun top(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.TOP).also { it.block(block) }.also(configurator) + fun bottom(block: Block, configurator: Node.() -> Unit) = relative(RelativeSide.BOTTOM).also { it.block(block) }.also(configurator) + + fun front(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.FRONT).also { it.block(block) }.also(configurator) + fun back(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.BACK).also { it.block(block) }.also(configurator) + fun left(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.LEFT).also { it.block(block) }.also(configurator) + fun right(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.RIGHT).also { it.block(block) }.also(configurator) + fun top(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.TOP).also { it.block(block) }.also(configurator) + fun bottom(block: BlockState, configurator: Node.() -> Unit) = relative(RelativeSide.BOTTOM).also { it.block(block) }.also(configurator) + + fun front(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.FRONT).also { it.predicate(predicate) }.also(configurator) + fun back(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.BACK).also { it.predicate(predicate) }.also(configurator) + fun left(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.LEFT).also { it.predicate(predicate) }.also(configurator) + fun right(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.RIGHT).also { it.predicate(predicate) }.also(configurator) + fun top(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.TOP).also { it.predicate(predicate) }.also(configurator) + fun bottom(predicate: BlockPredicate, configurator: Node.() -> Unit) = relative(RelativeSide.BOTTOM).also { it.predicate(predicate) }.also(configurator) + + fun build(): ShapedMultiblockFactory.Part { + return build(pos) + } + + override var strategy: Strategy = Strategy.OR_EITHER + } + + /** + * Creates new (or returns existing one) node at specified position and returns it + */ + fun node(at: BlockPos): Node { + return nodes[at] ?: Node(at) + } + + /** + * Returns node which represents multiblock "root" position (relative position of 0 0 0) + */ + fun root() = node(BlockPos.ZERO) + + /** + * Returns node which represents multiblock "root" position (relative position of 0 0 0), and configures it using provided [configurator] expression + */ + fun root(configurator: Node.() -> Unit) = node(BlockPos.ZERO).also(configurator) + + /** + * Makes a deep copy of this [ShapedMultiblockBuilder], which can be modified independently of this builder + */ + fun copy(): ShapedMultiblockBuilder { + val copied = ShapedMultiblockBuilder() + + for ((k, v) in nodes) { + val node = copied.Node(k) + copied.nodes[k] = node + node.predicates.addAll(v.predicates) + } + + return copied + } + + /** + * Creates [ShapedMultiblockFactory] out of this [ShapedMultiblockBuilder]. + * + * Created factory does not share reference(s) to this builder, and this builder can be mutated further without consequences. + */ + fun build(): ShapedMultiblockFactory { + return ShapedMultiblockFactory(nodes.values.iterator().map { it.build() }.collect(ImmutableSet.toImmutableSet()), ImmutableList.copyOf(customChecks)) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblockFactory.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblockFactory.kt new file mode 100644 index 000000000..9e25d9018 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/ShapedMultiblockFactory.kt @@ -0,0 +1,32 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSet +import net.minecraft.core.BlockPos +import net.minecraft.world.level.block.Rotation +import ru.dbotthepony.mc.otm.core.collect.collect +import ru.dbotthepony.mc.otm.core.collect.map +import java.util.function.Predicate + +class ShapedMultiblockFactory(val north: ImmutableSet, val customChecks: ImmutableList>) { + data class Part( + val pos: BlockPos, + val strategy: Strategy, + val predicate: ImmutableList, + val children: ImmutableList, + val blockStateTags: ImmutableSet, + val blockTags: ImmutableSet, + val blockEntityTags: ImmutableSet>, + ) + + /** + * Bakes multiblock configuration with specified [pos] as multiblock's root position + */ + fun create(pos: BlockPos): ShapedMultiblock { + return ShapedMultiblock(pos, this) + } + + val south: ImmutableSet = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.CLOCKWISE_180)) }.collect(ImmutableSet.toImmutableSet()) + val west: ImmutableSet = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.CLOCKWISE_90)) }.collect(ImmutableSet.toImmutableSet()) + val east: ImmutableSet = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.COUNTERCLOCKWISE_90)) }.collect(ImmutableSet.toImmutableSet()) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/Strategy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/Strategy.kt new file mode 100644 index 000000000..0cde129fc --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/multiblock/Strategy.kt @@ -0,0 +1,25 @@ +package ru.dbotthepony.mc.otm.core.multiblock + +enum class Strategy { + /** + * Tests `true` if and only if both are true: + * * at least one predicate tests `true` (or there are no predicates) + * * at least one children node tests `true` (or there are no children nodes) + */ + OR_BOTH, + + /** + * Same as [OR_BOTH], but equates predicates and children in single check, so + * if single predicate or single children tests true, node tests true. + * + * This is default behavior of multiblock nodes, and implied operation of "or" function. + */ + OR_EITHER, + + /** + * Tests `true` if and only if: + * * all predicates tests `true` (or there are no predicates) + * * all children node tests `true` (or there are no children nodes) + */ + AND; +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt index e5ad9dffa..c5b30d2a9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt @@ -17,6 +17,7 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.isNegative import ru.dbotthepony.mc.otm.core.math.isZero +import ru.dbotthepony.mc.otm.menu.widget.IProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import java.math.BigInteger import java.util.function.BooleanSupplier @@ -278,7 +279,7 @@ fun formatTickDuration(ticks: Int, longFormat: Boolean = false): String { */ fun formatHistory( result: MutableList>, - widget: ProfiledLevelGaugeWidget<*>, + widget: IProfiledLevelGaugeWidget, bias: Int = 0, decimals: Int = 3, verbose: BooleanSupplier = never, @@ -293,7 +294,7 @@ fun formatHistory( private fun formatHistoryChart( result: MutableList>, - widget: ProfiledLevelGaugeWidget<*>, + widget: IProfiledLevelGaugeWidget, bias: Int = 0, decimals: Int = 3, verbose: BooleanSupplier = never, @@ -378,7 +379,7 @@ private fun formatHistoryChart( ) ) } else { - val max = maxOf(widget.received.max(), widget.transferred.max()) + val max = maxOf(widget.received.maxOrNull() ?: Decimal.ZERO, widget.transferred.maxOrNull() ?: Decimal.ZERO) val labelNames = Float2ObjectArrayMap() labelNames[0.5f] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias) @@ -446,7 +447,7 @@ private fun formatHistoryChart( private fun formatHistoryLines( result: MutableList>, - widget: ProfiledLevelGaugeWidget<*>, + widget: IProfiledLevelGaugeWidget, bias: Int = 0, decimals: Int = 3, verbose: BooleanSupplier = never, @@ -476,8 +477,8 @@ private fun formatHistoryLines( } addLine( - widget.received.calcWeightedAverage(), - widget.transferred.calcWeightedAverage() + widget.received.calculateAverage(), + widget.transferred.calculateAverage() ) if (verbose.asBoolean) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/NotNullVar.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/NotNullVar.kt new file mode 100644 index 000000000..fcaa33016 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/NotNullVar.kt @@ -0,0 +1,32 @@ +package ru.dbotthepony.mc.otm.core.util + +import ru.dbotthepony.kommons.util.Delegate +import ru.dbotthepony.kommons.util.KOptional +import kotlin.properties.Delegates +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * Different from [Delegates.notNull] by allowing [V] to be nullable + */ +class NotNullVar : Delegate, ReadWriteProperty { + private var value: KOptional = KOptional() + + override fun accept(t: V) { + value = KOptional(t) + } + + override fun get(): V { + value.ifPresent { return it } + throw IllegalStateException("Uninitialized variable") + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): V { + value.ifPresent { return it } + throw IllegalStateException("Uninitialized variable ${property.name}") + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) { + accept(value) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt index 8b559b07f..8d845ee15 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -52,7 +52,7 @@ import ru.dbotthepony.mc.otm.network.decode import ru.dbotthepony.mc.otm.network.encode import ru.dbotthepony.mc.otm.network.nullable import ru.dbotthepony.mc.otm.network.readByteListUnbounded -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup import ru.dbotthepony.mc.otm.network.wrap import ru.dbotthepony.mc.otm.network.writeByteListUnbounded import java.util.* @@ -76,7 +76,7 @@ abstract class MatteryMenu( /** * Server->Client synchronizer */ - val mSynchronizer = Syncher() + val mSynchronizer = SynchableGroup() val synchronizerRemote = mSynchronizer.Remote() val player: Player get() = inventory.player val random: RandomSource = RandomSource.create() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt index af4335ec6..5011223d0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt @@ -16,7 +16,7 @@ import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.registry.MMenus -class BatteryBankMenu @JvmOverloads constructor( +class BatteryBankMenu( p_38852_: Int, inventory: Inventory, tile: BatteryBankBlockEntity? = null, 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 new file mode 100644 index 000000000..226776b0c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BlackHoleGeneratorMenu.kt @@ -0,0 +1,18 @@ +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.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.widget.CombinedProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class BlackHoleGeneratorMenu( + p_38852_: Int, + inventory: Inventory, + tile: BlackHoleGeneratorBlockEntity? = null, +) : MatteryMenu(MMenus.BLACK_HOLE_GENERATOR, p_38852_, inventory, tile) { + val drawBuildingGuide = BooleanInputWithFeedback(this, tile?.let { it::drawBuildingGuide }) + val energy = CombinedProfiledLevelGaugeWidget(this, tile?.energy) + val matter = CombinedProfiledLevelGaugeWidget(this, tile?.matter) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt index a5b9de73c..dcece84e5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt @@ -8,7 +8,7 @@ import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock import ru.dbotthepony.mc.otm.block.entity.tech.EnergyCounterBlockEntity -import ru.dbotthepony.mc.otm.core.DecimalHistoryChart +import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.toDecimal import ru.dbotthepony.mc.otm.menu.MatteryMenu diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/CombinedProfiledLevelGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/CombinedProfiledLevelGaugeWidget.kt new file mode 100644 index 000000000..874abec13 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/CombinedProfiledLevelGaugeWidget.kt @@ -0,0 +1,44 @@ +package ru.dbotthepony.mc.otm.menu.widget + +import it.unimi.dsi.fastutil.objects.ObjectIterators +import ru.dbotthepony.kommons.util.getValue +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage.Companion.HISTORY_SIZE +import ru.dbotthepony.mc.otm.capability.IProfiledStorage +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage +import ru.dbotthepony.mc.otm.core.chart.CombinedDecimalHistoryChart +import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.network.StreamCodecs +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup +import java.util.function.Supplier + +class CombinedProfiledLevelGaugeWidget

( + synchronizer: SynchableGroup, + val storage: P?, + override val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer) +) : IProfiledLevelGaugeWidget, IProfiledStorage.Combined { + constructor( + menu: MatteryMenu, + storage: P?, + gauge: LevelGaugeWidget = LevelGaugeWidget(menu.mSynchronizer) + ) : this(menu.mSynchronizer, storage, gauge) + + override val received = storage?.received ?: CombinedDecimalHistoryChart(Supplier { ObjectIterators.emptyIterator() }, ticks = HISTORY_SIZE) + override val transferred = storage?.transferred ?: CombinedDecimalHistoryChart(Supplier { ObjectIterators.emptyIterator() }, ticks = HISTORY_SIZE) + + override val receivedThisTick by synchronizer.computed({ storage?.receivedThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL) + override val transferredThisTick by synchronizer.computed({ storage?.transferredThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL) + + init { + if (storage is IMatterStorage) + gauge.with(storage) + else if (storage is IMatteryEnergyStorage) + gauge.with(storage) + + synchronizer.add(received) + synchronizer.add(transferred) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt index 8704ad54b..35585af95 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt @@ -2,7 +2,7 @@ package ru.dbotthepony.mc.otm.menu.widget import net.neoforged.neoforge.fluids.FluidStack import net.neoforged.neoforge.fluids.capability.IFluidHandler -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.menu.MatteryMenu @@ -10,7 +10,7 @@ import ru.dbotthepony.mc.otm.network.wrap import java.util.function.IntSupplier import java.util.function.Supplier -class FluidGaugeWidget(synchronizer: Syncher) { +class FluidGaugeWidget(synchronizer: SynchableGroup) { constructor(menu: MatteryMenu) : this(menu.mSynchronizer) var maxCapacitySupplier = IntSupplier { 0 } @@ -27,7 +27,7 @@ class FluidGaugeWidget(synchronizer: Syncher) { return (fluid.amount.toFloat() / maxCapacity.toFloat()).coerceIn(0f, 1f) } - constructor(synchronizer: Syncher, fluid: IFluidHandler?, tank: Int = 0) : this(synchronizer) { + constructor(synchronizer: SynchableGroup, fluid: IFluidHandler?, tank: Int = 0) : this(synchronizer) { if (fluid != null) { with(fluid, tank) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/IProfiledLevelGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/IProfiledLevelGaugeWidget.kt new file mode 100644 index 000000000..6bf70f7ae --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/IProfiledLevelGaugeWidget.kt @@ -0,0 +1,7 @@ +package ru.dbotthepony.mc.otm.menu.widget + +import ru.dbotthepony.mc.otm.capability.IProfiledStorage + +interface IProfiledLevelGaugeWidget : IProfiledStorage { + val gauge: LevelGaugeWidget +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt index b7f61ffba..d640e1198 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt @@ -7,10 +7,10 @@ import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.network.StreamCodecs -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup @Suppress("unused") -class LevelGaugeWidget(synchronizer: Syncher) { +class LevelGaugeWidget(synchronizer: SynchableGroup) { constructor(menu: MatteryMenu) : this(menu.mSynchronizer) var levelProvider = { Decimal.ONE } @@ -56,7 +56,7 @@ class LevelGaugeWidget(synchronizer: Syncher) { } constructor( - synchronizer: Syncher, + synchronizer: SynchableGroup, power: IMatteryEnergyStorage? ) : this(synchronizer) { if (power != null) { @@ -65,7 +65,7 @@ class LevelGaugeWidget(synchronizer: Syncher) { } constructor( - synchronizer: Syncher, + synchronizer: SynchableGroup, matter: IMatterStorage? ) : this(synchronizer) { if (matter != null) { @@ -74,7 +74,7 @@ class LevelGaugeWidget(synchronizer: Syncher) { } constructor( - synchronizer: Syncher, + synchronizer: SynchableGroup, patterns: IPatternStorage? ) : this(synchronizer) { if (patterns != null) { @@ -83,7 +83,7 @@ class LevelGaugeWidget(synchronizer: Syncher) { } constructor( - synchronizer: Syncher, + synchronizer: SynchableGroup, level: () -> Decimal, maxLevel: () -> Decimal, ) : this(synchronizer) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProfiledLevelGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProfiledLevelGaugeWidget.kt index d8baba81e..2a510d706 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProfiledLevelGaugeWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProfiledLevelGaugeWidget.kt @@ -3,30 +3,31 @@ package ru.dbotthepony.mc.otm.menu.widget import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage.Companion.HISTORY_SIZE +import ru.dbotthepony.mc.otm.capability.IProfiledStorage import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage -import ru.dbotthepony.mc.otm.core.DecimalHistoryChart +import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.network.StreamCodecs -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup class ProfiledLevelGaugeWidget

>( - synchronizer: Syncher, + synchronizer: SynchableGroup, val storage: P?, - val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer) -) { + override val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer) +) : IProfiledLevelGaugeWidget { constructor( menu: MatteryMenu, storage: P?, gauge: LevelGaugeWidget = LevelGaugeWidget(menu.mSynchronizer) ) : this(menu.mSynchronizer, storage, gauge) - val received = storage?.received ?: DecimalHistoryChart(ticks = HISTORY_SIZE) - val transferred = storage?.transferred ?: DecimalHistoryChart(ticks = HISTORY_SIZE) + override val received = storage?.received ?: DecimalHistoryChart(ticks = HISTORY_SIZE) + override val transferred = storage?.transferred ?: DecimalHistoryChart(ticks = HISTORY_SIZE) - val receivedThisTick by synchronizer.computed({ storage?.receivedThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL) - val transferredThisTick by synchronizer.computed({ storage?.transferredThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL) + override val receivedThisTick by synchronizer.computed({ storage?.receivedThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL) + override val transferredThisTick by synchronizer.computed({ storage?.transferredThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL) init { if (storage is IMatterStorage) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt index 99014f864..af5cf09c1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt @@ -1,6 +1,6 @@ package ru.dbotthepony.mc.otm.menu.widget -import ru.dbotthepony.mc.otm.network.syncher.Syncher +import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity @@ -9,7 +9,7 @@ import ru.dbotthepony.mc.otm.menu.MatteryMenu import java.util.function.BooleanSupplier @Suppress("unused") -class ProgressGaugeWidget(synchronizer: Syncher) { +class ProgressGaugeWidget(synchronizer: SynchableGroup) { constructor(menu: MatteryMenu) : this(menu.mSynchronizer) var progressSupplier: FloatSupplier = FloatSupplier { 0f } @@ -44,14 +44,14 @@ class ProgressGaugeWidget(synchronizer: Syncher) { } constructor( - synchronizer: Syncher, + synchronizer: SynchableGroup, progress: FloatSupplier ) : this(synchronizer) { this.progressSupplier = progress } constructor( - synchronizer: Syncher, + synchronizer: SynchableGroup, blockEntity: MatteryWorkerBlockEntity<*>? ) : this(synchronizer) { if (blockEntity != null) { @@ -60,7 +60,7 @@ class ProgressGaugeWidget(synchronizer: Syncher) { } constructor( - synchronizer: Syncher, + synchronizer: SynchableGroup, job: MachineJobEventLoop<*>? ) : this(synchronizer) { if (job != null) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/DynamicSynchable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/DynamicSynchable.kt new file mode 100644 index 000000000..cc0997d4a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/DynamicSynchable.kt @@ -0,0 +1,94 @@ +package ru.dbotthepony.mc.otm.network.syncher + +import net.minecraft.network.RegistryFriendlyByteBuf +import ru.dbotthepony.kommons.util.KOptional +import ru.dbotthepony.mc.otm.network.MatteryStreamCodec +import java.util.concurrent.CopyOnWriteArrayList + +class DynamicSynchable( + initialValue: KOptional, + private val codec: MatteryStreamCodec, + private val factory: (D) -> V +) : ISynchable { + constructor(codec: MatteryStreamCodec, factory: (D) -> V) : this(KOptional(), codec, factory) + + private var _synchable: KOptional = KOptional() + + val synchable: V + get() = _synchable.orThrow { IllegalStateException("Not initialized") } + + private var _value: KOptional = KOptional() + private val remotes = CopyOnWriteArrayList() + + var value: D + get() = _value.orThrow { IllegalStateException("Not initialized") } + set(value) { + if (_value.isEmpty || _value.value != value) { + _value = KOptional(value) + _synchable = KOptional(factory(value)) + + remotes.forEach { it.invalidateFull() } + } + } + + init { + initialValue.ifPresent { + _value = KOptional(it) + _synchable = KOptional(factory(it)) + } + } + + private inner class RemoteState(val listener: Runnable) : IRemoteState { + private var valueChanged = true + private var remote = synchable.createRemoteState(listener) + + override fun write(stream: RegistryFriendlyByteBuf) { + stream.writeBoolean(valueChanged) + + if (valueChanged) { + codec.encode(stream, _value.value) + } + + valueChanged = false + remote.write(stream) + } + + fun invalidateFull() { + valueChanged = true + remote.close() + remote = synchable.createRemoteState(listener) + listener.run() + } + + override fun invalidate() { + valueChanged = true + remote.invalidate() + } + + override fun close() { + remotes.remove(this) + remote.close() + } + } + + override var isRemote: Boolean = false + private set + + override fun read(stream: RegistryFriendlyByteBuf) { + isRemote = true + + if (stream.readBoolean()) { + _value = KOptional(codec.decode(stream)) + _synchable = KOptional(factory(_value.value)) + } + + _synchable.value.read(stream) + } + + override val hasRemotes: Boolean + get() = remotes.isNotEmpty() + + override fun createRemoteState(listener: Runnable): IRemoteState { + return RemoteState(listener) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/DynamicSynchableGroup.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/DynamicSynchableGroup.kt new file mode 100644 index 000000000..fb3d3ba6e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/DynamicSynchableGroup.kt @@ -0,0 +1,353 @@ +package ru.dbotthepony.mc.otm.network.syncher + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import it.unimi.dsi.fastutil.ints.IntArraySet +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet +import net.minecraft.network.RegistryFriendlyByteBuf +import ru.dbotthepony.kommons.util.KOptional +import ru.dbotthepony.mc.otm.core.IDAllocator +import java.io.Closeable +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Syncher group/set, which deals with synchables of only one type, and which are created and removed + * on remote (e.g. server adding and removing synchables at will), which makes it distinct from + * [SynchableGroup], in which attached synchables are created/removed manually on both sides. + */ +class DynamicSynchableGroup( + /** + * Constructs new [T] instance locally, when remote created one. + * Data written by [writer] must be read here, if there is any. + */ + private val reader: RegistryFriendlyByteBuf.() -> T, + + /** + * Allows to write additional data to network stream during + * first-time networking of [T] to remote + */ + private val writer: T.(RegistryFriendlyByteBuf) -> Unit = {} +) : ISynchable, MutableSet { + constructor(factory: () -> T) : this({ factory() }, {}) + + private inner class RemoteState(val listener: Runnable) : IRemoteState { + private inner class RemoteSlot(val slot: Slot, fromConstructor: Boolean) : Runnable, Closeable { + val remoteState = slot.synchable.createRemoteState(this) + val isDirty = AtomicBoolean(true) + var isRemoved = false + private set + + init { + if (!fromConstructor) + removals.remove(slot.id) + + firstTime.add(this) + dirty.add(this) + + if (firstTime.size != 1 && !fromConstructor) { + listener.run() + } + } + + override fun run() { + if (isDirty.compareAndSet(false, true)) { + dirty.add(this) + listener.run() + } + } + + fun markClean() { + isDirty.set(false) + } + + override fun close() { + isRemoved = true + isDirty.set(true) + remoteState.close() + } + } + + private val remotes = HashMap, RemoteSlot>() + private val dirty = ConcurrentLinkedQueue() + private val firstTime = ArrayList() + private val removals = IntArraySet() + private var sendClear = false + + init { + value2slot.values.forEach { + remotes[it] = RemoteSlot(it, true) + } + + remoteStates.add(this) + } + + fun add(element: Slot) { + remotes[element] = RemoteSlot(element, false) + listener.run() + } + + fun remove(element: Slot) { + val removed = remotes.remove(element) + checkNotNull(removed) + check(removed.slot == element) + removed.close() + listener.run() + } + + override fun write(stream: RegistryFriendlyByteBuf) { + if (sendClear) { + sendClear = false + stream.writeByte(CLEAR) + } + + run { + val itr = removals.iterator() + + while (itr.hasNext()) { + stream.writeByte(REMOVE_ENTRY) + stream.writeVarInt(itr.nextInt()) + } + + removals.clear() + } + + firstTime.forEach { + stream.writeByte(ADD_ENTRY) + stream.writeVarInt(it.slot.id) + writer(it.slot.synchable, stream) + } + + firstTime.clear() + + val seen = ReferenceOpenHashSet() + val slots = ArrayList() + var next = dirty.poll() + + while (next != null) { + if (!next.isRemoved) { + val status = seen.add(next) + next.markClean() + + if (status) { + slots.add(next) + } + } + + next = dirty.poll() + } + + slots.forEach { + stream.writeByte(SYNC_ENTRY) + stream.writeVarInt(it.slot.id) + it.remoteState.write(stream) + } + + stream.writeByte(END) + } + + override fun invalidate() { + firstTime.clear() + dirty.clear() + removals.clear() // is it necessary? + + remotes.values.forEach { + firstTime.add(it) + dirty.add(it) + } + } + + override fun close() { + remotes.values.forEach { it.close() } + + firstTime.clear() + dirty.clear() + removals.clear() + remotes.clear() + + remoteStates.remove(this) + } + + fun clear() { + sendClear = true + firstTime.clear() + dirty.clear() + removals.clear() + remotes.values.forEach { it.close() } + remotes.clear() + } + } + + private data class Slot(val synchable: T, val id: Int) + + private val remoteStates = CopyOnWriteArrayList() + private val value2slot = HashMap>() + private val id2slot = Int2ObjectOpenHashMap>() + private val idAllocator = IDAllocator() + + override val hasRemotes: Boolean + get() = remoteStates.isNotEmpty() + + override var isRemote: Boolean = false + private set + + override fun read(stream: RegistryFriendlyByteBuf) { + isRemote = true + var action = stream.readByte().toInt() + + while (action != END) { + when (action) { + REMOVE_ENTRY -> { + val id = stream.readVarInt() + val slot = checkNotNull(id2slot.remove(id)) { "No such slot with ID: $id" } + check(value2slot.remove(slot.synchable) == value2slot) + remoteStates.forEach { it.remove(slot) } + } + + ADD_ENTRY -> { + val id = stream.readVarInt() + + // it is expected that during very frequent updates entry gets removed and new entry takes up their ID, + // so we allow implicit removal if ID is allocated by some other entry before old entry could have a chance + // to broadcast that it have been removed + + if (id2slot.containsKey(id)) { + val slot = id2slot.remove(id)!! + check(value2slot.remove(slot.synchable) == value2slot) + remoteStates.forEach { it.remove(slot) } + } + + val value = reader(stream) + val slot = Slot(value, id) + value2slot[value] = slot + id2slot[id] = slot + } + + SYNC_ENTRY -> { + val id = stream.readVarInt() + val slot = checkNotNull(id2slot.get(id)) { "No such slot with ID: $id" } + slot.synchable.read(stream) + } + + CLEAR -> { + value2slot.clear() + id2slot.clear() + } + + else -> throw IllegalArgumentException("Unknown network action: $action") + } + + action = stream.readByte().toInt() + } + } + + override fun createRemoteState(listener: Runnable): IRemoteState { + return RemoteState(listener) + } + + override fun add(element: T): Boolean { + if (element in value2slot) { + return false + } + + val slot = Slot(element, idAllocator.allocate()) + value2slot[element] = slot + id2slot[slot.id] = slot + remoteStates.forEach { it.add(slot) } + return true + } + + override val size: Int + get() = value2slot.size + + override fun addAll(elements: Collection): Boolean { + var any = false + elements.forEach { any = add(it) || any } + return any + } + + override fun clear() { + if (value2slot.isNotEmpty()) { + value2slot.clear() + id2slot.clear() + + remoteStates.forEach { it.clear() } + } + } + + override fun contains(element: T): Boolean { + return element in value2slot + } + + override fun containsAll(elements: Collection): Boolean { + return value2slot.keys.containsAll(elements) + } + + override fun isEmpty(): Boolean { + return value2slot.isEmpty() + } + + override fun iterator(): MutableIterator { + return object : MutableIterator { + private val parent = value2slot.values.iterator() + private var last: KOptional> = KOptional() + + override fun hasNext(): Boolean { + return parent.hasNext() + } + + override fun next(): T { + val slot = parent.next() + last = KOptional(slot) + return slot.synchable + } + + override fun remove() { + parent.remove() + val slot = last.value + checkNotNull(id2slot.remove(slot.id)) + remoteStates.forEach { it.remove(slot) } + last = KOptional() + } + } + } + + override fun remove(element: T): Boolean { + if (element !in value2slot) { + return false + } + + val slot = value2slot.remove(element)!! + checkNotNull(id2slot.remove(slot.id)) + remoteStates.forEach { it.remove(slot) } + return true + } + + override fun removeAll(elements: Collection): Boolean { + var any = false + elements.forEach { any = remove(it) || any } + return any + } + + override fun retainAll(elements: Collection): Boolean { + val itr = iterator() + var any = false + + for (element in itr) { + if (element !in elements) { + itr.remove() + any = true + } + } + + return any + } + + companion object { + private const val END = 0 + private const val ADD_ENTRY = 1 + private const val SYNC_ENTRY = 2 + private const val REMOVE_ENTRY = 3 + private const val CLEAR = 4 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/ISynchable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/ISynchable.kt index 4b6cf43eb..dde9508da 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/ISynchable.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/ISynchable.kt @@ -3,19 +3,32 @@ package ru.dbotthepony.mc.otm.network.syncher import net.minecraft.network.RegistryFriendlyByteBuf import ru.dbotthepony.kommons.util.Observer +/** + * An object capable of synching its state to remotes + */ interface ISynchable : Observer { fun read(stream: RegistryFriendlyByteBuf) /** * Provided [listener] is to be considered thread-safe, and can be called at any time * - * Created [IRemoteState] is implied to have [IRemoteState.invalidate] called implicitly internally + * Created [IRemoteState] is implied to have [IRemoteState.invalidate] called implicitly internally upon creation */ fun createRemoteState(listener: Runnable): IRemoteState val shouldBeObserved: Boolean get() = false + /** + * Whenever this synchable has remotes, optional operation and can safely always return false + */ + val hasRemotes: Boolean + + /** + * Whenever [read] has been called at least once + */ + val isRemote: Boolean + override fun observe(): Boolean { // no op return false diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableDelegate.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableDelegate.kt index b4e2a1de7..4e6647d29 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableDelegate.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableDelegate.kt @@ -5,6 +5,7 @@ import ru.dbotthepony.kommons.util.Listenable import ru.dbotthepony.kommons.util.ListenableDelegate import ru.dbotthepony.kommons.util.Observer import ru.dbotthepony.mc.otm.network.MatteryStreamCodec +import java.util.concurrent.atomic.AtomicInteger import java.util.function.Consumer import java.util.function.Supplier @@ -13,14 +14,26 @@ class SynchableDelegate(val delegate: ListenableDelegate, val codec: Matte private val listeners = Listenable.Impl() private val l = delegate.addListener(listeners) + private val remoteCount = AtomicInteger() + + override val hasRemotes: Boolean + get() = remoteCount.get() > 0 + + override var isRemote: Boolean = false + private set override fun read(stream: RegistryFriendlyByteBuf) { + isRemote = false delegate.accept(codec.decode(stream)) } inner class Remote(listener: Runnable) : IRemoteState { private val l = listeners.addListener(listener) + init { + remoteCount.incrementAndGet() + } + override fun write(stream: RegistryFriendlyByteBuf) { codec.encode(stream, delegate.get()) } @@ -31,6 +44,7 @@ class SynchableDelegate(val delegate: ListenableDelegate, val codec: Matte override fun close() { l.remove() + remoteCount.decrementAndGet() // unsafe because close() can be called multiple times } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/Syncher.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableGroup.kt similarity index 91% rename from src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/Syncher.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableGroup.kt index 1fa0f7600..bc3ee3c78 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/Syncher.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableGroup.kt @@ -18,6 +18,8 @@ import ru.dbotthepony.kommons.util.Listenable import ru.dbotthepony.kommons.util.ListenableDelegate import ru.dbotthepony.kommons.util.Observer import ru.dbotthepony.mc.otm.OTM_CLEANER +import ru.dbotthepony.mc.otm.core.collect.filterNotNull +import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.network.MatteryStreamCodec import ru.dbotthepony.mc.otm.network.StreamCodecs @@ -37,29 +39,36 @@ import java.util.function.DoubleSupplier import java.util.function.IntSupplier import java.util.function.LongSupplier import java.util.function.Supplier -import kotlin.collections.ArrayList import kotlin.concurrent.withLock /** * Universal, one-to-many delegate/value synchronizer. * - * Values and delegates can be attached using [add], or by constructing subclassed directly. - * Delta changes are tracked by [Syncher.Remote] instances. - * - * In general, this class is not meant to be _structurally_ concurrently mutated by different threads, - * to avoid structure corruption a lock is employed. + * Values and delegates can be attached using [add]; + * Delta changes are tracked by [SynchableGroup.Remote] instances. * * Attached delegates can be safely mutated by multiple threads concurrently * (attached [ListenableDelegate] can call [Listenable.addListener] callback from different threads). + * + * Generally, this class is considered to be "root" of [ISynchable] operation (due to API), but this is not strictly necessary, + * and [SynchableGroup] can be as well embedded into other [SynchableGroup]s. */ @Suppress("UNUSED") -class Syncher : Observer, ISynchable { +class SynchableGroup : Observer, ISynchable, Iterable { private val lock = ReentrantLock() private val slots = ArrayList() private val gaps = IntAVLTreeSet() private val observers = ArrayList() private val remotes = ArrayList() - private var isRemote = false + override var isRemote = false + private set + + override val hasRemotes: Boolean + get() = remotes.isNotEmpty() + + override fun iterator(): Iterator { + return slots.iterator().map { it?.synchable }.filterNotNull() + } override fun observe(): Boolean { var any = false @@ -80,7 +89,7 @@ class Syncher : Observer, ISynchable { var readID = stream.readVarInt() while (readID > 0) { - val slot = lock.withLock { slots.getOrNull(readID - 1) ?: throw IndexOutOfBoundsException("Unknown networked slot ${readID - 1}!") } + val slot = slots.getOrNull(readID - 1) ?: throw IndexOutOfBoundsException("Unknown networked slot ${readID - 1}!") slot.synchable.read(stream) readID = stream.readVarInt() } @@ -95,18 +104,17 @@ class Syncher : Observer, ISynchable { internal inner class Slot(val synchable: ISynchable) : Closeable, Observer { val id: Int - private val isRemoved = AtomicBoolean() + private var isRemoved = false val remoteSlots = CopyOnWriteArrayList() override fun close() { - if (isRemoved.compareAndSet(false, true)) { - lock.withLock { - slots[id] = null - gaps.add(id) + if (!isRemoved) { + isRemoved = true + slots[id] = null + gaps.add(id) - observers.remove(this) - remoteSlots.forEach { it.remove() } - } + observers.remove(this) + remoteSlots.forEach { it.remove() } } } @@ -115,7 +123,7 @@ class Syncher : Observer, ISynchable { } private fun markDirty() { - if (!isRemote && !isRemoved.get()) { + if (!isRemote && !isRemoved) { remoteSlots.forEach { it.markDirty() } @@ -375,7 +383,7 @@ class Syncher : Observer, ISynchable { /** * Returns null if this remote is clean. * - * [Syncher.observe] is not called automatically for performance + * [SynchableGroup.observe] is not called automatically for performance * reasons, you must call it manually. */ fun write(registry: RegistryAccess): ByteArrayList? { @@ -421,18 +429,17 @@ class Syncher : Observer, ISynchable { override fun close() { if (!isRemoved) { + isRemoved = true + lock.withLock { - if (!isRemoved) { - remoteSlots.forEach { - it.remove() - it.slot.remoteSlots.remove(it) - } - - remotes.remove(this) + remoteSlots.forEach { + it.remove() + it.slot.remoteSlots.remove(it) } - } - dirty.clear() + remotes.remove(this) + dirty.clear() + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableMap.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableMap.kt index fc905fe6e..99e2f72f3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableMap.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableMap.kt @@ -12,7 +12,12 @@ class SynchableMap( val keyCodec: MatteryStreamCodec, val valueCodec: MatteryStreamCodec ) : ISynchable { + override var isRemote: Boolean = false + private set + override fun read(stream: RegistryFriendlyByteBuf) { + isRemote = true + var action = stream.readByte().toInt() while (true) { @@ -105,6 +110,9 @@ class SynchableMap( } } + override val hasRemotes: Boolean + get() = remoteSlots.isNotEmpty() + override fun createRemoteState(listener: Runnable): IRemoteState { return RemoteState(listener) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableObservedDelegate.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableObservedDelegate.kt index 5753f7175..093a8999b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableObservedDelegate.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableObservedDelegate.kt @@ -6,6 +6,7 @@ import ru.dbotthepony.kommons.util.Listenable import ru.dbotthepony.kommons.util.ListenableDelegate import ru.dbotthepony.kommons.util.ValueObserver import ru.dbotthepony.mc.otm.network.MatteryStreamCodec +import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.ReentrantLock import java.util.function.Consumer import kotlin.concurrent.withLock @@ -14,10 +15,15 @@ class SynchableObservedDelegate(val delegate: Delegate, val codec: Mattery private object Mark private val listeners = Listenable.Impl() + private val remoteCount = AtomicInteger() private var observed: Any? = Mark private val observeLock = ReentrantLock() + override var isRemote: Boolean = false + private set + override fun read(stream: RegistryFriendlyByteBuf) { + isRemote = true val read = codec.decode(stream) observed = codec.copy(read) delegate.accept(read) @@ -75,6 +81,10 @@ class SynchableObservedDelegate(val delegate: Delegate, val codec: Mattery private inner class RemoteState(listener: Runnable) : IRemoteState { private val l = listeners.addListener(listener) + init { + remoteCount.incrementAndGet() + } + override fun write(stream: RegistryFriendlyByteBuf) { codec.encode(stream, delegate.get()) } @@ -85,9 +95,13 @@ class SynchableObservedDelegate(val delegate: Delegate, val codec: Mattery override fun close() { l.remove() + remoteCount.decrementAndGet() // unsafe because close() can be called multiple times } } + override val hasRemotes: Boolean + get() = remoteCount.get() > 0 + override fun createRemoteState(listener: Runnable): IRemoteState { return RemoteState(listener) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableSet.kt index f5dd7f1d1..995ca73e9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableSet.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableSet.kt @@ -87,6 +87,12 @@ class SynchableSet(val delegate: ListenableSet, val codec: MatteryStreamCo } } + override var isRemote: Boolean = false + private set + + override val hasRemotes: Boolean + get() = remoteSlots.isNotEmpty() + override fun createRemoteState(listener: Runnable): IRemoteState { return RemoteState(listener) } @@ -96,6 +102,8 @@ class SynchableSet(val delegate: ListenableSet, val codec: MatteryStreamCo } override fun read(stream: RegistryFriendlyByteBuf) { + isRemote = true + var action = stream.readByte().toInt() while (true) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt index 61fd4e8c5..2250b5604 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt @@ -114,18 +114,17 @@ object MBlockEntities { } private fun registerClient(event: FMLClientSetupEvent) { - event.enqueueWork { - BlockEntityRenderers.register(BLACK_HOLE, ::BlackHoleRenderer) - BlockEntityRenderers.register(GRAVITATION_STABILIZER, ::GravitationStabilizerRenderer) - BlockEntityRenderers.register(ENERGY_COUNTER, ::EnergyCounterRenderer) - BlockEntityRenderers.register(BATTERY_BANK, ::BatteryBankRenderer) - BlockEntityRenderers.register(MATTER_CAPACITOR_BANK, ::MatterBatteryBankRenderer) - BlockEntityRenderers.register(MATTER_RECONSTRUCTOR, ::MatterReconstructorRenderer) - BlockEntityRenderers.register(MATTER_REPLICATOR, ::MatterReplicatorRenderer) - BlockEntityRenderers.register(MATTER_SCANNER, ::MatterScannerRenderer) - BlockEntityRenderers.register(POWERED_SMOKER, ::PoweredSmokerRenderer) - BlockEntityRenderers.register(HOLO_SIGN, ::HoloSignRenderer) - BlockEntityRenderers.register(FLUID_TANK, ::FluidTankRenderer) - } + BlockEntityRenderers.register(BLACK_HOLE, ::BlackHoleRenderer) + BlockEntityRenderers.register(BLACK_HOLE_GENERATOR, ::BlackHoleGeneratorRenderer) + BlockEntityRenderers.register(GRAVITATION_STABILIZER, ::GravitationStabilizerRenderer) + BlockEntityRenderers.register(ENERGY_COUNTER, ::EnergyCounterRenderer) + BlockEntityRenderers.register(BATTERY_BANK, ::BatteryBankRenderer) + BlockEntityRenderers.register(MATTER_CAPACITOR_BANK, ::MatterBatteryBankRenderer) + BlockEntityRenderers.register(MATTER_RECONSTRUCTOR, ::MatterReconstructorRenderer) + BlockEntityRenderers.register(MATTER_REPLICATOR, ::MatterReplicatorRenderer) + BlockEntityRenderers.register(MATTER_SCANNER, ::MatterScannerRenderer) + BlockEntityRenderers.register(POWERED_SMOKER, ::PoweredSmokerRenderer) + BlockEntityRenderers.register(HOLO_SIGN, ::HoloSignRenderer) + BlockEntityRenderers.register(FLUID_TANK, ::FluidTankRenderer) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockTags.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockTags.kt index 7f8323dc2..8a925eb96 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockTags.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockTags.kt @@ -56,4 +56,7 @@ object MBlockTags { val HARDENED_GLASS_YELLOW: TagKey = BlockTags.create(ResourceLocation("c", "hardened_glass/yellow")) val MACHINES: TagKey = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "machines")) + val MULTIBLOCK_STRUCTURE: TagKey = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "multiblock_structure")) + val MULTIBLOCK_HARD_STRUCTURE: TagKey = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "multiblock_hard_structure")) + val MULTIBLOCK_SOFT_STRUCTURE: TagKey = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "multiblock_soft_structure")) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt index 3fb3b140f..721be41bd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt @@ -174,7 +174,6 @@ object MBlocks { val FLUID_TANK: FluidTankBlock by registry.register(MNames.FLUID_TANK) { FluidTankBlock() } val DEV_CHEST: DevChestBlock by registry.register(MNames.DEV_CHEST) { DevChestBlock() } - val MULTIBLOCK_STRUCTURE by registry.register(MNames.MULTIBLOCK_STRUCTURE) { MatteryBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).requiresCorrectToolForDrops().destroyTime(2.5f).explosionResistance(160.0f)) } val BLACK_HOLE_GENERATOR by registry.register(MNames.BLACK_HOLE_GENERATOR) { BlackHoleGeneratorBlock() } val MATTER_INJECTOR by registry.register(MNames.MATTER_INJECTOR) { RotatableMatteryBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).requiresCorrectToolForDrops().destroyTime(2.5f).explosionResistance(160.0f)) } val ANTIMATTER_INJECTOR by registry.register(MNames.ANTIMATTER_INJECTOR) { RotatableMatteryBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).requiresCorrectToolForDrops().destroyTime(2.5f).explosionResistance(160.0f)) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt index 77e9ccf3c..364476925 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt @@ -136,6 +136,9 @@ object MItems { val MATTER_ENTANGLER: BlockItem by registry.register(MNames.MATTER_ENTANGLER) { BlockItem(MBlocks.MATTER_ENTANGLER, DEFAULT_PROPERTIES) } val BLACK_HOLE_GENERATOR by registry.register(MNames.BLACK_HOLE_GENERATOR) { BlockItem(MBlocks.BLACK_HOLE_GENERATOR, DEFAULT_PROPERTIES) } + val MATTER_INJECTOR by registry.register(MNames.MATTER_INJECTOR) { BlockItem(MBlocks.MATTER_INJECTOR, DEFAULT_PROPERTIES) } + val ANTIMATTER_INJECTOR by registry.register(MNames.ANTIMATTER_INJECTOR) { BlockItem(MBlocks.ANTIMATTER_INJECTOR, DEFAULT_PROPERTIES) } + val HIGH_ENERGY_PARTICLE_COLLECTOR by registry.register(MNames.HIGH_ENERGY_PARTICLE_COLLECTOR) { BlockItem(MBlocks.HIGH_ENERGY_PARTICLE_COLLECTOR, DEFAULT_PROPERTIES) } val ITEM_INPUT_HATCH by registry.register(MNames.ITEM_INPUT_HATCH) { BlockItem(MBlocks.ITEM_INPUT_HATCH, DEFAULT_PROPERTIES) } val ITEM_OUTPUT_HATCH by registry.register(MNames.ITEM_OUTPUT_HATCH) { BlockItem(MBlocks.ITEM_OUTPUT_HATCH, DEFAULT_PROPERTIES) } val ENERGY_INPUT_HATCH by registry.register(MNames.ENERGY_INPUT_HATCH) { BlockItem(MBlocks.ENERGY_INPUT_HATCH, DEFAULT_PROPERTIES) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt index 912826c41..035d1ef39 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt @@ -31,6 +31,7 @@ import ru.dbotthepony.mc.otm.client.screen.tech.AbstractProcessingMachineScreen import ru.dbotthepony.mc.otm.client.screen.tech.AndroidChargerScreen import ru.dbotthepony.mc.otm.client.screen.tech.AndroidStationScreen import ru.dbotthepony.mc.otm.client.screen.tech.BatteryBankScreen +import ru.dbotthepony.mc.otm.client.screen.tech.BlackHoleGeneratorScreen import ru.dbotthepony.mc.otm.client.screen.tech.ChemicalGeneratorScreen import ru.dbotthepony.mc.otm.client.screen.tech.CobblerScreen import ru.dbotthepony.mc.otm.client.screen.tech.EnergyCounterScreen @@ -63,6 +64,7 @@ import ru.dbotthepony.mc.otm.menu.storage.StoragePowerSupplierMenu import ru.dbotthepony.mc.otm.menu.tech.AndroidChargerMenu import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu import ru.dbotthepony.mc.otm.menu.tech.BatteryBankMenu +import ru.dbotthepony.mc.otm.menu.tech.BlackHoleGeneratorMenu import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu @@ -114,6 +116,7 @@ object MMenus { val MATTER_OUTPUT_HATCH by registry.register(MNames.MATTER_OUTPUT_HATCH) { MenuType(MatterHatchMenu::output, FeatureFlags.VANILLA_SET) } val ENERGY_INPUT_HATCH by registry.register(MNames.ENERGY_INPUT_HATCH) { MenuType(EnergyHatchMenu::input, FeatureFlags.VANILLA_SET) } val ENERGY_OUTPUT_HATCH by registry.register(MNames.ENERGY_OUTPUT_HATCH) { MenuType(EnergyHatchMenu::output, FeatureFlags.VANILLA_SET) } + val BLACK_HOLE_GENERATOR by registry.register(MNames.BLACK_HOLE_GENERATOR) { MenuType(::BlackHoleGeneratorMenu, FeatureFlags.VANILLA_SET) } val STORAGE_BUS by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu, FeatureFlags.VANILLA_SET) } val STORAGE_IMPORTER_EXPORTER by registry.register(MNames.STORAGE_IMPORTER) { MenuType(::StorageImporterExporterMenu, FeatureFlags.VANILLA_SET) } @@ -165,5 +168,6 @@ object MMenus { event.register(MATTER_OUTPUT_HATCH, ::MatterHatchScreen) event.register(ENERGY_INPUT_HATCH, ::EnergyHatchScreen) event.register(ENERGY_OUTPUT_HATCH, ::EnergyHatchScreen) + event.register(BLACK_HOLE_GENERATOR, ::BlackHoleGeneratorScreen) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt index 9ac45681a..a13d05887 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt @@ -15,7 +15,6 @@ object MNames { const val ANDROID_CHARGER = "android_charger" const val INFINITE_WATER_SOURCE = "infinite_water_source" const val DEV_CHEST = "dev_chest" - const val MULTIBLOCK_STRUCTURE = "multiblock_structure" const val BLACK_HOLE_GENERATOR = "black_hole_generator" const val MATTER_INJECTOR = "matter_injector" const val ANTIMATTER_INJECTOR = "antimatter_injector"