Update MatteryInventoryChangeTrigger
This commit is contained in:
parent
567e03498e
commit
4120660ebc
@ -1,204 +1,110 @@
|
||||
package ru.dbotthepony.mc.otm.triggers
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.Hash.Strategy
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
|
||||
import com.mojang.serialization.Codec
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
||||
import net.minecraft.advancements.CriteriaTriggers
|
||||
import net.minecraft.advancements.CriterionTrigger
|
||||
import net.minecraft.advancements.critereon.InventoryChangeTrigger
|
||||
import net.minecraft.advancements.critereon.MinMaxBounds
|
||||
import net.minecraft.server.PlayerAdvancements
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.Container
|
||||
import net.minecraft.world.entity.player.Inventory
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import ru.dbotthepony.mc.otm.capability.matteryPlayer
|
||||
import ru.dbotthepony.mc.otm.container.get
|
||||
import ru.dbotthepony.mc.otm.container.util.iterator
|
||||
import ru.dbotthepony.mc.otm.core.collect.flatMap
|
||||
import ru.dbotthepony.mc.otm.core.collect.toList
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
import java.util.stream.Collectors
|
||||
import java.util.stream.Stream
|
||||
|
||||
private typealias TriggerSet = ObjectOpenHashSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>
|
||||
|
||||
private fun items(of: CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>): List<Item> {
|
||||
return of.trigger.items
|
||||
.stream()
|
||||
.flatMap { it.items.map { it.stream() }.orElse(Stream.empty()) }
|
||||
.map { it.value() }
|
||||
.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* This object detours all necessary InventoryChangeTrigger methods
|
||||
*
|
||||
* Reason behind this is to support arbitrary containers (not just [Inventory], done for Exopack) and to improve performance by using search tree
|
||||
* Reason behind this is to support arbitrary containers (not just [Inventory], done for Exopack) and to improve performance
|
||||
*/
|
||||
object MatteryInventoryChangeTrigger : CriterionTrigger<InventoryChangeTrigger.TriggerInstance> {
|
||||
private object BoundsStrategy : Strategy<MinMaxBounds.Ints?> {
|
||||
override fun equals(a: MinMaxBounds.Ints?, b: MinMaxBounds.Ints?): Boolean {
|
||||
return a?.min == b?.min && a?.max == b?.max
|
||||
}
|
||||
|
||||
override fun hashCode(o: MinMaxBounds.Ints?): Int {
|
||||
return o?.let { Integer.rotateLeft(it.min.hashCode(), 16) xor it.max.hashCode() } ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
private object DefaultStrategy : Strategy<Any?> {
|
||||
override fun equals(a: Any?, b: Any?): Boolean {
|
||||
return a == b
|
||||
}
|
||||
|
||||
override fun hashCode(o: Any?): Int {
|
||||
return o.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
private fun interface Tester<T> {
|
||||
fun test(value: T, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int): Boolean
|
||||
}
|
||||
|
||||
private fun interface Hint<T> {
|
||||
fun getHints(inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int): Collection<T>?
|
||||
}
|
||||
|
||||
private class Node<T>(
|
||||
val strategy: Strategy<in T?>,
|
||||
private val getter: InventoryChangeTrigger.TriggerInstance.() -> Collection<T>,
|
||||
val test: Tester<T?>, // if hint is present, tester is always going to be called with `null` value once
|
||||
val hint: Hint<T>? = null
|
||||
) {
|
||||
fun getValues(instance: InventoryChangeTrigger.TriggerInstance): Set<T> {
|
||||
val result = ObjectArraySet<T>()
|
||||
result.addAll(getter.invoke(instance))
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private val nodes = ArrayList<Node<*>>()
|
||||
|
||||
init {
|
||||
nodes.add(Node(BoundsStrategy, { listOf(slots.occupied) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsOccupied) }))
|
||||
nodes.add(Node(BoundsStrategy, { listOf(slots.full) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsFull) }))
|
||||
nodes.add(Node(BoundsStrategy, { listOf(slots.empty) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsEmpty) }))
|
||||
|
||||
nodes.add(Node(
|
||||
DefaultStrategy,
|
||||
{ items.iterator().flatMap { (it.items.map { it.map { it.value() } }.orElse(listOf(null))).iterator() }.toList() },
|
||||
{ v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v == null || item.item == v },
|
||||
{ inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> mutableListOf(item.item) }))
|
||||
|
||||
nodes.add(Node(
|
||||
DefaultStrategy,
|
||||
{ items.map { it.tag.orElse(null) } },
|
||||
{ v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v == null || item.`is`(v) },
|
||||
{ inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> item.tags.collect(Collectors.toCollection(::ArrayList)) }))
|
||||
|
||||
nodes.add(Node(BoundsStrategy, { predicates.map { it.count } }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(item.count) }))
|
||||
|
||||
nodes.add(Node(
|
||||
BoundsStrategy,
|
||||
{ items.map { it.durability } },
|
||||
{ v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.isAny || item.isDamageableItem && v.matches(item.maxDamage - item.damageValue) }))
|
||||
}
|
||||
|
||||
private class ListenerTree(private val advancements: PlayerAdvancements) {
|
||||
private val set = ObjectOpenHashSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>()
|
||||
private val tree = Object2ObjectOpenCustomHashMap<Any?, Any>(nodes.first().strategy as Strategy<in Any?>)
|
||||
|
||||
private fun search(instance: InventoryChangeTrigger.TriggerInstance, tree: MutableMap<Any?, Any>, nodeId: Int): Collection<MutableSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>> {
|
||||
val node = nodes[nodeId]
|
||||
|
||||
if (nodeId + 1 != nodes.size) {
|
||||
val result = ArrayList<MutableSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>>()
|
||||
|
||||
for (v in node.getValues(instance)) {
|
||||
result.addAll(
|
||||
search(
|
||||
instance,
|
||||
tree.computeIfAbsent(v, Object2ObjectFunction { Object2ObjectOpenCustomHashMap<Any, Any>(4, nodes[nodeId + 1].strategy as Strategy<in Any?>) }) as MutableMap<Any?, Any>,
|
||||
nodeId + 1))
|
||||
}
|
||||
|
||||
return result
|
||||
} else {
|
||||
val result = ArrayList<MutableSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>>()
|
||||
|
||||
for (v in node.getValues(instance)) {
|
||||
result.add(tree.computeIfAbsent(v, Object2ObjectFunction { ObjectOpenHashSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>(4) }) as MutableSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
// fastutil for lesser memory footprint, even if it causes lesser performance
|
||||
private val sorted = Reference2ObjectOpenHashMap<Item, TriggerSet>()
|
||||
private val unsorted = TriggerSet()
|
||||
private val tracked = TriggerSet()
|
||||
|
||||
fun add(listener: CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>) {
|
||||
if (set.add(listener)) {
|
||||
search(listener.trigger, tree, 0).forEach { it.add(listener) }
|
||||
if (tracked.add(listener)) {
|
||||
val items = items(listener)
|
||||
|
||||
if (items.isEmpty()) {
|
||||
unsorted.add(listener)
|
||||
} else {
|
||||
for (item in items) {
|
||||
sorted.computeIfAbsent(item, Reference2ObjectFunction { TriggerSet() }).add(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(listener: CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>) {
|
||||
if (set.remove(listener)) {
|
||||
search(listener.trigger, tree, 0).forEach { it.remove(listener) }
|
||||
if (tracked.remove(listener)) {
|
||||
unsorted.remove(listener)
|
||||
|
||||
for (item in items(listener)) {
|
||||
val set = sorted[item]
|
||||
|
||||
if (set != null && set.remove(listener) && set.isEmpty()) {
|
||||
sorted.remove(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addNull(input: Collection<Any?>): Collection<Any?> {
|
||||
return if (null in input)
|
||||
input
|
||||
else if (input is ArrayList) {
|
||||
input.add(null)
|
||||
input
|
||||
} else {
|
||||
ArrayList(input).also { it.add(null) }
|
||||
}
|
||||
}
|
||||
private fun trigger(inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int, elements: TriggerSet, matches: TriggerSet) {
|
||||
for (l in elements) {
|
||||
with (l.trigger) {
|
||||
if (slots.matches(slotsFull, slotsEmpty, slotsOccupied)) {
|
||||
if (items.isEmpty() || items.size == 1 && item.isNotEmpty && this.items[0].test(item)) {
|
||||
matches.add(l)
|
||||
} else if (items.size > 1) {
|
||||
val unsatisfied = ObjectArrayList(items)
|
||||
unsatisfied.removeIf { it.test(ItemStack.EMPTY) }
|
||||
|
||||
private fun trigger(inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int, tree: MutableMap<Any?, Any>, nodeId: Int, set: MutableSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>) {
|
||||
val node = nodes[nodeId] as Node<Any?>
|
||||
val keys = node.hint?.getHints(inventory, item, slotsFull, slotsEmpty, slotsOccupied)?.let(::addNull) ?: tree.keys
|
||||
for (inventoryItem in inventory) {
|
||||
unsatisfied.removeIf { it.test(inventoryItem) }
|
||||
if (unsatisfied.isEmpty) break
|
||||
}
|
||||
|
||||
for (k in keys) {
|
||||
val v = tree[k] ?: continue
|
||||
|
||||
if (node.test.test(k, inventory, item, slotsFull, slotsEmpty, slotsOccupied)) {
|
||||
if (nodeId + 1 == nodes.size) {
|
||||
for (l in v as Set<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>) {
|
||||
// переделываем matches у InventoryTriggerInstance
|
||||
with (l.trigger) {
|
||||
if (
|
||||
this.slotsFull.matches(slotsFull) &&
|
||||
this.slotsEmpty.matches(slotsEmpty) &&
|
||||
this.slotsOccupied.matches(slotsOccupied)
|
||||
) {
|
||||
if (this.predicates.isEmpty() || this.predicates.size == 1 && item.isNotEmpty && this.predicates[0].matches(item)) {
|
||||
set.add(l)
|
||||
} else if (this.predicates.size > 1) {
|
||||
val unsatisfied = ObjectArrayList(this.predicates)
|
||||
unsatisfied.removeIf { it.matches(ItemStack.EMPTY) }
|
||||
|
||||
for (inventoryItem in inventory) {
|
||||
unsatisfied.removeIf { it.matches(inventoryItem) }
|
||||
if (unsatisfied.isEmpty) break
|
||||
}
|
||||
|
||||
if (unsatisfied.isEmpty) {
|
||||
set.add(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unsatisfied.isEmpty) {
|
||||
matches.add(l)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
trigger(inventory, item, slotsFull, slotsEmpty, slotsOccupied, v as MutableMap<Any?, Any>, nodeId + 1, set)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun trigger(inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int) {
|
||||
val matches = ObjectOpenHashSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>()
|
||||
val matches = TriggerSet()
|
||||
|
||||
trigger(inventory, item, slotsFull, slotsEmpty, slotsOccupied, tree, 0, matches)
|
||||
trigger(inventory, item, slotsFull, slotsEmpty, slotsOccupied, unsorted, matches)
|
||||
|
||||
val sorted = sorted[item.item]
|
||||
|
||||
if (sorted != null) {
|
||||
trigger(inventory, item, slotsFull, slotsEmpty, slotsOccupied, sorted, matches)
|
||||
}
|
||||
|
||||
for (l in matches) {
|
||||
l.run(advancements)
|
||||
@ -220,18 +126,14 @@ object MatteryInventoryChangeTrigger : CriterionTrigger<InventoryChangeTrigger.T
|
||||
listeners.remove(advancements)
|
||||
}
|
||||
|
||||
override fun createInstance(data: JsonObject, context: DeserializationContext): InventoryChangeTrigger.TriggerInstance {
|
||||
return CriteriaTriggers.INVENTORY_CHANGED.createInstance(data, context)
|
||||
override fun codec(): Codec<InventoryChangeTrigger.TriggerInstance> {
|
||||
return CriteriaTriggers.INVENTORY_CHANGED.codec()
|
||||
}
|
||||
|
||||
// реплицирует ванильный метод
|
||||
fun trigger(player: ServerPlayer, inventory: Container, item: ItemStack) {
|
||||
if (inventory === player.inventory) {
|
||||
val mattery = player.matteryPlayer
|
||||
|
||||
if (mattery != null) {
|
||||
return trigger(player, mattery.combinedInventory, item)
|
||||
}
|
||||
return trigger(player, player.matteryPlayer.combinedInventory, item)
|
||||
}
|
||||
|
||||
var slotsFull = 0
|
||||
|
Loading…
Reference in New Issue
Block a user