From 9dfb534f9a940f586313658bf7fc8af28076bf9e Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 19 Oct 2022 17:10:48 +0700 Subject: [PATCH] ContainerProxy, trick minecraft's click logic --- .../mc/otm/container/ContainerProxy.kt | 192 ++++++++++++++++++ .../ru/dbotthepony/mc/otm/menu/MatteryMenu.kt | 47 +++-- 2 files changed, 217 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerProxy.kt diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerProxy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerProxy.kt new file mode 100644 index 000000000..fb78a9aac --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerProxy.kt @@ -0,0 +1,192 @@ +package ru.dbotthepony.mc.otm.container + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.google.common.collect.ImmutableSet +import it.unimi.dsi.fastutil.ints.IntAVLTreeSet +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import java.util.LinkedList +import java.util.function.Consumer +import java.util.function.Supplier +import java.util.stream.Stream +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +open class ContainerProxy(containers: Stream>>) : Container { + data class ContainerSlot(val container: Container, val outerIndex: Int, val index: Int) : ReadWriteProperty, Supplier, Consumer { + var item: ItemStack + get() = container[index] + set(value) { container[index] = value } + + override fun getValue(thisRef: Any, property: KProperty<*>): ItemStack { + return item + } + + override fun setValue(thisRef: Any, property: KProperty<*>, value: ItemStack) { + this.item = value + } + + override fun get(): ItemStack { + return item + } + + override fun accept(t: ItemStack) { + item = t + } + + val isEmpty: Boolean get() = item.isEmpty + } + + protected val slots: List + protected val slotsMap: Map> + protected val containers: Set + protected val fullCoverage: List + protected val notFullCoverage: Map> + + init { + val list = ImmutableList.Builder() + var i = 0 + val validationMap = Reference2ObjectOpenHashMap() + val slotsMap = Reference2ObjectOpenHashMap>() + + for ((container, slots) in containers) { + val validator = validationMap.computeIfAbsent(container, Object2ObjectFunction { IntAVLTreeSet() }) + val slotList = slotsMap.computeIfAbsent(container, Object2ObjectFunction { ArrayList() }) + + for (slot in slots) { + if (validator.add(slot)) { + val slotObj = ContainerSlot(container, i++, slot) + list.add(slotObj) + slotList.add(slotObj) + } else { + throw IllegalArgumentException("Duplicate mapping for $container at $i for slot $slot") + } + } + } + + slots = list.build() + this.containers = ImmutableSet.copyOf(validationMap.keys) + + this.slotsMap = slotsMap.entries + .stream() + .map { it.key to ImmutableList.copyOf(it.value) } + .collect(ImmutableMap.toImmutableMap({ it.first }, { it.second })) + + this.fullCoverage = this.slotsMap.entries + .stream() + .filter { it.value.size == it.key.containerSize } + .map { it.key } + .collect(ImmutableList.toImmutableList()) + + this.notFullCoverage = this.slotsMap.entries + .stream() + .filter { it.key !in this.fullCoverage } + .collect(ImmutableMap.toImmutableMap({ it.key }, { it.value })) + } + + override fun clearContent() { + for (container in fullCoverage) { + container.clearContent() + } + + for (slots in notFullCoverage.values) { + for (slot in slots) { + slot.item = ItemStack.EMPTY + } + } + } + + override fun getContainerSize(): Int { + return slots.size + } + + override fun isEmpty(): Boolean { + for (container in fullCoverage) + if (!container.isEmpty) + return false + + for (slots in notFullCoverage.values) + for (slot in slots) + if (!slot.isEmpty) + return false + + return true + } + + fun slotAt(index: Int): ContainerSlot { + return slots[index] + } + + override fun getItem(index: Int): ItemStack { + // do not violate contract of getItem not throwing exceptions when index is invalid + return slots.getOrNull(index)?.item ?: ItemStack.EMPTY + } + + override fun removeItem(index: Int, count: Int): ItemStack { + val data = slots.getOrNull(index) ?: return ItemStack.EMPTY + return data.container.removeItem(data.index, count) + } + + override fun removeItemNoUpdate(index: Int): ItemStack { + val data = slots[index] + return data.container.removeItemNoUpdate(data.index) + } + + override fun setItem(index: Int, value: ItemStack) { + slots[index].item = value + } + + override fun setChanged() { + for (container in containers) { + container.setChanged() + } + } + + override fun stillValid(player: Player): Boolean { + for (container in containers) + if (!container.stillValid(player)) + return false + + return true + } + + class Builder { + private var built = false + private val values = LinkedList>>() + + fun add(container: Container): Builder { + check(!built) { "Already built!" } + values.add(container to (0 until container.containerSize).iterator()) + return this + } + + fun ofRange(container: Container, from: Int = 0, to: Int = container.containerSize - 1): Builder { + check(!built) { "Already built!" } + values.add(container to (from .. to).iterator()) + return this + } + + fun of(container: Container, slots: Iterator): Builder { + check(!built) { "Already built!" } + values.add(container to slots) + return this + } + + fun of(container: Container, slots: Iterable): Builder { + check(!built) { "Already built!" } + values.add(container to slots.iterator()) + return this + } + + fun build(): ContainerProxy { + check(!built) { "Already built!" } + val value = ContainerProxy(values.stream()) + values.clear() + return value + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt index b125c3536..8435926b5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -20,6 +20,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorSlots import ru.dbotthepony.mc.otm.compat.curios.isCurioSlot +import ru.dbotthepony.mc.otm.container.ContainerProxy import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget @@ -158,36 +159,38 @@ abstract class MatteryMenu @JvmOverloads protected constructor( protected fun addInventorySlots(autoFrame: Boolean = !ply.isSpectator) { check(_playerInventorySlots.isEmpty()) { "Already created inventory slots" } - autoCreateInventoryFrame = autoFrame - - for (i in 9 .. 35) { - val slot = InventorySlot(inventory, i) - - _playerInventorySlots.add(slot) - _playerMainSlots.add(slot) - _playerCombinedInventorySlots.add(slot) - addSlot(slot) - } val mattery = ply.matteryPlayer - if (mattery != null && mattery.hasExoSuit) { - for (i in 0 until mattery.exoSuitContainer.containerSize) { - val slot = InventorySlot(mattery.exoSuitContainer, i) + // trick minecraft's code into thinking that slots come from contiguous container + val proxyBuilder = ContainerProxy.Builder() - _playerInventorySlots.add(slot) - _playerExoSuitSlots.add(slot) - _playerCombinedInventorySlots.add(slot) - addSlot(slot) - } + proxyBuilder.of(inventory, 0 .. 35) + + if (mattery != null && mattery.hasExoSuit) { + proxyBuilder.add(mattery.exoSuitContainer) } - for (i in 0..8) { - val slot = InventorySlot(inventory, i) + val proxy = proxyBuilder.build() + + autoCreateInventoryFrame = autoFrame + + for (slotId in 0 until proxy.containerSize) { + val slot = InventorySlot(proxy, slotId) + + if (slotId in 0 .. 8) { + _playerHotbarSlots.add(slot) + addSlot(slot) + continue + } else if (slotId in 9 .. 35) { + _playerMainSlots.add(slot) + } else { + _playerExoSuitSlots.add(slot) + } - addSlot(slot) _playerInventorySlots.add(slot) - _playerHotbarSlots.add(slot) + _playerCombinedInventorySlots.add(slot) + addSlot(slot) } }