User defined slot filters on MatteryContainer level, streamlined slot-level filter interface, cargo crates now allow slots to be filtered

This commit is contained in:
DBotThePony 2023-03-16 21:30:19 +07:00
parent 30f07e2fed
commit 3c61a03fd8
Signed by: DBot
GPG Key ID: DCC23B5715498507
17 changed files with 397 additions and 191 deletions

View File

@ -678,6 +678,10 @@ private fun gui(provider: MatteryLanguageProvider) {
gui("essence_capsule", "(Almost) Everything you ever knew is within")
gui("essence_capsule2", "This item can be recycled at Essence Servo")
gui("slot_filter.filtered", "This slot is filtered for automation")
gui("slot_filter.forbidden", "This slot is forbidden for automation mechanisms")
gui("slot_filter.hint", "To remove slot filter press Ctrl + LMB")
}
}

View File

@ -680,6 +680,10 @@ private fun gui(provider: MatteryLanguageProvider) {
gui("essence_capsule", "(Почти) Всё, что вы знали, хранится внутри")
gui("essence_capsule2", "Данный предмет может быть переработан внутри хранилища эссенции")
gui("slot_filter.filtered", "Данный слот отфильтрован для автоматизации")
gui("slot_filter.forbidden", "Данный слот запрещён для взаимодействия средствами автоматизации")
gui("slot_filter.hint", "Для удаления фильтра нажмите Ctrl + ЛКМ")
}
}

View File

@ -214,7 +214,7 @@ class ItemMonitorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
// a lot of code is hardcoded to take CraftingContainer as it's input
// hence we are forced to work around this by providing proxy container
val craftingGrid = object : MatteryContainer(this, 3 * 3) {
val craftingGrid = object : MatteryContainer(::setChangedLight, 3 * 3) {
override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
super.setChanged(slot, new, old)
craftingGridDummy[slot] = new

View File

@ -1,12 +1,10 @@
package ru.dbotthepony.mc.otm.capability
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import net.minecraft.ChatFormatting
import net.minecraft.core.Direction
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.NumericTag
import net.minecraft.nbt.StringTag
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
@ -20,7 +18,6 @@ import net.minecraft.world.entity.boss.wither.WitherBoss
import net.minecraft.world.entity.monster.Phantom
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.ProjectileWeaponItem
@ -63,14 +60,11 @@ import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.minus
import ru.dbotthepony.mc.otm.core.nbt.getCompoundList
import ru.dbotthepony.mc.otm.core.nbt.getStringList
import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.util.IntValueCodec
import ru.dbotthepony.mc.otm.core.util.ItemStackValueCodec
import ru.dbotthepony.mc.otm.core.util.ItemValueCodec
import ru.dbotthepony.mc.otm.core.util.Savetables
import ru.dbotthepony.mc.otm.core.util.UUIDValueCodec
import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec
import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu
import ru.dbotthepony.mc.otm.network.*
import ru.dbotthepony.mc.otm.registry.AndroidFeatures
@ -144,10 +138,8 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
* For fields that need to be synchronized only to owning player
*
* Please mind if you really need to use this in your mod;
* don't forget to specify field names when you add them to synchronizer.
* any differences in field order/types/etc will break *everything*
*
* Even if other side does not have your field defined, both sides will negotiate
* and figure out how to deal with this situation as long as you specify field name.
*/
val synchronizer = FieldSynchronizer()
@ -160,10 +152,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
* For fields that need to be synchronized to everyone
*
* Please mind if you really need to use this in your mod;
* don't forget to specify field names when you add them to synchronizer.
*
* Even if other side does not have your field defined, both sides will negotiate
* and figure out how to deal with this situation as long as you specify field name.
* any differences in field order/types/etc will break *everything*
*/
val publicSynchronizer = FieldSynchronizer()
@ -192,13 +181,10 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
* If you want to properly extend Exopack suit capacity, add your value into this map
*/
val exoPackSlotModifier = UUIDIntModifiersMap(observer = observer@{
if (ply !is ServerPlayer)
return@observer
if (it < 0) {
exoPackSlotCount = 0
exoPackContainer = PlayerMatteryContainer(0)
} else {
exoPackSlotCount = it
exoPackContainer = PlayerMatteryContainer(it)
}
}, backingMap = this.exoPackSlotModifierMap)
@ -206,49 +192,28 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
synchronizer.Field(null, ItemValueCodec.nullable)
}
val exoPackSlotFilters by synchronizer.Map(
keyCodec = VarIntValueCodec,
valueCodec = ItemValueCodec,
backingMap = Int2ObjectOpenHashMap()
)
/**
* Current slot count of Exopack
*
* For properly affecting this value please look at [exoPackSlotModifier]
*/
var exoPackSlotCount by publicSynchronizer.int(setter = setter@{ value, access, _ ->
require(value >= 0) { "Invalid slot count $value" }
if (value != access.read()) {
access.write(value)
_exoPackMenu = null
exoPackContainer = PlayerMatteryContainer(value)
}
})
/**
* Exopack container, which actually store items inside Exopack
*/
var exoPackContainer: MatteryContainer = PlayerMatteryContainer(0)
private set(value) {
_exoPackMenu = null
field.removeFilterSynchronizer()
@Suppress("SENSELESS_COMPARISON") // false positive - fields of player can easily be nulls, despite annotations saying otherwise
if (ply.containerMenu != null && (ply !is ServerPlayer || ply.connection != null)) {
ply.closeContainer()
}
for (i in 0 until value.containerSize.coerceAtMost(field.containerSize)) {
if (!field[i].isEmpty) {
value[i] = field[i]
}
}
for (i in value.containerSize until field.containerSize) {
ply.spawnAtLocation(field[i])
if (ply is ServerPlayer)
ply.spawnAtLocation(field[i])
field[i] = ItemStack.EMPTY
}
value.deserializeNBT(field.serializeNBT())
value.addFilterSynchronizer(synchronizer)
field = value
}
@ -699,15 +664,6 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
}
}
tag["exoPackSlotFilters"] = ListTag().also {
for ((slot, filter) in exoPackSlotFilters) {
it.add(CompoundTag().also {
it["slot"] = slot
it["filter"] = filter.registryName!!.toString()
})
}
}
tag["regularSlotFilters"] = ListTag().also {
for (filter in regularSlotFilters) {
it.add(StringTag.valueOf(filter.value?.registryName?.toString() ?: ""))
@ -724,17 +680,6 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
filter.value = null
}
exoPackSlotFilters.clear()
for (elem in tag.getCompoundList("exoPackSlotFilters")) {
val slot = (elem["slot"] as? NumericTag)?.asInt ?: continue
val filter = (elem["filter"] as? StringTag)?.asString ?: continue
if (slot >= 0) {
exoPackSlotFilters[slot] = ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(filter) ?: continue) ?: Items.AIR
}
}
val regularSlotFilters = tag.getStringList("regularSlotFilters")
for (i in 0 until regularSlotFilters.size.coerceAtMost(this.regularSlotFilters.size)) {
@ -1046,7 +991,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
}
for (i in 0 until exoPackContainer.containerSize) {
if (exoPackContainer[i].isEmpty && (exoPackSlotFilters[i] === null || exoPackSlotFilters[i] === stack.item)) {
if (exoPackContainer[i].isEmpty && exoPackContainer.testSlotFilter(i, stack)) {
exoPackContainer[i] = stack.copy()
exoPackContainer[i].popTime = 5
stack.count = 0
@ -1100,7 +1045,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
}
for (i in 0 until exoPackContainer.containerSize) {
if (exoPackContainer[i].isEmpty && exoPackSlotFilters[i] === stack.item) {
if (exoPackContainer[i].isEmpty && exoPackContainer.hasSlotFilter(i) && exoPackContainer.testSlotFilter(i, stack)) {
exoPackContainer[i] = stack.split(stack.count.coerceAtMost(stack.maxStackSize))
exoPackContainer[i].popTime = 5
@ -1123,7 +1068,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
}
for (i in 0 until exoPackContainer.containerSize) {
if (exoPackContainer[i].isEmpty && exoPackSlotFilters[i] === null) {
if (exoPackContainer[i].isEmpty && !exoPackContainer.hasSlotFilter(i)) {
exoPackContainer[i] = stack.split(stack.count.coerceAtMost(stack.maxStackSize))
exoPackContainer[i].popTime = 5

View File

@ -10,7 +10,7 @@ import ru.dbotthepony.mc.otm.client.render.sprite
import ru.dbotthepony.mc.otm.client.screen.panels.*
import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilteredSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.setMousePos
@ -83,7 +83,7 @@ class ExoPackInventoryScreen(menu: ExoPackInventoryMenu) : MatteryScreen<ExoPack
mainInventoryLine.dock = Dock.BOTTOM
for (slot in menu.playerHotbarSlots) {
FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also {
UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
it.dock = Dock.LEFT
}
}

View File

@ -18,24 +18,16 @@ import net.minecraftforge.client.event.ContainerScreenEvent.Render.Foreground
import net.minecraftforge.common.MinecraftForge
import org.lwjgl.opengl.GL11
import org.lwjgl.opengl.GL13
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.config.ClientConfig
import ru.dbotthepony.mc.otm.client.moveMousePosScaled
import ru.dbotthepony.mc.otm.client.screen.panels.*
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilteredSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.FoldableSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.HeightControls
import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollBarConstants
import ru.dbotthepony.mc.otm.compat.cos.CosmeticToggleButton
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.math.integerDivisionDown
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.PlayerSlot
import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
import ru.dbotthepony.mc.otm.network.SetInventoryFilterPacket
import java.util.Collections
/**
@ -169,7 +161,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
hotbarStrip.dock = Dock.BOTTOM
for (slot in menu.playerHotbarSlots) {
FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also {
UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
it.dock = Dock.LEFT
}
}
@ -219,7 +211,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
hotbarStrip.dock = Dock.BOTTOM
for (slot in menu.playerHotbarSlots) {
FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also {
UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
it.dock = Dock.LEFT
}
}
@ -262,7 +254,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
for (i in 0 .. (8).coerceAtMost(menu.playerCombinedInventorySlots.size - offset - 1)) {
val slot = menu.playerCombinedInventorySlots[offset + i]
object : FilteredSlotPanel<MatteryScreen<*>, Slot>(this@MatteryScreen, canvas, slot) {
object : UserFilteredSlotPanel<MatteryScreen<*>, Slot>(this@MatteryScreen, canvas, slot) {
init {
dock = Dock.LEFT
}

View File

@ -6,6 +6,7 @@ import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel
import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu
class CargoCrateScreen(menu: CargoCrateMenu, inventory: Inventory, title: Component) : MatteryScreen<CargoCrateMenu>(menu, inventory, title) {
@ -14,7 +15,7 @@ class CargoCrateScreen(menu: CargoCrateMenu, inventory: Inventory, title: Compon
val grid = GridPanel(this, frame, 8f, 18f, 9f * 18f, 6f * 18f, 9, 6)
for (slot in menu.storageSlots)
SlotPanel(this, grid, slot)
UserFilteredSlotPanel.of(this, grid, slot)
return frame
}

View File

@ -2,6 +2,8 @@ package ru.dbotthepony.mc.otm.client.screen.panels.slot
import com.mojang.blaze3d.platform.InputConstants
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
@ -16,9 +18,12 @@ import ru.dbotthepony.mc.otm.client.render.drawRect
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.RGBAColor
import ru.dbotthepony.mc.otm.menu.UserFilteredSlot
abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
abstract class UserFilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
screen: S,
parent: EditablePanel<*>?,
slot: T,
@ -39,9 +44,12 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
screen.renderItemStack(absoluteX, absoluteY, itemStack, null)
clearDepth(stack)
drawColor = SLOT_FILTER_COLOR
} else {
drawColor = SLOT_BLOCK_COLOR
}
drawColor = SLOT_FILTER_COLOR
drawRect(stack, 0f, 0f, width, height)
}
}
@ -52,12 +60,27 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
screen.renderComponentTooltip(
stack,
getItemStackTooltip(itemstack),
getItemStackTooltip(itemstack).toMutableList().also {
it.add(0, TranslatableComponent("otm.gui.slot_filter.filtered").withStyle(ChatFormatting.GRAY))
it.add(1, TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY))
it.add(2, TextComponent(""))
},
mouseX.toInt(),
mouseY.toInt(),
IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: screen.font,
itemstack
)
} else if (isHovered && slotFilter === Items.AIR && itemStack.isEmpty) {
screen.renderComponentTooltip(
stack,
ArrayList<Component>().also {
it.add(TranslatableComponent("otm.gui.slot_filter.forbidden").withStyle(ChatFormatting.GRAY))
it.add(TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY))
},
mouseX.toInt(),
mouseY.toInt(),
screen.font
)
}
return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick)
@ -93,6 +116,7 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
companion object {
val SLOT_FILTER_COLOR = RGBAColor(85, 113, 216, 150)
val SLOT_BLOCK_COLOR = RGBAColor(219, 113, 113, 150)
fun <S : MatteryScreen<*>, T : Slot> of(
screen: S,
@ -104,10 +128,27 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
height: Float = SIZE,
noItemIcon: MatterySprite? = null,
filter: GetterSetter<Item?>
): FilteredSlotPanel<S, T> {
return object : FilteredSlotPanel<S, T>(screen, parent, slot, x, y, width, height, noItemIcon) {
): UserFilteredSlotPanel<S, T> {
return object : UserFilteredSlotPanel<S, T>(screen, parent, slot, x, y, width, height, noItemIcon) {
override var slotFilter: Item? by filter
}
}
fun <S : MatteryScreen<*>, T : UserFilteredSlot> of(
screen: S,
parent: EditablePanel<*>?,
slot: T,
x: Float = 0f,
y: Float = 0f,
width: Float = SIZE,
height: Float = SIZE,
noItemIcon: MatterySprite? = null
): UserFilteredSlotPanel<S, T> {
return object : UserFilteredSlotPanel<S, T>(screen, parent, slot, x, y, width, height, noItemIcon) {
override var slotFilter: Item?
get() = slot.filter?.get()
set(value) { slot.filter?.accept(value) }
}
}
}
}

View File

@ -1,7 +1,7 @@
package ru.dbotthepony.mc.otm.container
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.util.LazyOptional
import net.minecraft.world.item.Items
import net.minecraftforge.items.IItemHandler
class ContainerHandler @JvmOverloads internal constructor(
@ -12,7 +12,7 @@ class ContainerHandler @JvmOverloads internal constructor(
override fun getStackInSlot(slot: Int) = container[slot]
override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack {
if (!filter.canInsert(slot, stack))
if (!container.testSlotFilter(slot, stack) || !filter.canInsert(slot, stack))
return stack
filter.preInsert(slot, stack, simulate)
@ -47,11 +47,9 @@ class ContainerHandler @JvmOverloads internal constructor(
}
override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack {
if (amount == 0)
if (amount <= 0 || container.isSlotForbiddenForAutomation(slot))
return ItemStack.EMPTY
require(amount >= 0) { "Can not extract negative amount of items" }
filter.preExtract(slot, amount, simulate)
val localStack = container.getItem(slot)
@ -76,6 +74,6 @@ class ContainerHandler @JvmOverloads internal constructor(
}
override fun isItemValid(slot: Int, stack: ItemStack): Boolean {
return filter.canInsert(slot, stack)
return container.testSlotFilter(slot, stack) && filter.canInsert(slot, stack)
}
}

View File

@ -1,22 +1,33 @@
package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
import net.minecraft.world.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.Container
import kotlin.jvm.JvmOverloads
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.registries.ForgeRegistries
import ru.dbotthepony.mc.otm.core.forValidRefs
import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.core.util.ItemValueCodec
import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec
import ru.dbotthepony.mc.otm.network.FieldSynchronizer
import ru.dbotthepony.mc.otm.network.IField
import java.lang.ref.WeakReference
import java.util.*
import kotlin.collections.ArrayList
@Suppress("UNUSED")
open class MatteryContainer(val watcher: Runnable, private val size: Int) : Container, Iterable<ItemStack>, INBTSerializable<Tag?> {
constructor(watcher: BlockEntity, size: Int) : this(watcher::setChanged, size)
open class MatteryContainer(protected val watcher: Runnable, private val size: Int) : Container, Iterable<ItemStack>, INBTSerializable<Tag?> {
constructor(size: Int) : this({}, size)
init {
@ -26,6 +37,72 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont
private var ignoreChangeNotifications = 0
protected val slots: Array<ItemStack> = Array(size) { ItemStack.EMPTY }
private val trackedSlots: Array<ItemStack> = Array(size) { ItemStack.EMPTY }
private val filters: Array<Item?> = arrayOfNulls(size)
private var filterSynchronizer: WeakReference<IField<MutableMap<Int, Item>>>? = null
fun clearSlotFilters() {
Arrays.fill(filters, null)
filterSynchronizer?.get()?.value?.clear()
}
fun removeFilterSynchronizer() {
filterSynchronizer?.get()?.remove()
filterSynchronizer = null
}
fun addFilterSynchronizer(synchronizer: FieldSynchronizer): IField<MutableMap<Int, Item>> {
check(filterSynchronizer?.get() == null) { "Already has filter synchronizer" }
val field = synchronizer.Map(
keyCodec = VarIntValueCodec,
valueCodec = ItemValueCodec,
backingMap = Int2ObjectOpenHashMap(),
callback = {
for (change in it) {
change.map({ k, v -> filters[k] = v }, { k -> filters[k] = null }, { Arrays.fill(filters, null) })
}
}
)
for ((i, filter) in filters.withIndex()) {
if (filter != null) {
field.value[i] = filter
}
}
filterSynchronizer = WeakReference(field)
return field
}
fun setSlotFilter(slot: Int, filter: Item? = null) {
if (filters[slot] !== filter) {
filters[slot] = filter
filterSynchronizer?.get()?.let {
if (filter == null) {
it.value.remove(slot)
} else {
it.value[slot] = filter
}
}
}
}
fun getSlotFilter(slot: Int) = filters[slot]
fun hasSlotFilter(slot: Int) = filters[slot] !== null
fun isSlotForbiddenForAutomation(slot: Int) = filters[slot] === Items.AIR
fun testSlotFilter(slot: Int, itemStack: ItemStack): Boolean {
return testSlotFilter(slot, itemStack.item)
}
fun testSlotFilter(slot: Int, item: Item): Boolean {
if (filters[slot] == null) {
return true
} else {
return filters[slot] === item
}
}
final override fun getContainerSize() = size
@ -44,42 +121,31 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont
protected open fun startedIgnoringUpdates() {}
protected open fun stoppedIgnoringUpdates() {}
private fun deserializeNBT(tag: CompoundTag?) {
Arrays.fill(slots, ItemStack.EMPTY)
if (tag == null) {
setChanged()
return
}
private fun deserializeNBT(tag: CompoundTag) {
// нам не интересен размер
tag.map("items") { it: ListTag ->
deserializeNBT(it)
}
tag.map("filters") { it: ListTag ->
val map = filterSynchronizer?.get()
for (i in 0 until it.size.coerceAtMost(size)) {
slots[i] = ItemStack.of(it[i] as CompoundTag)
val nbt = it[i] as CompoundTag
val index = nbt.getInt("slotIndex")
filters[index] = ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(nbt.getString("filter")) ?: continue)
if (filters[index] != null) {
map?.value?.put(index, filters[index]!!)
}
}
}
setChanged()
}
private fun deserializeNBT(tag: ListTag?) {
Arrays.fill(slots, ItemStack.EMPTY)
if (tag == null) {
setChanged()
return
}
var isIndexed = true
for (i in 0 until tag.size.coerceAtMost(size)) {
if (!(tag[i] as CompoundTag).contains("slotIndex")) {
isIndexed = false
break
}
}
if (isIndexed) {
private fun deserializeNBT(tag: ListTag) {
if (tag.all { (it as CompoundTag).contains("slotIndex") }) {
val freeSlots = IntAVLTreeSet()
for (i in 0 until size)
@ -104,18 +170,21 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont
slots[i] = ItemStack.of(tag[i] as CompoundTag)
}
}
setChanged()
}
override fun deserializeNBT(tag: Tag?) {
Arrays.fill(slots, ItemStack.EMPTY)
Arrays.fill(filters, null)
filterSynchronizer?.get()?.value?.clear()
when (tag) {
is CompoundTag -> deserializeNBT(tag)
is ListTag -> deserializeNBT(tag)
else -> {
Arrays.fill(slots, ItemStack.EMPTY)
is ListTag -> {
deserializeNBT(tag)
setChanged()
}
else -> setChanged()
}
}
@ -123,13 +192,26 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont
if (ignoreChangeNotifications == 0) watcher.run()
}
override fun serializeNBT(): ListTag {
return ListTag().also {
for ((i, item) in slots.withIndex()) {
if (!item.isEmpty) {
it.add(item.serializeNBT().also {
it["slotIndex"] = i
})
override fun serializeNBT(): CompoundTag {
return CompoundTag().also {
it["items"] = ListTag().also {
for ((i, item) in slots.withIndex()) {
if (!item.isEmpty) {
it.add(item.serializeNBT().also {
it["slotIndex"] = i
})
}
}
}
it["filters"] = ListTag().also {
for ((i, filter) in filters.withIndex()) {
if (filter != null) {
it.add(CompoundTag().also {
it["filter"] = filter.registryName!!.toString()
it["slotIndex"] = i
})
}
}
}
}

View File

@ -173,8 +173,8 @@ class ExoPackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMen
return super.quickMoveStack(ply, slotIndex)
}
override fun canTakeItemForPickAll(p_38908_: ItemStack, p_38909_: Slot): Boolean {
return p_38909_.container != craftingResultContainer && super.canTakeItemForPickAll(p_38908_, p_38909_)
override fun canTakeItemForPickAll(itemStack: ItemStack, slot: Slot): Boolean {
return slot.container != craftingResultContainer && super.canTakeItemForPickAll(itemStack, slot)
}
companion object : ContainerSynchronizer {

View File

@ -27,15 +27,18 @@ import ru.dbotthepony.mc.otm.compat.curios.curiosSlots
import ru.dbotthepony.mc.otm.compat.curios.isCurioSlot
import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.util.BigDecimalValueCodec
import ru.dbotthepony.mc.otm.core.util.BinaryStringCodec
import ru.dbotthepony.mc.otm.core.util.BooleanValueCodec
import ru.dbotthepony.mc.otm.core.util.IStreamCodec
import ru.dbotthepony.mc.otm.core.util.ItemValueCodec
import ru.dbotthepony.mc.otm.core.util.NullValueCodec
import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec
import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget
import ru.dbotthepony.mc.otm.network.FieldSynchronizer
import ru.dbotthepony.mc.otm.network.IField
import ru.dbotthepony.mc.otm.network.MatteryPacket
import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
import ru.dbotthepony.mc.otm.network.MenuFieldPacket
@ -239,26 +242,22 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return _matteryWidgets[index]
}
open inner class InventorySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) {
open inner class InventorySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : UserFilteredSlot(container, index, x, y) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return super.mayPlace(itemStack) && !isInventorySlotLocked(index)
return !isInventorySlotLocked(index) && super.mayPlace(itemStack)
}
override fun mayPickup(player: Player): Boolean {
return super.mayPickup(player) && !isInventorySlotLocked(index)
return !isInventorySlotLocked(index) && super.mayPickup(player)
}
override fun isSameInventory(other: Slot): Boolean {
if (container === inventory || container === ply.matteryPlayer?.exoPackContainer)
return other.container === inventory || other.container === ply.matteryPlayer?.exoPackContainer
return (other.container === inventory || other.container === ply.matteryPlayer?.exoPackContainer) && isSameFilter(other)
return super.isSameInventory(other)
}
// фильтр существует только для автоматизации
// игрок всё равно может класть предмет вручную, даже если он не соответствует фильтру
internal val filter: GetterSetter<Item?>?
init {
val mattery = ply.matteryPlayer
@ -270,7 +269,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
)
} else if (container === mattery.exoPackContainer) {
filter = GetterSetter.of(
getter = { mattery.exoPackSlotFilters[slotIndex] },
getter = { mattery.exoPackContainer.getSlotFilter(slotIndex) },
setter = { MatteryPlayerNetworkChannel.sendToServer(SetInventoryFilterPacket(SetInventoryFilterPacket.Type.EXOPACK, slotIndex, it)) }
)
} else {
@ -359,7 +358,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
val payload = mSynchronizer.collectNetworkPayload()
if (payload != null) {
MenuNetworkChannel.send(ply, MenuFieldPacket(payload))
MenuNetworkChannel.send(ply, MenuFieldPacket(containerId, payload))
}
super.broadcastChanges()
@ -384,7 +383,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
val payload = mSynchronizer.collectNetworkPayload()
if (payload != null) {
MenuNetworkChannel.send(ply, MenuFieldPacket(payload))
MenuNetworkChannel.send(ply, MenuFieldPacket(containerId, payload))
}
super.broadcastFullState()
@ -420,6 +419,23 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return pSlot
}
if (pSlot is UserFilteredSlot && !pSlot.hasSetFilter) {
val container = pSlot.container
val input: PlayerInput<Item?>
val field: IField<Item?>
if (container is MatteryContainer) {
input = PlayerInput(ItemValueCodec.nullable, handler = { container.setSlotFilter(pSlot.slotIndex, it) })
field = mSynchronizer.ComputedField(getter = { container.getSlotFilter(pSlot.slotIndex) }, ItemValueCodec.nullable)
} else {
input = PlayerInput(ItemValueCodec.nullable, handler = { throw UnsupportedOperationException() })
field = mSynchronizer.ComputedField(getter = { null }, ItemValueCodec.nullable)
}
pSlot.filter = GetterSetter.of(getter = field::value, setter = input::input)
}
return super.addSlot(pSlot)
}
@ -488,7 +504,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
val copy = slot.item.copy()
var any = false
if (target.any { it.any { it is InventorySlot && it.filter != null } }) {
if (target.any { it.any { it is UserFilteredSlot && it.filter != null } }) {
for (collection in target) {
if (moveItemStackTo(slot, collection, onlyFiltered = true)) {
any = true
@ -525,11 +541,11 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return moveItemStackToSlots(stack, _playerInventorySlots, simulate = simulate)
}
override fun canTakeItemForPickAll(p_38908_: ItemStack, p_38909_: Slot): Boolean {
if (p_38909_ is EquipmentSlot)
override fun canTakeItemForPickAll(itemStack: ItemStack, slot: Slot): Boolean {
if (slot is EquipmentSlot)
return false
return super.canTakeItemForPickAll(p_38908_, p_38909_) && (p_38909_ !is MatterySlot || p_38909_.canTakeItemForPickAll()) && !p_38909_.isCurioSlot
return super.canTakeItemForPickAll(itemStack, slot) && (slot !is MatterySlot || slot.canTakeItemForPickAll()) && !slot.isCurioSlot
}
override fun moveItemStackTo(
@ -579,9 +595,9 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
// first pass - stack with existing slots
if (copy.isStackable) {
for (slot in slots) {
if (onlyFiltered && (slot !is InventorySlot || slot.filter == null || slot.filter.get() != item.item)) {
if (onlyFiltered && (slot !is UserFilteredSlot || slot.filter == null || slot.filter!!.get() != item.item)) {
continue
} else if (!onlyFiltered && slot is InventorySlot && slot.filter != null && slot.filter.get() != null && slot.filter.get() != item.item) {
} else if (!onlyFiltered && slot is UserFilteredSlot && slot.filter != null && slot.filter!!.get() != null && slot.filter!!.get() != item.item) {
continue
}
@ -606,9 +622,9 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
// second pass - drop stack into first free slot
for (slot in slots) {
if (onlyFiltered && (slot !is InventorySlot || slot.filter == null || slot.filter.get() != item.item)) {
if (onlyFiltered && (slot !is UserFilteredSlot || slot.filter == null || slot.filter!!.get() != item.item)) {
continue
} else if (!onlyFiltered && slot is InventorySlot && slot.filter != null && slot.filter.get() != null && slot.filter.get() != item.item) {
} else if (!onlyFiltered && slot is UserFilteredSlot && slot.filter != null && slot.filter!!.get() != null && slot.filter!!.get() != item.item) {
continue
}

View File

@ -3,11 +3,13 @@ package ru.dbotthepony.mc.otm.menu
import net.minecraft.world.Container
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.runOnClient
open class MatterySlot @JvmOverloads constructor(container: Container, index: Int, x: Int = 0, y: Int = 0) : Slot(container, index, x, y) {
@ -26,6 +28,35 @@ open class MatterySlot @JvmOverloads constructor(container: Container, index: In
}
}
open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) {
var hasSetFilter = false
private set
var filter: GetterSetter<Item?>? = null
set(value) {
hasSetFilter = true
field = value
}
override fun canTakeItemForPickAll(): Boolean {
return filter?.get() == null
}
fun isSameFilter(other: Slot): Boolean {
if (other !is UserFilteredSlot)
return filter?.get() == null
return (
(other.filter == null && filter == null) ||
(other.filter != null && filter != null && other.filter!!.get() == filter!!.get())
)
}
override fun isSameInventory(other: Slot): Boolean {
return isSameFilter(other) && super.isSameInventory(other)
}
}
open class MachineOutputSlot @JvmOverloads constructor(container: Container, index: Int, x: Int = 0, y: Int = 0, val onTake: () -> Unit = {}) : MatterySlot(container, index, x, y) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return false

View File

@ -7,6 +7,7 @@ import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.UserFilteredSlot
import ru.dbotthepony.mc.otm.registry.MMenus
class CargoCrateMenu @JvmOverloads constructor(
@ -14,7 +15,7 @@ class CargoCrateMenu @JvmOverloads constructor(
inventory: Inventory,
tile: CargoCrateBlockEntity? = null
) : MatteryMenu(MMenus.CARGO_CRATE, p_38852_, inventory, tile) {
val storageSlots: List<MatterySlot>
val storageSlots: List<UserFilteredSlot>
private val trackedPlayerOpen = !inventory.player.isSpectator
@ -22,7 +23,7 @@ class CargoCrateMenu @JvmOverloads constructor(
val container = tile?.container ?: SimpleContainer(CargoCrateBlockEntity.CAPACITY)
storageSlots = immutableList(CargoCrateBlockEntity.CAPACITY) {
addStorageSlot(MatterySlot(container, it))
addStorageSlot(UserFilteredSlot(container, it))
}
if (trackedPlayerOpen) {

View File

@ -57,6 +57,9 @@ sealed interface IField<V> : ReadOnlyProperty<Any?, V>, Supplier<V>, () -> V {
fun markDirty()
fun markDirty(endpoint: FieldSynchronizer.Endpoint)
val value: V
val isRemoved: Boolean
fun remove()
fun write(stream: DataOutputStream, endpoint: FieldSynchronizer.Endpoint)
fun read(stream: DataInputStream)
@ -94,7 +97,23 @@ data class MapChangeset<out K, out V>(
val action: MapAction,
val key: K?,
val value: V?
)
) {
inline fun map(add: (K, V) -> Unit, remove: (K) -> Unit) {
when (action) {
MapAction.ADD -> add.invoke(key!!, value!!)
MapAction.REMOVE -> remove.invoke(key!!)
else -> {}
}
}
inline fun map(add: (K, V) -> Unit, remove: (K) -> Unit, clear: () -> Unit) {
when (action) {
MapAction.CLEAR -> clear.invoke()
MapAction.ADD -> add.invoke(key!!, value!!)
MapAction.REMOVE -> remove.invoke(key!!)
}
}
}
enum class MapAction {
CLEAR, ADD, REMOVE
@ -109,7 +128,13 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
constructor() : this(Runnable {}, false)
constructor(callback: Runnable) : this(callback, false)
private val fields = ArrayList<AbstractField<*>>(0)
private var freeSlots = 0
// почему не удалять поля напрямую?
// чтоб не возникло проблем в состоянии гонки
// формируем пакет -> удаляем поле по обе стороны -> клиент принимает пакет -> клиент считывает неверные данные
// конечно, всё равно всё сломается если было удалено поле, которое находится в пакете
// но если поля нет в пакете, то всё окей
private val fields = ArrayList<AbstractField<*>?>(0)
private val observers = ArrayList<AbstractField<*>>(0)
private var nextFieldID = 0
@ -463,7 +488,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
fun markDirty() {
for (field in fields) {
field.markDirty(this)
field?.markDirty(this)
}
}
@ -475,6 +500,10 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
dirtyFields.add(field)
}
internal fun removeDirtyField(field: AbstractField<*>) {
dirtyFields.remove(field)
}
internal fun <K, V> getMapBacklog(map: Map<K, V>): LinkedList<Pair<Any?, (DataOutputStream) -> Unit>> {
if (unused) {
return LinkedList()
@ -521,10 +550,57 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
@Suppress("LeakingThis")
abstract inner class AbstractField<V> : IField<V> {
val id: Int = fields.size + 1
val id: Int
init {
fields.add(this)
if (freeSlots > 0) {
var found = -1
for (i in fields.indices) {
if (fields[i] == null) {
fields[i] = this
found = i + 1
freeSlots--
break
}
}
if (found == -1) {
throw RuntimeException("freeSlots = $freeSlots but no null entries in field list!")
} else {
id = found
}
} else {
fields.add(this)
id = fields.size
}
}
final override var isRemoved = false
private set
override fun remove() {
if (isRemoved)
return
isRemoved = true
freeSlots++
fields[id - 1] = null
observers.remove(this)
while (fields[fields.size - 1] == null) {
fields.removeAt(fields.size - 1)
freeSlots--
}
forEachEndpoint {
it.removeDirtyField(this)
}
}
override fun markDirty(endpoint: Endpoint) {
check(!isRemoved) { "Field was removed" }
endpoint.addDirtyField(this)
}
}
@ -564,6 +640,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
override fun observe(): Boolean {
check(!isRemoved) { "Field was removed" }
if (!isDirty && !codec.compare(remote, field)) {
notifyEndpoints(this@Field)
isDirty = true
@ -603,21 +680,20 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
override fun markDirty() {
check(!isRemoved) { "Field was removed" }
notifyEndpoints(this@Field)
isDirty = true
}
override fun markDirty(endpoint: Endpoint) {
endpoint.addDirtyField(this)
}
override fun write(stream: DataOutputStream, endpoint: Endpoint) {
check(!isRemoved) { "Field was removed" }
codec.write(stream, field)
isDirty = false
remote = codec.copy(field)
}
override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" }
val value = codec.read(stream)
val setter = this.setter
@ -647,6 +723,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
override fun observe(): Boolean {
check(!isRemoved) { "Field was removed" }
if (!isDirty && (remote == null || !codec.compare(remote ?: throw ConcurrentModificationException(), value))) {
notifyEndpoints(this)
isDirty = true
@ -657,24 +734,23 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
override fun markDirty() {
check(!isRemoved) { "Field was removed" }
notifyEndpoints(this)
isDirty = true
}
override fun markDirty(endpoint: Endpoint) {
endpoint.addDirtyField(this)
}
override val value: V
get() = clientValue ?: getter.invoke()
override fun write(stream: DataOutputStream, endpoint: Endpoint) {
check(!isRemoved) { "Field was removed" }
val value = value
codec.write(stream, value)
isDirty = false
}
override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" }
val newValue = codec.read(stream)
clientValue = newValue
observer.invoke(newValue)
@ -715,6 +791,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
override fun observe(): Boolean {
check(!isRemoved) { "Field was removed" }
if (!isDirty && !codec.compare(remote, value)) {
notifyEndpoints(this)
isDirty = true
@ -725,21 +803,20 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
override fun markDirty() {
check(!isRemoved) { "Field was removed" }
notifyEndpoints(this)
isDirty = true
}
override fun markDirty(endpoint: Endpoint) {
endpoint.addDirtyField(this)
}
override fun write(stream: DataOutputStream, endpoint: Endpoint) {
check(!isRemoved) { "Field was removed" }
val value = value
codec.write(stream, value)
isDirty = false
}
override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" }
this.value = codec.read(stream)
}
}
@ -782,6 +859,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
override fun observe(): Boolean {
check(!isRemoved) { "Field was removed" }
if (isRemote) {
return false
}
@ -815,6 +894,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
override fun markDirty() {
check(!isRemoved) { "Field was removed" }
if (isRemote) {
return
}
@ -857,6 +938,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
override fun markDirty(endpoint: Endpoint) {
check(!isRemoved) { "Field was removed" }
if (isRemote) {
return
}
@ -1025,7 +1108,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
*/
fun invalidate() {
for (field in fields) {
field.markDirty()
field?.markDirty()
}
forEachEndpoint {

View File

@ -544,12 +544,8 @@ class SetInventoryFilterPacket(val type: Type, val slot: Int, val item: Item?) :
}
Type.EXOPACK -> {
if (slot in 0 until player.exoPackSlotCount) {
if (item == null) {
player.exoPackSlotFilters.remove(slot)
} else {
player.exoPackSlotFilters[slot] = item
}
if (slot in 0 until player.exoPackContainer.containerSize) {
player.exoPackContainer.setSlotFilter(slot, item)
}
}
}

View File

@ -6,9 +6,11 @@ import net.minecraft.world.item.ItemStack
import net.minecraftforge.network.NetworkDirection
import net.minecraftforge.network.NetworkEvent
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.compat.InventoryScrollPacket
import ru.dbotthepony.mc.otm.container.ItemFilterSlotPacket
import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu
import ru.dbotthepony.mc.otm.menu.matter.CancelTaskPacket
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.matter.PatternsChangePacket
@ -22,24 +24,34 @@ import ru.dbotthepony.mc.otm.menu.data.StackRemovePacket
import java.io.ByteArrayInputStream
import java.util.function.Supplier
class MenuFieldPacket(val bytes: ByteArray, val length: Int) : MatteryPacket {
constructor(stream: FastByteArrayOutputStream) : this(stream.array, stream.length)
class MenuFieldPacket(val containerId: Int, val bytes: ByteArray, val length: Int) : MatteryPacket {
constructor(containerId: Int, stream: FastByteArrayOutputStream) : this(containerId, stream.array, stream.length)
override fun write(buff: FriendlyByteBuf) {
buff.writeVarInt(containerId)
buff.writeBytes(bytes, 0, length)
}
override fun play(context: Supplier<NetworkEvent.Context>) {
context.packetHandled = true
context.get().enqueueWork {
(minecraft.player?.containerMenu as? MatteryMenu)?.mSynchronizer?.read(ByteArrayInputStream(bytes, 0, length))
context.enqueueWork {
if (containerId == ExoPackInventoryMenu.CONTAINER_ID) {
minecraft.player?.matteryPlayer?.exoPackMenu?.mSynchronizer?.read(ByteArrayInputStream(bytes, 0, length))
} else {
val menu = minecraft.player?.containerMenu as? MatteryMenu ?: return@enqueueWork
if (menu.containerId == containerId)
menu.mSynchronizer.read(ByteArrayInputStream(bytes, 0, length))
}
}
}
companion object {
fun read(buff: FriendlyByteBuf): MenuFieldPacket {
val containerId = buff.readVarInt()
val readable = buff.readableBytes()
return MenuFieldPacket(ByteArray(readable).also(buff::readBytes), readable)
return MenuFieldPacket(containerId, ByteArray(readable).also(buff::readBytes), readable)
}
}
}
@ -62,7 +74,7 @@ class SetCarriedPacket(val item: ItemStack) : MatteryPacket {
}
object MenuNetworkChannel : MatteryNetworkChannel(
version = "2",
version = "3",
name = "menu"
) {
fun register() {