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 package ru.dbotthepony.mc.otm.triggers
import com.google.gson.JsonObject import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.Hash.Strategy import com.mojang.serialization.Codec
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
import it.unimi.dsi.fastutil.objects.ObjectArrayList 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.ObjectOpenHashSet
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import net.minecraft.advancements.CriteriaTriggers import net.minecraft.advancements.CriteriaTriggers
import net.minecraft.advancements.CriterionTrigger import net.minecraft.advancements.CriterionTrigger
import net.minecraft.advancements.critereon.InventoryChangeTrigger import net.minecraft.advancements.critereon.InventoryChangeTrigger
import net.minecraft.advancements.critereon.MinMaxBounds
import net.minecraft.server.PlayerAdvancements import net.minecraft.server.PlayerAdvancements
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container import net.minecraft.world.Container
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.container.util.iterator 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 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 * 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> { 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 class ListenerTree(private val advancements: PlayerAdvancements) {
private val set = ObjectOpenHashSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>() // fastutil for lesser memory footprint, even if it causes lesser performance
private val tree = Object2ObjectOpenCustomHashMap<Any?, Any>(nodes.first().strategy as Strategy<in Any?>) private val sorted = Reference2ObjectOpenHashMap<Item, TriggerSet>()
private val unsorted = TriggerSet()
private fun search(instance: InventoryChangeTrigger.TriggerInstance, tree: MutableMap<Any?, Any>, nodeId: Int): Collection<MutableSet<CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>>> { private val tracked = TriggerSet()
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
}
}
fun add(listener: CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>) { fun add(listener: CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>) {
if (set.add(listener)) { if (tracked.add(listener)) {
search(listener.trigger, tree, 0).forEach { it.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>) { fun remove(listener: CriterionTrigger.Listener<InventoryChangeTrigger.TriggerInstance>) {
if (set.remove(listener)) { if (tracked.remove(listener)) {
search(listener.trigger, tree, 0).forEach { it.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?> { private fun trigger(inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int, elements: TriggerSet, matches: TriggerSet) {
return if (null in input) for (l in elements) {
input with (l.trigger) {
else if (input is ArrayList) { if (slots.matches(slotsFull, slotsEmpty, slotsOccupied)) {
input.add(null) if (items.isEmpty() || items.size == 1 && item.isNotEmpty && this.items[0].test(item)) {
input matches.add(l)
} else { } else if (items.size > 1) {
ArrayList(input).also { it.add(null) } 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>>) { for (inventoryItem in inventory) {
val node = nodes[nodeId] as Node<Any?> unsatisfied.removeIf { it.test(inventoryItem) }
val keys = node.hint?.getHints(inventory, item, slotsFull, slotsEmpty, slotsOccupied)?.let(::addNull) ?: tree.keys if (unsatisfied.isEmpty) break
}
for (k in keys) { if (unsatisfied.isEmpty) {
val v = tree[k] ?: continue matches.add(l)
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)
}
}
}
} }
} }
} 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) { 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) { for (l in matches) {
l.run(advancements) l.run(advancements)
@ -220,18 +126,14 @@ object MatteryInventoryChangeTrigger : CriterionTrigger<InventoryChangeTrigger.T
listeners.remove(advancements) listeners.remove(advancements)
} }
override fun createInstance(data: JsonObject, context: DeserializationContext): InventoryChangeTrigger.TriggerInstance { override fun codec(): Codec<InventoryChangeTrigger.TriggerInstance> {
return CriteriaTriggers.INVENTORY_CHANGED.createInstance(data, context) return CriteriaTriggers.INVENTORY_CHANGED.codec()
} }
// реплицирует ванильный метод // реплицирует ванильный метод
fun trigger(player: ServerPlayer, inventory: Container, item: ItemStack) { fun trigger(player: ServerPlayer, inventory: Container, item: ItemStack) {
if (inventory === player.inventory) { if (inventory === player.inventory) {
val mattery = player.matteryPlayer return trigger(player, player.matteryPlayer.combinedInventory, item)
if (mattery != null) {
return trigger(player, mattery.combinedInventory, item)
}
} }
var slotsFull = 0 var slotsFull = 0