diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index 8efb2f333..afbde691b 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -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") } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt index de15bde54..859b16be3 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt @@ -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 + ЛКМ") } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt index 614b60b56..e19cee385 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt @@ -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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt index 27895053c..31e6b749f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt @@ -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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoPackInventoryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoPackInventoryScreen.kt index fdea69675..739edf842 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoPackInventoryScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoPackInventoryScreen.kt @@ -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(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(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(menu: T, inventory: Inventory, tit for (i in 0 .. (8).coerceAtMost(menu.playerCombinedInventorySlots.size - offset - 1)) { val slot = menu.playerCombinedInventorySlots[offset + i] - object : FilteredSlotPanel, Slot>(this@MatteryScreen, canvas, slot) { + object : UserFilteredSlotPanel, Slot>(this@MatteryScreen, canvas, slot) { init { dock = Dock.LEFT } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt index a4f7c0206..a3febd6c8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt @@ -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(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 } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilteredSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt similarity index 64% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilteredSlotPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt index b031c0d88..97772a9bc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilteredSlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt @@ -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 T : Slot>( +abstract class UserFilteredSlotPanel, out T : Slot>( screen: S, parent: EditablePanel<*>?, slot: T, @@ -39,9 +44,12 @@ abstract class FilteredSlotPanel, 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 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().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 T : Slot>( companion object { val SLOT_FILTER_COLOR = RGBAColor(85, 113, 216, 150) + val SLOT_BLOCK_COLOR = RGBAColor(219, 113, 113, 150) fun , T : Slot> of( screen: S, @@ -104,10 +128,27 @@ abstract class FilteredSlotPanel, out T : Slot>( height: Float = SIZE, noItemIcon: MatterySprite? = null, filter: GetterSetter - ): FilteredSlotPanel { - return object : FilteredSlotPanel(screen, parent, slot, x, y, width, height, noItemIcon) { + ): UserFilteredSlotPanel { + return object : UserFilteredSlotPanel(screen, parent, slot, x, y, width, height, noItemIcon) { override var slotFilter: Item? by filter } } + + fun , 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 { + return object : UserFilteredSlotPanel(screen, parent, slot, x, y, width, height, noItemIcon) { + override var slotFilter: Item? + get() = slot.filter?.get() + set(value) { slot.filter?.accept(value) } + } + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt index cbba0cd31..7a6c610b0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt @@ -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) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt index f2bc86f6e..b0af02659 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt @@ -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, INBTSerializable { - constructor(watcher: BlockEntity, size: Int) : this(watcher::setChanged, size) +open class MatteryContainer(protected val watcher: Runnable, private val size: Int) : Container, Iterable, INBTSerializable { 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 = Array(size) { ItemStack.EMPTY } private val trackedSlots: Array = Array(size) { ItemStack.EMPTY } + private val filters: Array = arrayOfNulls(size) + private var filterSynchronizer: WeakReference>>? = null + + fun clearSlotFilters() { + Arrays.fill(filters, null) + filterSynchronizer?.get()?.value?.clear() + } + + fun removeFilterSynchronizer() { + filterSynchronizer?.get()?.remove() + filterSynchronizer = null + } + + fun addFilterSynchronizer(synchronizer: FieldSynchronizer): IField> { + 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 + }) + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoPackInventoryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoPackInventoryMenu.kt index 2aafa2b0a..ee2b9525b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoPackInventoryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoPackInventoryMenu.kt @@ -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 { 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 0aab8f4b5..bb5872939 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -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? - 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 + val field: IField + + 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 } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt index 3b545428b..0d312681e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt @@ -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? = 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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt index 0a2ee3dc2..df4e05718 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt @@ -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 + val storageSlots: List 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) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt index 371d2f0c2..00a0d1dd8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt @@ -57,6 +57,9 @@ sealed interface IField : ReadOnlyProperty, Supplier, () -> 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( 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>(0) + private var freeSlots = 0 + // почему не удалять поля напрямую? + // чтоб не возникло проблем в состоянии гонки + // формируем пакет -> удаляем поле по обе стороны -> клиент принимает пакет -> клиент считывает неверные данные + // конечно, всё равно всё сломается если было удалено поле, которое находится в пакете + // но если поля нет в пакете, то всё окей + private val fields = ArrayList?>(0) private val observers = ArrayList>(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 getMapBacklog(map: Map): LinkedList Unit>> { if (unused) { return LinkedList() @@ -521,10 +550,57 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa @Suppress("LeakingThis") abstract inner class AbstractField : IField { - 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 { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt index c8ed89fc6..8445f9c01 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt @@ -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) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt index 0c5fd3cb4..5cd4aeabb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt @@ -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) { 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() {