Compare commits
5 Commits
bb7d3425f4
...
35ae7ed88a
Author | SHA1 | Date | |
---|---|---|---|
35ae7ed88a | |||
58af515f9a | |||
b832873203 | |||
e2d4f810d4 | |||
fb573cb483 |
@ -515,6 +515,10 @@ private fun blocks(provider: MatteryLanguageProvider) {
|
||||
add(MBlocks.ITEM_OUTPUT_HATCH, "Item Output Hatch")
|
||||
add(MBlocks.MATTER_OUTPUT_HATCH, "Matter Output Hatch")
|
||||
|
||||
add(MBlocks.MATTER_INJECTOR, "Matter Injector")
|
||||
add(MBlocks.ANTIMATTER_INJECTOR, "Antimatter Injector")
|
||||
add(MBlocks.HIGH_ENERGY_PARTICLE_COLLECTOR, "High Energy Particle Collector")
|
||||
|
||||
addBlock(MBlocks.COBBLESTONE_GENERATOR.values, "Cobblestone Generator")
|
||||
add(MBlocks.INFINITE_WATER_SOURCE, "Infinite Water Source")
|
||||
add(MBlocks.INFINITE_WATER_SOURCE, "desc", "Pushes water into all neighbour blocks")
|
||||
|
@ -525,6 +525,10 @@ private fun blocks(provider: MatteryLanguageProvider) {
|
||||
add(MBlocks.ITEM_OUTPUT_HATCH, "Выходной предметный клапан")
|
||||
add(MBlocks.MATTER_OUTPUT_HATCH, "Выходной клапан материи")
|
||||
|
||||
add(MBlocks.MATTER_INJECTOR, "Инжектор материи")
|
||||
add(MBlocks.ANTIMATTER_INJECTOR, "Инжектор анти-материи")
|
||||
add(MBlocks.HIGH_ENERGY_PARTICLE_COLLECTOR, "Коллектор высокоэнергичных частиц")
|
||||
|
||||
add(MBlocks.DEV_CHEST, "Сундук разработчика")
|
||||
add(MBlocks.DEV_CHEST, "desc", "Хранит все предметы, которые есть в игре")
|
||||
add(MBlocks.PAINTER, "Стол маляра")
|
||||
|
@ -41,6 +41,18 @@ fun addTags(tagsProvider: TagsProvider) {
|
||||
tagsProvider.storageBlocksAsBlock.add("tritanium", MBlocks.TRITANIUM_INGOT_BLOCK)
|
||||
tagsProvider.blocks.Appender(BlockTags.BEACON_BASE_BLOCKS).add(MBlocks.TRITANIUM_INGOT_BLOCK)
|
||||
|
||||
tagsProvider.blocks.Appender(MBlockTags.MULTIBLOCK_STRUCTURE)
|
||||
.add(MBlockTags.MULTIBLOCK_HARD_STRUCTURE, MBlockTags.MULTIBLOCK_SOFT_STRUCTURE)
|
||||
|
||||
tagsProvider.blocks.Appender(MBlockTags.MULTIBLOCK_SOFT_STRUCTURE)
|
||||
.add(MBlockTags.HARDENED_GLASS)
|
||||
|
||||
tagsProvider.blocks.Appender(MBlockTags.MULTIBLOCK_HARD_STRUCTURE)
|
||||
.add(MRegistry.VENT.allBlocks.values)
|
||||
.add(MRegistry.VENT_ALTERNATIVE.allBlocks.values)
|
||||
.add(MRegistry.TRITANIUM_BLOCK.allBlocks.values)
|
||||
.add(MRegistry.TRITANIUM_STRIPED_BLOCK.blocks.values.stream().flatMap { it.values.stream() })
|
||||
|
||||
tagsProvider.stoneOre("tritanium", MBlocks.TRITANIUM_ORE)
|
||||
tagsProvider.deepslateOre("tritanium", MBlocks.DEEPSLATE_TRITANIUM_ORE)
|
||||
tagsProvider.singleDropOre(
|
||||
|
@ -5,7 +5,7 @@ import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import ru.dbotthepony.mc.otm.core.MultiblockKt;
|
||||
import ru.dbotthepony.mc.otm.core.multiblock.GlobalBlockEntityRemovalListener;
|
||||
|
||||
// because i know
|
||||
// someone
|
||||
@ -20,6 +20,6 @@ public abstract class BlockEntityMixin {
|
||||
remap = false
|
||||
)
|
||||
public void setRemovedListener(CallbackInfo ci) {
|
||||
MultiblockKt.onBlockEntityInvalidated((BlockEntity) (Object) this);
|
||||
GlobalBlockEntityRemovalListener.Companion.onBlockEntityInvalidated((BlockEntity) (Object) this);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryPlayer
|
||||
@ -12,7 +12,7 @@ import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
|
||||
abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: MatteryPlayer) : INBTSerializable<CompoundTag> {
|
||||
val ply get() = android.ply
|
||||
val syncher = Syncher()
|
||||
val syncher = SynchableGroup()
|
||||
val syncherRemote = syncher.Remote()
|
||||
|
||||
open var level by syncher.int(setter = setter@{ field, value ->
|
||||
|
@ -1,6 +1,5 @@
|
||||
package ru.dbotthepony.mc.otm.android
|
||||
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
@ -10,7 +9,7 @@ import net.minecraft.world.entity.player.Player
|
||||
import net.neoforged.bus.api.Event
|
||||
import net.neoforged.neoforge.common.NeoForge
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.mc.otm.OverdriveThatMatters
|
||||
@ -22,7 +21,6 @@ import ru.dbotthepony.mc.otm.core.registryName
|
||||
import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
import ru.dbotthepony.mc.otm.milliTime
|
||||
import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger
|
||||
import java.io.InputStream
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlayer) : INBTSerializable<CompoundTag> {
|
||||
@ -48,7 +46,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
|
||||
|
||||
val ply: Player get() = capability.ply
|
||||
|
||||
val syncher = Syncher()
|
||||
val syncher = SynchableGroup()
|
||||
val syncherRemote = syncher.Remote()
|
||||
|
||||
var isResearched by syncher.boolean()
|
||||
|
@ -53,7 +53,7 @@ import ru.dbotthepony.mc.otm.core.util.Savetables
|
||||
import ru.dbotthepony.mc.otm.core.util.TickList
|
||||
import ru.dbotthepony.mc.otm.core.util.countingLazy
|
||||
import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
import ru.dbotthepony.mc.otm.onceServer
|
||||
import ru.dbotthepony.mc.otm.registry.MBlocks
|
||||
import ru.dbotthepony.mc.otm.sometimeServer
|
||||
@ -369,8 +369,8 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
|
||||
}
|
||||
}
|
||||
|
||||
val syncher = Syncher()
|
||||
private val synchers = Object2ObjectArrayMap<ServerPlayer, Syncher.Remote>()
|
||||
val syncher = SynchableGroup()
|
||||
private val synchers = Object2ObjectArrayMap<ServerPlayer, SynchableGroup.Remote>()
|
||||
|
||||
override fun setLevel(level: Level) {
|
||||
val old = this.level
|
||||
|
@ -3,12 +3,11 @@ package ru.dbotthepony.mc.otm.block.entity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.tags.BlockTags
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.entity.FurnaceBlockEntity
|
||||
import net.minecraft.world.level.block.entity.HopperBlockEntity
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import ru.dbotthepony.mc.otm.core.multiblockConfiguration
|
||||
import ru.dbotthepony.mc.otm.core.multiblockEntity
|
||||
import ru.dbotthepony.mc.otm.core.multiblock.multiblockEntity
|
||||
import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock
|
||||
import ru.dbotthepony.mc.otm.registry.MBlockEntities
|
||||
import ru.dbotthepony.mc.otm.registry.MBlocks
|
||||
|
||||
@ -28,7 +27,7 @@ class MultiblockTestBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
|
||||
private val HOPPERS = multiblockEntity<HopperBlockEntity>()
|
||||
private val FURNACES = multiblockEntity<FurnaceBlockEntity>()
|
||||
|
||||
val CONFIG = multiblockConfiguration {
|
||||
val CONFIG = shapedMultiblock {
|
||||
block(MBlocks.MULTIBLOCK_TEST)
|
||||
|
||||
and {
|
||||
|
@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.block.entity
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
@ -79,7 +79,7 @@ class RedstoneControl(private val valueChanges: (new: Boolean, old: Boolean) ->
|
||||
}
|
||||
|
||||
class SynchronizedRedstoneControl(
|
||||
synchronizer: Syncher,
|
||||
synchronizer: SynchableGroup,
|
||||
private val valueChanges: (new: Boolean, old: Boolean) -> Unit,
|
||||
) : AbstractRedstoneControl() {
|
||||
override var redstoneSetting: RedstoneSetting by synchronizer.enum(RedstoneSetting.LOW, setter = { access, value ->
|
||||
|
@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.core.SectionPos
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
@ -26,6 +27,8 @@ import ru.dbotthepony.mc.otm.block.BlackHoleBlock
|
||||
import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity
|
||||
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
|
||||
import ru.dbotthepony.mc.otm.config.ServerConfig
|
||||
import ru.dbotthepony.mc.otm.core.addAll
|
||||
import ru.dbotthepony.mc.otm.core.collect.map
|
||||
import ru.dbotthepony.mc.otm.core.damageType
|
||||
import ru.dbotthepony.mc.otm.core.getExplosionResistance
|
||||
import ru.dbotthepony.mc.otm.core.gracefulBlockBreak
|
||||
@ -182,6 +185,9 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
|
||||
}
|
||||
}
|
||||
|
||||
private val sphericalPositionsCache = ArrayList<BlockPos>()
|
||||
private var lastRadii = -1
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
if (--sleepTicks > 0) return
|
||||
@ -245,7 +251,30 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
|
||||
var sphereIterator = sphereIterator
|
||||
|
||||
if (sphereIterator == null || !sphereIterator.hasNext()) {
|
||||
sphereIterator = getSphericalBlockPositions((gravitationStrength * 18.0).roundToInt())
|
||||
val radii = (gravitationStrength * 18.0).roundToInt()
|
||||
|
||||
if (lastRadii != radii) {
|
||||
sphericalPositionsCache.clear()
|
||||
lastRadii = radii
|
||||
|
||||
if (radii <= 24) {
|
||||
sphericalPositionsCache.addAll(getSphericalBlockPositions(radii).map { it + blockPos })
|
||||
|
||||
// Sort to maximize chunk cache
|
||||
sphericalPositionsCache.sortWith { o1, o2 ->
|
||||
val cmp = SectionPos.blockToSectionCoord(o1.x).compareTo(SectionPos.blockToSectionCoord(o2.x))
|
||||
if (cmp != 0) return@sortWith cmp
|
||||
return@sortWith SectionPos.blockToSectionCoord(o1.z).compareTo(SectionPos.blockToSectionCoord(o2.z))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sphericalPositionsCache.isNotEmpty()) {
|
||||
sphereIterator = sphericalPositionsCache.iterator()
|
||||
} else {
|
||||
sphereIterator = getSphericalBlockPositions(radii).map { it + blockPos }
|
||||
}
|
||||
|
||||
this.sphereIterator = sphereIterator
|
||||
}
|
||||
|
||||
@ -254,7 +283,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery
|
||||
while (iterations < ITERATIONS && sphereIterator.hasNext()) {
|
||||
iterations++
|
||||
|
||||
val pos = sphereIterator.next() + blockPos
|
||||
val pos = sphereIterator.next()
|
||||
val getBlock = level.getBlockState(pos)
|
||||
|
||||
if (!getBlock.isAir && getBlock.block !is BlackHoleBlock) {
|
||||
|
@ -2,29 +2,83 @@ package ru.dbotthepony.mc.otm.block.entity.blackhole
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterators
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.player.Inventory
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.neoforged.neoforge.capabilities.Capabilities
|
||||
import ru.dbotthepony.kommons.util.DelegateSetter
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kommons.util.value
|
||||
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
|
||||
import ru.dbotthepony.mc.otm.block.entity.tech.EnergyHatchBlockEntity
|
||||
import ru.dbotthepony.mc.otm.block.entity.tech.MatterHatchBlockEntity
|
||||
import ru.dbotthepony.mc.otm.capability.FlowDirection
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryCapability
|
||||
import ru.dbotthepony.mc.otm.capability.energy.CombinedProfiledEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.capability.energy.receiveEnergy
|
||||
import ru.dbotthepony.mc.otm.capability.matter.CombinedProfiledMatterStorage
|
||||
import ru.dbotthepony.mc.otm.config.MachinesConfig
|
||||
import ru.dbotthepony.mc.otm.core.Multiblock
|
||||
import ru.dbotthepony.mc.otm.core.MultiblockBuilder
|
||||
import ru.dbotthepony.mc.otm.core.RandomSource2Generator
|
||||
import ru.dbotthepony.mc.otm.core.collect.map
|
||||
import ru.dbotthepony.mc.otm.core.multiblock.ShapedMultiblock
|
||||
import ru.dbotthepony.mc.otm.core.getBlockStateNow
|
||||
import ru.dbotthepony.mc.otm.core.immutableList
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.math.plus
|
||||
import ru.dbotthepony.mc.otm.core.math.times
|
||||
import ru.dbotthepony.mc.otm.core.multiblockConfiguration
|
||||
import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag
|
||||
import ru.dbotthepony.mc.otm.core.multiblock.Strategy
|
||||
import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock
|
||||
import ru.dbotthepony.mc.otm.menu.tech.BlackHoleGeneratorMenu
|
||||
import ru.dbotthepony.mc.otm.registry.MBlockEntities
|
||||
import ru.dbotthepony.mc.otm.registry.MBlockTags
|
||||
import ru.dbotthepony.mc.otm.registry.MBlocks
|
||||
import java.io.Closeable
|
||||
|
||||
class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.BLACK_HOLE_GENERATOR, blockPos, blockState) {
|
||||
private var multiblock: Multiblock? = null
|
||||
private var lastRange = -1
|
||||
var multiblock: ShapedMultiblock? = null
|
||||
private set
|
||||
private var multiblockSyncher: Closeable? = null
|
||||
|
||||
var lastRange by syncher.int(-1, setter = DelegateSetter { field, value ->
|
||||
if (value == -1) {
|
||||
multiblockSyncher?.close()
|
||||
multiblockSyncher = null
|
||||
multiblock?.close()
|
||||
multiblock = null
|
||||
field.accept(value)
|
||||
} else if (value != field.value) {
|
||||
multiblockSyncher?.close()
|
||||
multiblock = CONFIGURATIONS[value - 5].value.create(blockPos)
|
||||
multiblockSyncher = syncher.add0(multiblock!!)
|
||||
field.accept(value)
|
||||
}
|
||||
})
|
||||
private set
|
||||
|
||||
var drawBuildingGuide by syncher.boolean()
|
||||
|
||||
val energy = CombinedProfiledEnergyStorage(
|
||||
FlowDirection.NONE,
|
||||
{ multiblock?.blockEntities(EnergyHatchBlockEntity.OUTPUT_TAG)?.iterator()?.map { it.energy } ?: ObjectIterators.emptyIterator() },
|
||||
{ level?.random?.let { RandomSource2Generator(it) } }
|
||||
)
|
||||
|
||||
val matter = CombinedProfiledMatterStorage(
|
||||
FlowDirection.NONE,
|
||||
{ multiblock?.blockEntities(MatterHatchBlockEntity.INPUT_TAG)?.iterator()?.map { it.matter } ?: ObjectIterators.emptyIterator() },
|
||||
{ level?.random?.let { RandomSource2Generator(it) } }
|
||||
)
|
||||
|
||||
init {
|
||||
exposeSideless(MatteryCapability.MATTER_BLOCK, matter)
|
||||
exposeSideless(MatteryCapability.BLOCK_ENERGY, energy)
|
||||
exposeSideless(Capabilities.EnergyStorage.BLOCK, energy)
|
||||
}
|
||||
|
||||
private fun findBlackHoleRange(): Int {
|
||||
val normal = blockRotation.normal
|
||||
@ -52,69 +106,29 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
|
||||
energy.tick()
|
||||
matter.tick()
|
||||
|
||||
val level = level!!
|
||||
|
||||
if (lastRange == -1) {
|
||||
val found = findBlackHoleRange()
|
||||
|
||||
if (found != -1) {
|
||||
lastRange = found
|
||||
multiblock = CONFIGURATIONS[found - 5].value.create(blockPos)
|
||||
} else {
|
||||
multiblock = null
|
||||
}
|
||||
this.lastRange = findBlackHoleRange()
|
||||
}
|
||||
|
||||
val multiblock = multiblock
|
||||
|
||||
if (multiblock != null) {
|
||||
if (!multiblock.update(level, blockRotation.front)) {
|
||||
val found = findBlackHoleRange()
|
||||
|
||||
if (found == -1) {
|
||||
this.multiblock = null
|
||||
} else if (found != lastRange) {
|
||||
lastRange = found
|
||||
this.multiblock = CONFIGURATIONS[found - 5].value.create(blockPos)
|
||||
}
|
||||
this.lastRange = findBlackHoleRange()
|
||||
} else {
|
||||
val blackHole = multiblock.blockEntities(BLACK_HOLE).first()
|
||||
val matterHatches = multiblock.blockEntities(MatterHatchBlockEntity.INPUT_TAG)
|
||||
val energyHatches = multiblock.blockEntities(EnergyHatchBlockEntity.OUTPUT_TAG)
|
||||
|
||||
if (matterHatches.isEmpty() || energyHatches.isEmpty()) return
|
||||
if (matter.extractMatter(MachinesConfig.BlackHoleGenerator.MATTER_RATE, true) != MachinesConfig.BlackHoleGenerator.MATTER_RATE) return
|
||||
val energyToStore = blackHole.mass / MachinesConfig.BlackHoleGenerator.MASS_DIVISOR
|
||||
if (energy.receiveEnergy(energyToStore, true) != energyToStore) return
|
||||
|
||||
var required = MachinesConfig.BlackHoleGenerator.MATTER_RATE
|
||||
|
||||
for (hatch in matterHatches) {
|
||||
required -= hatch.matter.extractMatter(required, true)
|
||||
if (required <= Decimal.ZERO) break
|
||||
}
|
||||
|
||||
if (required > Decimal.ZERO) return
|
||||
|
||||
var energyLeftover = blackHole.mass / MachinesConfig.BlackHoleGenerator.MASS_DIVISOR
|
||||
|
||||
for (hatch in energyHatches) {
|
||||
energyLeftover -= hatch.energy.receiveEnergy(energyLeftover, true)
|
||||
if (energyLeftover <= Decimal.ZERO) break
|
||||
}
|
||||
|
||||
if (energyLeftover > Decimal.ZERO) return
|
||||
|
||||
required = MachinesConfig.BlackHoleGenerator.MATTER_RATE
|
||||
|
||||
for (hatch in matterHatches) {
|
||||
required -= hatch.matter.extractMatter(required, false)
|
||||
if (required <= Decimal.ZERO) break
|
||||
}
|
||||
|
||||
energyLeftover = blackHole.mass / MachinesConfig.BlackHoleGenerator.MASS_DIVISOR
|
||||
|
||||
for (hatch in energyHatches) {
|
||||
energyLeftover -= hatch.energy.receiveEnergy(energyLeftover, false)
|
||||
if (energyLeftover <= Decimal.ZERO) break
|
||||
}
|
||||
matter.extractMatter(MachinesConfig.BlackHoleGenerator.MATTER_RATE, false)
|
||||
energy.receiveEnergy(energyToStore, false)
|
||||
|
||||
blackHole.mass += MachinesConfig.BlackHoleGenerator.MASS_FEEDING_RATIO * MachinesConfig.BlackHoleGenerator.MATTER_RATE
|
||||
}
|
||||
@ -122,11 +136,11 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
|
||||
}
|
||||
|
||||
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? {
|
||||
return null
|
||||
return BlackHoleGeneratorMenu(containerID, inventory, this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val BLACK_HOLE = MultiblockBuilder.EntityTag(BlackHoleBlockEntity::class)
|
||||
val BLACK_HOLE = BlockEntityTag(BlackHoleBlockEntity::class)
|
||||
|
||||
private fun points(x: Int, z: Int, result: ObjectArraySet<BlockPos>) {
|
||||
if (x == 0) {
|
||||
@ -187,7 +201,7 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
|
||||
val i = i2 + 5
|
||||
|
||||
accept(lazy {
|
||||
multiblockConfiguration {
|
||||
shapedMultiblock {
|
||||
builder.customCheck {
|
||||
val blocks = it.blocks()
|
||||
|
||||
@ -226,9 +240,9 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
|
||||
tag(MatterHatchBlockEntity.INPUT_TAG)
|
||||
}
|
||||
|
||||
node.block(MBlocks.MULTIBLOCK_STRUCTURE)
|
||||
node.block(MBlockTags.MULTIBLOCK_STRUCTURE)
|
||||
} else {
|
||||
node.block(MBlocks.MULTIBLOCK_STRUCTURE)
|
||||
node.block(MBlockTags.MULTIBLOCK_HARD_STRUCTURE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
|
||||
import ru.dbotthepony.mc.otm.capability.*
|
||||
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.core.*
|
||||
import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart
|
||||
import ru.dbotthepony.mc.otm.core.math.BlockRotation
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.math.RelativeSide
|
||||
|
@ -17,7 +17,7 @@ import ru.dbotthepony.mc.otm.config.EnergyBalanceValues
|
||||
import ru.dbotthepony.mc.otm.config.MachinesConfig
|
||||
import ru.dbotthepony.mc.otm.container.HandlerFilter
|
||||
import ru.dbotthepony.mc.otm.container.MatteryContainer
|
||||
import ru.dbotthepony.mc.otm.core.MultiblockBuilder
|
||||
import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag
|
||||
import ru.dbotthepony.mc.otm.menu.tech.EnergyHatchMenu
|
||||
import ru.dbotthepony.mc.otm.registry.MBlockEntities
|
||||
|
||||
@ -70,8 +70,8 @@ class EnergyHatchBlockEntity(
|
||||
companion object {
|
||||
const val CAPACITY = 1
|
||||
|
||||
val INPUT_TAG = MultiblockBuilder.EntityTag(EnergyHatchBlockEntity::class) { it.isInput }
|
||||
val OUTPUT_TAG = MultiblockBuilder.EntityTag(EnergyHatchBlockEntity::class) { !it.isInput }
|
||||
val INPUT_TAG = BlockEntityTag(EnergyHatchBlockEntity::class) { it.isInput }
|
||||
val OUTPUT_TAG = BlockEntityTag(EnergyHatchBlockEntity::class) { !it.isInput }
|
||||
|
||||
fun input(blockPos: BlockPos, blockState: BlockState): EnergyHatchBlockEntity {
|
||||
return EnergyHatchBlockEntity(true, MachinesConfig.ENERGY_HATCH, MBlockEntities.ENERGY_INPUT_HATCH, blockPos, blockState)
|
||||
|
@ -11,7 +11,7 @@ import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
|
||||
import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity
|
||||
import ru.dbotthepony.mc.otm.container.HandlerFilter
|
||||
import ru.dbotthepony.mc.otm.container.MatteryContainer
|
||||
import ru.dbotthepony.mc.otm.core.MultiblockBuilder
|
||||
import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag
|
||||
import ru.dbotthepony.mc.otm.menu.tech.ItemHatchMenu
|
||||
import ru.dbotthepony.mc.otm.registry.MBlockEntities
|
||||
|
||||
@ -36,8 +36,8 @@ class ItemHatchBlockEntity(
|
||||
companion object {
|
||||
const val CAPACITY = CargoCrateBlockEntity.CAPACITY
|
||||
|
||||
val INPUT_TAG = MultiblockBuilder.EntityTag(ItemHatchBlockEntity::class) { it.isInput }
|
||||
val OUTPUT_TAG = MultiblockBuilder.EntityTag(ItemHatchBlockEntity::class) { !it.isInput }
|
||||
val INPUT_TAG = BlockEntityTag(ItemHatchBlockEntity::class) { it.isInput }
|
||||
val OUTPUT_TAG = BlockEntityTag(ItemHatchBlockEntity::class) { !it.isInput }
|
||||
|
||||
fun input(blockPos: BlockPos, blockState: BlockState): ItemHatchBlockEntity {
|
||||
return ItemHatchBlockEntity(true, MBlockEntities.ITEM_INPUT_HATCH, blockPos, blockState)
|
||||
|
@ -17,8 +17,8 @@ import ru.dbotthepony.mc.otm.capability.moveMatter
|
||||
import ru.dbotthepony.mc.otm.config.MachinesConfig
|
||||
import ru.dbotthepony.mc.otm.container.HandlerFilter
|
||||
import ru.dbotthepony.mc.otm.container.MatteryContainer
|
||||
import ru.dbotthepony.mc.otm.core.MultiblockBuilder
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag
|
||||
import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu
|
||||
import ru.dbotthepony.mc.otm.registry.MBlockEntities
|
||||
|
||||
@ -74,8 +74,8 @@ class MatterHatchBlockEntity(
|
||||
companion object {
|
||||
const val CAPACITY = 1
|
||||
|
||||
val INPUT_TAG = MultiblockBuilder.EntityTag(MatterHatchBlockEntity::class) { it.isInput }
|
||||
val OUTPUT_TAG = MultiblockBuilder.EntityTag(MatterHatchBlockEntity::class) { !it.isInput }
|
||||
val INPUT_TAG = BlockEntityTag(MatterHatchBlockEntity::class) { it.isInput }
|
||||
val OUTPUT_TAG = BlockEntityTag(MatterHatchBlockEntity::class) { !it.isInput }
|
||||
|
||||
fun input(blockPos: BlockPos, blockState: BlockState): MatterHatchBlockEntity {
|
||||
return MatterHatchBlockEntity(true, MBlockEntities.MATTER_INPUT_HATCH, blockPos, blockState)
|
||||
|
@ -4,18 +4,18 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import ru.dbotthepony.mc.otm.core.DecimalHistoryChart
|
||||
import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.nbt.map
|
||||
import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
|
||||
abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<CompoundTag?> {
|
||||
val received = DecimalHistoryChart(ticks = HISTORY_SIZE)
|
||||
val transferred = DecimalHistoryChart(ticks = HISTORY_SIZE)
|
||||
abstract class AbstractProfiledStorage<out P>(val parent: P) : IProfiledStorage, INBTSerializable<CompoundTag?> {
|
||||
final override val received = DecimalHistoryChart(ticks = HISTORY_SIZE)
|
||||
final override val transferred = DecimalHistoryChart(ticks = HISTORY_SIZE)
|
||||
|
||||
var receivedThisTick = Decimal.ZERO
|
||||
final override var receivedThisTick = Decimal.ZERO
|
||||
private set
|
||||
var transferredThisTick = Decimal.ZERO
|
||||
final override var transferredThisTick = Decimal.ZERO
|
||||
private set
|
||||
|
||||
private var isTicking = false
|
||||
|
@ -1,6 +1,7 @@
|
||||
package ru.dbotthepony.mc.otm.capability
|
||||
|
||||
import com.google.common.collect.Streams
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
@ -33,7 +34,10 @@ import ru.dbotthepony.mc.otm.core.collect.filter
|
||||
import ru.dbotthepony.mc.otm.core.collect.map
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.shuffle
|
||||
import ru.dbotthepony.mc.otm.core.util.formatFluidLevel
|
||||
import java.util.function.Supplier
|
||||
import java.util.random.RandomGenerator
|
||||
import java.util.stream.Stream
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
@ -41,6 +45,37 @@ private val LOGGER = LogManager.getLogger()
|
||||
val Player.matteryPlayer: MatteryPlayer get() = (this as IMatteryPlayer).otmPlayer
|
||||
val LivingEntity.matteryPlayer: MatteryPlayer? get() = (this as? IMatteryPlayer)?.otmPlayer
|
||||
|
||||
fun <T> Supplier<out Iterator<T>>.iterateProviders(random: RandomGenerator?, value: Decimal, simulate: Boolean, walker: T.(Decimal, Boolean) -> Decimal): Decimal {
|
||||
val providers = get()
|
||||
val iteratorProvider: () -> Iterator<T>
|
||||
|
||||
if (random == null) {
|
||||
iteratorProvider = providers::iterator
|
||||
} else {
|
||||
val providerList = ObjectArrayList(providers)
|
||||
providerList.shuffle(random)
|
||||
iteratorProvider = providerList::iterator
|
||||
}
|
||||
|
||||
var leftover = value
|
||||
|
||||
for (provider in iteratorProvider()) {
|
||||
leftover -= walker(provider, leftover, true)
|
||||
if (leftover.signum() <= 0) break
|
||||
}
|
||||
|
||||
if (simulate) return value - leftover
|
||||
|
||||
leftover = value
|
||||
|
||||
for (provider in iteratorProvider()) {
|
||||
leftover -= walker(provider, leftover, false)
|
||||
if (leftover.signum() <= 0) break
|
||||
}
|
||||
|
||||
return value - leftover
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a checked energy receive, calls [IMatteryEnergyStorage.receiveEnergyChecked] if possible
|
||||
*/
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -11,10 +11,8 @@ import net.minecraft.client.player.AbstractClientPlayer
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.IntTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.nbt.StringTag
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
@ -55,7 +53,7 @@ import org.apache.logging.log4j.LogManager
|
||||
import org.joml.Vector4f
|
||||
import ru.dbotthepony.kommons.collect.ListenableMap
|
||||
import ru.dbotthepony.kommons.collect.ListenableSet
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
@ -76,7 +74,6 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
|
||||
import ru.dbotthepony.mc.otm.capability.energy.receiveEnergyExact
|
||||
import ru.dbotthepony.mc.otm.client.minecraft
|
||||
import ru.dbotthepony.mc.otm.client.onceClient
|
||||
import ru.dbotthepony.mc.otm.config.AndroidConfig
|
||||
import ru.dbotthepony.mc.otm.config.ExopackConfig
|
||||
import ru.dbotthepony.mc.otm.container.CombinedContainer
|
||||
@ -180,7 +177,7 @@ class MatteryPlayer(val ply: Player) {
|
||||
* any differences in field order/types/etc between client and server
|
||||
* will break *everything*
|
||||
*/
|
||||
val syncher = Syncher()
|
||||
val syncher = SynchableGroup()
|
||||
|
||||
/**
|
||||
* Remote for "this" player
|
||||
@ -194,10 +191,10 @@ class MatteryPlayer(val ply: Player) {
|
||||
* any differences in field order/types/etc between client and server
|
||||
* will break *everything*
|
||||
*/
|
||||
val publicSyncher = Syncher()
|
||||
val publicSyncher = SynchableGroup()
|
||||
|
||||
val publicSyncherRemote = publicSyncher.Remote()
|
||||
val remoteSynchers = Object2ObjectArrayMap<ServerPlayer, Syncher.Remote>()
|
||||
val remoteSynchers = Object2ObjectArrayMap<ServerPlayer, SynchableGroup.Remote>()
|
||||
|
||||
/**
|
||||
* For data to be stored to and loaded from NBT automatically
|
||||
|
@ -8,7 +8,7 @@ import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.ticks.ContainerSingleItem
|
||||
import net.neoforged.neoforge.capabilities.Capabilities
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.mc.otm.capability.FlowDirection
|
||||
@ -23,7 +23,7 @@ import ru.dbotthepony.mc.otm.triggers.ExopackBatterySlotTrigger
|
||||
|
||||
class BatteryBackedEnergyStorage(
|
||||
private val ply: Player,
|
||||
synchronizer: Syncher,
|
||||
synchronizer: SynchableGroup,
|
||||
initialCharge: Decimal,
|
||||
maxCharge: Decimal,
|
||||
val isAndroid: Boolean,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import net.neoforged.neoforge.energy.IEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.capability.FlowDirection
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.capability.IProfiledStorage
|
||||
import java.math.BigInteger
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -72,7 +73,7 @@ interface IMatteryEnergyStorage : IEnergyStorage {
|
||||
* * Returned value CAN NOT be bigger than requested [howMuch], implementations failing to obey this contract will cause undefined behavior in upstream code.
|
||||
* Upstream code SHOULD NOT check for this contract.
|
||||
*
|
||||
* @return energy extracted
|
||||
* @return energy received
|
||||
*/
|
||||
fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal
|
||||
|
||||
@ -81,7 +82,7 @@ interface IMatteryEnergyStorage : IEnergyStorage {
|
||||
*
|
||||
* Interface implementations generally do not need to override this.
|
||||
*
|
||||
* @return energy extracted
|
||||
* @return energy received
|
||||
*/
|
||||
fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal {
|
||||
if (howMuch.isNegative || !energyFlow.input)
|
||||
@ -122,12 +123,16 @@ interface IMatteryEnergyStorage : IEnergyStorage {
|
||||
* Empties power of this energy storage
|
||||
*
|
||||
* @see batteryLevel
|
||||
* @return whenever operation was successful
|
||||
* @return whenever operation was successful, including partial completion
|
||||
*/
|
||||
fun drainBattery(): Boolean {
|
||||
try {
|
||||
batteryLevel = Decimal.ZERO
|
||||
if (canSetBatteryLevel)
|
||||
batteryLevel = Decimal.ZERO
|
||||
else
|
||||
return false
|
||||
} catch(err: UnsupportedOperationException) {
|
||||
// anti-stupid
|
||||
return false
|
||||
}
|
||||
|
||||
@ -138,12 +143,16 @@ interface IMatteryEnergyStorage : IEnergyStorage {
|
||||
* Fully fills power in this energy storage
|
||||
*
|
||||
* @see batteryLevel
|
||||
* @return whenever operation was successful
|
||||
* @return whenever operation was successful, including partial completion
|
||||
*/
|
||||
fun fillBattery(): Boolean {
|
||||
try {
|
||||
batteryLevel = maxBatteryLevel
|
||||
if (canSetBatteryLevel)
|
||||
batteryLevel = maxBatteryLevel
|
||||
else
|
||||
return false
|
||||
} catch(err: UnsupportedOperationException) {
|
||||
// anti-stupid
|
||||
return false
|
||||
}
|
||||
|
||||
@ -374,3 +383,5 @@ fun IMatteryEnergyStorage.getBarColor(): Int {
|
||||
|
||||
return RGBAColor.LOW_POWER.linearInterpolation((batteryLevel / maxBatteryLevel).toFloat(), RGBAColor.FULL_POWER).toBGR()
|
||||
}
|
||||
|
||||
interface IProfiledMatteryEnergyStorage : IProfiledStorage, IMatteryEnergyStorage
|
||||
|
@ -4,7 +4,7 @@ import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
|
||||
import ru.dbotthepony.mc.otm.capability.FlowDirection
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
|
||||
class ProfiledEnergyStorage<out E : IMatteryEnergyStorage>(parent: E) : AbstractProfiledStorage<E>(parent), IMatteryEnergyStorage {
|
||||
class ProfiledEnergyStorage<out E : IMatteryEnergyStorage>(parent: E) : AbstractProfiledStorage<E>(parent), IProfiledMatteryEnergyStorage {
|
||||
override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
|
||||
return recordTransfer(parent.extractEnergy(howMuch, simulate), simulate)
|
||||
}
|
||||
|
@ -40,4 +40,28 @@ class ProxiedEnergyStorage<T : IMatteryEnergyStorage>(var parent: T? = null) : I
|
||||
override fun fillBattery(): Boolean {
|
||||
return parent?.fillBattery() ?: false
|
||||
}
|
||||
|
||||
override fun receiveEnergy(maxReceive: Int, simulate: Boolean): Int {
|
||||
return parent?.receiveEnergy(maxReceive, simulate) ?: 0
|
||||
}
|
||||
|
||||
override fun extractEnergy(maxReceive: Int, simulate: Boolean): Int {
|
||||
return parent?.extractEnergy(maxReceive, simulate) ?: 0
|
||||
}
|
||||
|
||||
override fun getEnergyStored(): Int {
|
||||
return parent?.getEnergyStored() ?: 0
|
||||
}
|
||||
|
||||
override fun getMaxEnergyStored(): Int {
|
||||
return parent?.getMaxEnergyStored() ?: 0
|
||||
}
|
||||
|
||||
override fun canExtract(): Boolean {
|
||||
return parent?.canExtract() == true
|
||||
}
|
||||
|
||||
override fun canReceive(): Boolean {
|
||||
return parent?.canReceive() == true
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.capability.matter
|
||||
import ru.dbotthepony.mc.otm.capability.FlowDirection
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.capability.IProfiledStorage
|
||||
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -39,21 +40,41 @@ interface IMatterStorage {
|
||||
/**
|
||||
* Empties matter stored of this matter storage, if possible
|
||||
*
|
||||
* @throws [UnsupportedOperationException]
|
||||
* @see storedMatter
|
||||
* @return whenever operation was successful, including partial completion
|
||||
*/
|
||||
fun drainMatter() {
|
||||
storedMatter = Decimal.ZERO
|
||||
fun drainMatter(): Boolean {
|
||||
try {
|
||||
if (canSetMatterLevel)
|
||||
storedMatter = Decimal.ZERO
|
||||
else
|
||||
return false
|
||||
} catch (err: UnsupportedOperationException) {
|
||||
// anti-stupid
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully fills matter stored, if possible
|
||||
*
|
||||
* @throws [UnsupportedOperationException]
|
||||
* @see storedMatter
|
||||
* @return whenever operation was successful, including partial completion
|
||||
*/
|
||||
fun fillMatter() {
|
||||
storedMatter = maxStoredMatter
|
||||
fun fillMatter(): Boolean {
|
||||
try {
|
||||
if (canSetMatterLevel)
|
||||
storedMatter = maxStoredMatter
|
||||
else
|
||||
return false
|
||||
} catch (err: UnsupportedOperationException) {
|
||||
// anti-stupid
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,3 +143,5 @@ fun IMatterStorage.getBarWidth(): Int {
|
||||
fun IMatterStorage.getBarColor(): Int {
|
||||
return RGBAColor.LOW_MATTER.linearInterpolation((storedMatter / maxStoredMatter).toFloat(), RGBAColor.FULL_MATTER).toBGR()
|
||||
}
|
||||
|
||||
interface IProfiledMatterStorage : IMatterStorage, IProfiledStorage
|
||||
|
@ -32,12 +32,12 @@ class ProfiledMatterStorage<out M : IMatterStorage>(parent: M) : AbstractProfile
|
||||
override val canSetMatterLevel: Boolean
|
||||
get() = parent.canSetMatterLevel
|
||||
|
||||
override fun drainMatter() {
|
||||
parent.drainMatter()
|
||||
override fun drainMatter(): Boolean {
|
||||
return parent.drainMatter()
|
||||
}
|
||||
|
||||
override fun fillMatter() {
|
||||
parent.fillMatter()
|
||||
override fun fillMatter(): Boolean {
|
||||
return parent.fillMatter()
|
||||
}
|
||||
|
||||
override val missingMatter: Decimal
|
||||
|
@ -13,6 +13,7 @@ import net.minecraft.client.renderer.Sheets
|
||||
import net.minecraft.client.resources.model.ModelBakery
|
||||
import net.neoforged.fml.ModLoader
|
||||
import net.neoforged.neoforge.client.event.RegisterRenderBuffersEvent
|
||||
import ru.dbotthepony.mc.otm.client.render.blockentity.BlackHoleGeneratorRenderer
|
||||
import java.io.Closeable
|
||||
|
||||
private fun equals(existing: ImmutableList<RenderType>?, types: Array<out RenderType>): Boolean {
|
||||
@ -179,6 +180,7 @@ class DynamicBufferSource(
|
||||
next = State(RenderType.entityGlintDirect(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
|
||||
next = State(RenderType.waterMask(), ImmutableList.of(next.type), immutableAfter = true, chained = false)
|
||||
|
||||
next = State(MULTIBLOCK_STATUS_RENDER_TYPE, ImmutableList.of(next.type), immutableAfter = true, chained = true)
|
||||
next = State(rectRenderType(true, true, true), ImmutableList.of(next.type), immutableAfter = true, chained = true)
|
||||
next = State(rectRenderType(true, true, false), ImmutableList.of(next.type), immutableAfter = true, chained = true)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -88,7 +88,8 @@ class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Con
|
||||
1f to TextComponent("∞"),
|
||||
0f to Decimal.ZERO.formatPower(),
|
||||
),
|
||||
font = font
|
||||
font = font,
|
||||
textGravity = RenderGravity.TOP_LEFT
|
||||
)
|
||||
} else {
|
||||
normalized = FloatArray(chart.width) { (chart[it] / maximum).toFloat() }
|
||||
@ -102,7 +103,8 @@ class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Con
|
||||
|
||||
levelLabels = ChartLevelLabels(
|
||||
labels = map,
|
||||
font = font
|
||||
font = font,
|
||||
textGravity = RenderGravity.TOP_LEFT
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package ru.dbotthepony.mc.otm.client.screen.panels
|
||||
|
||||
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
|
||||
import ru.dbotthepony.mc.otm.core.AbstractHistoryChart
|
||||
import ru.dbotthepony.mc.otm.core.chart.AbstractHistoryChart
|
||||
|
||||
abstract class AbstractHistoryGraphPanel<out S : MatteryScreen<*>, G : AbstractHistoryChart<V>, V : Any>(
|
||||
screen: S,
|
||||
|
@ -8,7 +8,7 @@ import ru.dbotthepony.mc.otm.client.render.ChartMouseLabels
|
||||
import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
|
||||
import ru.dbotthepony.mc.otm.client.render.renderChart
|
||||
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
|
||||
import ru.dbotthepony.mc.otm.core.DecimalHistoryChart
|
||||
import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart
|
||||
import ru.dbotthepony.mc.otm.core.TextComponent
|
||||
import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
|
@ -496,7 +496,7 @@ private enum class PreviewScrollers(
|
||||
)
|
||||
}
|
||||
|
||||
class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_97743_: Component) :
|
||||
class AndroidStationScreen(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_97743_: Component) :
|
||||
MatteryScreen<AndroidStationMenu>(p_97741_, p_97742_, p_97743_) {
|
||||
|
||||
private fun makeCanvas(isPreview: Boolean): DraggableCanvasPanel<AndroidStationScreen> {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -10,27 +10,26 @@ import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.SpritePanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.MatterCapacitorSlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.widget.HorizontalProfiledPowerGaugePanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
|
||||
import ru.dbotthepony.mc.otm.menu.tech.EnergyHatchMenu
|
||||
import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu
|
||||
|
||||
class EnergyHatchScreen(menu: EnergyHatchMenu, inventory: Inventory, title: Component) : MatteryScreen<EnergyHatchMenu>(menu, inventory, title) {
|
||||
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
|
||||
val frame = FramePanel(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, AbstractSlotPanel.SIZE, getTitle())
|
||||
val frame = FramePanel.padded(this, null, INVENTORY_FRAME_WIDTH, AbstractSlotPanel.SIZE, getTitle())
|
||||
|
||||
frame.makeCloseButton()
|
||||
frame.onClose { onClose() }
|
||||
|
||||
for (slot in menu.inputSlots) {
|
||||
val panel = MatterCapacitorSlotPanel(this, frame, slot)
|
||||
val panel = BatterySlotPanel(this, frame, slot)
|
||||
panel.dock = Dock.LEFT
|
||||
panel.dockResize = DockResizeMode.NONE
|
||||
}
|
||||
|
||||
val arrow = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND)
|
||||
arrow.dockLeft = 2f
|
||||
arrow.dockLeft = 20f
|
||||
arrow.dockRight = 2f
|
||||
arrow.dock = Dock.LEFT
|
||||
arrow.dockResize = DockResizeMode.NONE
|
||||
|
@ -16,7 +16,7 @@ import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu
|
||||
|
||||
class MatterHatchScreen(menu: MatterHatchMenu, inventory: Inventory, title: Component) : MatteryScreen<MatterHatchMenu>(menu, inventory, title) {
|
||||
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
|
||||
val frame = FramePanel(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, AbstractSlotPanel.SIZE, getTitle())
|
||||
val frame = FramePanel.padded(this, null, INVENTORY_FRAME_WIDTH, AbstractSlotPanel.SIZE, getTitle())
|
||||
|
||||
frame.makeCloseButton()
|
||||
frame.onClose { onClose() }
|
||||
@ -28,7 +28,7 @@ class MatterHatchScreen(menu: MatterHatchMenu, inventory: Inventory, title: Comp
|
||||
}
|
||||
|
||||
val arrow = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND)
|
||||
arrow.dockLeft = 2f
|
||||
arrow.dockLeft = 20f
|
||||
arrow.dockRight = 2f
|
||||
arrow.dock = Dock.LEFT
|
||||
arrow.dockResize = DockResizeMode.NONE
|
||||
|
@ -5,6 +5,7 @@ import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
|
||||
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
|
||||
import ru.dbotthepony.mc.otm.client.render.WidgetLocation
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
|
||||
import ru.dbotthepony.mc.otm.menu.widget.IProfiledLevelGaugeWidget
|
||||
import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget
|
||||
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
|
||||
|
||||
@ -73,7 +74,7 @@ fun <S : Screen> TallHorizontalPowerGaugePanel(
|
||||
open class HorizontalProfiledPowerGaugePanel<out S : Screen>(
|
||||
screen: S,
|
||||
parent: EditablePanel<*>? = null,
|
||||
widget: ProfiledLevelGaugeWidget<*>,
|
||||
widget: IProfiledLevelGaugeWidget,
|
||||
x: Float = 0f,
|
||||
y: Float = 0f,
|
||||
width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND.width,
|
||||
@ -93,7 +94,7 @@ open class HorizontalProfiledPowerGaugePanel<out S : Screen>(
|
||||
fun <S : Screen> TallHorizontalProfiledPowerGaugePanel(
|
||||
screen: S,
|
||||
parent: EditablePanel<*>? = null,
|
||||
widget: ProfiledLevelGaugeWidget<*>,
|
||||
widget: IProfiledLevelGaugeWidget,
|
||||
x: Float = 0f,
|
||||
y: Float = 0f,
|
||||
width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width,
|
||||
|
@ -5,30 +5,20 @@ import com.mojang.blaze3d.vertex.BufferUploader
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat
|
||||
import com.mojang.blaze3d.vertex.VertexFormat
|
||||
import com.mojang.datafixers.util.Either
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
import net.minecraft.client.renderer.GameRenderer
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.FormattedText
|
||||
import net.minecraft.world.inventory.tooltip.TooltipComponent
|
||||
import org.lwjgl.opengl.GL11
|
||||
import ru.dbotthepony.kommons.util.value
|
||||
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
|
||||
import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
|
||||
import ru.dbotthepony.mc.otm.client.ShiftPressedCond
|
||||
import ru.dbotthepony.mc.otm.client.isShiftDown
|
||||
import ru.dbotthepony.mc.otm.client.minecraft
|
||||
import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
|
||||
import ru.dbotthepony.mc.otm.client.render.WidgetLocation
|
||||
import ru.dbotthepony.mc.otm.client.render.tesselator
|
||||
import ru.dbotthepony.mc.otm.client.render.uv
|
||||
import ru.dbotthepony.mc.otm.client.render.vertex
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
|
||||
import ru.dbotthepony.mc.otm.core.TextComponent
|
||||
import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.util.formatHistory
|
||||
import ru.dbotthepony.mc.otm.core.util.formatMatter
|
||||
import ru.dbotthepony.mc.otm.core.util.formatMatterLevel
|
||||
import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget
|
||||
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
|
||||
|
@ -12,6 +12,7 @@ import ru.dbotthepony.mc.otm.client.render.*
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
|
||||
import ru.dbotthepony.mc.otm.core.util.formatHistory
|
||||
import ru.dbotthepony.mc.otm.core.util.formatPowerLevel
|
||||
import ru.dbotthepony.mc.otm.menu.widget.IProfiledLevelGaugeWidget
|
||||
import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget
|
||||
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
|
||||
|
||||
@ -92,7 +93,7 @@ fun <S : Screen> WidePowerGaugePanel(
|
||||
open class ProfiledPowerGaugePanel<out S : Screen>(
|
||||
screen: S,
|
||||
parent: EditablePanel<*>? = null,
|
||||
val profiledWidget: ProfiledLevelGaugeWidget<*>,
|
||||
val profiledWidget: IProfiledLevelGaugeWidget,
|
||||
x: Float = 0f,
|
||||
y: Float = 0f,
|
||||
width: Float = GAUGE_BACKGROUND.width,
|
||||
@ -117,7 +118,7 @@ open class ProfiledPowerGaugePanel<out S : Screen>(
|
||||
fun <S : Screen> WideProfiledPowerGaugePanel(
|
||||
screen: S,
|
||||
parent: EditablePanel<*>? = null,
|
||||
widget: ProfiledLevelGaugeWidget<*>,
|
||||
widget: IProfiledLevelGaugeWidget,
|
||||
x: Float = 0f,
|
||||
y: Float = 0f,
|
||||
width: Float = 18f,
|
||||
|
@ -4,18 +4,15 @@ import net.minecraft.ChatFormatting
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.neoforged.neoforge.capabilities.BlockCapability
|
||||
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.capability.IProfiledStorage
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryCapability
|
||||
import ru.dbotthepony.mc.otm.compat.jade.JadeColors
|
||||
import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys
|
||||
import ru.dbotthepony.mc.otm.compat.jade.JadeUids
|
||||
import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
|
||||
import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage
|
||||
import ru.dbotthepony.mc.otm.core.getCapability
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.math.getDecimal
|
||||
import ru.dbotthepony.mc.otm.core.math.putDecimal
|
||||
import ru.dbotthepony.mc.otm.core.util.formatMatter
|
||||
@ -37,14 +34,14 @@ object MatterStorageProvider : IBlockComponentProvider, IServerDataProvider<Bloc
|
||||
matterData.putDecimal("storedMatter", it.storedMatter)
|
||||
matterData.putDecimal("maxStoredMatter", it.maxStoredMatter)
|
||||
|
||||
if (it is AbstractProfiledStorage<*>) {
|
||||
if (it is IProfiledStorage) {
|
||||
val profiledData = CompoundTag()
|
||||
|
||||
// profiledData.putDecimal("lastTickReceive", it.lastTickReceive)
|
||||
// profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer)
|
||||
|
||||
profiledData.putDecimal("weightedReceive", it.received.calcWeightedAverage())
|
||||
profiledData.putDecimal("weightedTransfer", it.transferred.calcWeightedAverage())
|
||||
profiledData.putDecimal("weightedReceive", it.received.calculateAverage())
|
||||
profiledData.putDecimal("weightedTransfer", it.transferred.calculateAverage())
|
||||
|
||||
matterData.put("profiledData", profiledData)
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import net.minecraft.ChatFormatting
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryCapability
|
||||
import ru.dbotthepony.mc.otm.compat.jade.JadeColors
|
||||
import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys
|
||||
@ -12,6 +11,7 @@ import ru.dbotthepony.mc.otm.compat.jade.JadeUids
|
||||
import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.capability.IProfiledStorage
|
||||
import ru.dbotthepony.mc.otm.core.getCapability
|
||||
import ru.dbotthepony.mc.otm.core.math.getDecimal
|
||||
import ru.dbotthepony.mc.otm.core.math.putDecimal
|
||||
@ -31,14 +31,14 @@ object MatteryEnergyProvider : IBlockComponentProvider, IServerDataProvider<Bloc
|
||||
energyData.putDecimal("batteryLevel", it.batteryLevel)
|
||||
energyData.putDecimal("maxBatteryLevel", it.maxBatteryLevel)
|
||||
|
||||
if (it is AbstractProfiledStorage<*>) {
|
||||
if (it is IProfiledStorage) {
|
||||
val profiledData = CompoundTag()
|
||||
|
||||
// profiledData.putDecimal("lastTickReceive", it.lastTickReceive)
|
||||
// profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer)
|
||||
|
||||
profiledData.putDecimal("weightedReceive", it.received.calcWeightedAverage())
|
||||
profiledData.putDecimal("weightedTransfer", it.transferred.calcWeightedAverage())
|
||||
profiledData.putDecimal("weightedReceive", it.received.calculateAverage())
|
||||
profiledData.putDecimal("weightedTransfer", it.transferred.calculateAverage())
|
||||
|
||||
energyData.put("profiledData", profiledData)
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||
import it.unimi.dsi.fastutil.ints.IntComparators
|
||||
@ -12,10 +11,8 @@ import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.nbt.NbtOps
|
||||
import net.minecraft.nbt.Tag
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.world.Container
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.entity.player.StackedContents
|
||||
@ -24,9 +21,7 @@ import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.Items
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.collect.ListenableMap
|
||||
import ru.dbotthepony.kommons.util.Delegate
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.container.util.IContainerSlot
|
||||
import ru.dbotthepony.mc.otm.core.addSorted
|
||||
import ru.dbotthepony.mc.otm.core.collect.any
|
||||
@ -37,11 +32,8 @@ import ru.dbotthepony.mc.otm.core.collect.map
|
||||
import ru.dbotthepony.mc.otm.core.collect.toList
|
||||
import ru.dbotthepony.mc.otm.core.immutableList
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
import ru.dbotthepony.mc.otm.core.registryName
|
||||
import ru.dbotthepony.mc.otm.data.minRange
|
||||
import ru.dbotthepony.mc.otm.network.StreamCodecs
|
||||
import ru.dbotthepony.mc.otm.network.syncher.IRemoteState
|
||||
import ru.dbotthepony.mc.otm.network.syncher.ISynchable
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableObservedDelegate
|
||||
import java.util.*
|
||||
|
@ -59,6 +59,7 @@ import java.util.concurrent.Callable
|
||||
import java.util.concurrent.Future
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Supplier
|
||||
import java.util.random.RandomGenerator
|
||||
import java.util.stream.Stream
|
||||
import java.util.stream.StreamSupport
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
@ -154,6 +155,24 @@ fun <T : Enum<T>> T.prev(values: Array<out T>): T {
|
||||
return values[next]
|
||||
}
|
||||
|
||||
fun IntArray.shuffle(random: RandomGenerator) {
|
||||
for (i in lastIndex downTo 1) {
|
||||
val j = random.nextInt(i + 1)
|
||||
val copy = this[i]
|
||||
this[i] = this[j]
|
||||
this[j] = copy
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.shuffle(random: RandomGenerator) {
|
||||
for (i in lastIndex downTo 1) {
|
||||
val j = random.nextInt(i + 1)
|
||||
val copy = this[i]
|
||||
this[i] = this[j]
|
||||
this[j] = copy
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T : Any> immutableList(size: Int, initializer: (index: Int) -> T): ImmutableList<T> {
|
||||
require(size >= 0) { "Invalid list size $size" }
|
||||
|
||||
|
37
src/main/kotlin/ru/dbotthepony/mc/otm/core/IDAllocator.kt
Normal file
37
src/main/kotlin/ru/dbotthepony/mc/otm/core/IDAllocator.kt
Normal 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()
|
||||
}
|
||||
}
|
@ -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.isEmptyBlock(pos)
|
||||
}
|
||||
}
|
||||
|
||||
object NotAir : BlockPredicate {
|
||||
override fun test(pos: BlockPos, access: LevelAccessor): Boolean {
|
||||
return !access.isEmptyBlock(pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package ru.dbotthepony.mc.otm.core
|
||||
package ru.dbotthepony.mc.otm.core.chart
|
||||
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
@ -14,16 +14,9 @@ import ru.dbotthepony.mc.otm.network.syncher.ISynchable
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
abstract class AbstractHistoryChart<V : Any>(
|
||||
/**
|
||||
* How many measurements one graph value contains
|
||||
*/
|
||||
val resolution: Int,
|
||||
|
||||
/**
|
||||
* Limit on how many graph values to store
|
||||
*/
|
||||
val width: Int,
|
||||
) : Iterable<V>, INBTSerializable<CompoundTag>, ISynchable {
|
||||
final override val resolution: Int,
|
||||
final override val width: Int,
|
||||
) : IHistoryChart<V>, ISynchable, INBTSerializable<CompoundTag> {
|
||||
constructor(ticks: Int) : this(1, ticks)
|
||||
|
||||
init {
|
||||
@ -39,13 +32,13 @@ abstract class AbstractHistoryChart<V : Any>(
|
||||
protected abstract fun sum(input: List<V>): V
|
||||
protected abstract fun identity(): V
|
||||
|
||||
fun calculateAverage(width: Int = this.width): V {
|
||||
fun calculateAverage(width: Int): V {
|
||||
require(width >= 0) { "Invalid time frame: $width" }
|
||||
|
||||
if (width == 0 || this.values.isEmpty())
|
||||
return identity()
|
||||
|
||||
return calculateAverage(this.values.subList(0, width.coerceAtMost(this.values.size)))
|
||||
return calculateAverage(values.subList(0, width.coerceAtMost(values.size)))
|
||||
}
|
||||
|
||||
fun calculateSum(width: Int = this.width): V {
|
||||
@ -61,6 +54,11 @@ abstract class AbstractHistoryChart<V : Any>(
|
||||
abstract val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>
|
||||
|
||||
private val remotes = CopyOnWriteArrayList<RemoteState>()
|
||||
final override val hasRemotes: Boolean
|
||||
get() = remotes.isNotEmpty()
|
||||
|
||||
final override var isRemote: Boolean = false
|
||||
private set
|
||||
|
||||
private inner class RemoteState(val listener: Runnable) : IRemoteState {
|
||||
init {
|
||||
@ -98,7 +96,9 @@ abstract class AbstractHistoryChart<V : Any>(
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
final override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
isRemote = true
|
||||
|
||||
if (stream.readBoolean()) {
|
||||
values.clear()
|
||||
}
|
||||
@ -110,11 +110,11 @@ abstract class AbstractHistoryChart<V : Any>(
|
||||
while (values.size > width) values.removeLast()
|
||||
}
|
||||
|
||||
override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
final override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
return RemoteState(listener)
|
||||
}
|
||||
|
||||
fun calculateAverage(): V {
|
||||
final override fun calculateAverage(): V {
|
||||
if (values.isEmpty()) {
|
||||
return identity()
|
||||
}
|
||||
@ -143,7 +143,7 @@ abstract class AbstractHistoryChart<V : Any>(
|
||||
val size: Int
|
||||
get() = values.size
|
||||
|
||||
operator fun get(index: Int): V {
|
||||
final override operator fun get(index: Int): V {
|
||||
return values.getOrNull(index) ?: identity()
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ abstract class AbstractHistoryChart<V : Any>(
|
||||
it.group(
|
||||
list.fieldOf("accumulator").forGetter { it.accumulated },
|
||||
list.fieldOf("values").forGetter { it.values },
|
||||
).apply(it, ::SData)
|
||||
).apply(it) { a, b -> SData(a, b) }
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
package ru.dbotthepony.mc.otm.core
|
||||
package ru.dbotthepony.mc.otm.core.chart
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.mojang.serialization.Codec
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import ru.dbotthepony.mc.otm.core.collect.reduce
|
||||
import ru.dbotthepony.mc.otm.core.immutableList
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.data.DecimalCodec
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
@ -33,10 +34,6 @@ class DecimalHistoryChart : AbstractHistoryChart<Decimal> {
|
||||
override val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, Decimal>
|
||||
get() = DecimalCodec.NETWORK
|
||||
|
||||
fun calcWeightedAverage(): Decimal {
|
||||
return calcWeightedAverage(this::get)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val HISTORY_WEIGHTERS: ImmutableList<Decimal> = immutableList {
|
||||
/*for (i in 0 until 20) {
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package ru.dbotthepony.mc.otm.core.multiblock
|
||||
|
||||
import net.minecraft.core.BlockPos
|
||||
|
||||
interface IMultiblockNode {
|
||||
val pos: BlockPos
|
||||
val status: NodeStatus
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package ru.dbotthepony.mc.otm.core.multiblock
|
||||
|
||||
enum class NodeStatus {
|
||||
UNKNOWN,
|
||||
VALID,
|
||||
INVALID;
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
@ -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;
|
||||
}
|
@ -17,6 +17,7 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.math.isNegative
|
||||
import ru.dbotthepony.mc.otm.core.math.isZero
|
||||
import ru.dbotthepony.mc.otm.menu.widget.IProfiledLevelGaugeWidget
|
||||
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
|
||||
import java.math.BigInteger
|
||||
import java.util.function.BooleanSupplier
|
||||
@ -278,7 +279,7 @@ fun formatTickDuration(ticks: Int, longFormat: Boolean = false): String {
|
||||
*/
|
||||
fun formatHistory(
|
||||
result: MutableList<Either<FormattedText, TooltipComponent>>,
|
||||
widget: ProfiledLevelGaugeWidget<*>,
|
||||
widget: IProfiledLevelGaugeWidget,
|
||||
bias: Int = 0,
|
||||
decimals: Int = 3,
|
||||
verbose: BooleanSupplier = never,
|
||||
@ -293,7 +294,7 @@ fun formatHistory(
|
||||
|
||||
private fun formatHistoryChart(
|
||||
result: MutableList<Either<FormattedText, TooltipComponent>>,
|
||||
widget: ProfiledLevelGaugeWidget<*>,
|
||||
widget: IProfiledLevelGaugeWidget,
|
||||
bias: Int = 0,
|
||||
decimals: Int = 3,
|
||||
verbose: BooleanSupplier = never,
|
||||
@ -378,7 +379,7 @@ private fun formatHistoryChart(
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val max = maxOf(widget.received.max(), widget.transferred.max())
|
||||
val max = maxOf(widget.received.maxOrNull() ?: Decimal.ZERO, widget.transferred.maxOrNull() ?: Decimal.ZERO)
|
||||
val labelNames = Float2ObjectArrayMap<Component>()
|
||||
|
||||
labelNames[0.5f] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
@ -446,7 +447,7 @@ private fun formatHistoryChart(
|
||||
|
||||
private fun formatHistoryLines(
|
||||
result: MutableList<Either<FormattedText, TooltipComponent>>,
|
||||
widget: ProfiledLevelGaugeWidget<*>,
|
||||
widget: IProfiledLevelGaugeWidget,
|
||||
bias: Int = 0,
|
||||
decimals: Int = 3,
|
||||
verbose: BooleanSupplier = never,
|
||||
@ -476,8 +477,8 @@ private fun formatHistoryLines(
|
||||
}
|
||||
|
||||
addLine(
|
||||
widget.received.calcWeightedAverage(),
|
||||
widget.transferred.calcWeightedAverage()
|
||||
widget.received.calculateAverage(),
|
||||
widget.transferred.calculateAverage()
|
||||
)
|
||||
|
||||
if (verbose.asBoolean) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -1,17 +1,19 @@
|
||||
package ru.dbotthepony.mc.otm.core.util
|
||||
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class WriteOnce<V>(private val customMessage: String? = null) : ReadWriteProperty<Any?, V> {
|
||||
private var value: V? = null
|
||||
private var value: KOptional<V> = KOptional()
|
||||
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): V {
|
||||
return checkNotNull(value) { customMessage ?: "Property ${property.name} is not initialized" }
|
||||
value.ifPresent { return it }
|
||||
throw IllegalStateException(customMessage ?: "Property ${property.name} is not initialized")
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
|
||||
check(this.value == null) { "Property ${property.name} is already initialized" }
|
||||
this.value = value
|
||||
this.value.ifPresent { throw IllegalStateException("Property ${property.name} is already initialized") }
|
||||
this.value = KOptional(value)
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ import ru.dbotthepony.mc.otm.network.decode
|
||||
import ru.dbotthepony.mc.otm.network.encode
|
||||
import ru.dbotthepony.mc.otm.network.nullable
|
||||
import ru.dbotthepony.mc.otm.network.readByteListUnbounded
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
import ru.dbotthepony.mc.otm.network.wrap
|
||||
import ru.dbotthepony.mc.otm.network.writeByteListUnbounded
|
||||
import java.util.*
|
||||
@ -76,7 +76,7 @@ abstract class MatteryMenu(
|
||||
/**
|
||||
* Server->Client synchronizer
|
||||
*/
|
||||
val mSynchronizer = Syncher()
|
||||
val mSynchronizer = SynchableGroup()
|
||||
val synchronizerRemote = mSynchronizer.Remote()
|
||||
val player: Player get() = inventory.player
|
||||
val random: RandomSource = RandomSource.create()
|
||||
|
@ -16,7 +16,7 @@ import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput
|
||||
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
|
||||
import ru.dbotthepony.mc.otm.registry.MMenus
|
||||
|
||||
class BatteryBankMenu @JvmOverloads constructor(
|
||||
class BatteryBankMenu(
|
||||
p_38852_: Int,
|
||||
inventory: Inventory,
|
||||
tile: BatteryBankBlockEntity? = null,
|
||||
|
@ -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)
|
||||
}
|
@ -8,7 +8,7 @@ import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting
|
||||
import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock
|
||||
import ru.dbotthepony.mc.otm.block.entity.tech.EnergyCounterBlockEntity
|
||||
import ru.dbotthepony.mc.otm.core.DecimalHistoryChart
|
||||
import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.math.toDecimal
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ package ru.dbotthepony.mc.otm.menu.widget
|
||||
|
||||
import net.neoforged.neoforge.fluids.FluidStack
|
||||
import net.neoforged.neoforge.fluids.capability.IFluidHandler
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.mc.otm.container.get
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
@ -10,7 +10,7 @@ import ru.dbotthepony.mc.otm.network.wrap
|
||||
import java.util.function.IntSupplier
|
||||
import java.util.function.Supplier
|
||||
|
||||
class FluidGaugeWidget(synchronizer: Syncher) {
|
||||
class FluidGaugeWidget(synchronizer: SynchableGroup) {
|
||||
constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
|
||||
|
||||
var maxCapacitySupplier = IntSupplier { 0 }
|
||||
@ -27,7 +27,7 @@ class FluidGaugeWidget(synchronizer: Syncher) {
|
||||
return (fluid.amount.toFloat() / maxCapacity.toFloat()).coerceIn(0f, 1f)
|
||||
}
|
||||
|
||||
constructor(synchronizer: Syncher, fluid: IFluidHandler?, tank: Int = 0) : this(synchronizer) {
|
||||
constructor(synchronizer: SynchableGroup, fluid: IFluidHandler?, tank: Int = 0) : this(synchronizer) {
|
||||
if (fluid != null) {
|
||||
with(fluid, tank)
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
package ru.dbotthepony.mc.otm.menu.widget
|
||||
|
||||
import ru.dbotthepony.mc.otm.capability.IProfiledStorage
|
||||
|
||||
interface IProfiledLevelGaugeWidget : IProfiledStorage {
|
||||
val gauge: LevelGaugeWidget
|
||||
}
|
@ -7,10 +7,10 @@ import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
import ru.dbotthepony.mc.otm.network.StreamCodecs
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
|
||||
@Suppress("unused")
|
||||
class LevelGaugeWidget(synchronizer: Syncher) {
|
||||
class LevelGaugeWidget(synchronizer: SynchableGroup) {
|
||||
constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
|
||||
|
||||
var levelProvider = { Decimal.ONE }
|
||||
@ -56,7 +56,7 @@ class LevelGaugeWidget(synchronizer: Syncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: Syncher,
|
||||
synchronizer: SynchableGroup,
|
||||
power: IMatteryEnergyStorage?
|
||||
) : this(synchronizer) {
|
||||
if (power != null) {
|
||||
@ -65,7 +65,7 @@ class LevelGaugeWidget(synchronizer: Syncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: Syncher,
|
||||
synchronizer: SynchableGroup,
|
||||
matter: IMatterStorage?
|
||||
) : this(synchronizer) {
|
||||
if (matter != null) {
|
||||
@ -74,7 +74,7 @@ class LevelGaugeWidget(synchronizer: Syncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: Syncher,
|
||||
synchronizer: SynchableGroup,
|
||||
patterns: IPatternStorage?
|
||||
) : this(synchronizer) {
|
||||
if (patterns != null) {
|
||||
@ -83,7 +83,7 @@ class LevelGaugeWidget(synchronizer: Syncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: Syncher,
|
||||
synchronizer: SynchableGroup,
|
||||
level: () -> Decimal,
|
||||
maxLevel: () -> Decimal,
|
||||
) : this(synchronizer) {
|
||||
|
@ -3,30 +3,31 @@ package ru.dbotthepony.mc.otm.menu.widget
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
|
||||
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage.Companion.HISTORY_SIZE
|
||||
import ru.dbotthepony.mc.otm.capability.IProfiledStorage
|
||||
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage
|
||||
import ru.dbotthepony.mc.otm.core.DecimalHistoryChart
|
||||
import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
import ru.dbotthepony.mc.otm.network.StreamCodecs
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
|
||||
class ProfiledLevelGaugeWidget<P : AbstractProfiledStorage<*>>(
|
||||
synchronizer: Syncher,
|
||||
synchronizer: SynchableGroup,
|
||||
val storage: P?,
|
||||
val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer)
|
||||
) {
|
||||
override val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer)
|
||||
) : IProfiledLevelGaugeWidget {
|
||||
constructor(
|
||||
menu: MatteryMenu,
|
||||
storage: P?,
|
||||
gauge: LevelGaugeWidget = LevelGaugeWidget(menu.mSynchronizer)
|
||||
) : this(menu.mSynchronizer, storage, gauge)
|
||||
|
||||
val received = storage?.received ?: DecimalHistoryChart(ticks = HISTORY_SIZE)
|
||||
val transferred = storage?.transferred ?: DecimalHistoryChart(ticks = HISTORY_SIZE)
|
||||
override val received = storage?.received ?: DecimalHistoryChart(ticks = HISTORY_SIZE)
|
||||
override val transferred = storage?.transferred ?: DecimalHistoryChart(ticks = HISTORY_SIZE)
|
||||
|
||||
val receivedThisTick by synchronizer.computed({ storage?.receivedThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
|
||||
val transferredThisTick by synchronizer.computed({ storage?.transferredThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
|
||||
override val receivedThisTick by synchronizer.computed({ storage?.receivedThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
|
||||
override val transferredThisTick by synchronizer.computed({ storage?.transferredThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
|
||||
|
||||
init {
|
||||
if (storage is IMatterStorage)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package ru.dbotthepony.mc.otm.menu.widget
|
||||
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop
|
||||
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
|
||||
@ -9,7 +9,7 @@ import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
import java.util.function.BooleanSupplier
|
||||
|
||||
@Suppress("unused")
|
||||
class ProgressGaugeWidget(synchronizer: Syncher) {
|
||||
class ProgressGaugeWidget(synchronizer: SynchableGroup) {
|
||||
constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
|
||||
|
||||
var progressSupplier: FloatSupplier = FloatSupplier { 0f }
|
||||
@ -44,14 +44,14 @@ class ProgressGaugeWidget(synchronizer: Syncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: Syncher,
|
||||
synchronizer: SynchableGroup,
|
||||
progress: FloatSupplier
|
||||
) : this(synchronizer) {
|
||||
this.progressSupplier = progress
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: Syncher,
|
||||
synchronizer: SynchableGroup,
|
||||
blockEntity: MatteryWorkerBlockEntity<*>?
|
||||
) : this(synchronizer) {
|
||||
if (blockEntity != null) {
|
||||
@ -60,7 +60,7 @@ class ProgressGaugeWidget(synchronizer: Syncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: Syncher,
|
||||
synchronizer: SynchableGroup,
|
||||
job: MachineJobEventLoop<*>?
|
||||
) : this(synchronizer) {
|
||||
if (job != null) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -3,19 +3,32 @@ package ru.dbotthepony.mc.otm.network.syncher
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import ru.dbotthepony.kommons.util.Observer
|
||||
|
||||
/**
|
||||
* An object capable of synching its state to remotes
|
||||
*/
|
||||
interface ISynchable : Observer {
|
||||
fun read(stream: RegistryFriendlyByteBuf)
|
||||
|
||||
/**
|
||||
* Provided [listener] is to be considered thread-safe, and can be called at any time
|
||||
*
|
||||
* Created [IRemoteState] is implied to have [IRemoteState.invalidate] called implicitly internally
|
||||
* Created [IRemoteState] is implied to have [IRemoteState.invalidate] called implicitly internally upon creation
|
||||
*/
|
||||
fun createRemoteState(listener: Runnable): IRemoteState
|
||||
|
||||
val shouldBeObserved: Boolean
|
||||
get() = false
|
||||
|
||||
/**
|
||||
* Whenever this synchable has remotes, optional operation and can safely always return false
|
||||
*/
|
||||
val hasRemotes: Boolean
|
||||
|
||||
/**
|
||||
* Whenever [read] has been called at least once
|
||||
*/
|
||||
val isRemote: Boolean
|
||||
|
||||
override fun observe(): Boolean {
|
||||
// no op
|
||||
return false
|
||||
|
@ -5,6 +5,7 @@ import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||
import ru.dbotthepony.kommons.util.Observer
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Supplier
|
||||
|
||||
@ -13,14 +14,26 @@ class SynchableDelegate<V>(val delegate: ListenableDelegate<V>, val codec: Matte
|
||||
|
||||
private val listeners = Listenable.Impl<V>()
|
||||
private val l = delegate.addListener(listeners)
|
||||
private val remoteCount = AtomicInteger()
|
||||
|
||||
override val hasRemotes: Boolean
|
||||
get() = remoteCount.get() > 0
|
||||
|
||||
override var isRemote: Boolean = false
|
||||
private set
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
isRemote = false
|
||||
delegate.accept(codec.decode(stream))
|
||||
}
|
||||
|
||||
inner class Remote(listener: Runnable) : IRemoteState {
|
||||
private val l = listeners.addListener(listener)
|
||||
|
||||
init {
|
||||
remoteCount.incrementAndGet()
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
codec.encode(stream, delegate.get())
|
||||
}
|
||||
@ -31,6 +44,7 @@ class SynchableDelegate<V>(val delegate: ListenableDelegate<V>, val codec: Matte
|
||||
|
||||
override fun close() {
|
||||
l.remove()
|
||||
remoteCount.decrementAndGet() // unsafe because close() can be called multiple times
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,8 @@ import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||
import ru.dbotthepony.kommons.util.Observer
|
||||
import ru.dbotthepony.mc.otm.OTM_CLEANER
|
||||
import ru.dbotthepony.mc.otm.core.collect.filterNotNull
|
||||
import ru.dbotthepony.mc.otm.core.collect.map
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import ru.dbotthepony.mc.otm.network.StreamCodecs
|
||||
@ -37,29 +39,36 @@ import java.util.function.DoubleSupplier
|
||||
import java.util.function.IntSupplier
|
||||
import java.util.function.LongSupplier
|
||||
import java.util.function.Supplier
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Universal, one-to-many delegate/value synchronizer.
|
||||
*
|
||||
* Values and delegates can be attached using [add], or by constructing subclassed directly.
|
||||
* Delta changes are tracked by [Syncher.Remote] instances.
|
||||
*
|
||||
* In general, this class is not meant to be _structurally_ concurrently mutated by different threads,
|
||||
* to avoid structure corruption a lock is employed.
|
||||
* Values and delegates can be attached using [add];
|
||||
* Delta changes are tracked by [SynchableGroup.Remote] instances.
|
||||
*
|
||||
* Attached delegates can be safely mutated by multiple threads concurrently
|
||||
* (attached [ListenableDelegate] can call [Listenable.addListener] callback from different threads).
|
||||
*
|
||||
* Generally, this class is considered to be "root" of [ISynchable] operation (due to API), but this is not strictly necessary,
|
||||
* and [SynchableGroup] can be as well embedded into other [SynchableGroup]s.
|
||||
*/
|
||||
@Suppress("UNUSED")
|
||||
class Syncher : Observer, ISynchable {
|
||||
class SynchableGroup : Observer, ISynchable, Iterable<ISynchable> {
|
||||
private val lock = ReentrantLock()
|
||||
private val slots = ArrayList<Slot?>()
|
||||
private val gaps = IntAVLTreeSet()
|
||||
private val observers = ArrayList<Slot>()
|
||||
private val remotes = ArrayList<Remote>()
|
||||
private var isRemote = false
|
||||
override var isRemote = false
|
||||
private set
|
||||
|
||||
override val hasRemotes: Boolean
|
||||
get() = remotes.isNotEmpty()
|
||||
|
||||
override fun iterator(): Iterator<ISynchable> {
|
||||
return slots.iterator().map { it?.synchable }.filterNotNull()
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
var any = false
|
||||
@ -80,7 +89,7 @@ class Syncher : Observer, ISynchable {
|
||||
var readID = stream.readVarInt()
|
||||
|
||||
while (readID > 0) {
|
||||
val slot = lock.withLock { slots.getOrNull(readID - 1) ?: throw IndexOutOfBoundsException("Unknown networked slot ${readID - 1}!") }
|
||||
val slot = slots.getOrNull(readID - 1) ?: throw IndexOutOfBoundsException("Unknown networked slot ${readID - 1}!")
|
||||
slot.synchable.read(stream)
|
||||
readID = stream.readVarInt()
|
||||
}
|
||||
@ -95,18 +104,17 @@ class Syncher : Observer, ISynchable {
|
||||
internal inner class Slot(val synchable: ISynchable) : Closeable, Observer {
|
||||
val id: Int
|
||||
|
||||
private val isRemoved = AtomicBoolean()
|
||||
private var isRemoved = false
|
||||
val remoteSlots = CopyOnWriteArrayList<Remote.RemoteSlot>()
|
||||
|
||||
override fun close() {
|
||||
if (isRemoved.compareAndSet(false, true)) {
|
||||
lock.withLock {
|
||||
slots[id] = null
|
||||
gaps.add(id)
|
||||
if (!isRemoved) {
|
||||
isRemoved = true
|
||||
slots[id] = null
|
||||
gaps.add(id)
|
||||
|
||||
observers.remove(this)
|
||||
remoteSlots.forEach { it.remove() }
|
||||
}
|
||||
observers.remove(this)
|
||||
remoteSlots.forEach { it.remove() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +123,7 @@ class Syncher : Observer, ISynchable {
|
||||
}
|
||||
|
||||
private fun markDirty() {
|
||||
if (!isRemote && !isRemoved.get()) {
|
||||
if (!isRemote && !isRemoved) {
|
||||
remoteSlots.forEach {
|
||||
it.markDirty()
|
||||
}
|
||||
@ -375,7 +383,7 @@ class Syncher : Observer, ISynchable {
|
||||
/**
|
||||
* Returns null if this remote is clean.
|
||||
*
|
||||
* [Syncher.observe] is not called automatically for performance
|
||||
* [SynchableGroup.observe] is not called automatically for performance
|
||||
* reasons, you must call it manually.
|
||||
*/
|
||||
fun write(registry: RegistryAccess): ByteArrayList? {
|
||||
@ -421,18 +429,17 @@ class Syncher : Observer, ISynchable {
|
||||
|
||||
override fun close() {
|
||||
if (!isRemoved) {
|
||||
isRemoved = true
|
||||
|
||||
lock.withLock {
|
||||
if (!isRemoved) {
|
||||
remoteSlots.forEach {
|
||||
it.remove()
|
||||
it.slot.remoteSlots.remove(it)
|
||||
}
|
||||
|
||||
remotes.remove(this)
|
||||
remoteSlots.forEach {
|
||||
it.remove()
|
||||
it.slot.remoteSlots.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
dirty.clear()
|
||||
remotes.remove(this)
|
||||
dirty.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,12 @@ class SynchableMap<K, V>(
|
||||
val keyCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, K>,
|
||||
val valueCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>
|
||||
) : ISynchable {
|
||||
override var isRemote: Boolean = false
|
||||
private set
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
isRemote = true
|
||||
|
||||
var action = stream.readByte().toInt()
|
||||
|
||||
while (true) {
|
||||
@ -105,6 +110,9 @@ class SynchableMap<K, V>(
|
||||
}
|
||||
}
|
||||
|
||||
override val hasRemotes: Boolean
|
||||
get() = remoteSlots.isNotEmpty()
|
||||
|
||||
override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
return RemoteState(listener)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||
import ru.dbotthepony.kommons.util.ValueObserver
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.Consumer
|
||||
import kotlin.concurrent.withLock
|
||||
@ -14,10 +15,15 @@ class SynchableObservedDelegate<V>(val delegate: Delegate<V>, val codec: Mattery
|
||||
private object Mark
|
||||
|
||||
private val listeners = Listenable.Impl<V>()
|
||||
private val remoteCount = AtomicInteger()
|
||||
private var observed: Any? = Mark
|
||||
private val observeLock = ReentrantLock()
|
||||
|
||||
override var isRemote: Boolean = false
|
||||
private set
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
isRemote = true
|
||||
val read = codec.decode(stream)
|
||||
observed = codec.copy(read)
|
||||
delegate.accept(read)
|
||||
@ -75,6 +81,10 @@ class SynchableObservedDelegate<V>(val delegate: Delegate<V>, val codec: Mattery
|
||||
private inner class RemoteState(listener: Runnable) : IRemoteState {
|
||||
private val l = listeners.addListener(listener)
|
||||
|
||||
init {
|
||||
remoteCount.incrementAndGet()
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
codec.encode(stream, delegate.get())
|
||||
}
|
||||
@ -85,9 +95,13 @@ class SynchableObservedDelegate<V>(val delegate: Delegate<V>, val codec: Mattery
|
||||
|
||||
override fun close() {
|
||||
l.remove()
|
||||
remoteCount.decrementAndGet() // unsafe because close() can be called multiple times
|
||||
}
|
||||
}
|
||||
|
||||
override val hasRemotes: Boolean
|
||||
get() = remoteCount.get() > 0
|
||||
|
||||
override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
return RemoteState(listener)
|
||||
}
|
||||
|
@ -87,6 +87,12 @@ class SynchableSet<V>(val delegate: ListenableSet<V>, val codec: MatteryStreamCo
|
||||
}
|
||||
}
|
||||
|
||||
override var isRemote: Boolean = false
|
||||
private set
|
||||
|
||||
override val hasRemotes: Boolean
|
||||
get() = remoteSlots.isNotEmpty()
|
||||
|
||||
override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
return RemoteState(listener)
|
||||
}
|
||||
@ -96,6 +102,8 @@ class SynchableSet<V>(val delegate: ListenableSet<V>, val codec: MatteryStreamCo
|
||||
}
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
isRemote = true
|
||||
|
||||
var action = stream.readByte().toInt()
|
||||
|
||||
while (true) {
|
||||
|
@ -114,18 +114,17 @@ object MBlockEntities {
|
||||
}
|
||||
|
||||
private fun registerClient(event: FMLClientSetupEvent) {
|
||||
event.enqueueWork {
|
||||
BlockEntityRenderers.register(BLACK_HOLE, ::BlackHoleRenderer)
|
||||
BlockEntityRenderers.register(GRAVITATION_STABILIZER, ::GravitationStabilizerRenderer)
|
||||
BlockEntityRenderers.register(ENERGY_COUNTER, ::EnergyCounterRenderer)
|
||||
BlockEntityRenderers.register(BATTERY_BANK, ::BatteryBankRenderer)
|
||||
BlockEntityRenderers.register(MATTER_CAPACITOR_BANK, ::MatterBatteryBankRenderer)
|
||||
BlockEntityRenderers.register(MATTER_RECONSTRUCTOR, ::MatterReconstructorRenderer)
|
||||
BlockEntityRenderers.register(MATTER_REPLICATOR, ::MatterReplicatorRenderer)
|
||||
BlockEntityRenderers.register(MATTER_SCANNER, ::MatterScannerRenderer)
|
||||
BlockEntityRenderers.register(POWERED_SMOKER, ::PoweredSmokerRenderer)
|
||||
BlockEntityRenderers.register(HOLO_SIGN, ::HoloSignRenderer)
|
||||
BlockEntityRenderers.register(FLUID_TANK, ::FluidTankRenderer)
|
||||
}
|
||||
BlockEntityRenderers.register(BLACK_HOLE, ::BlackHoleRenderer)
|
||||
BlockEntityRenderers.register(BLACK_HOLE_GENERATOR, ::BlackHoleGeneratorRenderer)
|
||||
BlockEntityRenderers.register(GRAVITATION_STABILIZER, ::GravitationStabilizerRenderer)
|
||||
BlockEntityRenderers.register(ENERGY_COUNTER, ::EnergyCounterRenderer)
|
||||
BlockEntityRenderers.register(BATTERY_BANK, ::BatteryBankRenderer)
|
||||
BlockEntityRenderers.register(MATTER_CAPACITOR_BANK, ::MatterBatteryBankRenderer)
|
||||
BlockEntityRenderers.register(MATTER_RECONSTRUCTOR, ::MatterReconstructorRenderer)
|
||||
BlockEntityRenderers.register(MATTER_REPLICATOR, ::MatterReplicatorRenderer)
|
||||
BlockEntityRenderers.register(MATTER_SCANNER, ::MatterScannerRenderer)
|
||||
BlockEntityRenderers.register(POWERED_SMOKER, ::PoweredSmokerRenderer)
|
||||
BlockEntityRenderers.register(HOLO_SIGN, ::HoloSignRenderer)
|
||||
BlockEntityRenderers.register(FLUID_TANK, ::FluidTankRenderer)
|
||||
}
|
||||
}
|
||||
|
@ -56,4 +56,7 @@ object MBlockTags {
|
||||
val HARDENED_GLASS_YELLOW: TagKey<Block> = BlockTags.create(ResourceLocation("c", "hardened_glass/yellow"))
|
||||
|
||||
val MACHINES: TagKey<Block> = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "machines"))
|
||||
val MULTIBLOCK_STRUCTURE: TagKey<Block> = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "multiblock_structure"))
|
||||
val MULTIBLOCK_HARD_STRUCTURE: TagKey<Block> = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "multiblock_hard_structure"))
|
||||
val MULTIBLOCK_SOFT_STRUCTURE: TagKey<Block> = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "multiblock_soft_structure"))
|
||||
}
|
||||
|
@ -174,7 +174,6 @@ object MBlocks {
|
||||
val FLUID_TANK: FluidTankBlock by registry.register(MNames.FLUID_TANK) { FluidTankBlock() }
|
||||
val DEV_CHEST: DevChestBlock by registry.register(MNames.DEV_CHEST) { DevChestBlock() }
|
||||
|
||||
val MULTIBLOCK_STRUCTURE by registry.register(MNames.MULTIBLOCK_STRUCTURE) { MatteryBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).requiresCorrectToolForDrops().destroyTime(2.5f).explosionResistance(160.0f)) }
|
||||
val BLACK_HOLE_GENERATOR by registry.register(MNames.BLACK_HOLE_GENERATOR) { BlackHoleGeneratorBlock() }
|
||||
val MATTER_INJECTOR by registry.register(MNames.MATTER_INJECTOR) { RotatableMatteryBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).requiresCorrectToolForDrops().destroyTime(2.5f).explosionResistance(160.0f)) }
|
||||
val ANTIMATTER_INJECTOR by registry.register(MNames.ANTIMATTER_INJECTOR) { RotatableMatteryBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).requiresCorrectToolForDrops().destroyTime(2.5f).explosionResistance(160.0f)) }
|
||||
|
@ -136,6 +136,9 @@ object MItems {
|
||||
val MATTER_ENTANGLER: BlockItem by registry.register(MNames.MATTER_ENTANGLER) { BlockItem(MBlocks.MATTER_ENTANGLER, DEFAULT_PROPERTIES) }
|
||||
|
||||
val BLACK_HOLE_GENERATOR by registry.register(MNames.BLACK_HOLE_GENERATOR) { BlockItem(MBlocks.BLACK_HOLE_GENERATOR, DEFAULT_PROPERTIES) }
|
||||
val MATTER_INJECTOR by registry.register(MNames.MATTER_INJECTOR) { BlockItem(MBlocks.MATTER_INJECTOR, DEFAULT_PROPERTIES) }
|
||||
val ANTIMATTER_INJECTOR by registry.register(MNames.ANTIMATTER_INJECTOR) { BlockItem(MBlocks.ANTIMATTER_INJECTOR, DEFAULT_PROPERTIES) }
|
||||
val HIGH_ENERGY_PARTICLE_COLLECTOR by registry.register(MNames.HIGH_ENERGY_PARTICLE_COLLECTOR) { BlockItem(MBlocks.HIGH_ENERGY_PARTICLE_COLLECTOR, DEFAULT_PROPERTIES) }
|
||||
val ITEM_INPUT_HATCH by registry.register(MNames.ITEM_INPUT_HATCH) { BlockItem(MBlocks.ITEM_INPUT_HATCH, DEFAULT_PROPERTIES) }
|
||||
val ITEM_OUTPUT_HATCH by registry.register(MNames.ITEM_OUTPUT_HATCH) { BlockItem(MBlocks.ITEM_OUTPUT_HATCH, DEFAULT_PROPERTIES) }
|
||||
val ENERGY_INPUT_HATCH by registry.register(MNames.ENERGY_INPUT_HATCH) { BlockItem(MBlocks.ENERGY_INPUT_HATCH, DEFAULT_PROPERTIES) }
|
||||
|
@ -31,6 +31,7 @@ import ru.dbotthepony.mc.otm.client.screen.tech.AbstractProcessingMachineScreen
|
||||
import ru.dbotthepony.mc.otm.client.screen.tech.AndroidChargerScreen
|
||||
import ru.dbotthepony.mc.otm.client.screen.tech.AndroidStationScreen
|
||||
import ru.dbotthepony.mc.otm.client.screen.tech.BatteryBankScreen
|
||||
import ru.dbotthepony.mc.otm.client.screen.tech.BlackHoleGeneratorScreen
|
||||
import ru.dbotthepony.mc.otm.client.screen.tech.ChemicalGeneratorScreen
|
||||
import ru.dbotthepony.mc.otm.client.screen.tech.CobblerScreen
|
||||
import ru.dbotthepony.mc.otm.client.screen.tech.EnergyCounterScreen
|
||||
@ -63,6 +64,7 @@ import ru.dbotthepony.mc.otm.menu.storage.StoragePowerSupplierMenu
|
||||
import ru.dbotthepony.mc.otm.menu.tech.AndroidChargerMenu
|
||||
import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
|
||||
import ru.dbotthepony.mc.otm.menu.tech.BatteryBankMenu
|
||||
import ru.dbotthepony.mc.otm.menu.tech.BlackHoleGeneratorMenu
|
||||
import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu
|
||||
import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu
|
||||
import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu
|
||||
@ -114,6 +116,7 @@ object MMenus {
|
||||
val MATTER_OUTPUT_HATCH by registry.register(MNames.MATTER_OUTPUT_HATCH) { MenuType(MatterHatchMenu::output, FeatureFlags.VANILLA_SET) }
|
||||
val ENERGY_INPUT_HATCH by registry.register(MNames.ENERGY_INPUT_HATCH) { MenuType(EnergyHatchMenu::input, FeatureFlags.VANILLA_SET) }
|
||||
val ENERGY_OUTPUT_HATCH by registry.register(MNames.ENERGY_OUTPUT_HATCH) { MenuType(EnergyHatchMenu::output, FeatureFlags.VANILLA_SET) }
|
||||
val BLACK_HOLE_GENERATOR by registry.register(MNames.BLACK_HOLE_GENERATOR) { MenuType(::BlackHoleGeneratorMenu, FeatureFlags.VANILLA_SET) }
|
||||
|
||||
val STORAGE_BUS by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu, FeatureFlags.VANILLA_SET) }
|
||||
val STORAGE_IMPORTER_EXPORTER by registry.register(MNames.STORAGE_IMPORTER) { MenuType(::StorageImporterExporterMenu, FeatureFlags.VANILLA_SET) }
|
||||
@ -165,5 +168,6 @@ object MMenus {
|
||||
event.register(MATTER_OUTPUT_HATCH, ::MatterHatchScreen)
|
||||
event.register(ENERGY_INPUT_HATCH, ::EnergyHatchScreen)
|
||||
event.register(ENERGY_OUTPUT_HATCH, ::EnergyHatchScreen)
|
||||
event.register(BLACK_HOLE_GENERATOR, ::BlackHoleGeneratorScreen)
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ object MNames {
|
||||
const val ANDROID_CHARGER = "android_charger"
|
||||
const val INFINITE_WATER_SOURCE = "infinite_water_source"
|
||||
const val DEV_CHEST = "dev_chest"
|
||||
const val MULTIBLOCK_STRUCTURE = "multiblock_structure"
|
||||
const val BLACK_HOLE_GENERATOR = "black_hole_generator"
|
||||
const val MATTER_INJECTOR = "matter_injector"
|
||||
const val ANTIMATTER_INJECTOR = "antimatter_injector"
|
||||
|
Loading…
Reference in New Issue
Block a user