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_capsule", "(Almost) Everything you ever knew is within")
gui("essence_capsule2", "This item can be recycled at Essence Servo") 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_capsule", "(Почти) Всё, что вы знали, хранится внутри")
gui("essence_capsule2", "Данный предмет может быть переработан внутри хранилища эссенции") 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 // 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 // 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) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
super.setChanged(slot, new, old) super.setChanged(slot, new, old)
craftingGridDummy[slot] = new craftingGridDummy[slot] = new

View File

@ -1,12 +1,10 @@
package ru.dbotthepony.mc.otm.capability package ru.dbotthepony.mc.otm.capability
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import net.minecraft.ChatFormatting import net.minecraft.ChatFormatting
import net.minecraft.core.Direction import net.minecraft.core.Direction
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag import net.minecraft.nbt.ListTag
import net.minecraft.nbt.NumericTag
import net.minecraft.nbt.StringTag import net.minecraft.nbt.StringTag
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation 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.monster.Phantom
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items import net.minecraft.world.item.Items
import net.minecraft.world.item.ProjectileWeaponItem 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.math.minus
import ru.dbotthepony.mc.otm.core.nbt.getCompoundList import ru.dbotthepony.mc.otm.core.nbt.getCompoundList
import ru.dbotthepony.mc.otm.core.nbt.getStringList 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.nbt.set
import ru.dbotthepony.mc.otm.core.util.IntValueCodec 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.ItemValueCodec
import ru.dbotthepony.mc.otm.core.util.Savetables import ru.dbotthepony.mc.otm.core.util.Savetables
import ru.dbotthepony.mc.otm.core.util.UUIDValueCodec 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.menu.ExoPackInventoryMenu
import ru.dbotthepony.mc.otm.network.* import ru.dbotthepony.mc.otm.network.*
import ru.dbotthepony.mc.otm.registry.AndroidFeatures 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 * For fields that need to be synchronized only to owning player
* *
* Please mind if you really need to use this in your mod; * 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() val synchronizer = FieldSynchronizer()
@ -160,10 +152,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
* For fields that need to be synchronized to everyone * For fields that need to be synchronized to everyone
* *
* Please mind if you really need to use this in your mod; * 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 publicSynchronizer = FieldSynchronizer() 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 * If you want to properly extend Exopack suit capacity, add your value into this map
*/ */
val exoPackSlotModifier = UUIDIntModifiersMap(observer = observer@{ val exoPackSlotModifier = UUIDIntModifiersMap(observer = observer@{
if (ply !is ServerPlayer)
return@observer
if (it < 0) { if (it < 0) {
exoPackSlotCount = 0 exoPackContainer = PlayerMatteryContainer(0)
} else { } else {
exoPackSlotCount = it exoPackContainer = PlayerMatteryContainer(it)
} }
}, backingMap = this.exoPackSlotModifierMap) }, backingMap = this.exoPackSlotModifierMap)
@ -206,49 +192,28 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
synchronizer.Field(null, ItemValueCodec.nullable) 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 * Exopack container, which actually store items inside Exopack
*/ */
var exoPackContainer: MatteryContainer = PlayerMatteryContainer(0) var exoPackContainer: MatteryContainer = PlayerMatteryContainer(0)
private set(value) { private set(value) {
_exoPackMenu = null _exoPackMenu = null
field.removeFilterSynchronizer()
@Suppress("SENSELESS_COMPARISON") // false positive - fields of player can easily be nulls, despite annotations saying otherwise @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)) { if (ply.containerMenu != null && (ply !is ServerPlayer || ply.connection != null)) {
ply.closeContainer() 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) { 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 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 { tag["regularSlotFilters"] = ListTag().also {
for (filter in regularSlotFilters) { for (filter in regularSlotFilters) {
it.add(StringTag.valueOf(filter.value?.registryName?.toString() ?: "")) it.add(StringTag.valueOf(filter.value?.registryName?.toString() ?: ""))
@ -724,17 +680,6 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
filter.value = null 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") val regularSlotFilters = tag.getStringList("regularSlotFilters")
for (i in 0 until regularSlotFilters.size.coerceAtMost(this.regularSlotFilters.size)) { 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) { 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] = stack.copy()
exoPackContainer[i].popTime = 5 exoPackContainer[i].popTime = 5
stack.count = 0 stack.count = 0
@ -1100,7 +1045,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
for (i in 0 until exoPackContainer.containerSize) { 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] = stack.split(stack.count.coerceAtMost(stack.maxStackSize))
exoPackContainer[i].popTime = 5 exoPackContainer[i].popTime = 5
@ -1123,7 +1068,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
for (i in 0 until exoPackContainer.containerSize) { 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] = stack.split(stack.count.coerceAtMost(stack.maxStackSize))
exoPackContainer[i].popTime = 5 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.*
import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel 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.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.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.setMousePos import ru.dbotthepony.mc.otm.client.setMousePos
@ -83,7 +83,7 @@ class ExoPackInventoryScreen(menu: ExoPackInventoryMenu) : MatteryScreen<ExoPack
mainInventoryLine.dock = Dock.BOTTOM mainInventoryLine.dock = Dock.BOTTOM
for (slot in menu.playerHotbarSlots) { for (slot in menu.playerHotbarSlots) {
FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also { UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
it.dock = Dock.LEFT it.dock = Dock.LEFT
} }
} }

View File

@ -18,24 +18,16 @@ import net.minecraftforge.client.event.ContainerScreenEvent.Render.Foreground
import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.MinecraftForge
import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL11
import org.lwjgl.opengl.GL13 import org.lwjgl.opengl.GL13
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.config.ClientConfig import ru.dbotthepony.mc.otm.config.ClientConfig
import ru.dbotthepony.mc.otm.client.moveMousePosScaled import ru.dbotthepony.mc.otm.client.moveMousePosScaled
import ru.dbotthepony.mc.otm.client.screen.panels.* 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.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.FoldableSlotPanel
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.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.HeightControls 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.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.core.math.integerDivisionDown
import ru.dbotthepony.mc.otm.menu.MatteryMenu 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 import java.util.Collections
/** /**
@ -169,7 +161,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
hotbarStrip.dock = Dock.BOTTOM hotbarStrip.dock = Dock.BOTTOM
for (slot in menu.playerHotbarSlots) { for (slot in menu.playerHotbarSlots) {
FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also { UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
it.dock = Dock.LEFT it.dock = Dock.LEFT
} }
} }
@ -219,7 +211,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
hotbarStrip.dock = Dock.BOTTOM hotbarStrip.dock = Dock.BOTTOM
for (slot in menu.playerHotbarSlots) { for (slot in menu.playerHotbarSlots) {
FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also { UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
it.dock = Dock.LEFT 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)) { for (i in 0 .. (8).coerceAtMost(menu.playerCombinedInventorySlots.size - offset - 1)) {
val slot = menu.playerCombinedInventorySlots[offset + i] val slot = menu.playerCombinedInventorySlots[offset + i]
object : FilteredSlotPanel<MatteryScreen<*>, Slot>(this@MatteryScreen, canvas, slot) { object : UserFilteredSlotPanel<MatteryScreen<*>, Slot>(this@MatteryScreen, canvas, slot) {
init { init {
dock = Dock.LEFT 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.FramePanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel 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.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel
import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu
class CargoCrateScreen(menu: CargoCrateMenu, inventory: Inventory, title: Component) : MatteryScreen<CargoCrateMenu>(menu, inventory, title) { 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) val grid = GridPanel(this, frame, 8f, 18f, 9f * 18f, 6f * 18f, 9, 6)
for (slot in menu.storageSlots) for (slot in menu.storageSlots)
SlotPanel(this, grid, slot) UserFilteredSlotPanel.of(this, grid, slot)
return frame 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.platform.InputConstants
import com.mojang.blaze3d.vertex.PoseStack 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.inventory.Slot
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack 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.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.core.GetterSetter 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.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, screen: S,
parent: EditablePanel<*>?, parent: EditablePanel<*>?,
slot: T, slot: T,
@ -39,9 +44,12 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
screen.renderItemStack(absoluteX, absoluteY, itemStack, null) screen.renderItemStack(absoluteX, absoluteY, itemStack, null)
clearDepth(stack) clearDepth(stack)
drawColor = SLOT_FILTER_COLOR
} else {
drawColor = SLOT_BLOCK_COLOR
} }
drawColor = SLOT_FILTER_COLOR
drawRect(stack, 0f, 0f, width, height) drawRect(stack, 0f, 0f, width, height)
} }
} }
@ -52,12 +60,27 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
screen.renderComponentTooltip( screen.renderComponentTooltip(
stack, 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(), mouseX.toInt(),
mouseY.toInt(), mouseY.toInt(),
IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: screen.font, IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: screen.font,
itemstack 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) return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick)
@ -93,6 +116,7 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
companion object { companion object {
val SLOT_FILTER_COLOR = RGBAColor(85, 113, 216, 150) val SLOT_FILTER_COLOR = RGBAColor(85, 113, 216, 150)
val SLOT_BLOCK_COLOR = RGBAColor(219, 113, 113, 150)
fun <S : MatteryScreen<*>, T : Slot> of( fun <S : MatteryScreen<*>, T : Slot> of(
screen: S, screen: S,
@ -104,10 +128,27 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
height: Float = SIZE, height: Float = SIZE,
noItemIcon: MatterySprite? = null, noItemIcon: MatterySprite? = null,
filter: GetterSetter<Item?> filter: GetterSetter<Item?>
): FilteredSlotPanel<S, T> { ): UserFilteredSlotPanel<S, T> {
return object : FilteredSlotPanel<S, T>(screen, parent, slot, x, y, width, height, noItemIcon) { return object : UserFilteredSlotPanel<S, T>(screen, parent, slot, x, y, width, height, noItemIcon) {
override var slotFilter: Item? by filter 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 package ru.dbotthepony.mc.otm.container
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.util.LazyOptional import net.minecraft.world.item.Items
import net.minecraftforge.items.IItemHandler import net.minecraftforge.items.IItemHandler
class ContainerHandler @JvmOverloads internal constructor( class ContainerHandler @JvmOverloads internal constructor(
@ -12,7 +12,7 @@ class ContainerHandler @JvmOverloads internal constructor(
override fun getStackInSlot(slot: Int) = container[slot] override fun getStackInSlot(slot: Int) = container[slot]
override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { 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 return stack
filter.preInsert(slot, stack, simulate) filter.preInsert(slot, stack, simulate)
@ -47,11 +47,9 @@ class ContainerHandler @JvmOverloads internal constructor(
} }
override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack {
if (amount == 0) if (amount <= 0 || container.isSlotForbiddenForAutomation(slot))
return ItemStack.EMPTY return ItemStack.EMPTY
require(amount >= 0) { "Can not extract negative amount of items" }
filter.preExtract(slot, amount, simulate) filter.preExtract(slot, amount, simulate)
val localStack = container.getItem(slot) val localStack = container.getItem(slot)
@ -76,6 +74,6 @@ class ContainerHandler @JvmOverloads internal constructor(
} }
override fun isItemValid(slot: Int, stack: ItemStack): Boolean { 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 package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.Container import net.minecraft.world.Container
import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmOverloads
import net.minecraft.world.entity.player.Player 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.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.map
import ru.dbotthepony.mc.otm.core.nbt.set 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 java.util.*
import kotlin.collections.ArrayList
@Suppress("UNUSED") @Suppress("UNUSED")
open class MatteryContainer(val watcher: Runnable, private val size: Int) : Container, Iterable<ItemStack>, INBTSerializable<Tag?> { open class MatteryContainer(protected val watcher: Runnable, private val size: Int) : Container, Iterable<ItemStack>, INBTSerializable<Tag?> {
constructor(watcher: BlockEntity, size: Int) : this(watcher::setChanged, size)
constructor(size: Int) : this({}, size) constructor(size: Int) : this({}, size)
init { init {
@ -26,6 +37,72 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont
private var ignoreChangeNotifications = 0 private var ignoreChangeNotifications = 0
protected val slots: Array<ItemStack> = Array(size) { ItemStack.EMPTY } protected val slots: Array<ItemStack> = Array(size) { ItemStack.EMPTY }
private val trackedSlots: 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 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 startedIgnoringUpdates() {}
protected open fun stoppedIgnoringUpdates() {} protected open fun stoppedIgnoringUpdates() {}
private fun deserializeNBT(tag: CompoundTag?) { private fun deserializeNBT(tag: CompoundTag) {
Arrays.fill(slots, ItemStack.EMPTY)
if (tag == null) {
setChanged()
return
}
// нам не интересен размер // нам не интересен размер
tag.map("items") { it: ListTag -> 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)) { 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() setChanged()
} }
private fun deserializeNBT(tag: ListTag?) { private fun deserializeNBT(tag: ListTag) {
Arrays.fill(slots, ItemStack.EMPTY) if (tag.all { (it as CompoundTag).contains("slotIndex") }) {
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) {
val freeSlots = IntAVLTreeSet() val freeSlots = IntAVLTreeSet()
for (i in 0 until size) 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) slots[i] = ItemStack.of(tag[i] as CompoundTag)
} }
} }
setChanged()
} }
override fun deserializeNBT(tag: Tag?) { override fun deserializeNBT(tag: Tag?) {
Arrays.fill(slots, ItemStack.EMPTY)
Arrays.fill(filters, null)
filterSynchronizer?.get()?.value?.clear()
when (tag) { when (tag) {
is CompoundTag -> deserializeNBT(tag) is CompoundTag -> deserializeNBT(tag)
is ListTag -> deserializeNBT(tag) is ListTag -> {
else -> { deserializeNBT(tag)
Arrays.fill(slots, ItemStack.EMPTY)
setChanged() setChanged()
} }
else -> setChanged()
} }
} }
@ -123,13 +192,26 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont
if (ignoreChangeNotifications == 0) watcher.run() if (ignoreChangeNotifications == 0) watcher.run()
} }
override fun serializeNBT(): ListTag { override fun serializeNBT(): CompoundTag {
return ListTag().also { return CompoundTag().also {
for ((i, item) in slots.withIndex()) { it["items"] = ListTag().also {
if (!item.isEmpty) { for ((i, item) in slots.withIndex()) {
it.add(item.serializeNBT().also { if (!item.isEmpty) {
it["slotIndex"] = i 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) return super.quickMoveStack(ply, slotIndex)
} }
override fun canTakeItemForPickAll(p_38908_: ItemStack, p_38909_: Slot): Boolean { override fun canTakeItemForPickAll(itemStack: ItemStack, slot: Slot): Boolean {
return p_38909_.container != craftingResultContainer && super.canTakeItemForPickAll(p_38908_, p_38909_) return slot.container != craftingResultContainer && super.canTakeItemForPickAll(itemStack, slot)
} }
companion object : ContainerSynchronizer { 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.compat.curios.isCurioSlot
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.container.MatteryContainer
import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.util.BigDecimalValueCodec import ru.dbotthepony.mc.otm.core.util.BigDecimalValueCodec
import ru.dbotthepony.mc.otm.core.util.BinaryStringCodec import ru.dbotthepony.mc.otm.core.util.BinaryStringCodec
import ru.dbotthepony.mc.otm.core.util.BooleanValueCodec import ru.dbotthepony.mc.otm.core.util.BooleanValueCodec
import ru.dbotthepony.mc.otm.core.util.IStreamCodec 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.NullValueCodec
import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec
import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget
import ru.dbotthepony.mc.otm.network.FieldSynchronizer 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.MatteryPacket
import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
import ru.dbotthepony.mc.otm.network.MenuFieldPacket import ru.dbotthepony.mc.otm.network.MenuFieldPacket
@ -239,26 +242,22 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return _matteryWidgets[index] 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 { override fun mayPlace(itemStack: ItemStack): Boolean {
return super.mayPlace(itemStack) && !isInventorySlotLocked(index) return !isInventorySlotLocked(index) && super.mayPlace(itemStack)
} }
override fun mayPickup(player: Player): Boolean { override fun mayPickup(player: Player): Boolean {
return super.mayPickup(player) && !isInventorySlotLocked(index) return !isInventorySlotLocked(index) && super.mayPickup(player)
} }
override fun isSameInventory(other: Slot): Boolean { override fun isSameInventory(other: Slot): Boolean {
if (container === inventory || container === ply.matteryPlayer?.exoPackContainer) 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) return super.isSameInventory(other)
} }
// фильтр существует только для автоматизации
// игрок всё равно может класть предмет вручную, даже если он не соответствует фильтру
internal val filter: GetterSetter<Item?>?
init { init {
val mattery = ply.matteryPlayer val mattery = ply.matteryPlayer
@ -270,7 +269,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
) )
} else if (container === mattery.exoPackContainer) { } else if (container === mattery.exoPackContainer) {
filter = GetterSetter.of( filter = GetterSetter.of(
getter = { mattery.exoPackSlotFilters[slotIndex] }, getter = { mattery.exoPackContainer.getSlotFilter(slotIndex) },
setter = { MatteryPlayerNetworkChannel.sendToServer(SetInventoryFilterPacket(SetInventoryFilterPacket.Type.EXOPACK, slotIndex, it)) } setter = { MatteryPlayerNetworkChannel.sendToServer(SetInventoryFilterPacket(SetInventoryFilterPacket.Type.EXOPACK, slotIndex, it)) }
) )
} else { } else {
@ -359,7 +358,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
val payload = mSynchronizer.collectNetworkPayload() val payload = mSynchronizer.collectNetworkPayload()
if (payload != null) { if (payload != null) {
MenuNetworkChannel.send(ply, MenuFieldPacket(payload)) MenuNetworkChannel.send(ply, MenuFieldPacket(containerId, payload))
} }
super.broadcastChanges() super.broadcastChanges()
@ -384,7 +383,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
val payload = mSynchronizer.collectNetworkPayload() val payload = mSynchronizer.collectNetworkPayload()
if (payload != null) { if (payload != null) {
MenuNetworkChannel.send(ply, MenuFieldPacket(payload)) MenuNetworkChannel.send(ply, MenuFieldPacket(containerId, payload))
} }
super.broadcastFullState() super.broadcastFullState()
@ -420,6 +419,23 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return pSlot 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) return super.addSlot(pSlot)
} }
@ -488,7 +504,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
val copy = slot.item.copy() val copy = slot.item.copy()
var any = false 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) { for (collection in target) {
if (moveItemStackTo(slot, collection, onlyFiltered = true)) { if (moveItemStackTo(slot, collection, onlyFiltered = true)) {
any = true any = true
@ -525,11 +541,11 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return moveItemStackToSlots(stack, _playerInventorySlots, simulate = simulate) return moveItemStackToSlots(stack, _playerInventorySlots, simulate = simulate)
} }
override fun canTakeItemForPickAll(p_38908_: ItemStack, p_38909_: Slot): Boolean { override fun canTakeItemForPickAll(itemStack: ItemStack, slot: Slot): Boolean {
if (p_38909_ is EquipmentSlot) if (slot is EquipmentSlot)
return false 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( override fun moveItemStackTo(
@ -579,9 +595,9 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
// first pass - stack with existing slots // first pass - stack with existing slots
if (copy.isStackable) { if (copy.isStackable) {
for (slot in slots) { 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 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 continue
} }
@ -606,9 +622,9 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
// second pass - drop stack into first free slot // second pass - drop stack into first free slot
for (slot in slots) { 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 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 continue
} }

View File

@ -3,11 +3,13 @@ package ru.dbotthepony.mc.otm.menu
import net.minecraft.world.Container import net.minecraft.world.Container
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.Slot import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy import ru.dbotthepony.mc.otm.capability.energy
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.runOnClient 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) { 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) { 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 { override fun mayPlace(itemStack: ItemStack): Boolean {
return false 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.block.entity.decorative.CargoCrateBlockEntity
import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.UserFilteredSlot
import ru.dbotthepony.mc.otm.registry.MMenus import ru.dbotthepony.mc.otm.registry.MMenus
class CargoCrateMenu @JvmOverloads constructor( class CargoCrateMenu @JvmOverloads constructor(
@ -14,7 +15,7 @@ class CargoCrateMenu @JvmOverloads constructor(
inventory: Inventory, inventory: Inventory,
tile: CargoCrateBlockEntity? = null tile: CargoCrateBlockEntity? = null
) : MatteryMenu(MMenus.CARGO_CRATE, p_38852_, inventory, tile) { ) : MatteryMenu(MMenus.CARGO_CRATE, p_38852_, inventory, tile) {
val storageSlots: List<MatterySlot> val storageSlots: List<UserFilteredSlot>
private val trackedPlayerOpen = !inventory.player.isSpectator private val trackedPlayerOpen = !inventory.player.isSpectator
@ -22,7 +23,7 @@ class CargoCrateMenu @JvmOverloads constructor(
val container = tile?.container ?: SimpleContainer(CargoCrateBlockEntity.CAPACITY) val container = tile?.container ?: SimpleContainer(CargoCrateBlockEntity.CAPACITY)
storageSlots = immutableList(CargoCrateBlockEntity.CAPACITY) { storageSlots = immutableList(CargoCrateBlockEntity.CAPACITY) {
addStorageSlot(MatterySlot(container, it)) addStorageSlot(UserFilteredSlot(container, it))
} }
if (trackedPlayerOpen) { if (trackedPlayerOpen) {

View File

@ -57,6 +57,9 @@ sealed interface IField<V> : ReadOnlyProperty<Any?, V>, Supplier<V>, () -> V {
fun markDirty() fun markDirty()
fun markDirty(endpoint: FieldSynchronizer.Endpoint) fun markDirty(endpoint: FieldSynchronizer.Endpoint)
val value: V val value: V
val isRemoved: Boolean
fun remove()
fun write(stream: DataOutputStream, endpoint: FieldSynchronizer.Endpoint) fun write(stream: DataOutputStream, endpoint: FieldSynchronizer.Endpoint)
fun read(stream: DataInputStream) fun read(stream: DataInputStream)
@ -94,7 +97,23 @@ data class MapChangeset<out K, out V>(
val action: MapAction, val action: MapAction,
val key: K?, val key: K?,
val value: V? 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 { enum class MapAction {
CLEAR, ADD, REMOVE CLEAR, ADD, REMOVE
@ -109,7 +128,13 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
constructor() : this(Runnable {}, false) constructor() : this(Runnable {}, false)
constructor(callback: Runnable) : this(callback, 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 val observers = ArrayList<AbstractField<*>>(0)
private var nextFieldID = 0 private var nextFieldID = 0
@ -463,7 +488,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
fun markDirty() { fun markDirty() {
for (field in fields) { 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) 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>> { internal fun <K, V> getMapBacklog(map: Map<K, V>): LinkedList<Pair<Any?, (DataOutputStream) -> Unit>> {
if (unused) { if (unused) {
return LinkedList() return LinkedList()
@ -521,10 +550,57 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
@Suppress("LeakingThis") @Suppress("LeakingThis")
abstract inner class AbstractField<V> : IField<V> { abstract inner class AbstractField<V> : IField<V> {
val id: Int = fields.size + 1 val id: Int
init { 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 { override fun observe(): Boolean {
check(!isRemoved) { "Field was removed" }
if (!isDirty && !codec.compare(remote, field)) { if (!isDirty && !codec.compare(remote, field)) {
notifyEndpoints(this@Field) notifyEndpoints(this@Field)
isDirty = true isDirty = true
@ -603,21 +680,20 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
override fun markDirty() { override fun markDirty() {
check(!isRemoved) { "Field was removed" }
notifyEndpoints(this@Field) notifyEndpoints(this@Field)
isDirty = true isDirty = true
} }
override fun markDirty(endpoint: Endpoint) {
endpoint.addDirtyField(this)
}
override fun write(stream: DataOutputStream, endpoint: Endpoint) { override fun write(stream: DataOutputStream, endpoint: Endpoint) {
check(!isRemoved) { "Field was removed" }
codec.write(stream, field) codec.write(stream, field)
isDirty = false isDirty = false
remote = codec.copy(field) remote = codec.copy(field)
} }
override fun read(stream: DataInputStream) { override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" }
val value = codec.read(stream) val value = codec.read(stream)
val setter = this.setter val setter = this.setter
@ -647,6 +723,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
override fun observe(): Boolean { override fun observe(): Boolean {
check(!isRemoved) { "Field was removed" }
if (!isDirty && (remote == null || !codec.compare(remote ?: throw ConcurrentModificationException(), value))) { if (!isDirty && (remote == null || !codec.compare(remote ?: throw ConcurrentModificationException(), value))) {
notifyEndpoints(this) notifyEndpoints(this)
isDirty = true isDirty = true
@ -657,24 +734,23 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
override fun markDirty() { override fun markDirty() {
check(!isRemoved) { "Field was removed" }
notifyEndpoints(this) notifyEndpoints(this)
isDirty = true isDirty = true
} }
override fun markDirty(endpoint: Endpoint) {
endpoint.addDirtyField(this)
}
override val value: V override val value: V
get() = clientValue ?: getter.invoke() get() = clientValue ?: getter.invoke()
override fun write(stream: DataOutputStream, endpoint: Endpoint) { override fun write(stream: DataOutputStream, endpoint: Endpoint) {
check(!isRemoved) { "Field was removed" }
val value = value val value = value
codec.write(stream, value) codec.write(stream, value)
isDirty = false isDirty = false
} }
override fun read(stream: DataInputStream) { override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" }
val newValue = codec.read(stream) val newValue = codec.read(stream)
clientValue = newValue clientValue = newValue
observer.invoke(newValue) observer.invoke(newValue)
@ -715,6 +791,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
override fun observe(): Boolean { override fun observe(): Boolean {
check(!isRemoved) { "Field was removed" }
if (!isDirty && !codec.compare(remote, value)) { if (!isDirty && !codec.compare(remote, value)) {
notifyEndpoints(this) notifyEndpoints(this)
isDirty = true isDirty = true
@ -725,21 +803,20 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
override fun markDirty() { override fun markDirty() {
check(!isRemoved) { "Field was removed" }
notifyEndpoints(this) notifyEndpoints(this)
isDirty = true isDirty = true
} }
override fun markDirty(endpoint: Endpoint) {
endpoint.addDirtyField(this)
}
override fun write(stream: DataOutputStream, endpoint: Endpoint) { override fun write(stream: DataOutputStream, endpoint: Endpoint) {
check(!isRemoved) { "Field was removed" }
val value = value val value = value
codec.write(stream, value) codec.write(stream, value)
isDirty = false isDirty = false
} }
override fun read(stream: DataInputStream) { override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" }
this.value = codec.read(stream) this.value = codec.read(stream)
} }
} }
@ -782,6 +859,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
override fun observe(): Boolean { override fun observe(): Boolean {
check(!isRemoved) { "Field was removed" }
if (isRemote) { if (isRemote) {
return false return false
} }
@ -815,6 +894,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
override fun markDirty() { override fun markDirty() {
check(!isRemoved) { "Field was removed" }
if (isRemote) { if (isRemote) {
return return
} }
@ -857,6 +938,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
override fun markDirty(endpoint: Endpoint) { override fun markDirty(endpoint: Endpoint) {
check(!isRemoved) { "Field was removed" }
if (isRemote) { if (isRemote) {
return return
} }
@ -1025,7 +1108,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
*/ */
fun invalidate() { fun invalidate() {
for (field in fields) { for (field in fields) {
field.markDirty() field?.markDirty()
} }
forEachEndpoint { forEachEndpoint {

View File

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

View File

@ -6,9 +6,11 @@ import net.minecraft.world.item.ItemStack
import net.minecraftforge.network.NetworkDirection import net.minecraftforge.network.NetworkDirection
import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.NetworkEvent
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings 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.client.minecraft
import ru.dbotthepony.mc.otm.compat.InventoryScrollPacket import ru.dbotthepony.mc.otm.compat.InventoryScrollPacket
import ru.dbotthepony.mc.otm.container.ItemFilterSlotPacket 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.matter.CancelTaskPacket
import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.matter.PatternsChangePacket 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.io.ByteArrayInputStream
import java.util.function.Supplier import java.util.function.Supplier
class MenuFieldPacket(val bytes: ByteArray, val length: Int) : MatteryPacket { class MenuFieldPacket(val containerId: Int, val bytes: ByteArray, val length: Int) : MatteryPacket {
constructor(stream: FastByteArrayOutputStream) : this(stream.array, stream.length) constructor(containerId: Int, stream: FastByteArrayOutputStream) : this(containerId, stream.array, stream.length)
override fun write(buff: FriendlyByteBuf) { override fun write(buff: FriendlyByteBuf) {
buff.writeVarInt(containerId)
buff.writeBytes(bytes, 0, length) buff.writeBytes(bytes, 0, length)
} }
override fun play(context: Supplier<NetworkEvent.Context>) { override fun play(context: Supplier<NetworkEvent.Context>) {
context.packetHandled = true 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 { companion object {
fun read(buff: FriendlyByteBuf): MenuFieldPacket { fun read(buff: FriendlyByteBuf): MenuFieldPacket {
val containerId = buff.readVarInt()
val readable = buff.readableBytes() 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( object MenuNetworkChannel : MatteryNetworkChannel(
version = "2", version = "3",
name = "menu" name = "menu"
) { ) {
fun register() { fun register() {