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.ITEM_OUTPUT_HATCH, "Item Output Hatch")
add(MBlocks.MATTER_OUTPUT_HATCH, "Matter 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") addBlock(MBlocks.COBBLESTONE_GENERATOR.values, "Cobblestone Generator")
add(MBlocks.INFINITE_WATER_SOURCE, "Infinite Water Source") add(MBlocks.INFINITE_WATER_SOURCE, "Infinite Water Source")
add(MBlocks.INFINITE_WATER_SOURCE, "desc", "Pushes water into all neighbour blocks") 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.ITEM_OUTPUT_HATCH, "Выходной предметный клапан")
add(MBlocks.MATTER_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, "Сундук разработчика")
add(MBlocks.DEV_CHEST, "desc", "Хранит все предметы, которые есть в игре") add(MBlocks.DEV_CHEST, "desc", "Хранит все предметы, которые есть в игре")
add(MBlocks.PAINTER, "Стол маляра") add(MBlocks.PAINTER, "Стол маляра")

View File

@ -41,6 +41,18 @@ fun addTags(tagsProvider: TagsProvider) {
tagsProvider.storageBlocksAsBlock.add("tritanium", MBlocks.TRITANIUM_INGOT_BLOCK) tagsProvider.storageBlocksAsBlock.add("tritanium", MBlocks.TRITANIUM_INGOT_BLOCK)
tagsProvider.blocks.Appender(BlockTags.BEACON_BASE_BLOCKS).add(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.stoneOre("tritanium", MBlocks.TRITANIUM_ORE)
tagsProvider.deepslateOre("tritanium", MBlocks.DEEPSLATE_TRITANIUM_ORE) tagsProvider.deepslateOre("tritanium", MBlocks.DEEPSLATE_TRITANIUM_ORE)
tagsProvider.singleDropOre( 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.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 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 // because i know
// someone // someone
@ -20,6 +20,6 @@ public abstract class BlockEntityMixin {
remap = false remap = false
) )
public void setRemovedListener(CallbackInfo ci) { 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.minecraft.nbt.CompoundTag
import net.neoforged.neoforge.common.util.INBTSerializable import net.neoforged.neoforge.common.util.INBTSerializable
import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent 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.getValue
import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.mc.otm.capability.MatteryPlayer 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> { abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: MatteryPlayer) : INBTSerializable<CompoundTag> {
val ply get() = android.ply val ply get() = android.ply
val syncher = Syncher() val syncher = SynchableGroup()
val syncherRemote = syncher.Remote() val syncherRemote = syncher.Remote()
open var level by syncher.int(setter = setter@{ field, value -> open var level by syncher.int(setter = setter@{ field, value ->

View File

@ -1,6 +1,5 @@
package ru.dbotthepony.mc.otm.android package ru.dbotthepony.mc.otm.android
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import net.minecraft.ChatFormatting import net.minecraft.ChatFormatting
import net.minecraft.core.HolderLookup import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag 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.bus.api.Event
import net.neoforged.neoforge.common.NeoForge import net.neoforged.neoforge.common.NeoForge
import net.neoforged.neoforge.common.util.INBTSerializable 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.getValue
import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.mc.otm.OverdriveThatMatters 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.core.nbt.set
import ru.dbotthepony.mc.otm.milliTime import ru.dbotthepony.mc.otm.milliTime
import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger
import java.io.InputStream
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlayer) : INBTSerializable<CompoundTag> { 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 ply: Player get() = capability.ply
val syncher = Syncher() val syncher = SynchableGroup()
val syncherRemote = syncher.Remote() val syncherRemote = syncher.Remote()
var isResearched by syncher.boolean() 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.TickList
import ru.dbotthepony.mc.otm.core.util.countingLazy import ru.dbotthepony.mc.otm.core.util.countingLazy
import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket 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.onceServer
import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.registry.MBlocks
import ru.dbotthepony.mc.otm.sometimeServer import ru.dbotthepony.mc.otm.sometimeServer
@ -369,8 +369,8 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
} }
} }
val syncher = Syncher() val syncher = SynchableGroup()
private val synchers = Object2ObjectArrayMap<ServerPlayer, Syncher.Remote>() private val synchers = Object2ObjectArrayMap<ServerPlayer, SynchableGroup.Remote>()
override fun setLevel(level: Level) { override fun setLevel(level: Level) {
val old = this.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.core.BlockPos
import net.minecraft.tags.BlockTags import net.minecraft.tags.BlockTags
import net.minecraft.world.level.block.Blocks 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.FurnaceBlockEntity
import net.minecraft.world.level.block.entity.HopperBlockEntity import net.minecraft.world.level.block.entity.HopperBlockEntity
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.core.multiblockConfiguration import ru.dbotthepony.mc.otm.core.multiblock.multiblockEntity
import ru.dbotthepony.mc.otm.core.multiblockEntity import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MBlocks 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 HOPPERS = multiblockEntity<HopperBlockEntity>()
private val FURNACES = multiblockEntity<FurnaceBlockEntity>() private val FURNACES = multiblockEntity<FurnaceBlockEntity>()
val CONFIG = multiblockConfiguration { val CONFIG = shapedMultiblock {
block(MBlocks.MULTIBLOCK_TEST) block(MBlocks.MULTIBLOCK_TEST)
and { and {

View File

@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.block.entity
import net.minecraft.core.HolderLookup import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.neoforged.neoforge.common.util.INBTSerializable 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.Listenable
import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.util.setValue
@ -79,7 +79,7 @@ class RedstoneControl(private val valueChanges: (new: Boolean, old: Boolean) ->
} }
class SynchronizedRedstoneControl( class SynchronizedRedstoneControl(
synchronizer: Syncher, synchronizer: SynchableGroup,
private val valueChanges: (new: Boolean, old: Boolean) -> Unit, private val valueChanges: (new: Boolean, old: Boolean) -> Unit,
) : AbstractRedstoneControl() { ) : AbstractRedstoneControl() {
override var redstoneSetting: RedstoneSetting by synchronizer.enum(RedstoneSetting.LOW, setter = { access, value -> 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.client.Minecraft
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup import net.minecraft.core.HolderLookup
import net.minecraft.core.SectionPos
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer 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.tech.GravitationStabilizerBlockEntity
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.config.ServerConfig 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.damageType
import ru.dbotthepony.mc.otm.core.getExplosionResistance import ru.dbotthepony.mc.otm.core.getExplosionResistance
import ru.dbotthepony.mc.otm.core.gracefulBlockBreak 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() { override fun tick() {
super.tick() super.tick()
if (--sleepTicks > 0) return if (--sleepTicks > 0) return
@ -245,7 +251,30 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
var sphereIterator = sphereIterator var sphereIterator = sphereIterator
if (sphereIterator == null || !sphereIterator.hasNext()) { 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 this.sphereIterator = sphereIterator
} }
@ -254,7 +283,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
while (iterations < ITERATIONS && sphereIterator.hasNext()) { while (iterations < ITERATIONS && sphereIterator.hasNext()) {
iterations++ iterations++
val pos = sphereIterator.next() + blockPos val pos = sphereIterator.next()
val getBlock = level.getBlockState(pos) val getBlock = level.getBlockState(pos)
if (!getBlock.isAir && getBlock.block !is BlackHoleBlock) { 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.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ObjectIterators
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.level.block.state.BlockState 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.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.block.entity.tech.EnergyHatchBlockEntity import ru.dbotthepony.mc.otm.block.entity.tech.EnergyHatchBlockEntity
import ru.dbotthepony.mc.otm.block.entity.tech.MatterHatchBlockEntity 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.config.MachinesConfig
import ru.dbotthepony.mc.otm.core.Multiblock import ru.dbotthepony.mc.otm.core.RandomSource2Generator
import ru.dbotthepony.mc.otm.core.MultiblockBuilder 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.getBlockStateNow
import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.times 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.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MBlockTags
import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.registry.MBlocks
import java.io.Closeable
class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.BLACK_HOLE_GENERATOR, blockPos, blockState) { class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.BLACK_HOLE_GENERATOR, blockPos, blockState) {
private var multiblock: Multiblock? = null var multiblock: ShapedMultiblock? = null
private var lastRange = -1 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 { private fun findBlackHoleRange(): Int {
val normal = blockRotation.normal val normal = blockRotation.normal
@ -52,69 +106,29 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
override fun tick() { override fun tick() {
super.tick() super.tick()
energy.tick()
matter.tick()
val level = level!! val level = level!!
if (lastRange == -1) { if (lastRange == -1) {
val found = findBlackHoleRange() this.lastRange = findBlackHoleRange()
if (found != -1) {
lastRange = found
multiblock = CONFIGURATIONS[found - 5].value.create(blockPos)
} else {
multiblock = null
}
} }
val multiblock = multiblock val multiblock = multiblock
if (multiblock != null) { if (multiblock != null) {
if (!multiblock.update(level, blockRotation.front)) { if (!multiblock.update(level, blockRotation.front)) {
val found = findBlackHoleRange() this.lastRange = findBlackHoleRange()
if (found == -1) {
this.multiblock = null
} else if (found != lastRange) {
lastRange = found
this.multiblock = CONFIGURATIONS[found - 5].value.create(blockPos)
}
} else { } else {
val blackHole = multiblock.blockEntities(BLACK_HOLE).first() 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 matter.extractMatter(MachinesConfig.BlackHoleGenerator.MATTER_RATE, false)
energy.receiveEnergy(energyToStore, false)
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
}
blackHole.mass += MachinesConfig.BlackHoleGenerator.MASS_FEEDING_RATIO * MachinesConfig.BlackHoleGenerator.MATTER_RATE 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? { override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? {
return null return BlackHoleGeneratorMenu(containerID, inventory, this)
} }
companion object { 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>) { private fun points(x: Int, z: Int, result: ObjectArraySet<BlockPos>) {
if (x == 0) { if (x == 0) {
@ -187,7 +201,7 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
val i = i2 + 5 val i = i2 + 5
accept(lazy { accept(lazy {
multiblockConfiguration { shapedMultiblock {
builder.customCheck { builder.customCheck {
val blocks = it.blocks() val blocks = it.blocks()
@ -226,9 +240,9 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
tag(MatterHatchBlockEntity.INPUT_TAG) tag(MatterHatchBlockEntity.INPUT_TAG)
} }
node.block(MBlocks.MULTIBLOCK_STRUCTURE) node.block(MBlockTags.MULTIBLOCK_STRUCTURE)
} else { } 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.*
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.core.* 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.BlockRotation
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.RelativeSide

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.config.MachinesConfig
import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer 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.menu.tech.EnergyHatchMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
@ -70,8 +70,8 @@ class EnergyHatchBlockEntity(
companion object { companion object {
const val CAPACITY = 1 const val CAPACITY = 1
val INPUT_TAG = MultiblockBuilder.EntityTag(EnergyHatchBlockEntity::class) { it.isInput } val INPUT_TAG = BlockEntityTag(EnergyHatchBlockEntity::class) { it.isInput }
val OUTPUT_TAG = MultiblockBuilder.EntityTag(EnergyHatchBlockEntity::class) { !it.isInput } val OUTPUT_TAG = BlockEntityTag(EnergyHatchBlockEntity::class) { !it.isInput }
fun input(blockPos: BlockPos, blockState: BlockState): EnergyHatchBlockEntity { fun input(blockPos: BlockPos, blockState: BlockState): EnergyHatchBlockEntity {
return EnergyHatchBlockEntity(true, MachinesConfig.ENERGY_HATCH, MBlockEntities.ENERGY_INPUT_HATCH, blockPos, blockState) 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.block.entity.decorative.CargoCrateBlockEntity
import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer 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.menu.tech.ItemHatchMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
@ -36,8 +36,8 @@ class ItemHatchBlockEntity(
companion object { companion object {
const val CAPACITY = CargoCrateBlockEntity.CAPACITY const val CAPACITY = CargoCrateBlockEntity.CAPACITY
val INPUT_TAG = MultiblockBuilder.EntityTag(ItemHatchBlockEntity::class) { it.isInput } val INPUT_TAG = BlockEntityTag(ItemHatchBlockEntity::class) { it.isInput }
val OUTPUT_TAG = MultiblockBuilder.EntityTag(ItemHatchBlockEntity::class) { !it.isInput } val OUTPUT_TAG = BlockEntityTag(ItemHatchBlockEntity::class) { !it.isInput }
fun input(blockPos: BlockPos, blockState: BlockState): ItemHatchBlockEntity { fun input(blockPos: BlockPos, blockState: BlockState): ItemHatchBlockEntity {
return ItemHatchBlockEntity(true, MBlockEntities.ITEM_INPUT_HATCH, blockPos, blockState) 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.config.MachinesConfig
import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer 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.math.Decimal
import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag
import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
@ -74,8 +74,8 @@ class MatterHatchBlockEntity(
companion object { companion object {
const val CAPACITY = 1 const val CAPACITY = 1
val INPUT_TAG = MultiblockBuilder.EntityTag(MatterHatchBlockEntity::class) { it.isInput } val INPUT_TAG = BlockEntityTag(MatterHatchBlockEntity::class) { it.isInput }
val OUTPUT_TAG = MultiblockBuilder.EntityTag(MatterHatchBlockEntity::class) { !it.isInput } val OUTPUT_TAG = BlockEntityTag(MatterHatchBlockEntity::class) { !it.isInput }
fun input(blockPos: BlockPos, blockState: BlockState): MatterHatchBlockEntity { fun input(blockPos: BlockPos, blockState: BlockState): MatterHatchBlockEntity {
return MatterHatchBlockEntity(true, MBlockEntities.MATTER_INPUT_HATCH, blockPos, blockState) 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.core.HolderLookup
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.neoforged.neoforge.common.util.INBTSerializable 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.math.Decimal
import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.nbt.set
abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<CompoundTag?> { abstract class AbstractProfiledStorage<out P>(val parent: P) : IProfiledStorage, INBTSerializable<CompoundTag?> {
val received = DecimalHistoryChart(ticks = HISTORY_SIZE) final override val received = DecimalHistoryChart(ticks = HISTORY_SIZE)
val transferred = DecimalHistoryChart(ticks = HISTORY_SIZE) final override val transferred = DecimalHistoryChart(ticks = HISTORY_SIZE)
var receivedThisTick = Decimal.ZERO final override var receivedThisTick = Decimal.ZERO
private set private set
var transferredThisTick = Decimal.ZERO final override var transferredThisTick = Decimal.ZERO
private set private set
private var isTicking = false private var isTicking = false

View File

@ -1,6 +1,7 @@
package ru.dbotthepony.mc.otm.capability package ru.dbotthepony.mc.otm.capability
import com.google.common.collect.Streams import com.google.common.collect.Streams
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import net.minecraft.ChatFormatting import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.world.entity.LivingEntity 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.collect.map
import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.shuffle
import ru.dbotthepony.mc.otm.core.util.formatFluidLevel import ru.dbotthepony.mc.otm.core.util.formatFluidLevel
import java.util.function.Supplier
import java.util.random.RandomGenerator
import java.util.stream.Stream import java.util.stream.Stream
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
@ -41,6 +45,37 @@ private val LOGGER = LogManager.getLogger()
val Player.matteryPlayer: MatteryPlayer get() = (this as IMatteryPlayer).otmPlayer val Player.matteryPlayer: MatteryPlayer get() = (this as IMatteryPlayer).otmPlayer
val LivingEntity.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 * 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.HolderLookup
import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.IntTag
import net.minecraft.nbt.ListTag import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag import net.minecraft.nbt.StringTag
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.common.custom.CustomPacketPayload import net.minecraft.network.protocol.common.custom.CustomPacketPayload
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
@ -59,7 +57,7 @@ import org.apache.logging.log4j.LogManager
import org.joml.Vector4f import org.joml.Vector4f
import ru.dbotthepony.kommons.collect.ListenableMap import ru.dbotthepony.kommons.collect.ListenableMap
import ru.dbotthepony.kommons.collect.ListenableSet 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.ListenableDelegate
import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue 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.extractEnergyExact
import ru.dbotthepony.mc.otm.capability.energy.receiveEnergyExact import ru.dbotthepony.mc.otm.capability.energy.receiveEnergyExact
import ru.dbotthepony.mc.otm.client.minecraft 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.AndroidConfig
import ru.dbotthepony.mc.otm.config.ExopackConfig import ru.dbotthepony.mc.otm.config.ExopackConfig
import ru.dbotthepony.mc.otm.container.CombinedContainer 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 * any differences in field order/types/etc between client and server
* will break *everything* * will break *everything*
*/ */
val syncher = Syncher() val syncher = SynchableGroup()
/** /**
* Remote for "this" player * Remote for "this" player
@ -198,10 +195,10 @@ class MatteryPlayer(val ply: Player) {
* any differences in field order/types/etc between client and server * any differences in field order/types/etc between client and server
* will break *everything* * will break *everything*
*/ */
val publicSyncher = Syncher() val publicSyncher = SynchableGroup()
val publicSyncherRemote = publicSyncher.Remote() 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 * 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.minecraft.world.ticks.ContainerSingleItem
import net.neoforged.neoforge.capabilities.Capabilities import net.neoforged.neoforge.capabilities.Capabilities
import net.neoforged.neoforge.common.util.INBTSerializable 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.getValue
import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.FlowDirection
@ -23,7 +23,7 @@ import ru.dbotthepony.mc.otm.triggers.ExopackBatterySlotTrigger
class BatteryBackedEnergyStorage( class BatteryBackedEnergyStorage(
private val ply: Player, private val ply: Player,
synchronizer: Syncher, synchronizer: SynchableGroup,
initialCharge: Decimal, initialCharge: Decimal,
maxCharge: Decimal, maxCharge: Decimal,
val isAndroid: Boolean, 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.capability.FlowDirection
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.capability.IProfiledStorage
import java.math.BigInteger import java.math.BigInteger
import kotlin.math.roundToInt 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. * * 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. * Upstream code SHOULD NOT check for this contract.
* *
* @return energy extracted * @return energy received
*/ */
fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal
@ -81,7 +82,7 @@ interface IMatteryEnergyStorage : IEnergyStorage {
* *
* Interface implementations generally do not need to override this. * Interface implementations generally do not need to override this.
* *
* @return energy extracted * @return energy received
*/ */
fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal {
if (howMuch.isNegative || !energyFlow.input) if (howMuch.isNegative || !energyFlow.input)
@ -122,12 +123,16 @@ interface IMatteryEnergyStorage : IEnergyStorage {
* Empties power of this energy storage * Empties power of this energy storage
* *
* @see batteryLevel * @see batteryLevel
* @return whenever operation was successful * @return whenever operation was successful, including partial completion
*/ */
fun drainBattery(): Boolean { fun drainBattery(): Boolean {
try { try {
batteryLevel = Decimal.ZERO if (canSetBatteryLevel)
batteryLevel = Decimal.ZERO
else
return false
} catch(err: UnsupportedOperationException) { } catch(err: UnsupportedOperationException) {
// anti-stupid
return false return false
} }
@ -138,12 +143,16 @@ interface IMatteryEnergyStorage : IEnergyStorage {
* Fully fills power in this energy storage * Fully fills power in this energy storage
* *
* @see batteryLevel * @see batteryLevel
* @return whenever operation was successful * @return whenever operation was successful, including partial completion
*/ */
fun fillBattery(): Boolean { fun fillBattery(): Boolean {
try { try {
batteryLevel = maxBatteryLevel if (canSetBatteryLevel)
batteryLevel = maxBatteryLevel
else
return false
} catch(err: UnsupportedOperationException) { } catch(err: UnsupportedOperationException) {
// anti-stupid
return false return false
} }
@ -374,3 +383,5 @@ fun IMatteryEnergyStorage.getBarColor(): Int {
return RGBAColor.LOW_POWER.linearInterpolation((batteryLevel / maxBatteryLevel).toFloat(), RGBAColor.FULL_POWER).toBGR() 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.capability.FlowDirection
import ru.dbotthepony.mc.otm.core.math.Decimal 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 { override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
return recordTransfer(parent.extractEnergy(howMuch, simulate), simulate) 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 { override fun fillBattery(): Boolean {
return parent?.fillBattery() ?: false 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.capability.FlowDirection
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.capability.IProfiledStorage
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -39,21 +40,41 @@ interface IMatterStorage {
/** /**
* Empties matter stored of this matter storage, if possible * Empties matter stored of this matter storage, if possible
* *
* @throws [UnsupportedOperationException]
* @see storedMatter * @see storedMatter
* @return whenever operation was successful, including partial completion
*/ */
fun drainMatter() { fun drainMatter(): Boolean {
storedMatter = Decimal.ZERO try {
if (canSetMatterLevel)
storedMatter = Decimal.ZERO
else
return false
} catch (err: UnsupportedOperationException) {
// anti-stupid
return false
}
return true
} }
/** /**
* Fully fills matter stored, if possible * Fully fills matter stored, if possible
* *
* @throws [UnsupportedOperationException]
* @see storedMatter * @see storedMatter
* @return whenever operation was successful, including partial completion
*/ */
fun fillMatter() { fun fillMatter(): Boolean {
storedMatter = maxStoredMatter 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 { fun IMatterStorage.getBarColor(): Int {
return RGBAColor.LOW_MATTER.linearInterpolation((storedMatter / maxStoredMatter).toFloat(), RGBAColor.FULL_MATTER).toBGR() 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 override val canSetMatterLevel: Boolean
get() = parent.canSetMatterLevel get() = parent.canSetMatterLevel
override fun drainMatter() { override fun drainMatter(): Boolean {
parent.drainMatter() return parent.drainMatter()
} }
override fun fillMatter() { override fun fillMatter(): Boolean {
parent.fillMatter() return parent.fillMatter()
} }
override val missingMatter: Decimal 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.minecraft.client.resources.model.ModelBakery
import net.neoforged.fml.ModLoader import net.neoforged.fml.ModLoader
import net.neoforged.neoforge.client.event.RegisterRenderBuffersEvent import net.neoforged.neoforge.client.event.RegisterRenderBuffersEvent
import ru.dbotthepony.mc.otm.client.render.blockentity.BlackHoleGeneratorRenderer
import java.io.Closeable import java.io.Closeable
private fun equals(existing: ImmutableList<RenderType>?, types: Array<out RenderType>): Boolean { 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.entityGlintDirect(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
next = State(RenderType.waterMask(), 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, true), ImmutableList.of(next.type), immutableAfter = true, chained = true)
next = State(rectRenderType(true, true, false), 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 package ru.dbotthepony.mc.otm.client.screen.panels
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen 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>( abstract class AbstractHistoryGraphPanel<out S : MatteryScreen<*>, G : AbstractHistoryChart<V>, V : Any>(
screen: S, 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.MGUIGraphics
import ru.dbotthepony.mc.otm.client.render.renderChart import ru.dbotthepony.mc.otm.client.render.renderChart
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen 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.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.Decimal 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_) { MatteryScreen<AndroidStationMenu>(p_97741_, p_97742_, p_97743_) {
private fun makeCanvas(isPreview: Boolean): DraggableCanvasPanel<AndroidStationScreen> { 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.SpritePanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls 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.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.HorizontalProfiledPowerGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
import ru.dbotthepony.mc.otm.menu.tech.EnergyHatchMenu 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) { class EnergyHatchScreen(menu: EnergyHatchMenu, inventory: Inventory, title: Component) : MatteryScreen<EnergyHatchMenu>(menu, inventory, title) {
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> { 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.makeCloseButton()
frame.onClose { onClose() } frame.onClose { onClose() }
for (slot in menu.inputSlots) { for (slot in menu.inputSlots) {
val panel = MatterCapacitorSlotPanel(this, frame, slot) val panel = BatterySlotPanel(this, frame, slot)
panel.dock = Dock.LEFT panel.dock = Dock.LEFT
panel.dockResize = DockResizeMode.NONE panel.dockResize = DockResizeMode.NONE
} }
val arrow = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND) val arrow = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND)
arrow.dockLeft = 2f arrow.dockLeft = 20f
arrow.dockRight = 2f arrow.dockRight = 2f
arrow.dock = Dock.LEFT arrow.dock = Dock.LEFT
arrow.dockResize = DockResizeMode.NONE 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) { class MatterHatchScreen(menu: MatterHatchMenu, inventory: Inventory, title: Component) : MatteryScreen<MatterHatchMenu>(menu, inventory, title) {
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> { 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.makeCloseButton()
frame.onClose { onClose() } frame.onClose { onClose() }
@ -28,7 +28,7 @@ class MatterHatchScreen(menu: MatterHatchMenu, inventory: Inventory, title: Comp
} }
val arrow = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND) val arrow = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND)
arrow.dockLeft = 2f arrow.dockLeft = 20f
arrow.dockRight = 2f arrow.dockRight = 2f
arrow.dock = Dock.LEFT arrow.dock = Dock.LEFT
arrow.dockResize = DockResizeMode.NONE 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.UVWindingOrder
import ru.dbotthepony.mc.otm.client.render.WidgetLocation import ru.dbotthepony.mc.otm.client.render.WidgetLocation
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel 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.LevelGaugeWidget
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
@ -73,7 +74,7 @@ fun <S : Screen> TallHorizontalPowerGaugePanel(
open class HorizontalProfiledPowerGaugePanel<out S : Screen>( open class HorizontalProfiledPowerGaugePanel<out S : Screen>(
screen: S, screen: S,
parent: EditablePanel<*>? = null, parent: EditablePanel<*>? = null,
widget: ProfiledLevelGaugeWidget<*>, widget: IProfiledLevelGaugeWidget,
x: Float = 0f, x: Float = 0f,
y: Float = 0f, y: Float = 0f,
width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND.width, width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND.width,
@ -93,7 +94,7 @@ open class HorizontalProfiledPowerGaugePanel<out S : Screen>(
fun <S : Screen> TallHorizontalProfiledPowerGaugePanel( fun <S : Screen> TallHorizontalProfiledPowerGaugePanel(
screen: S, screen: S,
parent: EditablePanel<*>? = null, parent: EditablePanel<*>? = null,
widget: ProfiledLevelGaugeWidget<*>, widget: IProfiledLevelGaugeWidget,
x: Float = 0f, x: Float = 0f,
y: Float = 0f, y: Float = 0f,
width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width, 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.DefaultVertexFormat
import com.mojang.blaze3d.vertex.VertexFormat import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.datafixers.util.Either 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.gui.screens.Screen
import net.minecraft.client.renderer.GameRenderer import net.minecraft.client.renderer.GameRenderer
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.FormattedText import net.minecraft.network.chat.FormattedText
import net.minecraft.world.inventory.tooltip.TooltipComponent import net.minecraft.world.inventory.tooltip.TooltipComponent
import org.lwjgl.opengl.GL11 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.ShiftPressedCond
import ru.dbotthepony.mc.otm.client.isShiftDown import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.WidgetLocation import ru.dbotthepony.mc.otm.client.render.WidgetLocation
import ru.dbotthepony.mc.otm.client.render.tesselator import ru.dbotthepony.mc.otm.client.render.tesselator
import ru.dbotthepony.mc.otm.client.render.uv import ru.dbotthepony.mc.otm.client.render.uv
import ru.dbotthepony.mc.otm.client.render.vertex import ru.dbotthepony.mc.otm.client.render.vertex
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel 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.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.util.formatHistory 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.core.util.formatMatterLevel
import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget 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.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.core.util.formatHistory import ru.dbotthepony.mc.otm.core.util.formatHistory
import ru.dbotthepony.mc.otm.core.util.formatPowerLevel 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.LevelGaugeWidget
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
@ -92,7 +93,7 @@ fun <S : Screen> WidePowerGaugePanel(
open class ProfiledPowerGaugePanel<out S : Screen>( open class ProfiledPowerGaugePanel<out S : Screen>(
screen: S, screen: S,
parent: EditablePanel<*>? = null, parent: EditablePanel<*>? = null,
val profiledWidget: ProfiledLevelGaugeWidget<*>, val profiledWidget: IProfiledLevelGaugeWidget,
x: Float = 0f, x: Float = 0f,
y: Float = 0f, y: Float = 0f,
width: Float = GAUGE_BACKGROUND.width, width: Float = GAUGE_BACKGROUND.width,
@ -117,7 +118,7 @@ open class ProfiledPowerGaugePanel<out S : Screen>(
fun <S : Screen> WideProfiledPowerGaugePanel( fun <S : Screen> WideProfiledPowerGaugePanel(
screen: S, screen: S,
parent: EditablePanel<*>? = null, parent: EditablePanel<*>? = null,
widget: ProfiledLevelGaugeWidget<*>, widget: IProfiledLevelGaugeWidget,
x: Float = 0f, x: Float = 0f,
y: Float = 0f, y: Float = 0f,
width: Float = 18f, width: Float = 18f,

View File

@ -4,18 +4,15 @@ import net.minecraft.ChatFormatting
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.neoforged.neoforge.capabilities.BlockCapability import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage import ru.dbotthepony.mc.otm.capability.IProfiledStorage
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.compat.jade.JadeColors import ru.dbotthepony.mc.otm.compat.jade.JadeColors
import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys
import ru.dbotthepony.mc.otm.compat.jade.JadeUids import ru.dbotthepony.mc.otm.compat.jade.JadeUids
import ru.dbotthepony.mc.otm.core.TranslatableComponent 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.getCapability
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.getDecimal import ru.dbotthepony.mc.otm.core.math.getDecimal
import ru.dbotthepony.mc.otm.core.math.putDecimal import ru.dbotthepony.mc.otm.core.math.putDecimal
import ru.dbotthepony.mc.otm.core.util.formatMatter import ru.dbotthepony.mc.otm.core.util.formatMatter
@ -37,14 +34,14 @@ object MatterStorageProvider : IBlockComponentProvider, IServerDataProvider<Bloc
matterData.putDecimal("storedMatter", it.storedMatter) matterData.putDecimal("storedMatter", it.storedMatter)
matterData.putDecimal("maxStoredMatter", it.maxStoredMatter) matterData.putDecimal("maxStoredMatter", it.maxStoredMatter)
if (it is AbstractProfiledStorage<*>) { if (it is IProfiledStorage) {
val profiledData = CompoundTag() val profiledData = CompoundTag()
// profiledData.putDecimal("lastTickReceive", it.lastTickReceive) // profiledData.putDecimal("lastTickReceive", it.lastTickReceive)
// profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer) // profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer)
profiledData.putDecimal("weightedReceive", it.received.calcWeightedAverage()) profiledData.putDecimal("weightedReceive", it.received.calculateAverage())
profiledData.putDecimal("weightedTransfer", it.transferred.calcWeightedAverage()) profiledData.putDecimal("weightedTransfer", it.transferred.calculateAverage())
matterData.put("profiledData", profiledData) matterData.put("profiledData", profiledData)
} }

View File

@ -4,7 +4,6 @@ import net.minecraft.ChatFormatting
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.compat.jade.JadeColors import ru.dbotthepony.mc.otm.compat.jade.JadeColors
import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys 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.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.kommons.math.RGBAColor 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.getCapability
import ru.dbotthepony.mc.otm.core.math.getDecimal import ru.dbotthepony.mc.otm.core.math.getDecimal
import ru.dbotthepony.mc.otm.core.math.putDecimal import ru.dbotthepony.mc.otm.core.math.putDecimal
@ -31,14 +31,14 @@ object MatteryEnergyProvider : IBlockComponentProvider, IServerDataProvider<Bloc
energyData.putDecimal("batteryLevel", it.batteryLevel) energyData.putDecimal("batteryLevel", it.batteryLevel)
energyData.putDecimal("maxBatteryLevel", it.maxBatteryLevel) energyData.putDecimal("maxBatteryLevel", it.maxBatteryLevel)
if (it is AbstractProfiledStorage<*>) { if (it is IProfiledStorage) {
val profiledData = CompoundTag() val profiledData = CompoundTag()
// profiledData.putDecimal("lastTickReceive", it.lastTickReceive) // profiledData.putDecimal("lastTickReceive", it.lastTickReceive)
// profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer) // profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer)
profiledData.putDecimal("weightedReceive", it.received.calcWeightedAverage()) profiledData.putDecimal("weightedReceive", it.received.calculateAverage())
profiledData.putDecimal("weightedTransfer", it.transferred.calcWeightedAverage()) profiledData.putDecimal("weightedTransfer", it.transferred.calculateAverage())
energyData.put("profiledData", profiledData) 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.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder 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.IntAVLTreeSet
import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.ints.IntComparators 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.core.registries.BuiltInRegistries
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.NbtOps import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.Tag import net.minecraft.nbt.Tag
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.Container import net.minecraft.world.Container
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.entity.player.StackedContents 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.minecraft.world.item.Items
import net.neoforged.neoforge.common.util.INBTSerializable import net.neoforged.neoforge.common.util.INBTSerializable
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.collect.ListenableMap
import ru.dbotthepony.kommons.util.Delegate 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.container.util.IContainerSlot
import ru.dbotthepony.mc.otm.core.addSorted import ru.dbotthepony.mc.otm.core.addSorted
import ru.dbotthepony.mc.otm.core.collect.any 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.collect.toList
import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.isNotEmpty 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.data.minRange
import ru.dbotthepony.mc.otm.network.StreamCodecs 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.ISynchable
import ru.dbotthepony.mc.otm.network.syncher.SynchableObservedDelegate import ru.dbotthepony.mc.otm.network.syncher.SynchableObservedDelegate
import java.util.* import java.util.*

View File

@ -59,6 +59,7 @@ import java.util.concurrent.Callable
import java.util.concurrent.Future import java.util.concurrent.Future
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Supplier import java.util.function.Supplier
import java.util.random.RandomGenerator
import java.util.stream.Stream import java.util.stream.Stream
import java.util.stream.StreamSupport import java.util.stream.StreamSupport
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
@ -154,6 +155,24 @@ fun <T : Enum<T>> T.prev(values: Array<out T>): T {
return values[next] 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> { inline fun <T : Any> immutableList(size: Int, initializer: (index: Int) -> T): ImmutableList<T> {
require(size >= 0) { "Invalid list size $size" } 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.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
@ -14,16 +14,9 @@ import ru.dbotthepony.mc.otm.network.syncher.ISynchable
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
abstract class AbstractHistoryChart<V : Any>( abstract class AbstractHistoryChart<V : Any>(
/** final override val resolution: Int,
* How many measurements one graph value contains final override val width: Int,
*/ ) : IHistoryChart<V>, ISynchable, INBTSerializable<CompoundTag> {
val resolution: Int,
/**
* Limit on how many graph values to store
*/
val width: Int,
) : Iterable<V>, INBTSerializable<CompoundTag>, ISynchable {
constructor(ticks: Int) : this(1, ticks) constructor(ticks: Int) : this(1, ticks)
init { init {
@ -39,13 +32,13 @@ abstract class AbstractHistoryChart<V : Any>(
protected abstract fun sum(input: List<V>): V protected abstract fun sum(input: List<V>): V
protected abstract fun identity(): 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" } require(width >= 0) { "Invalid time frame: $width" }
if (width == 0 || this.values.isEmpty()) if (width == 0 || this.values.isEmpty())
return identity() 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 { fun calculateSum(width: Int = this.width): V {
@ -61,6 +54,11 @@ abstract class AbstractHistoryChart<V : Any>(
abstract val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V> abstract val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>
private val remotes = CopyOnWriteArrayList<RemoteState>() 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 { private inner class RemoteState(val listener: Runnable) : IRemoteState {
init { 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()) { if (stream.readBoolean()) {
values.clear() values.clear()
} }
@ -110,11 +110,11 @@ abstract class AbstractHistoryChart<V : Any>(
while (values.size > width) values.removeLast() while (values.size > width) values.removeLast()
} }
override fun createRemoteState(listener: Runnable): IRemoteState { final override fun createRemoteState(listener: Runnable): IRemoteState {
return RemoteState(listener) return RemoteState(listener)
} }
fun calculateAverage(): V { final override fun calculateAverage(): V {
if (values.isEmpty()) { if (values.isEmpty()) {
return identity() return identity()
} }
@ -143,7 +143,7 @@ abstract class AbstractHistoryChart<V : Any>(
val size: Int val size: Int
get() = values.size get() = values.size
operator fun get(index: Int): V { final override operator fun get(index: Int): V {
return values.getOrNull(index) ?: identity() return values.getOrNull(index) ?: identity()
} }
@ -158,7 +158,7 @@ abstract class AbstractHistoryChart<V : Any>(
it.group( it.group(
list.fieldOf("accumulator").forGetter { it.accumulated }, list.fieldOf("accumulator").forGetter { it.accumulated },
list.fieldOf("values").forGetter { it.values }, 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.google.common.collect.ImmutableList
import com.mojang.serialization.Codec import com.mojang.serialization.Codec
import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.network.RegistryFriendlyByteBuf
import ru.dbotthepony.mc.otm.core.collect.reduce 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.core.math.Decimal
import ru.dbotthepony.mc.otm.data.DecimalCodec import ru.dbotthepony.mc.otm.data.DecimalCodec
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
@ -33,10 +34,6 @@ class DecimalHistoryChart : AbstractHistoryChart<Decimal> {
override val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, Decimal> override val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, Decimal>
get() = DecimalCodec.NETWORK get() = DecimalCodec.NETWORK
fun calcWeightedAverage(): Decimal {
return calcWeightedAverage(this::get)
}
companion object { companion object {
private val HISTORY_WEIGHTERS: ImmutableList<Decimal> = immutableList { private val HISTORY_WEIGHTERS: ImmutableList<Decimal> = immutableList {
/*for (i in 0 until 20) { /*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.Decimal
import ru.dbotthepony.mc.otm.core.math.isNegative import ru.dbotthepony.mc.otm.core.math.isNegative
import ru.dbotthepony.mc.otm.core.math.isZero 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 ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import java.math.BigInteger import java.math.BigInteger
import java.util.function.BooleanSupplier import java.util.function.BooleanSupplier
@ -278,7 +279,7 @@ fun formatTickDuration(ticks: Int, longFormat: Boolean = false): String {
*/ */
fun formatHistory( fun formatHistory(
result: MutableList<Either<FormattedText, TooltipComponent>>, result: MutableList<Either<FormattedText, TooltipComponent>>,
widget: ProfiledLevelGaugeWidget<*>, widget: IProfiledLevelGaugeWidget,
bias: Int = 0, bias: Int = 0,
decimals: Int = 3, decimals: Int = 3,
verbose: BooleanSupplier = never, verbose: BooleanSupplier = never,
@ -293,7 +294,7 @@ fun formatHistory(
private fun formatHistoryChart( private fun formatHistoryChart(
result: MutableList<Either<FormattedText, TooltipComponent>>, result: MutableList<Either<FormattedText, TooltipComponent>>,
widget: ProfiledLevelGaugeWidget<*>, widget: IProfiledLevelGaugeWidget,
bias: Int = 0, bias: Int = 0,
decimals: Int = 3, decimals: Int = 3,
verbose: BooleanSupplier = never, verbose: BooleanSupplier = never,
@ -378,7 +379,7 @@ private fun formatHistoryChart(
) )
) )
} else { } 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>() val labelNames = Float2ObjectArrayMap<Component>()
labelNames[0.5f] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias) labelNames[0.5f] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
@ -446,7 +447,7 @@ private fun formatHistoryChart(
private fun formatHistoryLines( private fun formatHistoryLines(
result: MutableList<Either<FormattedText, TooltipComponent>>, result: MutableList<Either<FormattedText, TooltipComponent>>,
widget: ProfiledLevelGaugeWidget<*>, widget: IProfiledLevelGaugeWidget,
bias: Int = 0, bias: Int = 0,
decimals: Int = 3, decimals: Int = 3,
verbose: BooleanSupplier = never, verbose: BooleanSupplier = never,
@ -476,8 +477,8 @@ private fun formatHistoryLines(
} }
addLine( addLine(
widget.received.calcWeightedAverage(), widget.received.calculateAverage(),
widget.transferred.calcWeightedAverage() widget.transferred.calculateAverage()
) )
if (verbose.asBoolean) { 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.encode
import ru.dbotthepony.mc.otm.network.nullable import ru.dbotthepony.mc.otm.network.nullable
import ru.dbotthepony.mc.otm.network.readByteListUnbounded 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.wrap
import ru.dbotthepony.mc.otm.network.writeByteListUnbounded import ru.dbotthepony.mc.otm.network.writeByteListUnbounded
import java.util.* import java.util.*
@ -76,7 +76,7 @@ abstract class MatteryMenu(
/** /**
* Server->Client synchronizer * Server->Client synchronizer
*/ */
val mSynchronizer = Syncher() val mSynchronizer = SynchableGroup()
val synchronizerRemote = mSynchronizer.Remote() val synchronizerRemote = mSynchronizer.Remote()
val player: Player get() = inventory.player val player: Player get() = inventory.player
val random: RandomSource = RandomSource.create() 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.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.registry.MMenus import ru.dbotthepony.mc.otm.registry.MMenus
class BatteryBankMenu @JvmOverloads constructor( class BatteryBankMenu(
p_38852_: Int, p_38852_: Int,
inventory: Inventory, inventory: Inventory,
tile: BatteryBankBlockEntity? = null, 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.entity.RedstoneSetting
import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock
import ru.dbotthepony.mc.otm.block.entity.tech.EnergyCounterBlockEntity 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.Decimal
import ru.dbotthepony.mc.otm.core.math.toDecimal import ru.dbotthepony.mc.otm.core.math.toDecimal
import ru.dbotthepony.mc.otm.menu.MatteryMenu 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.FluidStack
import net.neoforged.neoforge.fluids.capability.IFluidHandler 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.kommons.util.getValue
import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.menu.MatteryMenu 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.IntSupplier
import java.util.function.Supplier import java.util.function.Supplier
class FluidGaugeWidget(synchronizer: Syncher) { class FluidGaugeWidget(synchronizer: SynchableGroup) {
constructor(menu: MatteryMenu) : this(menu.mSynchronizer) constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
var maxCapacitySupplier = IntSupplier { 0 } var maxCapacitySupplier = IntSupplier { 0 }
@ -27,7 +27,7 @@ class FluidGaugeWidget(synchronizer: Syncher) {
return (fluid.amount.toFloat() / maxCapacity.toFloat()).coerceIn(0f, 1f) 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) { if (fluid != null) {
with(fluid, tank) 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.core.math.Decimal
import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.network.StreamCodecs 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") @Suppress("unused")
class LevelGaugeWidget(synchronizer: Syncher) { class LevelGaugeWidget(synchronizer: SynchableGroup) {
constructor(menu: MatteryMenu) : this(menu.mSynchronizer) constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
var levelProvider = { Decimal.ONE } var levelProvider = { Decimal.ONE }
@ -56,7 +56,7 @@ class LevelGaugeWidget(synchronizer: Syncher) {
} }
constructor( constructor(
synchronizer: Syncher, synchronizer: SynchableGroup,
power: IMatteryEnergyStorage? power: IMatteryEnergyStorage?
) : this(synchronizer) { ) : this(synchronizer) {
if (power != null) { if (power != null) {
@ -65,7 +65,7 @@ class LevelGaugeWidget(synchronizer: Syncher) {
} }
constructor( constructor(
synchronizer: Syncher, synchronizer: SynchableGroup,
matter: IMatterStorage? matter: IMatterStorage?
) : this(synchronizer) { ) : this(synchronizer) {
if (matter != null) { if (matter != null) {
@ -74,7 +74,7 @@ class LevelGaugeWidget(synchronizer: Syncher) {
} }
constructor( constructor(
synchronizer: Syncher, synchronizer: SynchableGroup,
patterns: IPatternStorage? patterns: IPatternStorage?
) : this(synchronizer) { ) : this(synchronizer) {
if (patterns != null) { if (patterns != null) {
@ -83,7 +83,7 @@ class LevelGaugeWidget(synchronizer: Syncher) {
} }
constructor( constructor(
synchronizer: Syncher, synchronizer: SynchableGroup,
level: () -> Decimal, level: () -> Decimal,
maxLevel: () -> Decimal, maxLevel: () -> Decimal,
) : this(synchronizer) { ) : this(synchronizer) {

View File

@ -3,30 +3,31 @@ package ru.dbotthepony.mc.otm.menu.widget
import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage.Companion.HISTORY_SIZE 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.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage 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.core.math.Decimal
import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.network.StreamCodecs 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<*>>( class ProfiledLevelGaugeWidget<P : AbstractProfiledStorage<*>>(
synchronizer: Syncher, synchronizer: SynchableGroup,
val storage: P?, val storage: P?,
val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer) override val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer)
) { ) : IProfiledLevelGaugeWidget {
constructor( constructor(
menu: MatteryMenu, menu: MatteryMenu,
storage: P?, storage: P?,
gauge: LevelGaugeWidget = LevelGaugeWidget(menu.mSynchronizer) gauge: LevelGaugeWidget = LevelGaugeWidget(menu.mSynchronizer)
) : this(menu.mSynchronizer, storage, gauge) ) : this(menu.mSynchronizer, storage, gauge)
val received = storage?.received ?: DecimalHistoryChart(ticks = HISTORY_SIZE) override val received = storage?.received ?: DecimalHistoryChart(ticks = HISTORY_SIZE)
val transferred = storage?.transferred ?: DecimalHistoryChart(ticks = HISTORY_SIZE) override val transferred = storage?.transferred ?: DecimalHistoryChart(ticks = HISTORY_SIZE)
val receivedThisTick by synchronizer.computed({ storage?.receivedThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL) override val receivedThisTick by synchronizer.computed({ storage?.receivedThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
val transferredThisTick by synchronizer.computed({ storage?.transferredThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL) override val transferredThisTick by synchronizer.computed({ storage?.transferredThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
init { init {
if (storage is IMatterStorage) if (storage is IMatterStorage)

View File

@ -1,6 +1,6 @@
package ru.dbotthepony.mc.otm.menu.widget 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.kommons.util.getValue
import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
@ -9,7 +9,7 @@ import ru.dbotthepony.mc.otm.menu.MatteryMenu
import java.util.function.BooleanSupplier import java.util.function.BooleanSupplier
@Suppress("unused") @Suppress("unused")
class ProgressGaugeWidget(synchronizer: Syncher) { class ProgressGaugeWidget(synchronizer: SynchableGroup) {
constructor(menu: MatteryMenu) : this(menu.mSynchronizer) constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
var progressSupplier: FloatSupplier = FloatSupplier { 0f } var progressSupplier: FloatSupplier = FloatSupplier { 0f }
@ -44,14 +44,14 @@ class ProgressGaugeWidget(synchronizer: Syncher) {
} }
constructor( constructor(
synchronizer: Syncher, synchronizer: SynchableGroup,
progress: FloatSupplier progress: FloatSupplier
) : this(synchronizer) { ) : this(synchronizer) {
this.progressSupplier = progress this.progressSupplier = progress
} }
constructor( constructor(
synchronizer: Syncher, synchronizer: SynchableGroup,
blockEntity: MatteryWorkerBlockEntity<*>? blockEntity: MatteryWorkerBlockEntity<*>?
) : this(synchronizer) { ) : this(synchronizer) {
if (blockEntity != null) { if (blockEntity != null) {
@ -60,7 +60,7 @@ class ProgressGaugeWidget(synchronizer: Syncher) {
} }
constructor( constructor(
synchronizer: Syncher, synchronizer: SynchableGroup,
job: MachineJobEventLoop<*>? job: MachineJobEventLoop<*>?
) : this(synchronizer) { ) : this(synchronizer) {
if (job != null) { 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 net.minecraft.network.RegistryFriendlyByteBuf
import ru.dbotthepony.kommons.util.Observer import ru.dbotthepony.kommons.util.Observer
/**
* An object capable of synching its state to remotes
*/
interface ISynchable : Observer { interface ISynchable : Observer {
fun read(stream: RegistryFriendlyByteBuf) fun read(stream: RegistryFriendlyByteBuf)
/** /**
* Provided [listener] is to be considered thread-safe, and can be called at any time * 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 fun createRemoteState(listener: Runnable): IRemoteState
val shouldBeObserved: Boolean val shouldBeObserved: Boolean
get() = false 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 { override fun observe(): Boolean {
// no op // no op
return false 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.ListenableDelegate
import ru.dbotthepony.kommons.util.Observer import ru.dbotthepony.kommons.util.Observer
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
import java.util.concurrent.atomic.AtomicInteger
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Supplier 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 listeners = Listenable.Impl<V>()
private val l = delegate.addListener(listeners) 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) { override fun read(stream: RegistryFriendlyByteBuf) {
isRemote = false
delegate.accept(codec.decode(stream)) delegate.accept(codec.decode(stream))
} }
inner class Remote(listener: Runnable) : IRemoteState { inner class Remote(listener: Runnable) : IRemoteState {
private val l = listeners.addListener(listener) private val l = listeners.addListener(listener)
init {
remoteCount.incrementAndGet()
}
override fun write(stream: RegistryFriendlyByteBuf) { override fun write(stream: RegistryFriendlyByteBuf) {
codec.encode(stream, delegate.get()) codec.encode(stream, delegate.get())
} }
@ -31,6 +44,7 @@ class SynchableDelegate<V>(val delegate: ListenableDelegate<V>, val codec: Matte
override fun close() { override fun close() {
l.remove() 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.ListenableDelegate
import ru.dbotthepony.kommons.util.Observer import ru.dbotthepony.kommons.util.Observer
import ru.dbotthepony.mc.otm.OTM_CLEANER 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.core.math.Decimal
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
import ru.dbotthepony.mc.otm.network.StreamCodecs 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.IntSupplier
import java.util.function.LongSupplier import java.util.function.LongSupplier
import java.util.function.Supplier import java.util.function.Supplier
import kotlin.collections.ArrayList
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
/** /**
* Universal, one-to-many delegate/value synchronizer. * Universal, one-to-many delegate/value synchronizer.
* *
* Values and delegates can be attached using [add], or by constructing subclassed directly. * Values and delegates can be attached using [add];
* Delta changes are tracked by [Syncher.Remote] instances. * Delta changes are tracked by [SynchableGroup.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.
* *
* Attached delegates can be safely mutated by multiple threads concurrently * Attached delegates can be safely mutated by multiple threads concurrently
* (attached [ListenableDelegate] can call [Listenable.addListener] callback from different threads). * (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") @Suppress("UNUSED")
class Syncher : Observer, ISynchable { class SynchableGroup : Observer, ISynchable, Iterable<ISynchable> {
private val lock = ReentrantLock() private val lock = ReentrantLock()
private val slots = ArrayList<Slot?>() private val slots = ArrayList<Slot?>()
private val gaps = IntAVLTreeSet() private val gaps = IntAVLTreeSet()
private val observers = ArrayList<Slot>() private val observers = ArrayList<Slot>()
private val remotes = ArrayList<Remote>() 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 { override fun observe(): Boolean {
var any = false var any = false
@ -80,7 +89,7 @@ class Syncher : Observer, ISynchable {
var readID = stream.readVarInt() var readID = stream.readVarInt()
while (readID > 0) { 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) slot.synchable.read(stream)
readID = stream.readVarInt() readID = stream.readVarInt()
} }
@ -95,18 +104,17 @@ class Syncher : Observer, ISynchable {
internal inner class Slot(val synchable: ISynchable) : Closeable, Observer { internal inner class Slot(val synchable: ISynchable) : Closeable, Observer {
val id: Int val id: Int
private val isRemoved = AtomicBoolean() private var isRemoved = false
val remoteSlots = CopyOnWriteArrayList<Remote.RemoteSlot>() val remoteSlots = CopyOnWriteArrayList<Remote.RemoteSlot>()
override fun close() { override fun close() {
if (isRemoved.compareAndSet(false, true)) { if (!isRemoved) {
lock.withLock { isRemoved = true
slots[id] = null slots[id] = null
gaps.add(id) gaps.add(id)
observers.remove(this) observers.remove(this)
remoteSlots.forEach { it.remove() } remoteSlots.forEach { it.remove() }
}
} }
} }
@ -115,7 +123,7 @@ class Syncher : Observer, ISynchable {
} }
private fun markDirty() { private fun markDirty() {
if (!isRemote && !isRemoved.get()) { if (!isRemote && !isRemoved) {
remoteSlots.forEach { remoteSlots.forEach {
it.markDirty() it.markDirty()
} }
@ -375,7 +383,7 @@ class Syncher : Observer, ISynchable {
/** /**
* Returns null if this remote is clean. * 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. * reasons, you must call it manually.
*/ */
fun write(registry: RegistryAccess): ByteArrayList? { fun write(registry: RegistryAccess): ByteArrayList? {
@ -421,18 +429,17 @@ class Syncher : Observer, ISynchable {
override fun close() { override fun close() {
if (!isRemoved) { if (!isRemoved) {
isRemoved = true
lock.withLock { lock.withLock {
if (!isRemoved) { remoteSlots.forEach {
remoteSlots.forEach { it.remove()
it.remove() it.slot.remoteSlots.remove(it)
it.slot.remoteSlots.remove(it)
}
remotes.remove(this)
} }
}
dirty.clear() remotes.remove(this)
dirty.clear()
}
} }
} }
} }

View File

@ -12,7 +12,12 @@ class SynchableMap<K, V>(
val keyCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, K>, val keyCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, K>,
val valueCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V> val valueCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>
) : ISynchable { ) : ISynchable {
override var isRemote: Boolean = false
private set
override fun read(stream: RegistryFriendlyByteBuf) { override fun read(stream: RegistryFriendlyByteBuf) {
isRemote = true
var action = stream.readByte().toInt() var action = stream.readByte().toInt()
while (true) { while (true) {
@ -105,6 +110,9 @@ class SynchableMap<K, V>(
} }
} }
override val hasRemotes: Boolean
get() = remoteSlots.isNotEmpty()
override fun createRemoteState(listener: Runnable): IRemoteState { override fun createRemoteState(listener: Runnable): IRemoteState {
return RemoteState(listener) 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.ListenableDelegate
import ru.dbotthepony.kommons.util.ValueObserver import ru.dbotthepony.kommons.util.ValueObserver
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import java.util.function.Consumer import java.util.function.Consumer
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
@ -14,10 +15,15 @@ class SynchableObservedDelegate<V>(val delegate: Delegate<V>, val codec: Mattery
private object Mark private object Mark
private val listeners = Listenable.Impl<V>() private val listeners = Listenable.Impl<V>()
private val remoteCount = AtomicInteger()
private var observed: Any? = Mark private var observed: Any? = Mark
private val observeLock = ReentrantLock() private val observeLock = ReentrantLock()
override var isRemote: Boolean = false
private set
override fun read(stream: RegistryFriendlyByteBuf) { override fun read(stream: RegistryFriendlyByteBuf) {
isRemote = true
val read = codec.decode(stream) val read = codec.decode(stream)
observed = codec.copy(read) observed = codec.copy(read)
delegate.accept(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 inner class RemoteState(listener: Runnable) : IRemoteState {
private val l = listeners.addListener(listener) private val l = listeners.addListener(listener)
init {
remoteCount.incrementAndGet()
}
override fun write(stream: RegistryFriendlyByteBuf) { override fun write(stream: RegistryFriendlyByteBuf) {
codec.encode(stream, delegate.get()) codec.encode(stream, delegate.get())
} }
@ -85,9 +95,13 @@ class SynchableObservedDelegate<V>(val delegate: Delegate<V>, val codec: Mattery
override fun close() { override fun close() {
l.remove() 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 { override fun createRemoteState(listener: Runnable): IRemoteState {
return RemoteState(listener) 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 { override fun createRemoteState(listener: Runnable): IRemoteState {
return RemoteState(listener) return RemoteState(listener)
} }
@ -96,6 +102,8 @@ class SynchableSet<V>(val delegate: ListenableSet<V>, val codec: MatteryStreamCo
} }
override fun read(stream: RegistryFriendlyByteBuf) { override fun read(stream: RegistryFriendlyByteBuf) {
isRemote = true
var action = stream.readByte().toInt() var action = stream.readByte().toInt()
while (true) { while (true) {

View File

@ -114,18 +114,17 @@ object MBlockEntities {
} }
private fun registerClient(event: FMLClientSetupEvent) { private fun registerClient(event: FMLClientSetupEvent) {
event.enqueueWork { BlockEntityRenderers.register(BLACK_HOLE, ::BlackHoleRenderer)
BlockEntityRenderers.register(BLACK_HOLE, ::BlackHoleRenderer) BlockEntityRenderers.register(BLACK_HOLE_GENERATOR, ::BlackHoleGeneratorRenderer)
BlockEntityRenderers.register(GRAVITATION_STABILIZER, ::GravitationStabilizerRenderer) BlockEntityRenderers.register(GRAVITATION_STABILIZER, ::GravitationStabilizerRenderer)
BlockEntityRenderers.register(ENERGY_COUNTER, ::EnergyCounterRenderer) BlockEntityRenderers.register(ENERGY_COUNTER, ::EnergyCounterRenderer)
BlockEntityRenderers.register(BATTERY_BANK, ::BatteryBankRenderer) BlockEntityRenderers.register(BATTERY_BANK, ::BatteryBankRenderer)
BlockEntityRenderers.register(MATTER_CAPACITOR_BANK, ::MatterBatteryBankRenderer) BlockEntityRenderers.register(MATTER_CAPACITOR_BANK, ::MatterBatteryBankRenderer)
BlockEntityRenderers.register(MATTER_RECONSTRUCTOR, ::MatterReconstructorRenderer) BlockEntityRenderers.register(MATTER_RECONSTRUCTOR, ::MatterReconstructorRenderer)
BlockEntityRenderers.register(MATTER_REPLICATOR, ::MatterReplicatorRenderer) BlockEntityRenderers.register(MATTER_REPLICATOR, ::MatterReplicatorRenderer)
BlockEntityRenderers.register(MATTER_SCANNER, ::MatterScannerRenderer) BlockEntityRenderers.register(MATTER_SCANNER, ::MatterScannerRenderer)
BlockEntityRenderers.register(POWERED_SMOKER, ::PoweredSmokerRenderer) BlockEntityRenderers.register(POWERED_SMOKER, ::PoweredSmokerRenderer)
BlockEntityRenderers.register(HOLO_SIGN, ::HoloSignRenderer) BlockEntityRenderers.register(HOLO_SIGN, ::HoloSignRenderer)
BlockEntityRenderers.register(FLUID_TANK, ::FluidTankRenderer) 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 HARDENED_GLASS_YELLOW: TagKey<Block> = BlockTags.create(ResourceLocation("c", "hardened_glass/yellow"))
val MACHINES: TagKey<Block> = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "machines")) 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 FLUID_TANK: FluidTankBlock by registry.register(MNames.FLUID_TANK) { FluidTankBlock() }
val DEV_CHEST: DevChestBlock by registry.register(MNames.DEV_CHEST) { DevChestBlock() } 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 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 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)) } 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 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 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_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 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) } 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.AndroidChargerScreen
import ru.dbotthepony.mc.otm.client.screen.tech.AndroidStationScreen 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.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.ChemicalGeneratorScreen
import ru.dbotthepony.mc.otm.client.screen.tech.CobblerScreen import ru.dbotthepony.mc.otm.client.screen.tech.CobblerScreen
import ru.dbotthepony.mc.otm.client.screen.tech.EnergyCounterScreen 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.AndroidChargerMenu
import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
import ru.dbotthepony.mc.otm.menu.tech.BatteryBankMenu 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.ChemicalGeneratorMenu
import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu
import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu 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 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_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 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_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) } 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(MATTER_OUTPUT_HATCH, ::MatterHatchScreen)
event.register(ENERGY_INPUT_HATCH, ::EnergyHatchScreen) event.register(ENERGY_INPUT_HATCH, ::EnergyHatchScreen)
event.register(ENERGY_OUTPUT_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 ANDROID_CHARGER = "android_charger"
const val INFINITE_WATER_SOURCE = "infinite_water_source" const val INFINITE_WATER_SOURCE = "infinite_water_source"
const val DEV_CHEST = "dev_chest" const val DEV_CHEST = "dev_chest"
const val MULTIBLOCK_STRUCTURE = "multiblock_structure"
const val BLACK_HOLE_GENERATOR = "black_hole_generator" const val BLACK_HOLE_GENERATOR = "black_hole_generator"
const val MATTER_INJECTOR = "matter_injector" const val MATTER_INJECTOR = "matter_injector"
const val ANTIMATTER_INJECTOR = "antimatter_injector" const val ANTIMATTER_INJECTOR = "antimatter_injector"