Update MatteryInventoryChangeTrigger

This commit is contained in:
DBotThePony 2024-08-25 20:53:55 +07:00
parent 567e03498e
commit 4120660ebc
Signed by: DBot
GPG Key ID: DCC23B5715498507

View File

@ -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