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:
parent
30f07e2fed
commit
3c61a03fd8
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 + ЛКМ")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -10,7 +10,7 @@ import ru.dbotthepony.mc.otm.client.render.sprite
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.*
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilteredSlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
|
||||
import ru.dbotthepony.mc.otm.client.setMousePos
|
||||
@ -83,7 +83,7 @@ class ExoPackInventoryScreen(menu: ExoPackInventoryMenu) : MatteryScreen<ExoPack
|
||||
mainInventoryLine.dock = Dock.BOTTOM
|
||||
|
||||
for (slot in menu.playerHotbarSlots) {
|
||||
FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also {
|
||||
UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
|
||||
it.dock = Dock.LEFT
|
||||
}
|
||||
}
|
||||
|
@ -18,24 +18,16 @@ import net.minecraftforge.client.event.ContainerScreenEvent.Render.Foreground
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import org.lwjgl.opengl.GL11
|
||||
import org.lwjgl.opengl.GL13
|
||||
import ru.dbotthepony.mc.otm.capability.matteryPlayer
|
||||
import ru.dbotthepony.mc.otm.config.ClientConfig
|
||||
import ru.dbotthepony.mc.otm.client.moveMousePosScaled
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.*
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilteredSlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.FoldableSlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.util.HeightControls
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollBarConstants
|
||||
import ru.dbotthepony.mc.otm.compat.cos.CosmeticToggleButton
|
||||
import ru.dbotthepony.mc.otm.core.GetterSetter
|
||||
import ru.dbotthepony.mc.otm.core.math.integerDivisionDown
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
import ru.dbotthepony.mc.otm.menu.PlayerSlot
|
||||
import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
|
||||
import ru.dbotthepony.mc.otm.network.SetInventoryFilterPacket
|
||||
import java.util.Collections
|
||||
|
||||
/**
|
||||
@ -169,7 +161,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
|
||||
hotbarStrip.dock = Dock.BOTTOM
|
||||
|
||||
for (slot in menu.playerHotbarSlots) {
|
||||
FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also {
|
||||
UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
|
||||
it.dock = Dock.LEFT
|
||||
}
|
||||
}
|
||||
@ -219,7 +211,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
|
||||
hotbarStrip.dock = Dock.BOTTOM
|
||||
|
||||
for (slot in menu.playerHotbarSlots) {
|
||||
FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also {
|
||||
UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
|
||||
it.dock = Dock.LEFT
|
||||
}
|
||||
}
|
||||
@ -262,7 +254,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
|
||||
for (i in 0 .. (8).coerceAtMost(menu.playerCombinedInventorySlots.size - offset - 1)) {
|
||||
val slot = menu.playerCombinedInventorySlots[offset + i]
|
||||
|
||||
object : FilteredSlotPanel<MatteryScreen<*>, Slot>(this@MatteryScreen, canvas, slot) {
|
||||
object : UserFilteredSlotPanel<MatteryScreen<*>, Slot>(this@MatteryScreen, canvas, slot) {
|
||||
init {
|
||||
dock = Dock.LEFT
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel
|
||||
import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu
|
||||
|
||||
class CargoCrateScreen(menu: CargoCrateMenu, inventory: Inventory, title: Component) : MatteryScreen<CargoCrateMenu>(menu, inventory, title) {
|
||||
@ -14,7 +15,7 @@ class CargoCrateScreen(menu: CargoCrateMenu, inventory: Inventory, title: Compon
|
||||
val grid = GridPanel(this, frame, 8f, 18f, 9f * 18f, 6f * 18f, 9, 6)
|
||||
|
||||
for (slot in menu.storageSlots)
|
||||
SlotPanel(this, grid, slot)
|
||||
UserFilteredSlotPanel.of(this, grid, slot)
|
||||
|
||||
return frame
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package ru.dbotthepony.mc.otm.client.screen.panels.slot
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.world.inventory.Slot
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
@ -16,9 +18,12 @@ import ru.dbotthepony.mc.otm.client.render.drawRect
|
||||
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
|
||||
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
|
||||
import ru.dbotthepony.mc.otm.core.GetterSetter
|
||||
import ru.dbotthepony.mc.otm.core.TextComponent
|
||||
import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
||||
import ru.dbotthepony.mc.otm.core.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.menu.UserFilteredSlot
|
||||
|
||||
abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
|
||||
abstract class UserFilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
|
||||
screen: S,
|
||||
parent: EditablePanel<*>?,
|
||||
slot: T,
|
||||
@ -39,9 +44,12 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
|
||||
|
||||
screen.renderItemStack(absoluteX, absoluteY, itemStack, null)
|
||||
clearDepth(stack)
|
||||
|
||||
drawColor = SLOT_FILTER_COLOR
|
||||
} else {
|
||||
drawColor = SLOT_BLOCK_COLOR
|
||||
}
|
||||
|
||||
drawColor = SLOT_FILTER_COLOR
|
||||
drawRect(stack, 0f, 0f, width, height)
|
||||
}
|
||||
}
|
||||
@ -52,12 +60,27 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
|
||||
|
||||
screen.renderComponentTooltip(
|
||||
stack,
|
||||
getItemStackTooltip(itemstack),
|
||||
getItemStackTooltip(itemstack).toMutableList().also {
|
||||
it.add(0, TranslatableComponent("otm.gui.slot_filter.filtered").withStyle(ChatFormatting.GRAY))
|
||||
it.add(1, TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY))
|
||||
it.add(2, TextComponent(""))
|
||||
},
|
||||
mouseX.toInt(),
|
||||
mouseY.toInt(),
|
||||
IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: screen.font,
|
||||
itemstack
|
||||
)
|
||||
} else if (isHovered && slotFilter === Items.AIR && itemStack.isEmpty) {
|
||||
screen.renderComponentTooltip(
|
||||
stack,
|
||||
ArrayList<Component>().also {
|
||||
it.add(TranslatableComponent("otm.gui.slot_filter.forbidden").withStyle(ChatFormatting.GRAY))
|
||||
it.add(TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY))
|
||||
},
|
||||
mouseX.toInt(),
|
||||
mouseY.toInt(),
|
||||
screen.font
|
||||
)
|
||||
}
|
||||
|
||||
return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick)
|
||||
@ -93,6 +116,7 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
|
||||
|
||||
companion object {
|
||||
val SLOT_FILTER_COLOR = RGBAColor(85, 113, 216, 150)
|
||||
val SLOT_BLOCK_COLOR = RGBAColor(219, 113, 113, 150)
|
||||
|
||||
fun <S : MatteryScreen<*>, T : Slot> of(
|
||||
screen: S,
|
||||
@ -104,10 +128,27 @@ abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
|
||||
height: Float = SIZE,
|
||||
noItemIcon: MatterySprite? = null,
|
||||
filter: GetterSetter<Item?>
|
||||
): FilteredSlotPanel<S, T> {
|
||||
return object : FilteredSlotPanel<S, T>(screen, parent, slot, x, y, width, height, noItemIcon) {
|
||||
): UserFilteredSlotPanel<S, T> {
|
||||
return object : UserFilteredSlotPanel<S, T>(screen, parent, slot, x, y, width, height, noItemIcon) {
|
||||
override var slotFilter: Item? by filter
|
||||
}
|
||||
}
|
||||
|
||||
fun <S : MatteryScreen<*>, T : UserFilteredSlot> of(
|
||||
screen: S,
|
||||
parent: EditablePanel<*>?,
|
||||
slot: T,
|
||||
x: Float = 0f,
|
||||
y: Float = 0f,
|
||||
width: Float = SIZE,
|
||||
height: Float = SIZE,
|
||||
noItemIcon: MatterySprite? = null
|
||||
): UserFilteredSlotPanel<S, T> {
|
||||
return object : UserFilteredSlotPanel<S, T>(screen, parent, slot, x, y, width, height, noItemIcon) {
|
||||
override var slotFilter: Item?
|
||||
get() = slot.filter?.get()
|
||||
set(value) { slot.filter?.accept(value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,33 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.nbt.Tag
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.Container
|
||||
import kotlin.jvm.JvmOverloads
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraftforge.common.util.INBTSerializable
|
||||
import net.minecraftforge.registries.ForgeRegistries
|
||||
import ru.dbotthepony.mc.otm.core.forValidRefs
|
||||
import ru.dbotthepony.mc.otm.core.nbt.map
|
||||
import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
import ru.dbotthepony.mc.otm.core.registryName
|
||||
import ru.dbotthepony.mc.otm.core.util.ItemValueCodec
|
||||
import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec
|
||||
import ru.dbotthepony.mc.otm.network.FieldSynchronizer
|
||||
import ru.dbotthepony.mc.otm.network.IField
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@Suppress("UNUSED")
|
||||
open class MatteryContainer(val watcher: Runnable, private val size: Int) : Container, Iterable<ItemStack>, INBTSerializable<Tag?> {
|
||||
constructor(watcher: BlockEntity, size: Int) : this(watcher::setChanged, size)
|
||||
open class MatteryContainer(protected val watcher: Runnable, private val size: Int) : Container, Iterable<ItemStack>, INBTSerializable<Tag?> {
|
||||
constructor(size: Int) : this({}, size)
|
||||
|
||||
init {
|
||||
@ -26,6 +37,72 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont
|
||||
private var ignoreChangeNotifications = 0
|
||||
protected val slots: Array<ItemStack> = Array(size) { ItemStack.EMPTY }
|
||||
private val trackedSlots: Array<ItemStack> = Array(size) { ItemStack.EMPTY }
|
||||
private val filters: Array<Item?> = arrayOfNulls(size)
|
||||
private var filterSynchronizer: WeakReference<IField<MutableMap<Int, Item>>>? = null
|
||||
|
||||
fun clearSlotFilters() {
|
||||
Arrays.fill(filters, null)
|
||||
filterSynchronizer?.get()?.value?.clear()
|
||||
}
|
||||
|
||||
fun removeFilterSynchronizer() {
|
||||
filterSynchronizer?.get()?.remove()
|
||||
filterSynchronizer = null
|
||||
}
|
||||
|
||||
fun addFilterSynchronizer(synchronizer: FieldSynchronizer): IField<MutableMap<Int, Item>> {
|
||||
check(filterSynchronizer?.get() == null) { "Already has filter synchronizer" }
|
||||
|
||||
val field = synchronizer.Map(
|
||||
keyCodec = VarIntValueCodec,
|
||||
valueCodec = ItemValueCodec,
|
||||
backingMap = Int2ObjectOpenHashMap(),
|
||||
callback = {
|
||||
for (change in it) {
|
||||
change.map({ k, v -> filters[k] = v }, { k -> filters[k] = null }, { Arrays.fill(filters, null) })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
for ((i, filter) in filters.withIndex()) {
|
||||
if (filter != null) {
|
||||
field.value[i] = filter
|
||||
}
|
||||
}
|
||||
|
||||
filterSynchronizer = WeakReference(field)
|
||||
return field
|
||||
}
|
||||
|
||||
fun setSlotFilter(slot: Int, filter: Item? = null) {
|
||||
if (filters[slot] !== filter) {
|
||||
filters[slot] = filter
|
||||
|
||||
filterSynchronizer?.get()?.let {
|
||||
if (filter == null) {
|
||||
it.value.remove(slot)
|
||||
} else {
|
||||
it.value[slot] = filter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getSlotFilter(slot: Int) = filters[slot]
|
||||
fun hasSlotFilter(slot: Int) = filters[slot] !== null
|
||||
fun isSlotForbiddenForAutomation(slot: Int) = filters[slot] === Items.AIR
|
||||
|
||||
fun testSlotFilter(slot: Int, itemStack: ItemStack): Boolean {
|
||||
return testSlotFilter(slot, itemStack.item)
|
||||
}
|
||||
|
||||
fun testSlotFilter(slot: Int, item: Item): Boolean {
|
||||
if (filters[slot] == null) {
|
||||
return true
|
||||
} else {
|
||||
return filters[slot] === item
|
||||
}
|
||||
}
|
||||
|
||||
final override fun getContainerSize() = size
|
||||
|
||||
@ -44,42 +121,31 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont
|
||||
protected open fun startedIgnoringUpdates() {}
|
||||
protected open fun stoppedIgnoringUpdates() {}
|
||||
|
||||
private fun deserializeNBT(tag: CompoundTag?) {
|
||||
Arrays.fill(slots, ItemStack.EMPTY)
|
||||
|
||||
if (tag == null) {
|
||||
setChanged()
|
||||
return
|
||||
}
|
||||
|
||||
private fun deserializeNBT(tag: CompoundTag) {
|
||||
// нам не интересен размер
|
||||
tag.map("items") { it: ListTag ->
|
||||
deserializeNBT(it)
|
||||
}
|
||||
|
||||
tag.map("filters") { it: ListTag ->
|
||||
val map = filterSynchronizer?.get()
|
||||
|
||||
for (i in 0 until it.size.coerceAtMost(size)) {
|
||||
slots[i] = ItemStack.of(it[i] as CompoundTag)
|
||||
val nbt = it[i] as CompoundTag
|
||||
val index = nbt.getInt("slotIndex")
|
||||
filters[index] = ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(nbt.getString("filter")) ?: continue)
|
||||
|
||||
if (filters[index] != null) {
|
||||
map?.value?.put(index, filters[index]!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setChanged()
|
||||
}
|
||||
|
||||
private fun deserializeNBT(tag: ListTag?) {
|
||||
Arrays.fill(slots, ItemStack.EMPTY)
|
||||
|
||||
if (tag == null) {
|
||||
setChanged()
|
||||
return
|
||||
}
|
||||
|
||||
var isIndexed = true
|
||||
|
||||
for (i in 0 until tag.size.coerceAtMost(size)) {
|
||||
if (!(tag[i] as CompoundTag).contains("slotIndex")) {
|
||||
isIndexed = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (isIndexed) {
|
||||
private fun deserializeNBT(tag: ListTag) {
|
||||
if (tag.all { (it as CompoundTag).contains("slotIndex") }) {
|
||||
val freeSlots = IntAVLTreeSet()
|
||||
|
||||
for (i in 0 until size)
|
||||
@ -104,18 +170,21 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont
|
||||
slots[i] = ItemStack.of(tag[i] as CompoundTag)
|
||||
}
|
||||
}
|
||||
|
||||
setChanged()
|
||||
}
|
||||
|
||||
override fun deserializeNBT(tag: Tag?) {
|
||||
Arrays.fill(slots, ItemStack.EMPTY)
|
||||
Arrays.fill(filters, null)
|
||||
filterSynchronizer?.get()?.value?.clear()
|
||||
|
||||
when (tag) {
|
||||
is CompoundTag -> deserializeNBT(tag)
|
||||
is ListTag -> deserializeNBT(tag)
|
||||
else -> {
|
||||
Arrays.fill(slots, ItemStack.EMPTY)
|
||||
is ListTag -> {
|
||||
deserializeNBT(tag)
|
||||
setChanged()
|
||||
}
|
||||
|
||||
else -> setChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,13 +192,26 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont
|
||||
if (ignoreChangeNotifications == 0) watcher.run()
|
||||
}
|
||||
|
||||
override fun serializeNBT(): ListTag {
|
||||
return ListTag().also {
|
||||
for ((i, item) in slots.withIndex()) {
|
||||
if (!item.isEmpty) {
|
||||
it.add(item.serializeNBT().also {
|
||||
it["slotIndex"] = i
|
||||
})
|
||||
override fun serializeNBT(): CompoundTag {
|
||||
return CompoundTag().also {
|
||||
it["items"] = ListTag().also {
|
||||
for ((i, item) in slots.withIndex()) {
|
||||
if (!item.isEmpty) {
|
||||
it.add(item.serializeNBT().also {
|
||||
it["slotIndex"] = i
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it["filters"] = ListTag().also {
|
||||
for ((i, filter) in filters.withIndex()) {
|
||||
if (filter != null) {
|
||||
it.add(CompoundTag().also {
|
||||
it["filter"] = filter.registryName!!.toString()
|
||||
it["slotIndex"] = i
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -27,15 +27,18 @@ import ru.dbotthepony.mc.otm.compat.curios.curiosSlots
|
||||
import ru.dbotthepony.mc.otm.compat.curios.isCurioSlot
|
||||
import ru.dbotthepony.mc.otm.container.ItemFilter
|
||||
import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot
|
||||
import ru.dbotthepony.mc.otm.container.MatteryContainer
|
||||
import ru.dbotthepony.mc.otm.core.GetterSetter
|
||||
import ru.dbotthepony.mc.otm.core.util.BigDecimalValueCodec
|
||||
import ru.dbotthepony.mc.otm.core.util.BinaryStringCodec
|
||||
import ru.dbotthepony.mc.otm.core.util.BooleanValueCodec
|
||||
import ru.dbotthepony.mc.otm.core.util.IStreamCodec
|
||||
import ru.dbotthepony.mc.otm.core.util.ItemValueCodec
|
||||
import ru.dbotthepony.mc.otm.core.util.NullValueCodec
|
||||
import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec
|
||||
import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget
|
||||
import ru.dbotthepony.mc.otm.network.FieldSynchronizer
|
||||
import ru.dbotthepony.mc.otm.network.IField
|
||||
import ru.dbotthepony.mc.otm.network.MatteryPacket
|
||||
import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
|
||||
import ru.dbotthepony.mc.otm.network.MenuFieldPacket
|
||||
@ -239,26 +242,22 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
|
||||
return _matteryWidgets[index]
|
||||
}
|
||||
|
||||
open inner class InventorySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) {
|
||||
open inner class InventorySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : UserFilteredSlot(container, index, x, y) {
|
||||
override fun mayPlace(itemStack: ItemStack): Boolean {
|
||||
return super.mayPlace(itemStack) && !isInventorySlotLocked(index)
|
||||
return !isInventorySlotLocked(index) && super.mayPlace(itemStack)
|
||||
}
|
||||
|
||||
override fun mayPickup(player: Player): Boolean {
|
||||
return super.mayPickup(player) && !isInventorySlotLocked(index)
|
||||
return !isInventorySlotLocked(index) && super.mayPickup(player)
|
||||
}
|
||||
|
||||
override fun isSameInventory(other: Slot): Boolean {
|
||||
if (container === inventory || container === ply.matteryPlayer?.exoPackContainer)
|
||||
return other.container === inventory || other.container === ply.matteryPlayer?.exoPackContainer
|
||||
return (other.container === inventory || other.container === ply.matteryPlayer?.exoPackContainer) && isSameFilter(other)
|
||||
|
||||
return super.isSameInventory(other)
|
||||
}
|
||||
|
||||
// фильтр существует только для автоматизации
|
||||
// игрок всё равно может класть предмет вручную, даже если он не соответствует фильтру
|
||||
internal val filter: GetterSetter<Item?>?
|
||||
|
||||
init {
|
||||
val mattery = ply.matteryPlayer
|
||||
|
||||
@ -270,7 +269,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
|
||||
)
|
||||
} else if (container === mattery.exoPackContainer) {
|
||||
filter = GetterSetter.of(
|
||||
getter = { mattery.exoPackSlotFilters[slotIndex] },
|
||||
getter = { mattery.exoPackContainer.getSlotFilter(slotIndex) },
|
||||
setter = { MatteryPlayerNetworkChannel.sendToServer(SetInventoryFilterPacket(SetInventoryFilterPacket.Type.EXOPACK, slotIndex, it)) }
|
||||
)
|
||||
} else {
|
||||
@ -359,7 +358,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
|
||||
val payload = mSynchronizer.collectNetworkPayload()
|
||||
|
||||
if (payload != null) {
|
||||
MenuNetworkChannel.send(ply, MenuFieldPacket(payload))
|
||||
MenuNetworkChannel.send(ply, MenuFieldPacket(containerId, payload))
|
||||
}
|
||||
|
||||
super.broadcastChanges()
|
||||
@ -384,7 +383,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
|
||||
val payload = mSynchronizer.collectNetworkPayload()
|
||||
|
||||
if (payload != null) {
|
||||
MenuNetworkChannel.send(ply, MenuFieldPacket(payload))
|
||||
MenuNetworkChannel.send(ply, MenuFieldPacket(containerId, payload))
|
||||
}
|
||||
|
||||
super.broadcastFullState()
|
||||
@ -420,6 +419,23 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
|
||||
return pSlot
|
||||
}
|
||||
|
||||
if (pSlot is UserFilteredSlot && !pSlot.hasSetFilter) {
|
||||
val container = pSlot.container
|
||||
|
||||
val input: PlayerInput<Item?>
|
||||
val field: IField<Item?>
|
||||
|
||||
if (container is MatteryContainer) {
|
||||
input = PlayerInput(ItemValueCodec.nullable, handler = { container.setSlotFilter(pSlot.slotIndex, it) })
|
||||
field = mSynchronizer.ComputedField(getter = { container.getSlotFilter(pSlot.slotIndex) }, ItemValueCodec.nullable)
|
||||
} else {
|
||||
input = PlayerInput(ItemValueCodec.nullable, handler = { throw UnsupportedOperationException() })
|
||||
field = mSynchronizer.ComputedField(getter = { null }, ItemValueCodec.nullable)
|
||||
}
|
||||
|
||||
pSlot.filter = GetterSetter.of(getter = field::value, setter = input::input)
|
||||
}
|
||||
|
||||
return super.addSlot(pSlot)
|
||||
}
|
||||
|
||||
@ -488,7 +504,7 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
|
||||
val copy = slot.item.copy()
|
||||
var any = false
|
||||
|
||||
if (target.any { it.any { it is InventorySlot && it.filter != null } }) {
|
||||
if (target.any { it.any { it is UserFilteredSlot && it.filter != null } }) {
|
||||
for (collection in target) {
|
||||
if (moveItemStackTo(slot, collection, onlyFiltered = true)) {
|
||||
any = true
|
||||
@ -525,11 +541,11 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
|
||||
return moveItemStackToSlots(stack, _playerInventorySlots, simulate = simulate)
|
||||
}
|
||||
|
||||
override fun canTakeItemForPickAll(p_38908_: ItemStack, p_38909_: Slot): Boolean {
|
||||
if (p_38909_ is EquipmentSlot)
|
||||
override fun canTakeItemForPickAll(itemStack: ItemStack, slot: Slot): Boolean {
|
||||
if (slot is EquipmentSlot)
|
||||
return false
|
||||
|
||||
return super.canTakeItemForPickAll(p_38908_, p_38909_) && (p_38909_ !is MatterySlot || p_38909_.canTakeItemForPickAll()) && !p_38909_.isCurioSlot
|
||||
return super.canTakeItemForPickAll(itemStack, slot) && (slot !is MatterySlot || slot.canTakeItemForPickAll()) && !slot.isCurioSlot
|
||||
}
|
||||
|
||||
override fun moveItemStackTo(
|
||||
@ -579,9 +595,9 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
|
||||
// first pass - stack with existing slots
|
||||
if (copy.isStackable) {
|
||||
for (slot in slots) {
|
||||
if (onlyFiltered && (slot !is InventorySlot || slot.filter == null || slot.filter.get() != item.item)) {
|
||||
if (onlyFiltered && (slot !is UserFilteredSlot || slot.filter == null || slot.filter!!.get() != item.item)) {
|
||||
continue
|
||||
} else if (!onlyFiltered && slot is InventorySlot && slot.filter != null && slot.filter.get() != null && slot.filter.get() != item.item) {
|
||||
} else if (!onlyFiltered && slot is UserFilteredSlot && slot.filter != null && slot.filter!!.get() != null && slot.filter!!.get() != item.item) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -606,9 +622,9 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
|
||||
|
||||
// second pass - drop stack into first free slot
|
||||
for (slot in slots) {
|
||||
if (onlyFiltered && (slot !is InventorySlot || slot.filter == null || slot.filter.get() != item.item)) {
|
||||
if (onlyFiltered && (slot !is UserFilteredSlot || slot.filter == null || slot.filter!!.get() != item.item)) {
|
||||
continue
|
||||
} else if (!onlyFiltered && slot is InventorySlot && slot.filter != null && slot.filter.get() != null && slot.filter.get() != item.item) {
|
||||
} else if (!onlyFiltered && slot is UserFilteredSlot && slot.filter != null && slot.filter!!.get() != null && slot.filter!!.get() != item.item) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,13 @@ package ru.dbotthepony.mc.otm.menu
|
||||
import net.minecraft.world.Container
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.inventory.Slot
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import ru.dbotthepony.mc.otm.capability.FlowDirection
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryCapability
|
||||
import ru.dbotthepony.mc.otm.capability.energy
|
||||
import ru.dbotthepony.mc.otm.client.minecraft
|
||||
import ru.dbotthepony.mc.otm.core.GetterSetter
|
||||
import ru.dbotthepony.mc.otm.runOnClient
|
||||
|
||||
open class MatterySlot @JvmOverloads constructor(container: Container, index: Int, x: Int = 0, y: Int = 0) : Slot(container, index, x, y) {
|
||||
@ -26,6 +28,35 @@ open class MatterySlot @JvmOverloads constructor(container: Container, index: In
|
||||
}
|
||||
}
|
||||
|
||||
open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) {
|
||||
var hasSetFilter = false
|
||||
private set
|
||||
|
||||
var filter: GetterSetter<Item?>? = null
|
||||
set(value) {
|
||||
hasSetFilter = true
|
||||
field = value
|
||||
}
|
||||
|
||||
override fun canTakeItemForPickAll(): Boolean {
|
||||
return filter?.get() == null
|
||||
}
|
||||
|
||||
fun isSameFilter(other: Slot): Boolean {
|
||||
if (other !is UserFilteredSlot)
|
||||
return filter?.get() == null
|
||||
|
||||
return (
|
||||
(other.filter == null && filter == null) ||
|
||||
(other.filter != null && filter != null && other.filter!!.get() == filter!!.get())
|
||||
)
|
||||
}
|
||||
|
||||
override fun isSameInventory(other: Slot): Boolean {
|
||||
return isSameFilter(other) && super.isSameInventory(other)
|
||||
}
|
||||
}
|
||||
|
||||
open class MachineOutputSlot @JvmOverloads constructor(container: Container, index: Int, x: Int = 0, y: Int = 0, val onTake: () -> Unit = {}) : MatterySlot(container, index, x, y) {
|
||||
override fun mayPlace(itemStack: ItemStack): Boolean {
|
||||
return false
|
||||
|
@ -7,6 +7,7 @@ import ru.dbotthepony.mc.otm.core.immutableList
|
||||
import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
import ru.dbotthepony.mc.otm.menu.MatterySlot
|
||||
import ru.dbotthepony.mc.otm.menu.UserFilteredSlot
|
||||
import ru.dbotthepony.mc.otm.registry.MMenus
|
||||
|
||||
class CargoCrateMenu @JvmOverloads constructor(
|
||||
@ -14,7 +15,7 @@ class CargoCrateMenu @JvmOverloads constructor(
|
||||
inventory: Inventory,
|
||||
tile: CargoCrateBlockEntity? = null
|
||||
) : MatteryMenu(MMenus.CARGO_CRATE, p_38852_, inventory, tile) {
|
||||
val storageSlots: List<MatterySlot>
|
||||
val storageSlots: List<UserFilteredSlot>
|
||||
|
||||
private val trackedPlayerOpen = !inventory.player.isSpectator
|
||||
|
||||
@ -22,7 +23,7 @@ class CargoCrateMenu @JvmOverloads constructor(
|
||||
val container = tile?.container ?: SimpleContainer(CargoCrateBlockEntity.CAPACITY)
|
||||
|
||||
storageSlots = immutableList(CargoCrateBlockEntity.CAPACITY) {
|
||||
addStorageSlot(MatterySlot(container, it))
|
||||
addStorageSlot(UserFilteredSlot(container, it))
|
||||
}
|
||||
|
||||
if (trackedPlayerOpen) {
|
||||
|
@ -57,6 +57,9 @@ sealed interface IField<V> : ReadOnlyProperty<Any?, V>, Supplier<V>, () -> V {
|
||||
fun markDirty()
|
||||
fun markDirty(endpoint: FieldSynchronizer.Endpoint)
|
||||
val value: V
|
||||
val isRemoved: Boolean
|
||||
|
||||
fun remove()
|
||||
|
||||
fun write(stream: DataOutputStream, endpoint: FieldSynchronizer.Endpoint)
|
||||
fun read(stream: DataInputStream)
|
||||
@ -94,7 +97,23 @@ data class MapChangeset<out K, out V>(
|
||||
val action: MapAction,
|
||||
val key: K?,
|
||||
val value: V?
|
||||
)
|
||||
) {
|
||||
inline fun map(add: (K, V) -> Unit, remove: (K) -> Unit) {
|
||||
when (action) {
|
||||
MapAction.ADD -> add.invoke(key!!, value!!)
|
||||
MapAction.REMOVE -> remove.invoke(key!!)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun map(add: (K, V) -> Unit, remove: (K) -> Unit, clear: () -> Unit) {
|
||||
when (action) {
|
||||
MapAction.CLEAR -> clear.invoke()
|
||||
MapAction.ADD -> add.invoke(key!!, value!!)
|
||||
MapAction.REMOVE -> remove.invoke(key!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class MapAction {
|
||||
CLEAR, ADD, REMOVE
|
||||
@ -109,7 +128,13 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
constructor() : this(Runnable {}, false)
|
||||
constructor(callback: Runnable) : this(callback, false)
|
||||
|
||||
private val fields = ArrayList<AbstractField<*>>(0)
|
||||
private var freeSlots = 0
|
||||
// почему не удалять поля напрямую?
|
||||
// чтоб не возникло проблем в состоянии гонки
|
||||
// формируем пакет -> удаляем поле по обе стороны -> клиент принимает пакет -> клиент считывает неверные данные
|
||||
// конечно, всё равно всё сломается если было удалено поле, которое находится в пакете
|
||||
// но если поля нет в пакете, то всё окей
|
||||
private val fields = ArrayList<AbstractField<*>?>(0)
|
||||
private val observers = ArrayList<AbstractField<*>>(0)
|
||||
|
||||
private var nextFieldID = 0
|
||||
@ -463,7 +488,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
|
||||
fun markDirty() {
|
||||
for (field in fields) {
|
||||
field.markDirty(this)
|
||||
field?.markDirty(this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,6 +500,10 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
dirtyFields.add(field)
|
||||
}
|
||||
|
||||
internal fun removeDirtyField(field: AbstractField<*>) {
|
||||
dirtyFields.remove(field)
|
||||
}
|
||||
|
||||
internal fun <K, V> getMapBacklog(map: Map<K, V>): LinkedList<Pair<Any?, (DataOutputStream) -> Unit>> {
|
||||
if (unused) {
|
||||
return LinkedList()
|
||||
@ -521,10 +550,57 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
abstract inner class AbstractField<V> : IField<V> {
|
||||
val id: Int = fields.size + 1
|
||||
val id: Int
|
||||
|
||||
init {
|
||||
fields.add(this)
|
||||
if (freeSlots > 0) {
|
||||
var found = -1
|
||||
|
||||
for (i in fields.indices) {
|
||||
if (fields[i] == null) {
|
||||
fields[i] = this
|
||||
found = i + 1
|
||||
freeSlots--
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (found == -1) {
|
||||
throw RuntimeException("freeSlots = $freeSlots but no null entries in field list!")
|
||||
} else {
|
||||
id = found
|
||||
}
|
||||
} else {
|
||||
fields.add(this)
|
||||
id = fields.size
|
||||
}
|
||||
}
|
||||
|
||||
final override var isRemoved = false
|
||||
private set
|
||||
|
||||
override fun remove() {
|
||||
if (isRemoved)
|
||||
return
|
||||
|
||||
isRemoved = true
|
||||
freeSlots++
|
||||
fields[id - 1] = null
|
||||
observers.remove(this)
|
||||
|
||||
while (fields[fields.size - 1] == null) {
|
||||
fields.removeAt(fields.size - 1)
|
||||
freeSlots--
|
||||
}
|
||||
|
||||
forEachEndpoint {
|
||||
it.removeDirtyField(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun markDirty(endpoint: Endpoint) {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
endpoint.addDirtyField(this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -564,6 +640,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
if (!isDirty && !codec.compare(remote, field)) {
|
||||
notifyEndpoints(this@Field)
|
||||
isDirty = true
|
||||
@ -603,21 +680,20 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
}
|
||||
|
||||
override fun markDirty() {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
notifyEndpoints(this@Field)
|
||||
isDirty = true
|
||||
}
|
||||
|
||||
override fun markDirty(endpoint: Endpoint) {
|
||||
endpoint.addDirtyField(this)
|
||||
}
|
||||
|
||||
override fun write(stream: DataOutputStream, endpoint: Endpoint) {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
codec.write(stream, field)
|
||||
isDirty = false
|
||||
remote = codec.copy(field)
|
||||
}
|
||||
|
||||
override fun read(stream: DataInputStream) {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
val value = codec.read(stream)
|
||||
val setter = this.setter
|
||||
|
||||
@ -647,6 +723,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
if (!isDirty && (remote == null || !codec.compare(remote ?: throw ConcurrentModificationException(), value))) {
|
||||
notifyEndpoints(this)
|
||||
isDirty = true
|
||||
@ -657,24 +734,23 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
}
|
||||
|
||||
override fun markDirty() {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
notifyEndpoints(this)
|
||||
isDirty = true
|
||||
}
|
||||
|
||||
override fun markDirty(endpoint: Endpoint) {
|
||||
endpoint.addDirtyField(this)
|
||||
}
|
||||
|
||||
override val value: V
|
||||
get() = clientValue ?: getter.invoke()
|
||||
|
||||
override fun write(stream: DataOutputStream, endpoint: Endpoint) {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
val value = value
|
||||
codec.write(stream, value)
|
||||
isDirty = false
|
||||
}
|
||||
|
||||
override fun read(stream: DataInputStream) {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
val newValue = codec.read(stream)
|
||||
clientValue = newValue
|
||||
observer.invoke(newValue)
|
||||
@ -715,6 +791,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
|
||||
if (!isDirty && !codec.compare(remote, value)) {
|
||||
notifyEndpoints(this)
|
||||
isDirty = true
|
||||
@ -725,21 +803,20 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
}
|
||||
|
||||
override fun markDirty() {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
notifyEndpoints(this)
|
||||
isDirty = true
|
||||
}
|
||||
|
||||
override fun markDirty(endpoint: Endpoint) {
|
||||
endpoint.addDirtyField(this)
|
||||
}
|
||||
|
||||
override fun write(stream: DataOutputStream, endpoint: Endpoint) {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
val value = value
|
||||
codec.write(stream, value)
|
||||
isDirty = false
|
||||
}
|
||||
|
||||
override fun read(stream: DataInputStream) {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
this.value = codec.read(stream)
|
||||
}
|
||||
}
|
||||
@ -782,6 +859,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
|
||||
if (isRemote) {
|
||||
return false
|
||||
}
|
||||
@ -815,6 +894,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
}
|
||||
|
||||
override fun markDirty() {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
|
||||
if (isRemote) {
|
||||
return
|
||||
}
|
||||
@ -857,6 +938,8 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
}
|
||||
|
||||
override fun markDirty(endpoint: Endpoint) {
|
||||
check(!isRemoved) { "Field was removed" }
|
||||
|
||||
if (isRemote) {
|
||||
return
|
||||
}
|
||||
@ -1025,7 +1108,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
|
||||
*/
|
||||
fun invalidate() {
|
||||
for (field in fields) {
|
||||
field.markDirty()
|
||||
field?.markDirty()
|
||||
}
|
||||
|
||||
forEachEndpoint {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,11 @@ import net.minecraft.world.item.ItemStack
|
||||
import net.minecraftforge.network.NetworkDirection
|
||||
import net.minecraftforge.network.NetworkEvent
|
||||
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings
|
||||
import ru.dbotthepony.mc.otm.capability.matteryPlayer
|
||||
import ru.dbotthepony.mc.otm.client.minecraft
|
||||
import ru.dbotthepony.mc.otm.compat.InventoryScrollPacket
|
||||
import ru.dbotthepony.mc.otm.container.ItemFilterSlotPacket
|
||||
import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu
|
||||
import ru.dbotthepony.mc.otm.menu.matter.CancelTaskPacket
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
import ru.dbotthepony.mc.otm.menu.matter.PatternsChangePacket
|
||||
@ -22,24 +24,34 @@ import ru.dbotthepony.mc.otm.menu.data.StackRemovePacket
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.util.function.Supplier
|
||||
|
||||
class MenuFieldPacket(val bytes: ByteArray, val length: Int) : MatteryPacket {
|
||||
constructor(stream: FastByteArrayOutputStream) : this(stream.array, stream.length)
|
||||
class MenuFieldPacket(val containerId: Int, val bytes: ByteArray, val length: Int) : MatteryPacket {
|
||||
constructor(containerId: Int, stream: FastByteArrayOutputStream) : this(containerId, stream.array, stream.length)
|
||||
|
||||
override fun write(buff: FriendlyByteBuf) {
|
||||
buff.writeVarInt(containerId)
|
||||
buff.writeBytes(bytes, 0, length)
|
||||
}
|
||||
|
||||
override fun play(context: Supplier<NetworkEvent.Context>) {
|
||||
context.packetHandled = true
|
||||
context.get().enqueueWork {
|
||||
(minecraft.player?.containerMenu as? MatteryMenu)?.mSynchronizer?.read(ByteArrayInputStream(bytes, 0, length))
|
||||
|
||||
context.enqueueWork {
|
||||
if (containerId == ExoPackInventoryMenu.CONTAINER_ID) {
|
||||
minecraft.player?.matteryPlayer?.exoPackMenu?.mSynchronizer?.read(ByteArrayInputStream(bytes, 0, length))
|
||||
} else {
|
||||
val menu = minecraft.player?.containerMenu as? MatteryMenu ?: return@enqueueWork
|
||||
|
||||
if (menu.containerId == containerId)
|
||||
menu.mSynchronizer.read(ByteArrayInputStream(bytes, 0, length))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun read(buff: FriendlyByteBuf): MenuFieldPacket {
|
||||
val containerId = buff.readVarInt()
|
||||
val readable = buff.readableBytes()
|
||||
return MenuFieldPacket(ByteArray(readable).also(buff::readBytes), readable)
|
||||
return MenuFieldPacket(containerId, ByteArray(readable).also(buff::readBytes), readable)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,7 +74,7 @@ class SetCarriedPacket(val item: ItemStack) : MatteryPacket {
|
||||
}
|
||||
|
||||
object MenuNetworkChannel : MatteryNetworkChannel(
|
||||
version = "2",
|
||||
version = "3",
|
||||
name = "menu"
|
||||
) {
|
||||
fun register() {
|
||||
|
Loading…
Reference in New Issue
Block a user