Update MatteryInventoryChangeTrigger
This commit is contained in:
parent
567e03498e
commit
4120660ebc
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user