ContainerProxy, trick minecraft's click logic

This commit is contained in:
DBotThePony 2022-10-19 17:10:48 +07:00
parent b6bb0ed4b3
commit 9dfb534f9a
Signed by: DBot
GPG Key ID: DCC23B5715498507
2 changed files with 217 additions and 22 deletions

View File

@ -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<Pair<Container, Iterator<Int>>>) : Container {
data class ContainerSlot(val container: Container, val outerIndex: Int, val index: Int) : ReadWriteProperty<Any, ItemStack>, Supplier<ItemStack>, Consumer<ItemStack> {
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<ContainerSlot>
protected val slotsMap: Map<Container, List<ContainerSlot>>
protected val containers: Set<Container>
protected val fullCoverage: List<Container>
protected val notFullCoverage: Map<Container, List<ContainerSlot>>
init {
val list = ImmutableList.Builder<ContainerSlot>()
var i = 0
val validationMap = Reference2ObjectOpenHashMap<Container, IntAVLTreeSet>()
val slotsMap = Reference2ObjectOpenHashMap<Container, ArrayList<ContainerSlot>>()
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<Pair<Container, Iterator<Int>>>()
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<Int>): Builder {
check(!built) { "Already built!" }
values.add(container to slots)
return this
}
fun of(container: Container, slots: Iterable<Int>): 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
}
}
}

View File

@ -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.client.screen.panels.SlotPanel
import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorSlots import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorSlots
import ru.dbotthepony.mc.otm.compat.curios.isCurioSlot 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.ItemFilter
import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot
import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget
@ -158,39 +159,41 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
protected fun addInventorySlots(autoFrame: Boolean = !ply.isSpectator) { protected fun addInventorySlots(autoFrame: Boolean = !ply.isSpectator) {
check(_playerInventorySlots.isEmpty()) { "Already created inventory slots" } 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 val mattery = ply.matteryPlayer
// trick minecraft's code into thinking that slots come from contiguous container
val proxyBuilder = ContainerProxy.Builder()
proxyBuilder.of(inventory, 0 .. 35)
if (mattery != null && mattery.hasExoSuit) { if (mattery != null && mattery.hasExoSuit) {
for (i in 0 until mattery.exoSuitContainer.containerSize) { proxyBuilder.add(mattery.exoSuitContainer)
val slot = InventorySlot(mattery.exoSuitContainer, 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)
}
_playerInventorySlots.add(slot) _playerInventorySlots.add(slot)
_playerExoSuitSlots.add(slot)
_playerCombinedInventorySlots.add(slot) _playerCombinedInventorySlots.add(slot)
addSlot(slot) addSlot(slot)
} }
} }
for (i in 0..8) {
val slot = InventorySlot(inventory, i)
addSlot(slot)
_playerInventorySlots.add(slot)
_playerHotbarSlots.add(slot)
}
}
override fun broadcastChanges() { override fun broadcastChanges() {
for (widget in _matteryWidgets) { for (widget in _matteryWidgets) {
widget.updateServer() widget.updateServer()