Compare commits

...

2 Commits

4 changed files with 160 additions and 16 deletions

View File

@ -1,15 +1,15 @@
package ru.dbotthepony.mc.otm.block.entity.tech
package ru.dbotthepony.mc.otm.block.entity.blackhole
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos
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 ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity
import ru.dbotthepony.mc.otm.block.entity.tech.EnergyHatchBlockEntity
import ru.dbotthepony.mc.otm.block.entity.tech.MatterHatchBlockEntity
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.core.Multiblock
import ru.dbotthepony.mc.otm.core.MultiblockBuilder

View File

@ -12,7 +12,7 @@ import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.material.MapColor
import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity
import ru.dbotthepony.mc.otm.block.entity.tech.BlackHoleGeneratorBlockEntity
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleGeneratorBlockEntity
import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.times

View File

@ -28,6 +28,7 @@ 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>>>()
@ -125,6 +126,9 @@ 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)
@ -135,6 +139,12 @@ class MultiblockBuilder {
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
@ -152,41 +162,71 @@ class MultiblockBuilder {
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
@ -196,6 +236,9 @@ class MultiblockBuilder {
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
@ -205,11 +248,17 @@ class MultiblockBuilder {
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
@ -219,18 +268,38 @@ class MultiblockBuilder {
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)
}
@ -248,6 +317,9 @@ class MultiblockBuilder {
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)
@ -259,6 +331,11 @@ class MultiblockBuilder {
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)
@ -278,26 +355,44 @@ class MultiblockBuilder {
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)
}
@ -365,13 +460,26 @@ class MultiblockBuilder {
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()
@ -384,6 +492,11 @@ class MultiblockBuilder {
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))
}
@ -400,6 +513,9 @@ class MultiblockFactory(val north: ImmutableSet<Part>, val customChecks: Immutab
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)
}
@ -685,12 +801,12 @@ class Multiblock(pos: BlockPos, factory: MultiblockFactory) {
return new
}
private val tag2BlockEntity = Object2ObjectOpenHashMap<MultiblockBuilder.EntityTag<*>, BEList<*>>()
private val tag2BlockState = Object2ObjectOpenHashMap<Any, Reference2IntMap<BlockState>>()
private val tag2Block = Object2ObjectOpenHashMap<Any, Reference2IntMap<Block>>()
private val tag2BlockEntity = HashMap<MultiblockBuilder.EntityTag<*>, BEList<*>>()
private val tag2BlockState = HashMap<Any, Reference2IntMap<BlockState>>()
private val tag2Block = HashMap<Any, Reference2IntMap<Block>>()
private val tag2BlockStateViews = Object2ObjectOpenHashMap<Any, Reference2IntMap<BlockState>>()
private val tag2BlockViews = Object2ObjectOpenHashMap<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>>()
@ -768,21 +884,33 @@ class Multiblock(pos: BlockPos, factory: MultiblockFactory) {
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()
@ -799,9 +927,15 @@ class Multiblock(pos: BlockPos, factory: MultiblockFactory) {
return true
} else if (activeConfig != null) {
for (config in configurations) {
if (config !== activeConfig && config.update(levelAccessor) && customChecks.all { it.test(this) }) {
if (config !== activeConfig && config.update(levelAccessor)) {
this.activeConfig = config
return true
if (customChecks.all { it.test(this) })
return true
else {
config.clear()
this.activeConfig = null
}
}
}
@ -810,10 +944,16 @@ class Multiblock(pos: BlockPos, factory: MultiblockFactory) {
return false
} else {
for (config in configurations) {
if (config.update(levelAccessor) && customChecks.all { it.test(this) }) {
if (config.update(levelAccessor)) {
this.activeConfig = config
this.isValid = true
return true
if (customChecks.all { it.test(this) }) {
this.isValid = true
return true
} else {
config.clear()
this.activeConfig = null
}
}
}
@ -835,10 +975,13 @@ class Multiblock(pos: BlockPos, factory: MultiblockFactory) {
else -> throw IllegalArgumentException(direction.name)
}
activeConfig = config
isValid = config.update(levelAccessor) && customChecks.all { it.test(this) }
if (isValid)
activeConfig = config
if (!isValid) {
config.clear()
activeConfig = null
}
return isValid
}

View File

@ -10,6 +10,7 @@ import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent
import ru.dbotthepony.mc.otm.block.entity.*
import ru.dbotthepony.mc.otm.block.entity.tech.*
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleGeneratorBlockEntity
import ru.dbotthepony.mc.otm.block.entity.cable.SimpleEnergyCableBlockEntity
import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity
import ru.dbotthepony.mc.otm.block.entity.decorative.DevChestBlockEntity