Multiblock docs
This commit is contained in:
parent
36ec7855f0
commit
7d088d6433
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user