diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Multiblock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Multiblock.kt index 4add83a9d..3e654cb29 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Multiblock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Multiblock.kt @@ -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>>() @@ -125,6 +126,9 @@ class MultiblockBuilder { private val nodes = Object2ObjectOpenHashMap() private val customChecks = ArrayList>() + /** + * Tags specific [T] block entities to be exposed through [Multiblock.blockEntities] when specific [Part]s are tagged using [Part.tag] + */ class EntityTag(val clazz: KClass, val predicate: Predicate = Predicate { true }) : Predicate { override fun test(t: BlockEntity): Boolean { return clazz.isInstance(t) && predicate.test(t as T) @@ -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): MultiblockBuilder { customChecks.add(predicate) return this @@ -152,41 +162,71 @@ class MultiblockBuilder { val blockTags = ObjectArraySet() val blockEntityTags = ObjectArraySet>() + /** + * Marks this node report its block in [Multiblock.blockStates] method when called with specified [value] + * + * [value] is searched using "value" semantics (`==`) + */ fun tagBlockState(value: Any): T { blockStateTags.add(value) return this as T } + /** + * Marks this node report its block in [Multiblock.blockStates] method when called without arguments + */ fun tagBlockState(): T { blockStateTags.add(GLOBAL_BLOCK_TAG) return this as T } + /** + * Marks this node report its block in [Multiblock.blocks] method when called with specified [value] + * + * [value] is searched for using "value" semantics (`==`) + */ fun tagBlock(value: Any): T { blockTags.add(value) return this as T } + /** + * Marks this node report its block in [Multiblock.blocks] method when called without arguments + */ fun tagBlock(): T { blockTags.add(GLOBAL_BLOCK_TAG) return this as T } + /** + * Marks this node to report block entities put at its position when called [Multiblock.blockEntities] with specified [value] + * + * [value] is searched for using "identity" semantics (`===`) + */ fun tag(value: EntityTag<*>): T { blockEntityTags.add(value) return this as T } + /** + * Removes all predicates, custom and prebuilt alike + */ fun clear(): T { predicates.clear() return this as T } + /** + * Adds a custom predicate on this node's position + */ fun predicate(predicate: BlockPredicate): T { predicates.add(predicate) return this as T } + /** + * Adds a block predicate, matching any valid blockstate of [block] on this node's position + */ fun block(block: Block): T { predicates.add { pos, access -> // use getChunk directly because we don't want to chunkload @@ -196,6 +236,9 @@ class MultiblockBuilder { return this as T } + /** + * Adds a block tag predicate on this node's position + */ fun block(block: TagKey): T { predicates.add { pos, access -> // use getChunk directly because we don't want to chunkload @@ -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 { return And(this as T) } + /** + * Creates "or" subnode, which requires at least one of its children to test true + * + * @see or + */ fun or(): Or { return Or(this as T) } + /** + * Creates "and" subnode, which requires all its children to test true + * + * @see And + */ fun and(configurator: And.() -> Unit): And { return And(this as T).also(configurator) } + /** + * Creates "or" subnode, which requires at least one of its children to test true + * + * @see or + */ fun or(configurator: Or.() -> Unit): Or { return Or(this as T).also(configurator) } @@ -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

>(val parent: P) : Part>() { 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

>(val parent: P) : Part>() { 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, val customChecks: Immutab val blockEntityTags: ImmutableSet>, ) + /** + * 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, BEList<*>>() - private val tag2BlockState = Object2ObjectOpenHashMap>() - private val tag2Block = Object2ObjectOpenHashMap>() + private val tag2BlockEntity = HashMap, BEList<*>>() + private val tag2BlockState = HashMap>() + private val tag2Block = HashMap>() - private val tag2BlockStateViews = Object2ObjectOpenHashMap>() - private val tag2BlockViews = Object2ObjectOpenHashMap>() + private val tag2BlockStateViews = HashMap>() + private val tag2BlockViews = HashMap>() private val blockEntityLists = ArrayList>() private val blockStateLists = ArrayList>() @@ -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 { if (!isValid) return Reference2IntMaps.emptyMap() return activeConfig?.blocks(tag) ?: Reference2IntMaps.emptyMap() } + /** + * Returns blockstate counts present on nodes tagged by [tag] + */ fun blockStates(tag: Any): Reference2IntMap { if (!isValid) return Reference2IntMaps.emptyMap() return activeConfig?.blockStates(tag) ?: Reference2IntMaps.emptyMap() } + /** + * Returns block counts present on nodes tagged by [MultiblockBuilder.Part.tagBlock] without arguments + */ fun blocks(): Reference2IntMap { if (!isValid) return Reference2IntMaps.emptyMap() return activeConfig?.blocks() ?: Reference2IntMaps.emptyMap() } + /** + * Returns blockstate counts present on nodes tagged by [MultiblockBuilder.Part.tagBlock] without arguments + */ fun blockStates(): Reference2IntMap { if (!isValid) return Reference2IntMaps.emptyMap() return activeConfig?.blockStates() ?: Reference2IntMaps.emptyMap() @@ -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 }