Black hole generator test (for real this time)

This commit is contained in:
DBotThePony 2024-10-30 20:54:41 +07:00
parent b832873203
commit 58af515f9a
Signed by: DBot
GPG Key ID: DCC23B5715498507
89 changed files with 2881 additions and 1254 deletions

View File

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

View File

@ -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, "Стол маляра")

View File

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

View File

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

View File

@ -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<CompoundTag> {
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 ->

View File

@ -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<CompoundTag> {
@ -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()

View File

@ -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<ServerPlayer, Syncher.Remote>()
val syncher = SynchableGroup()
private val synchers = Object2ObjectArrayMap<ServerPlayer, SynchableGroup.Remote>()
override fun setLevel(level: Level) {
val old = this.level

View File

@ -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<HopperBlockEntity>()
private val FURNACES = multiblockEntity<FurnaceBlockEntity>()
val CONFIG = multiblockConfiguration {
val CONFIG = shapedMultiblock {
block(MBlocks.MULTIBLOCK_TEST)
and {

View File

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

View File

@ -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<BlockPos>()
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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<out P>(val parent: P) : INBTSerializable<CompoundTag?> {
val received = DecimalHistoryChart(ticks = HISTORY_SIZE)
val transferred = DecimalHistoryChart(ticks = HISTORY_SIZE)
abstract class AbstractProfiledStorage<out P>(val parent: P) : IProfiledStorage, INBTSerializable<CompoundTag?> {
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

View File

@ -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 <T> Supplier<out Iterator<T>>.iterateProviders(random: RandomGenerator?, value: Decimal, simulate: Boolean, walker: T.(Decimal, Boolean) -> Decimal): Decimal {
val providers = get()
val iteratorProvider: () -> Iterator<T>
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
*/

View File

@ -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<Decimal>
val transferred: IHistoryChart<Decimal>
val receivedThisTick: Decimal
val transferredThisTick: Decimal
/**
* Just a middleware interface for [CombinedDecimalHistoryChart] carriers
*/
interface Combined : IProfiledStorage {
override val received: CombinedDecimalHistoryChart
override val transferred: CombinedDecimalHistoryChart
}
}

View File

@ -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<ServerPlayer, Syncher.Remote>()
val remoteSynchers = Object2ObjectArrayMap<ServerPlayer, SynchableGroup.Remote>()
/**
* For data to be stored to and loaded from NBT automatically

View File

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

View File

@ -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<out Iterator<IMatteryEnergyStorage>>,
val random: Supplier<RandomGenerator?> = 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
}
}

View File

@ -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<out Iterator<ProfiledEnergyStorage<*>>>,
random: Supplier<RandomGenerator?> = 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()
}
}

View File

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

View File

@ -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<out E : IMatteryEnergyStorage>(parent: E) : AbstractProfiledStorage<E>(parent), IMatteryEnergyStorage {
class ProfiledEnergyStorage<out E : IMatteryEnergyStorage>(parent: E) : AbstractProfiledStorage<E>(parent), IProfiledMatteryEnergyStorage {
override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
return recordTransfer(parent.extractEnergy(howMuch, simulate), simulate)
}

View File

@ -40,4 +40,28 @@ class ProxiedEnergyStorage<T : IMatteryEnergyStorage>(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
}
}

View File

@ -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<out Iterator<IMatterStorage>>,
val random: Supplier<RandomGenerator?> = 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)
}

View File

@ -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<out Iterator<ProfiledMatterStorage<*>>>,
random: Supplier<RandomGenerator?> = 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()
}
}

View File

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

View File

@ -32,12 +32,12 @@ class ProfiledMatterStorage<out M : IMatterStorage>(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

View File

@ -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<RenderType>?, types: Array<out RenderType>): 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)

View File

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

View File

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

View File

@ -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<out S : MatteryScreen<*>, G : AbstractHistoryChart<V>, V : Any>(
screen: S,

View File

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

View File

@ -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<AndroidStationMenu>(p_97741_, p_97742_, p_97743_) {
private fun makeCanvas(isPreview: Boolean): DraggableCanvasPanel<AndroidStationScreen> {

View File

@ -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<BlackHoleGeneratorMenu>(menu, inventory, title) {
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
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
}
}

View File

@ -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<EnergyHatchMenu>(menu, inventory, title) {
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
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

View File

@ -16,7 +16,7 @@ import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu
class MatterHatchScreen(menu: MatterHatchMenu, inventory: Inventory, title: Component) : MatteryScreen<MatterHatchMenu>(menu, inventory, title) {
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
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

View File

@ -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 <S : Screen> TallHorizontalPowerGaugePanel(
open class HorizontalProfiledPowerGaugePanel<out S : Screen>(
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<out S : Screen>(
fun <S : Screen> TallHorizontalProfiledPowerGaugePanel(
screen: S,
parent: EditablePanel<*>? = null,
widget: ProfiledLevelGaugeWidget<*>,
widget: IProfiledLevelGaugeWidget,
x: Float = 0f,
y: Float = 0f,
width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width,

View File

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

View File

@ -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 <S : Screen> WidePowerGaugePanel(
open class ProfiledPowerGaugePanel<out S : Screen>(
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<out S : Screen>(
fun <S : Screen> WideProfiledPowerGaugePanel(
screen: S,
parent: EditablePanel<*>? = null,
widget: ProfiledLevelGaugeWidget<*>,
widget: IProfiledLevelGaugeWidget,
x: Float = 0f,
y: Float = 0f,
width: Float = 18f,

View File

@ -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<Bloc
matterData.putDecimal("storedMatter", it.storedMatter)
matterData.putDecimal("maxStoredMatter", it.maxStoredMatter)
if (it is AbstractProfiledStorage<*>) {
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)
}

View File

@ -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<Bloc
energyData.putDecimal("batteryLevel", it.batteryLevel)
energyData.putDecimal("maxBatteryLevel", it.maxBatteryLevel)
if (it is AbstractProfiledStorage<*>) {
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)
}

View File

@ -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.*

View File

@ -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 : Enum<T>> T.prev(values: Array<out T>): 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 <T> MutableList<T>.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 <T : Any> immutableList(size: Int, initializer: (index: Int) -> T): ImmutableList<T> {
require(size >= 0) { "Invalid list size $size" }

View File

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

View File

@ -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<Level, WeakHashMap<BlockEntity, WeakHashSet<Multiblock>>>()
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>) : BlockPredicate {
constructor(vararg nodes: BlockPredicate) : this(ImmutableSet.copyOf(nodes))
constructor(nodes: Set<BlockPredicate>) : 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>) : BlockPredicate {
constructor(vararg nodes: BlockPredicate) : this(ImmutableSet.copyOf(nodes))
constructor(nodes: Set<BlockPredicate>) : 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 <reified T : BlockEntity> multiblockEntity(): MultiblockBuilder.EntityTag<T> {
return MultiblockBuilder.EntityTag(T::class)
}
private val GLOBAL_BLOCK_TAG = Any()
class MultiblockBuilder {
private val nodes = Object2ObjectOpenHashMap<BlockPos, Node>()
private val customChecks = ArrayList<Predicate<Multiblock>>()
/**
* Tags specific [T] block entities to be exposed through [Multiblock.blockEntities] when specific [Part]s are tagged using [Part.tag]
*/
class EntityTag<T : BlockEntity>(val clazz: KClass<T>, val predicate: Predicate<in T> = Predicate { true }) : Predicate<BlockEntity> {
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<Multiblock>): MultiblockBuilder {
customChecks.add(predicate)
return this
}
@Suppress("unchecked_cast")
abstract inner class Part<T : Part<T>> {
val predicates = ObjectArraySet<BlockPredicate>()
val children = ObjectArraySet<Part<*>>()
val builder: MultiblockBuilder
get() = this@MultiblockBuilder
val blockStateTags = ObjectArraySet<Any>()
val blockTags = ObjectArraySet<Any>()
val blockEntityTags = ObjectArraySet<EntityTag<*>>()
/**
* 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<Block>): 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<T> {
return And(this as T)
}
/**
* Creates "or" subnode, which requires at least one of its children to test true
*
* @see or
*/
fun or(): Or<T> {
return Or(this as T)
}
/**
* Creates "and" subnode, which requires all its children to test true
*
* @see And
*/
fun and(configurator: And<T>.() -> Unit): And<T> {
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<T>.() -> Unit): Or<T> {
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<P : Part<P>>(val parent: P) : Part<And<P>>() {
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<P : Part<P>>(val parent: P) : Part<Or<P>>() {
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<Node>() {
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<Part>, val customChecks: ImmutableList<Predicate<Multiblock>>) {
data class Part(
val pos: BlockPos,
val strategy: MultiblockBuilder.Strategy,
val predicate: ImmutableList<BlockPredicate>,
val children: ImmutableList<Part>,
val blockStateTags: ImmutableSet<Any>,
val blockTags: ImmutableSet<Any>,
val blockEntityTags: ImmutableSet<MultiblockBuilder.EntityTag<*>>,
)
/**
* Bakes multiblock configuration with specified [pos] as multiblock's root position
*/
fun create(pos: BlockPos): Multiblock {
return Multiblock(pos, this)
}
val south: ImmutableSet<Part> = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.CLOCKWISE_180)) }.collect(ImmutableSet.toImmutableSet())
val west: ImmutableSet<Part> = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.COUNTERCLOCKWISE_90)) }.collect(ImmutableSet.toImmutableSet())
val east: ImmutableSet<Part> = 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<T : BlockEntity>(val tag: MultiblockBuilder.EntityTag<T>) {
val list = ArrayList<T>()
val set = ObjectArraySet<T>()
val setView: Set<T> = 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<MultiblockFactory.Part>) {
private inner class Part(val pos: BlockPos, val prototype: MultiblockFactory.Part) : Comparable<Part> {
private var blockEntity: BlockEntity? = null
private var blockState: BlockState? = null
private val assignedBlockEntityLists = ArrayList<BEList<*>>(prototype.blockEntityTags.size)
private val assignedBlockStateLists = ArrayList<Reference2IntMap<BlockState>>()
private val assignedBlockLists = ArrayList<Reference2IntMap<Block>>()
private val children: ImmutableList<Part> by lazy(LazyThreadSafetyMode.NONE) { prototype.children.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 <T : BlockEntity> getBlockEntityList(tag: MultiblockBuilder.EntityTag<T>): BEList<T> {
val existing = tag2BlockEntity[tag]
if (existing != null)
return existing as BEList<T>
val new = BEList(tag)
blockEntityLists.add(new)
tag2BlockEntity[tag] = new
return new
}
private fun getBlockList(tag: Any): Reference2IntMap<Block> {
val existing = tag2Block[tag]
if (existing != null)
return existing
val new = Reference2IntOpenHashMap<Block>()
tag2Block[tag] = new
tag2BlockViews[tag] = Reference2IntMaps.unmodifiable(new)
blockLists.add(new)
return new
}
private fun getBlockStateList(tag: Any): Reference2IntMap<BlockState> {
val existing = tag2BlockState[tag]
if (existing != null)
return existing
val new = Reference2IntOpenHashMap<BlockState>()
tag2BlockState[tag] = new
tag2BlockStateViews[tag] = Reference2IntMaps.unmodifiable(new)
blockStateLists.add(new)
return new
}
private val tag2BlockEntity = HashMap<MultiblockBuilder.EntityTag<*>, BEList<*>>()
private val tag2BlockState = HashMap<Any, Reference2IntMap<BlockState>>()
private val tag2Block = HashMap<Any, Reference2IntMap<Block>>()
private val tag2BlockStateViews = HashMap<Any, Reference2IntMap<BlockState>>()
private val tag2BlockViews = HashMap<Any, Reference2IntMap<Block>>()
private val blockEntityLists = ArrayList<BEList<*>>()
private val blockStateLists = ArrayList<Reference2IntMap<BlockState>>()
private val blockLists = ArrayList<Reference2IntMap<Block>>()
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<Part> = 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 <T : BlockEntity> blockEntities(tag: MultiblockBuilder.EntityTag<T>): Set<T> {
return (tag2BlockEntity[tag]?.setView ?: setOf()) as Set<T>
}
fun blocks(tag: Any): Reference2IntMap<Block> {
return tag2BlockViews[tag] ?: Reference2IntMaps.emptyMap()
}
fun blockStates(tag: Any): Reference2IntMap<BlockState> {
return tag2BlockStateViews[tag] ?: Reference2IntMaps.emptyMap()
}
fun blocks(): Reference2IntMap<Block> {
return globalBlocksView
}
fun blockStates(): Reference2IntMap<BlockState> {
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 <T : BlockEntity> blockEntities(tag: MultiblockBuilder.EntityTag<T>): Set<T> {
if (!isValid) return emptySet()
return activeConfig?.blockEntities(tag) ?: setOf()
}
/**
* Returns block counts present on nodes tagged by [tag]
*/
fun blocks(tag: Any): Reference2IntMap<Block> {
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<BlockState> {
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<Block> {
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<BlockState> {
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
}
}

View File

@ -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<V : Any, CHART : AbstractHistoryChart<V>>(
protected val provider: Supplier<Iterator<CHART>>,
final override val width: Int,
final override val resolution: Int
) : IHistoryChart<V>, 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>): 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<V> {
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<V>(iterators.size)
return object : Iterator<V> {
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)
}
}

View File

@ -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<V : Any>(
/**
* How many measurements one graph value contains
*/
val resolution: Int,
/**
* Limit on how many graph values to store
*/
val width: Int,
) : Iterable<V>, INBTSerializable<CompoundTag>, ISynchable {
final override val resolution: Int,
final override val width: Int,
) : IHistoryChart<V>, ISynchable, INBTSerializable<CompoundTag> {
constructor(ticks: Int) : this(1, ticks)
init {
@ -39,13 +32,13 @@ abstract class AbstractHistoryChart<V : Any>(
protected abstract fun sum(input: List<V>): 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<V : Any>(
abstract val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>
private val remotes = CopyOnWriteArrayList<RemoteState>()
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<V : Any>(
}
}
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<V : Any>(
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<V : Any>(
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<V : Any>(
it.group(
list.fieldOf("accumulator").forGetter { it.accumulated },
list.fieldOf("values").forGetter { it.values },
).apply(it, ::SData)
).apply(it) { a, b -> SData(a, b) }
}
}

View File

@ -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<Iterator<DecimalHistoryChart>>,
resolution: Int,
width: Int,
) : AbstractCombinedHistoryChart<Decimal, DecimalHistoryChart>(provider, width, resolution) {
constructor(provider: Supplier<Iterator<DecimalHistoryChart>>, ticks: Int) : this(provider, 1, ticks)
override fun sum(values: List<Decimal>): 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)
}
}

View File

@ -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<Decimal> {
override val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, Decimal>
get() = DecimalCodec.NETWORK
fun calcWeightedAverage(): Decimal {
return calcWeightedAverage(this::get)
}
companion object {
private val HISTORY_WEIGHTERS: ImmutableList<Decimal> = immutableList {
/*for (i in 0 until 20) {

View File

@ -0,0 +1,20 @@
package ru.dbotthepony.mc.otm.core.chart
/**
* Common interface for reading chart values
*/
interface IHistoryChart<V : Any> : Iterable<V> {
/**
* 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
}

View File

@ -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<T : BlockEntity>(private val listener: GlobalBlockEntityRemovalListener, val tag: BlockEntityTag<T>) {
private val items = Reference2IntOpenHashMap<T>()
val set: Set<T> = 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()
}
}

View File

@ -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 <reified T : BlockEntity> multiblockEntity(): BlockEntityTag<T> {
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<T : BlockEntity>(val clazz: KClass<T>, val predicate: Predicate<in T> = Predicate { true }) :
Predicate<BlockEntity> {
override fun test(t: BlockEntity): Boolean {
return clazz.isInstance(t) && predicate.test(t as T)
}
}

View File

@ -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<BlockState>, blockEntity: Lazy<BlockEntity?>): 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<BlockState>, blockEntity: Lazy<BlockEntity?>): 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>) : BlockPredicate {
constructor(vararg nodes: BlockPredicate) : this(ImmutableSet.copyOf(nodes))
constructor(nodes: Set<BlockPredicate>) : this(ImmutableSet.copyOf(nodes))
override fun test(pos: BlockPos, access: LevelAccessor, blockState: Lazy<BlockState>, blockEntity: Lazy<BlockEntity?>): Boolean {
return nodes.all { it.test(pos, access, blockState, blockEntity) }
}
}
data class Or(val nodes: ImmutableSet<BlockPredicate>) : BlockPredicate {
constructor(vararg nodes: BlockPredicate) : this(ImmutableSet.copyOf(nodes))
constructor(nodes: Set<BlockPredicate>) : this(ImmutableSet.copyOf(nodes))
override fun test(pos: BlockPos, access: LevelAccessor, blockState: Lazy<BlockState>, blockEntity: Lazy<BlockEntity?>): Boolean {
return nodes.any { it.test(pos, access, blockState, blockEntity) }
}
}
object Air : BlockPredicate {
override fun test(pos: BlockPos, access: LevelAccessor, blockState: Lazy<BlockState>, blockEntity: Lazy<BlockEntity?>): Boolean {
return blockState.value.isAir
}
}
object NotAir : BlockPredicate {
override fun test(pos: BlockPos, access: LevelAccessor, blockState: Lazy<BlockState>, blockEntity: Lazy<BlockEntity?>): Boolean {
return !blockState.value.isAir
}
}
}

View File

@ -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<Level, WeakHashMap<BlockEntity, WeakHashSet<GlobalBlockEntityRemovalListener>>>()
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)
}
}
}
}
}

View File

@ -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<BlockPos, IMultiblockNode>
/**
* Returns block counts present on nodes tagged by [tag]
*/
fun blocks(tag: Any): Reference2IntMap<Block>
/**
* Returns blockstate counts present on nodes tagged by [tag]
*/
fun blockStates(tag: Any): Reference2IntMap<BlockState>
/**
* Returns block counts present on nodes tagged by [GLOBAL_BLOCK_TAG]
*/
fun blocks(): Reference2IntMap<Block> {
return blocks(GLOBAL_BLOCK_TAG)
}
/**
* Returns blockstate counts present on nodes tagged by [GLOBAL_BLOCK_TAG]
*/
fun blockStates(): Reference2IntMap<BlockState> {
return blockStates(GLOBAL_BLOCK_TAG)
}
fun <T : BlockEntity> blockEntities(tag: BlockEntityTag<T>): Set<T>
companion object {
val GLOBAL_BLOCK_TAG = Any()
}
}

View File

@ -0,0 +1,8 @@
package ru.dbotthepony.mc.otm.core.multiblock
import net.minecraft.core.BlockPos
interface IMultiblockNode {
val pos: BlockPos
val status: NodeStatus
}

View File

@ -0,0 +1,7 @@
package ru.dbotthepony.mc.otm.core.multiblock
enum class NodeStatus {
UNKNOWN,
VALID,
INVALID;
}

View File

@ -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<ShapedMultiblockFactory.Part>) : IMultiblockAccess, ISynchable, GlobalBlockEntityRemovalListener, Comparable<Config> {
private inner class Part(override val pos: BlockPos, val prototype: ShapedMultiblockFactory.Part) : Comparable<Part>, IMultiblockNode {
var index = -1
private var blockEntity: BlockEntity? = null
private var blockState: BlockState? = null
private val assignedBlockEntityLists = ArrayList<BlockEntitySet<*>>(prototype.blockEntityTags.size)
private val assignedBlockStateLists = ArrayList<Reference2IntMap<BlockState>>()
private val assignedBlockLists = ArrayList<Reference2IntMap<Block>>()
private val children: ImmutableList<Part> by lazy(LazyThreadSafetyMode.NONE) { prototype.children.iterator().map { Part(pos, it) }.collect(ImmutableList.toImmutableList()) }
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<BlockState>, blockEntity: Lazy<BlockEntity?>): 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<BlockState>, blockEntity: Lazy<BlockEntity?>): 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<BlockState>, blockEntity: Lazy<BlockEntity?>): 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<BlockPos, IMultiblockNode>
get() = parts
private val tag2BlockEntity = HashMap<BlockEntityTag<*>, BlockEntitySet<*>>()
private val tag2BlockState = HashMap<Any, Reference2IntMap<BlockState>>()
private val tag2Block = HashMap<Any, Reference2IntMap<Block>>()
private val tag2BlockStateViews = HashMap<Any, Reference2IntMap<BlockState>>()
private val tag2BlockViews = HashMap<Any, Reference2IntMap<Block>>()
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 <T : BlockEntity> getBlockEntityList(tag: BlockEntityTag<T>): BlockEntitySet<T> {
val existing = tag2BlockEntity[tag]
if (existing != null)
return existing as BlockEntitySet<T>
val new = BlockEntitySet(this, tag)
tag2BlockEntity[tag] = new
return new
}
private fun getBlockList(tag: Any): Reference2IntMap<Block> {
val existing = tag2Block[tag]
if (existing != null)
return existing
val new = Reference2IntOpenHashMap<Block>()
tag2Block[tag] = new
tag2BlockViews[tag] = Reference2IntMaps.unmodifiable(new)
return new
}
private fun getBlockStateList(tag: Any): Reference2IntMap<BlockState> {
val existing = tag2BlockState[tag]
if (existing != null)
return existing
val new = Reference2IntOpenHashMap<BlockState>()
tag2BlockState[tag] = new
tag2BlockStateViews[tag] = Reference2IntMaps.unmodifiable(new)
return new
}
val parts: ImmutableMap<BlockPos, Part> = 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<Part>()
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 <T : BlockEntity> blockEntities(tag: BlockEntityTag<T>): Set<T> {
return (tag2BlockEntity[tag]?.set ?: setOf()) as Set<T>
}
override fun blocks(tag: Any): Reference2IntMap<Block> {
return tag2BlockViews[tag] ?: Reference2IntMaps.emptyMap()
}
override fun blockStates(tag: Any): Reference2IntMap<BlockState> {
return tag2BlockStateViews[tag] ?: Reference2IntMaps.emptyMap()
}
override fun blocks(): Reference2IntMap<Block> {
return globalBlocksView
}
override fun blockStates(): Reference2IntMap<BlockState> {
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<RemoteState>()
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<RemoteState>()
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<BlockPos, IMultiblockNode>
get() = activeConfig.parts
override fun <T : BlockEntity> blockEntities(tag: BlockEntityTag<T>): Set<T> {
if (!isValid) return setOf()
return activeConfig.blockEntities(tag)
}
override fun blocks(tag: Any): Reference2IntMap<Block> {
if (!isValid) return Reference2IntMaps.emptyMap()
return activeConfig.blocks(tag)
}
override fun blockStates(tag: Any): Reference2IntMap<BlockState> {
if (!isValid) return Reference2IntMaps.emptyMap()
return activeConfig.blockStates(tag)
}
override fun blocks(): Reference2IntMap<Block> {
if (!isValid) return Reference2IntMaps.emptyMap()
return activeConfig.blocks()
}
override fun blockStates(): Reference2IntMap<BlockState> {
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<Config>()
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
}
}

View File

@ -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<BlockPos, Node>()
private val customChecks = ArrayList<Predicate<IMultiblockAccess>>()
/**
* 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<IMultiblockAccess>): ShapedMultiblockBuilder {
customChecks.add(predicate)
return this
}
@Suppress("unchecked_cast")
abstract inner class Part<T : Part<T>> {
val predicates = ObjectArraySet<BlockPredicate>()
val children = ObjectArraySet<Part<*>>()
val builder: ShapedMultiblockBuilder
get() = this@ShapedMultiblockBuilder
val blockStateTags = ObjectArraySet<Any>()
val blockTags = ObjectArraySet<Any>()
val blockEntityTags = ObjectArraySet<BlockEntityTag<*>>()
/**
* 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<Block>): 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<T> {
return SubNode(this as T, Strategy.AND)
}
/**
* Creates "or" subnode, which requires at least one of its children to test true
*/
fun or(): SubNode<T> {
return SubNode(this as T, Strategy.OR_EITHER)
}
/**
* Creates "and" subnode, which requires all its children to test true
*/
fun and(configurator: SubNode<T>.() -> Unit): SubNode<T> {
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<T>.() -> Unit): SubNode<T> {
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<P : Part<P>>(val parent: P, override val strategy: Strategy) : Part<SubNode<P>>() {
init {
parent.children.add(this)
}
fun end() = parent
}
/**
* By default, tests children using [Strategy.OR_BOTH] strategy
*/
inner class Node(val pos: BlockPos) : Part<Node>() {
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))
}
}

View File

@ -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<Part>, val customChecks: ImmutableList<Predicate<IMultiblockAccess>>) {
data class Part(
val pos: BlockPos,
val strategy: Strategy,
val predicate: ImmutableList<BlockPredicate>,
val children: ImmutableList<Part>,
val blockStateTags: ImmutableSet<Any>,
val blockTags: ImmutableSet<Any>,
val blockEntityTags: ImmutableSet<BlockEntityTag<*>>,
)
/**
* Bakes multiblock configuration with specified [pos] as multiblock's root position
*/
fun create(pos: BlockPos): ShapedMultiblock {
return ShapedMultiblock(pos, this)
}
val south: ImmutableSet<Part> = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.CLOCKWISE_180)) }.collect(ImmutableSet.toImmutableSet())
val west: ImmutableSet<Part> = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.CLOCKWISE_90)) }.collect(ImmutableSet.toImmutableSet())
val east: ImmutableSet<Part> = north.iterator().map { it.copy(pos = it.pos.rotate(Rotation.COUNTERCLOCKWISE_90)) }.collect(ImmutableSet.toImmutableSet())
}

View File

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

View File

@ -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<Either<FormattedText, TooltipComponent>>,
widget: ProfiledLevelGaugeWidget<*>,
widget: IProfiledLevelGaugeWidget,
bias: Int = 0,
decimals: Int = 3,
verbose: BooleanSupplier = never,
@ -293,7 +294,7 @@ fun formatHistory(
private fun formatHistoryChart(
result: MutableList<Either<FormattedText, TooltipComponent>>,
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<Component>()
labelNames[0.5f] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
@ -446,7 +447,7 @@ private fun formatHistoryChart(
private fun formatHistoryLines(
result: MutableList<Either<FormattedText, TooltipComponent>>,
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) {

View File

@ -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<V> : Delegate<V>, ReadWriteProperty<Any?, V> {
private var value: KOptional<V> = 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)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<P : IProfiledStorage.Combined>(
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)
}
}

View File

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

View File

@ -0,0 +1,7 @@
package ru.dbotthepony.mc.otm.menu.widget
import ru.dbotthepony.mc.otm.capability.IProfiledStorage
interface IProfiledLevelGaugeWidget : IProfiledStorage {
val gauge: LevelGaugeWidget
}

View File

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

View File

@ -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<P : AbstractProfiledStorage<*>>(
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)

View File

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

View File

@ -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<D, V : ISynchable>(
initialValue: KOptional<D>,
private val codec: MatteryStreamCodec<RegistryFriendlyByteBuf, D>,
private val factory: (D) -> V
) : ISynchable {
constructor(codec: MatteryStreamCodec<RegistryFriendlyByteBuf, D>, factory: (D) -> V) : this(KOptional(), codec, factory)
private var _synchable: KOptional<V> = KOptional()
val synchable: V
get() = _synchable.orThrow { IllegalStateException("Not initialized") }
private var _value: KOptional<D> = KOptional()
private val remotes = CopyOnWriteArrayList<RemoteState>()
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)
}
}

View File

@ -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<T : ISynchable>(
/**
* 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<T> {
constructor(factory: () -> T) : this({ factory() }, {})
private inner class RemoteState(val listener: Runnable) : IRemoteState {
private inner class RemoteSlot(val slot: Slot<T>, 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<Slot<T>, RemoteSlot>()
private val dirty = ConcurrentLinkedQueue<RemoteSlot>()
private val firstTime = ArrayList<RemoteSlot>()
private val removals = IntArraySet()
private var sendClear = false
init {
value2slot.values.forEach {
remotes[it] = RemoteSlot(it, true)
}
remoteStates.add(this)
}
fun add(element: Slot<T>) {
remotes[element] = RemoteSlot(element, false)
listener.run()
}
fun remove(element: Slot<T>) {
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<RemoteSlot>()
val slots = ArrayList<RemoteSlot>()
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<T : Any>(val synchable: T, val id: Int)
private val remoteStates = CopyOnWriteArrayList<RemoteState>()
private val value2slot = HashMap<T, Slot<T>>()
private val id2slot = Int2ObjectOpenHashMap<Slot<T>>()
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<T>): 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<T>): Boolean {
return value2slot.keys.containsAll(elements)
}
override fun isEmpty(): Boolean {
return value2slot.isEmpty()
}
override fun iterator(): MutableIterator<T> {
return object : MutableIterator<T> {
private val parent = value2slot.values.iterator()
private var last: KOptional<Slot<T>> = 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<T>): Boolean {
var any = false
elements.forEach { any = remove(it) || any }
return any
}
override fun retainAll(elements: Collection<T>): 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
}
}

View File

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

View File

@ -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<V>(val delegate: ListenableDelegate<V>, val codec: Matte
private val listeners = Listenable.Impl<V>()
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<V>(val delegate: ListenableDelegate<V>, val codec: Matte
override fun close() {
l.remove()
remoteCount.decrementAndGet() // unsafe because close() can be called multiple times
}
}

View File

@ -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<ISynchable> {
private val lock = ReentrantLock()
private val slots = ArrayList<Slot?>()
private val gaps = IntAVLTreeSet()
private val observers = ArrayList<Slot>()
private val remotes = ArrayList<Remote>()
private var isRemote = false
override var isRemote = false
private set
override val hasRemotes: Boolean
get() = remotes.isNotEmpty()
override fun iterator(): Iterator<ISynchable> {
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<Remote.RemoteSlot>()
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()
}
}
}
}

View File

@ -12,7 +12,12 @@ class SynchableMap<K, V>(
val keyCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, K>,
val valueCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>
) : 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<K, V>(
}
}
override val hasRemotes: Boolean
get() = remoteSlots.isNotEmpty()
override fun createRemoteState(listener: Runnable): IRemoteState {
return RemoteState(listener)
}

View File

@ -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<V>(val delegate: Delegate<V>, val codec: Mattery
private object Mark
private val listeners = Listenable.Impl<V>()
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<V>(val delegate: Delegate<V>, 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<V>(val delegate: Delegate<V>, 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)
}

View File

@ -87,6 +87,12 @@ class SynchableSet<V>(val delegate: ListenableSet<V>, 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<V>(val delegate: ListenableSet<V>, val codec: MatteryStreamCo
}
override fun read(stream: RegistryFriendlyByteBuf) {
isRemote = true
var action = stream.readByte().toInt()
while (true) {

View File

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

View File

@ -56,4 +56,7 @@ object MBlockTags {
val HARDENED_GLASS_YELLOW: TagKey<Block> = BlockTags.create(ResourceLocation("c", "hardened_glass/yellow"))
val MACHINES: TagKey<Block> = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "machines"))
val MULTIBLOCK_STRUCTURE: TagKey<Block> = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "multiblock_structure"))
val MULTIBLOCK_HARD_STRUCTURE: TagKey<Block> = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "multiblock_hard_structure"))
val MULTIBLOCK_SOFT_STRUCTURE: TagKey<Block> = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "multiblock_soft_structure"))
}

View File

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

View File

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

View File

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

View File

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