Inventory filters preview

This commit is contained in:
DBotThePony 2023-03-16 07:56:48 +07:00
parent 3354c698c4
commit 30f07e2fed
Signed by: DBot
GPG Key ID: DCC23B5715498507
14 changed files with 615 additions and 263 deletions

View File

@ -0,0 +1,49 @@
package ru.dbotthepony.mc.otm.mixin;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
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.minecraftforge.registries.ForgeRegistries;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import ru.dbotthepony.mc.otm.capability.MatteryCapability;
@Mixin(Inventory.class)
public class MixinInventory {
@Shadow Player player;
@Inject(
method = "add(ILnet/minecraft/world/item/ItemStack;)Z",
at = @At("HEAD"),
cancellable = true
)
private void add(int pSlot, ItemStack pStack, CallbackInfoReturnable<Boolean> hook) {
if (pStack.isEmpty()) {
return;
}
if (pSlot == -1) {
this.player.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresent(it -> {
try {
hook.setReturnValue(it.inventoryAddImpl(pStack));
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.forThrowable(throwable, "Adding item to inventory (Overdrive That Matters detour)");
CrashReportCategory crashreportcategory = crashreport.addCategory("Item being added");
crashreportcategory.setDetail("Registry Name", () -> String.valueOf(ForgeRegistries.ITEMS.getKey(pStack.getItem())));
crashreportcategory.setDetail("Item Class", () -> pStack.getItem().getClass().getName());
crashreportcategory.setDetail("Item ID", Item.getId(pStack.getItem()));
crashreportcategory.setDetail("Item data", pStack.getDamageValue());
crashreportcategory.setDetail("Item name", () -> pStack.getHoverName().getString());
throw new ReportedException(crashreport);
}
});
}
}
}

View File

@ -1,10 +1,12 @@
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
@ -18,7 +20,9 @@ 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.ProjectileWeaponItem import net.minecraft.world.item.ProjectileWeaponItem
import net.minecraft.world.item.enchantment.EnchantmentHelper.hasVanishingCurse import net.minecraft.world.item.enchantment.EnchantmentHelper.hasVanishingCurse
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
@ -58,11 +62,15 @@ import ru.dbotthepony.mc.otm.core.collect.nonEmpty
import ru.dbotthepony.mc.otm.core.math.Decimal 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.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.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.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
@ -108,6 +116,19 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
val level: Level get() = capability.ply.level val level: Level get() = capability.ply.level
} }
/**
* This event is fired on main event bus when [Inventory.add] was called and entire [ItemStack] can not be stored
* (both in [Inventory] and [MatteryPlayerCapability] exopack due to inventory filters or no free space).
*/
data class ItemStackLeftoverEvent(val stack: ItemStack, val capability: MatteryPlayerCapability) : Event() {
override fun isCancelable() = false
override fun hasResult() = false
override fun setResult(value: Result) {}
val player get() = capability.ply
val level: Level get() = capability.ply.level
}
private inner class PlayerMatteryContainer(size: Int) : MatteryContainer(size) { private inner class PlayerMatteryContainer(size: Int) : MatteryContainer(size) {
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)
@ -181,6 +202,16 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
}, backingMap = this.exoPackSlotModifierMap) }, backingMap = this.exoPackSlotModifierMap)
val regularSlotFilters = immutableList(Inventory.INVENTORY_SIZE) {
synchronizer.Field(null, ItemValueCodec.nullable)
}
val exoPackSlotFilters by synchronizer.Map(
keyCodec = VarIntValueCodec,
valueCodec = ItemValueCodec,
backingMap = Int2ObjectOpenHashMap()
)
/** /**
* Current slot count of Exopack * Current slot count of Exopack
* *
@ -291,15 +322,6 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
private var wasInLiquid = false private var wasInLiquid = false
private var lastDimension = ResourceLocation("overworld") private var lastDimension = ResourceLocation("overworld")
init {
savetables.int(::ticksIExist)
savetables.int(::iteration)
savetables.bool(::shouldSendIteration)
savetables.bool(::wasInLiquid)
savetables.vector(::lastOutsideLiquid)
savetables.location(::lastDimension)
}
/** /**
* Whenever player should become an Android once transformation conditions are met (e.g. player dies or sleeps in bed) * Whenever player should become an Android once transformation conditions are met (e.g. player dies or sleeps in bed)
*/ */
@ -324,6 +346,26 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
*/ */
val androidEnergy = AndroidPowerSource(ply, synchronizer, AndroidConfig.ANDROID_MAX_ENERGY, AndroidConfig.ANDROID_MAX_ENERGY) val androidEnergy = AndroidPowerSource(ply, synchronizer, AndroidConfig.ANDROID_MAX_ENERGY, AndroidConfig.ANDROID_MAX_ENERGY)
init {
savetables.int(::ticksIExist)
savetables.int(::iteration)
savetables.bool(::shouldSendIteration)
savetables.bool(::wasInLiquid)
savetables.bool(::isAndroid)
savetables.bool(::willBecomeAndroid)
savetables.bool(::hasExoPack, "hasExoSuit")
savetables.bool(::displayExoPack, "displayExoSuit")
savetables.bool(::isExoPackCraftingUpgraded, "isExoSuitCraftingUpgraded")
savetables.vector(::lastOutsideLiquid)
savetables.location(::lastDimension)
savetables.stateful(::exoPackSlotModifier, "exoSuitSlotCountModifiers")
savetables.stateful(::exoPackContainer, "exoSuitContainer")
savetables.stateful(::androidEnergy)
}
fun invalidateNetworkState() { fun invalidateNetworkState() {
synchronizer.invalidate() synchronizer.invalidate()
publicSynchronizer.invalidate() publicSynchronizer.invalidate()
@ -641,36 +683,36 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
} }
// exosuit tag["features"] = ListTag().also {
tag["hasExoSuit"] = hasExoPack
tag["displayExoSuit"] = displayExoPack
tag["exoSuitContainer"] = exoPackContainer.serializeNBT()
tag["isExoSuitCraftingUpgraded"] = isExoPackCraftingUpgraded
tag["exoSuitSlotCountModifiers"] = exoPackSlotModifier.serializeNBT()
// android
tag["androidEnergy"] = androidEnergy.serializeNBT()
tag["isAndroid"] = isAndroid
tag["willBecomeAndroid"] = willBecomeAndroid
val featureList = ListTag()
val researchList = ListTag()
for (feature in featureMap.values) { for (feature in featureMap.values) {
featureList.add(feature.serializeNBT().also { it.add(feature.serializeNBT().also {
it["id"] = feature.type.registryName!!.toString() it["id"] = feature.type.registryName!!.toString()
}) })
} }
}
tag["research"] = ListTag().also {
for ((type, instance) in research) { for ((type, instance) in research) {
researchList.add(instance.serializeNBT().also { it.add(instance.serializeNBT().also {
it["id"] = type.id.toString() it["id"] = type.id.toString()
}) })
} }
}
tag["features"] = featureList tag["exoPackSlotFilters"] = ListTag().also {
tag["research"] = researchList 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() ?: ""))
}
}
return tag return tag
} }
@ -678,6 +720,29 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
override fun deserializeNBT(tag: CompoundTag) { override fun deserializeNBT(tag: CompoundTag) {
savetables.deserializeNBT(tag) savetables.deserializeNBT(tag)
for (filter in regularSlotFilters) {
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)) {
val path = regularSlotFilters[i].asString
if (path == "") continue
this.regularSlotFilters[i].value = ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(path) ?: continue) ?: Items.AIR
}
// iterations // iterations
deathLog.clear() deathLog.clear()
@ -689,20 +754,6 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
} }
// exosuit
hasExoPack = tag.getBoolean("hasExoSuit")
displayExoPack = tag.getBoolean("displayExoSuit")
tag.map("exoSuitSlotCountModifiers", exoPackSlotModifier::deserializeNBT)
exoPackContainer.deserializeNBT(tag["exoSuitContainer"])
isExoPackCraftingUpgraded = tag.getBoolean("isExoSuitCraftingUpgraded")
// android
isAndroid = tag.getBoolean("isAndroid")
willBecomeAndroid = tag.getBoolean("willBecomeAndroid")
tag.map("androidEnergy", androidEnergy::deserializeNBT)
featureMap.clear() featureMap.clear()
research.clear() research.clear()
@ -977,8 +1028,141 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} else LazyOptional.empty() } else LazyOptional.empty()
} }
/**
* This re-implement [Inventory.add] logic (original method is redirected to this)
*/
fun inventoryAddImpl(stack: ItemStack): Boolean {
val items = ply.inventory.items
val exoPackContainer = exoPackContainer
if (!stack.isStackable) {
for (i in items.indices) {
if (items[i].isEmpty && (regularSlotFilters[i].value === null || regularSlotFilters[i].value === stack.item)) {
items[i] = stack.copy()
items[i].popTime = 5
stack.count = 0
return true
}
}
for (i in 0 until exoPackContainer.containerSize) {
if (exoPackContainer[i].isEmpty && (exoPackSlotFilters[i] === null || exoPackSlotFilters[i] === stack.item)) {
exoPackContainer[i] = stack.copy()
exoPackContainer[i].popTime = 5
stack.count = 0
return true
}
}
MinecraftForge.EVENT_BUS.post(ItemStackLeftoverEvent(stack, this))
if (ply.abilities.instabuild) {
stack.count = 0
}
return !stack.isEmpty
}
// двигаем в существующие слоты
items[ply.inventory.selected].also { stackStacks(it, stack) }
if (stack.isEmpty) return true
ply.inventory.offhand[0].also { stackStacks(it, stack) }
if (stack.isEmpty) return true
for (i in items.indices) {
if (stackStacks(items[i], stack) && stack.isEmpty) {
return true
}
}
for (i in 0 until exoPackContainer.containerSize) {
if (stackStacks(exoPackContainer[i], stack)) {
exoPackContainer.setChanged(i)
if (stack.isEmpty) {
return true
}
}
}
// двигаем в пустые слоты
// двигаем в отфильтрованные слоты
for (i in items.indices) {
if (items[i].isEmpty && regularSlotFilters[i].value === stack.item) {
items[i] = stack.split(stack.count.coerceAtMost(stack.maxStackSize))
items[i].popTime = 5
if (stack.isEmpty) {
return true
}
}
}
for (i in 0 until exoPackContainer.containerSize) {
if (exoPackContainer[i].isEmpty && exoPackSlotFilters[i] === stack.item) {
exoPackContainer[i] = stack.split(stack.count.coerceAtMost(stack.maxStackSize))
exoPackContainer[i].popTime = 5
if (stack.isEmpty) {
return true
}
}
}
// двигаем в обычные слоты
for (i in items.indices) {
if (items[i].isEmpty && regularSlotFilters[i].value === null) {
items[i] = stack.split(stack.count.coerceAtMost(stack.maxStackSize))
items[i].popTime = 5
if (stack.isEmpty) {
return true
}
}
}
for (i in 0 until exoPackContainer.containerSize) {
if (exoPackContainer[i].isEmpty && exoPackSlotFilters[i] === null) {
exoPackContainer[i] = stack.split(stack.count.coerceAtMost(stack.maxStackSize))
exoPackContainer[i].popTime = 5
if (stack.isEmpty) {
return true
}
}
}
MinecraftForge.EVENT_BUS.post(ItemStackLeftoverEvent(stack, this))
if (ply.abilities.instabuild) {
stack.count = 0
}
return !stack.isEmpty
}
@Suppress("unused") @Suppress("unused")
companion object { companion object {
private fun stackStacks(it: ItemStack, stack: ItemStack): Boolean {
if (
!it.isEmpty &&
it.maxStackSize > it.count &&
it.isStackable &&
ItemStack.isSameItemSameTags(it, stack)
) {
val new = (it.count + stack.count).coerceAtMost(it.maxStackSize)
val diff = new - it.count
it.count = new
stack.count -= diff
it.popTime = 5
return true
}
return false
}
init { init {
WitherBoss.TARGETING_CONDITIONS.selector(WitherBoss.TARGETING_CONDITIONS.selector!!.and { it.matteryPlayer?.isAndroid != true }) WitherBoss.TARGETING_CONDITIONS.selector(WitherBoss.TARGETING_CONDITIONS.selector!!.and { it.matteryPlayer?.isAndroid != true })
WitherBoss.LIVING_ENTITY_SELECTOR = WitherBoss.LIVING_ENTITY_SELECTOR.and { it.matteryPlayer?.isAndroid != true } WitherBoss.LIVING_ENTITY_SELECTOR = WitherBoss.LIVING_ENTITY_SELECTOR.and { it.matteryPlayer?.isAndroid != true }

View File

@ -23,7 +23,7 @@ class AndroidPowerSource(
synchronizer: FieldSynchronizer, synchronizer: FieldSynchronizer,
initialCharge: Decimal, initialCharge: Decimal,
maxCharge: Decimal maxCharge: Decimal
) : IMatteryEnergyStorage, INBTSerializable<CompoundTag> { ) : IMatteryEnergyStorage, INBTSerializable<CompoundTag?> {
override val energyFlow: FlowDirection override val energyFlow: FlowDirection
get() = FlowDirection.INPUT get() = FlowDirection.INPUT
@ -46,7 +46,8 @@ class AndroidPowerSource(
} }
} }
override fun deserializeNBT(tag: CompoundTag) { override fun deserializeNBT(tag: CompoundTag?) {
tag ?: return
battery = tag.getDecimal("battery") battery = tag.getDecimal("battery")
maxBattery = tag.getDecimal("maxBattery") maxBattery = tag.getDecimal("maxBattery")
item = tag.getItemStack("item") item = tag.getItemStack("item")

View File

@ -10,8 +10,8 @@ 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.SlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.BackgroundPanel
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
import ru.dbotthepony.mc.otm.client.shouldOpenVanillaInventory import ru.dbotthepony.mc.otm.client.shouldOpenVanillaInventory
@ -48,10 +48,10 @@ class ExoPackInventoryScreen(menu: ExoPackInventoryMenu) : MatteryScreen<ExoPack
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> { override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
val frame = FramePanel(this, width = 200f, height = FRAME_BASE_HEIGHT + inventoryRows * AbstractSlotPanel.SIZE, title = this.title) val frame = FramePanel(this, width = 200f, height = FRAME_BASE_HEIGHT + inventoryRows * AbstractSlotPanel.SIZE, title = this.title)
val toolbeltLine = EditablePanel(this, frame, height = 18f) val hotbarStrip = EditablePanel(this, frame, height = 18f)
toolbeltLine.dock = Dock.BOTTOM hotbarStrip.dock = Dock.BOTTOM
toolbeltLine.setDockMargin(top = 3f) hotbarStrip.setDockMargin(top = 3f)
scrollPanel = DiscreteScrollBarPanel(this, null, maxScroll = { integerDivisionDown(menu.playerCombinedInventorySlots.size, 9) }, scrollPanel = DiscreteScrollBarPanel(this, null, maxScroll = { integerDivisionDown(menu.playerCombinedInventorySlots.size, 9) },
scrollCallback = { scrollCallback = {
@ -83,11 +83,12 @@ class ExoPackInventoryScreen(menu: ExoPackInventoryMenu) : MatteryScreen<ExoPack
mainInventoryLine.dock = Dock.BOTTOM mainInventoryLine.dock = Dock.BOTTOM
for (slot in menu.playerHotbarSlots) { for (slot in menu.playerHotbarSlots) {
val panel = SlotPanel(this, toolbeltLine, slot) FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also {
panel.dock = Dock.LEFT it.dock = Dock.LEFT
}
} }
val offhand = SlotPanel(this, toolbeltLine, menu.offhandSlot) val offhand = SlotPanel(this, hotbarStrip, menu.offhandSlot)
offhand.dock = Dock.RIGHT offhand.dock = Dock.RIGHT
for (i in scrollPanel.scroll until scrollPanel.scroll + inventoryRows) { for (i in scrollPanel.scroll until scrollPanel.scroll + inventoryRows) {

View File

@ -11,25 +11,31 @@ import net.minecraft.client.renderer.entity.ItemRenderer
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
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 net.minecraftforge.client.event.ContainerScreenEvent.Render.Background import net.minecraftforge.client.event.ContainerScreenEvent.Render.Background
import net.minecraftforge.client.event.ContainerScreenEvent.Render.Foreground 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.FoldableSlotPanel 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.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.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.menu.PlayerSlot
import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
import ru.dbotthepony.mc.otm.network.SetInventoryFilterPacket
import java.util.Collections import java.util.Collections
/** /**
@ -163,7 +169,9 @@ 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) {
SlotPanel(this, hotbarStrip, slot).also { it.dock = Dock.LEFT } FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also {
it.dock = Dock.LEFT
}
} }
val canvas = EditablePanel(this, inventoryFrame) val canvas = EditablePanel(this, inventoryFrame)
@ -211,7 +219,9 @@ 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) {
SlotPanel(this, hotbarStrip, slot).also { it.dock = Dock.LEFT } FilteredSlotPanel.of(this, hotbarStrip, slot, filter = slot.filter!!).also {
it.dock = Dock.LEFT
}
} }
inventoryScrollbar.parent = slotListCanvas inventoryScrollbar.parent = slotListCanvas
@ -250,13 +260,19 @@ 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 = object : SlotPanel<MatteryScreen<*>, Slot>(this@MatteryScreen, canvas, menu.playerCombinedInventorySlots[offset + i]) { val slot = menu.playerCombinedInventorySlots[offset + i]
object : FilteredSlotPanel<MatteryScreen<*>, Slot>(this@MatteryScreen, canvas, slot) {
init {
dock = Dock.LEFT
}
override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean {
return false return false
} }
}
slot.dock = Dock.LEFT override var slotFilter: Item? by slot.filter!!
}
} }
return@Int2ObjectFunction canvas return@Int2ObjectFunction canvas

View File

@ -59,7 +59,7 @@ abstract class AbstractSlotPanel<out S : MatteryScreen<*>> @JvmOverloads constru
if (!itemstack.isEmpty) { if (!itemstack.isEmpty) {
// val font = RenderProperties.get(itemstack).getFont(itemstack) // val font = RenderProperties.get(itemstack).getFont(itemstack)
val font = (itemstack.item.renderPropertiesInternal as? IClientItemExtensions)?.getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) val font = IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP)
// TODO: WHERE???????????? // TODO: WHERE????????????
// GuiUtils.preItemToolTip(itemstack); // GuiUtils.preItemToolTip(itemstack);
@ -84,5 +84,6 @@ abstract class AbstractSlotPanel<out S : MatteryScreen<*>> @JvmOverloads constru
val SLOT_HIGHLIGHT_DRAG = RGBAColor(200, 200, 200, 150) val SLOT_HIGHLIGHT_DRAG = RGBAColor(200, 200, 200, 150)
const val SIZE = 18f const val SIZE = 18f
val SLOT_BACKGROUND = WidgetLocation.MISC.sprite(0f, 0f, SIZE, SIZE) val SLOT_BACKGROUND = WidgetLocation.MISC.sprite(0f, 0f, SIZE, SIZE)
val FILTERED_SLOT_BACKGROUND = WidgetLocation.MISC.sprite(46f, 0f, SIZE, SIZE)
} }
} }

View File

@ -0,0 +1,113 @@
package ru.dbotthepony.mc.otm.client.screen.panels.slot
import com.mojang.blaze3d.platform.InputConstants
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraftforge.client.extensions.common.IClientItemExtensions
import ru.dbotthepony.mc.otm.client.isCtrlDown
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.playGuiClickSound
import ru.dbotthepony.mc.otm.client.render.MatterySprite
import ru.dbotthepony.mc.otm.client.render.drawColor
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.math.RGBAColor
abstract class FilteredSlotPanel<out S : MatteryScreen<*>, out T : Slot>(
screen: S,
parent: EditablePanel<*>?,
slot: T,
x: Float = 0f,
y: Float = 0f,
width: Float = SIZE,
height: Float = SIZE,
noItemIcon: MatterySprite? = null
) : SlotPanel<S, T>(screen, parent, slot, x, y, width, height, noItemIcon) {
abstract var slotFilter: Item?
override fun renderSlotBackground(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
super.renderSlotBackground(stack, mouseX, mouseY, partialTick)
if (slotFilter != null) {
if (slotFilter !== Items.AIR) {
val itemStack = ItemStack(slotFilter!!, 1)
screen.renderItemStack(absoluteX, absoluteY, itemStack, null)
clearDepth(stack)
}
drawColor = SLOT_FILTER_COLOR
drawRect(stack, 0f, 0f, width, height)
}
}
override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean {
if (isHovered && slotFilter != null && slotFilter !== Items.AIR && itemStack.isEmpty) {
val itemstack = ItemStack(slotFilter!!, 1)
screen.renderComponentTooltip(
stack,
getItemStackTooltip(itemstack),
mouseX.toInt(),
mouseY.toInt(),
IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: screen.font,
itemstack
)
}
return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick)
}
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.window.isCtrlDown) {
if (slotFilter === null) {
if (screen.menu.carried.isEmpty) {
slotFilter = slot.item.item
} else {
slotFilter = screen.menu.carried.item
}
} else {
slotFilter = null
}
playGuiClickSound()
return true
} else {
return super.mouseClickedInner(x, y, button)
}
}
override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean {
if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.window.isCtrlDown) {
return true
}
return super.mouseReleasedInner(x, y, button)
}
companion object {
val SLOT_FILTER_COLOR = RGBAColor(85, 113, 216, 150)
fun <S : MatteryScreen<*>, T : Slot> of(
screen: S,
parent: EditablePanel<*>?,
slot: T,
x: Float = 0f,
y: Float = 0f,
width: Float = SIZE,
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) {
override var slotFilter: Item? by filter
}
}
}
}

View File

@ -11,6 +11,7 @@ import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtAccounter
import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.ComponentContents import net.minecraft.network.chat.ComponentContents
import net.minecraft.network.chat.contents.TranslatableContents import net.minecraft.network.chat.contents.TranslatableContents
@ -18,6 +19,7 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.Entity import net.minecraft.world.entity.Entity
import net.minecraft.world.item.Item 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.crafting.Ingredient import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
@ -32,7 +34,11 @@ import net.minecraftforge.registries.ForgeRegistry
import net.minecraftforge.registries.IForgeRegistry import net.minecraftforge.registries.IForgeRegistry
import ru.dbotthepony.mc.otm.core.math.Vector import ru.dbotthepony.mc.otm.core.math.Vector
import ru.dbotthepony.mc.otm.core.util.readInt import ru.dbotthepony.mc.otm.core.util.readInt
import ru.dbotthepony.mc.otm.core.util.readVarIntLE
import ru.dbotthepony.mc.otm.core.util.writeInt
import ru.dbotthepony.mc.otm.core.util.writeVarIntLE
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream
import java.lang.ref.Reference import java.lang.ref.Reference
import java.math.BigInteger import java.math.BigInteger
import java.util.Arrays import java.util.Arrays
@ -167,12 +173,16 @@ fun FriendlyByteBuf.writeItemType(value: Item) {
writeInt(ForgeRegistries.ITEMS.getID(value)) writeInt(ForgeRegistries.ITEMS.getID(value))
} }
fun FriendlyByteBuf.readItemType(): Item? { fun OutputStream.writeItemType(value: Item) {
return ForgeRegistries.ITEMS.getValue(readInt()) writeVarIntLE(ForgeRegistries.ITEMS.getID(value))
} }
fun InputStream.readItemType(): Item? { fun FriendlyByteBuf.readItemType(): Item {
return ForgeRegistries.ITEMS.getValue(readInt()) return ForgeRegistries.ITEMS.getValue(readInt()) ?: Items.AIR
}
fun InputStream.readItemType(sizeLimit: NbtAccounter? = null): Item {
return ForgeRegistries.ITEMS.getValue(readVarIntLE(sizeLimit)) ?: Items.AIR
} }
operator fun <T : Comparable<T>> StateHolder<*, *>.get(property: Property<T>): T { operator fun <T : Comparable<T>> StateHolder<*, *>.get(property: Property<T>): T {

View File

@ -10,6 +10,8 @@ import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.core.immutableMap import ru.dbotthepony.mc.otm.core.immutableMap
import ru.dbotthepony.mc.otm.core.math.readDecimal import ru.dbotthepony.mc.otm.core.math.readDecimal
import ru.dbotthepony.mc.otm.core.math.writeDecimal import ru.dbotthepony.mc.otm.core.math.writeDecimal
import ru.dbotthepony.mc.otm.core.readItemType
import ru.dbotthepony.mc.otm.core.writeItemType
import java.io.DataInput import java.io.DataInput
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutput import java.io.DataOutput
@ -60,6 +62,30 @@ class StreamCodec<V>(
comparator: ((a: V, b: V) -> Boolean) = { a, b -> a == b } comparator: ((a: V, b: V) -> Boolean) = { a, b -> a == b }
) : this({ stream, sizeLimit -> sizeLimit.accountBytes(payloadSize); reader.invoke(stream) }, writer, copier, comparator) ) : this({ stream, sizeLimit -> sizeLimit.accountBytes(payloadSize); reader.invoke(stream) }, writer, copier, comparator)
val nullable = object : IStreamCodec<V?> {
override fun read(stream: DataInputStream, sizeLimit: NbtAccounter): V? {
sizeLimit.accountBytes(1L)
return if (stream.read() == 0) null else reader.invoke(stream, sizeLimit)
}
override fun write(stream: DataOutputStream, value: V?) {
if (value === null) stream.write(0) else {
stream.write(1)
writer.invoke(stream, value)
}
}
override fun copy(value: V?): V? {
return if (value === null) null else copier.invoke(value)
}
override fun compare(a: V?, b: V?): Boolean {
if (a === null && b === null) return true
if (a === null || b === null) return false
return comparator.invoke(a, b)
}
}
override fun read(stream: DataInputStream, sizeLimit: NbtAccounter): V { override fun read(stream: DataInputStream, sizeLimit: NbtAccounter): V {
return reader.invoke(stream, sizeLimit) return reader.invoke(stream, sizeLimit)
} }
@ -86,6 +112,7 @@ val LongValueCodec = StreamCodec(DataInputStream::readLong, 8L, DataOutputStream
val FloatValueCodec = StreamCodec(DataInputStream::readFloat, 4L, DataOutputStream::writeFloat) val FloatValueCodec = StreamCodec(DataInputStream::readFloat, 4L, DataOutputStream::writeFloat)
val DoubleValueCodec = StreamCodec(DataInputStream::readDouble, 8L, DataOutputStream::writeDouble) val DoubleValueCodec = StreamCodec(DataInputStream::readDouble, 8L, DataOutputStream::writeDouble)
val ItemStackValueCodec = StreamCodec(DataInputStream::readItem, DataOutputStream::writeItem, ItemStack::copy) { a, b -> a.equals(b, true) } val ItemStackValueCodec = StreamCodec(DataInputStream::readItem, DataOutputStream::writeItem, ItemStack::copy) { a, b -> a.equals(b, true) }
val ItemValueCodec = StreamCodec(DataInputStream::readItemType, DataOutputStream::writeItemType)
val DecimalValueCodec = StreamCodec(DataInputStream::readDecimal, DataOutputStream::writeDecimal) val DecimalValueCodec = StreamCodec(DataInputStream::readDecimal, DataOutputStream::writeDecimal)
val BigDecimalValueCodec = StreamCodec(DataInputStream::readBigDecimal, DataOutputStream::writeBigDecimal) val BigDecimalValueCodec = StreamCodec(DataInputStream::readBigDecimal, DataOutputStream::writeBigDecimal)
val UUIDValueCodec = StreamCodec({ s, a -> a.accountBytes(8L); UUID(s.readLong(), s.readLong()) }, { s, v -> s.writeLong(v.mostSignificantBits); s.writeLong(v.leastSignificantBits) }) val UUIDValueCodec = StreamCodec({ s, a -> a.accountBytes(8L); UUID(s.readLong(), s.readLong()) }, { s, v -> s.writeLong(v.mostSignificantBits); s.writeLong(v.leastSignificantBits) })
@ -331,7 +358,7 @@ fun OutputStream.writeBinaryString(input: String) {
private data class IndexedStreamCodec<T>( private data class IndexedStreamCodec<T>(
val condition: Predicate<Any?>, val condition: Predicate<Any?>,
val id: Int, val id: Int,
val codec: StreamCodec<T> val codec: IStreamCodec<T>
) { ) {
fun read(stream: DataInputStream, sizeLimit: NbtAccounter = NbtAccounter(1L shl 18 /* 256 KiB */)): T { fun read(stream: DataInputStream, sizeLimit: NbtAccounter = NbtAccounter(1L shl 18 /* 256 KiB */)): T {
return codec.read(stream, sizeLimit) return codec.read(stream, sizeLimit)

View File

@ -14,6 +14,7 @@ import net.minecraft.world.Container
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.inventory.* import net.minecraft.world.inventory.*
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.enchantment.EnchantmentHelper.hasBindingCurse import net.minecraft.world.item.enchantment.EnchantmentHelper.hasBindingCurse
import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntity
@ -26,6 +27,7 @@ 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.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
@ -35,8 +37,10 @@ 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.MatteryPacket import ru.dbotthepony.mc.otm.network.MatteryPacket
import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel
import ru.dbotthepony.mc.otm.network.MenuFieldPacket import ru.dbotthepony.mc.otm.network.MenuFieldPacket
import ru.dbotthepony.mc.otm.network.MenuNetworkChannel import ru.dbotthepony.mc.otm.network.MenuNetworkChannel
import ru.dbotthepony.mc.otm.network.SetInventoryFilterPacket
import ru.dbotthepony.mc.otm.network.enqueueWork import ru.dbotthepony.mc.otm.network.enqueueWork
import ru.dbotthepony.mc.otm.network.packetHandled import ru.dbotthepony.mc.otm.network.packetHandled
import ru.dbotthepony.mc.otm.network.sender import ru.dbotthepony.mc.otm.network.sender
@ -70,12 +74,12 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
private val _matteryWidgets = ArrayList<AbstractWidget>() private val _matteryWidgets = ArrayList<AbstractWidget>()
val matteryWidgets: List<AbstractWidget> = Collections.unmodifiableList(_matteryWidgets) val matteryWidgets: List<AbstractWidget> = Collections.unmodifiableList(_matteryWidgets)
private val _playerInventorySlots = ArrayList<MatterySlot>() private val _playerInventorySlots = ArrayList<InventorySlot>()
private val _playerInventorySlots2 = ArrayList<MatterySlot>() private val _playerInventorySlots2 = ArrayList<InventorySlot>()
private val _playerHotbarSlots = ArrayList<MatterySlot>() private val _playerHotbarSlots = ArrayList<InventorySlot>()
private val _playerMainSlots = ArrayList<MatterySlot>() private val _playerMainSlots = ArrayList<InventorySlot>()
private val _playerExoSuitSlots = ArrayList<MatterySlot>() private val _playerExoSuitSlots = ArrayList<InventorySlot>()
private val _playerCombinedInventorySlots = ArrayList<MatterySlot>() private val _playerCombinedInventorySlots = ArrayList<InventorySlot>()
private val playerInputs = ArrayList<PlayerInput<*>>() private val playerInputs = ArrayList<PlayerInput<*>>()
@ -155,32 +159,32 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
/** /**
* inventory + exosuit + hotbar (in this order) * inventory + exosuit + hotbar (in this order)
*/ */
val playerInventorySlots: List<MatterySlot> = Collections.unmodifiableList(_playerInventorySlots) val playerInventorySlots: List<InventorySlot> = Collections.unmodifiableList(_playerInventorySlots)
/** /**
* hotbar + inventory + exosuit (in this order) * hotbar + inventory + exosuit (in this order)
*/ */
val playerInventorySlots2: List<MatterySlot> = Collections.unmodifiableList(_playerInventorySlots2) val playerInventorySlots2: List<InventorySlot> = Collections.unmodifiableList(_playerInventorySlots2)
/** /**
* hotbar only * hotbar only
*/ */
val playerHotbarSlots: List<MatterySlot> = Collections.unmodifiableList(_playerHotbarSlots) val playerHotbarSlots: List<InventorySlot> = Collections.unmodifiableList(_playerHotbarSlots)
/** /**
* inventory only * inventory only
*/ */
val playerMainSlots: List<MatterySlot> = Collections.unmodifiableList(_playerMainSlots) val playerMainSlots: List<InventorySlot> = Collections.unmodifiableList(_playerMainSlots)
/** /**
* exosuit only * exosuit only
*/ */
val playerExoSuitSlots: List<MatterySlot> = Collections.unmodifiableList(_playerExoSuitSlots) val playerExoSuitSlots: List<InventorySlot> = Collections.unmodifiableList(_playerExoSuitSlots)
/** /**
* inventory + exosuit (in this order) * inventory + exosuit (in this order)
*/ */
val playerCombinedInventorySlots: List<MatterySlot> = Collections.unmodifiableList(_playerCombinedInventorySlots) val playerCombinedInventorySlots: List<InventorySlot> = Collections.unmodifiableList(_playerCombinedInventorySlots)
var autoCreateInventoryFrame = true var autoCreateInventoryFrame = true
private set private set
@ -250,6 +254,32 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return super.isSameInventory(other) return super.isSameInventory(other)
} }
// фильтр существует только для автоматизации
// игрок всё равно может класть предмет вручную, даже если он не соответствует фильтру
internal val filter: GetterSetter<Item?>?
init {
val mattery = ply.matteryPlayer
if (mattery != null) {
if (container === inventory && slotIndex in mattery.regularSlotFilters.indices) {
filter = GetterSetter.of(
getter = { mattery.regularSlotFilters[slotIndex].value },
setter = { MatteryPlayerNetworkChannel.sendToServer(SetInventoryFilterPacket(SetInventoryFilterPacket.Type.INVENTORY, slotIndex, it)) }
)
} else if (container === mattery.exoPackContainer) {
filter = GetterSetter.of(
getter = { mattery.exoPackSlotFilters[slotIndex] },
setter = { MatteryPlayerNetworkChannel.sendToServer(SetInventoryFilterPacket(SetInventoryFilterPacket.Type.EXOPACK, slotIndex, it)) }
)
} else {
filter = null
}
} else {
filter = null
}
}
} }
open inner class EquipmentSlot(container: Container, index: Int, val type: net.minecraft.world.entity.EquipmentSlot, x: Int = 0, y: Int = 0) : InventorySlot(container, index, x, y) { open inner class EquipmentSlot(container: Container, index: Int, val type: net.minecraft.world.entity.EquipmentSlot, x: Int = 0, y: Int = 0) : InventorySlot(container, index, x, y) {
@ -458,6 +488,18 @@ 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 } }) {
for (collection in target) {
if (moveItemStackTo(slot, collection, onlyFiltered = true)) {
any = true
if (!slot.hasItem()) {
return copy
}
}
}
}
for (collection in target) { for (collection in target) {
if (moveItemStackTo(slot, collection)) { if (moveItemStackTo(slot, collection)) {
any = true any = true
@ -506,25 +548,12 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return true return true
} }
fun moveItemStackTo(
item: ItemStack,
slots: Collection<Slot>
): Boolean {
val remainder = moveItemStackToSlots(item, slots)
if (remainder.count == item.count) {
return false
}
item.count = remainder.count
return true
}
fun moveItemStackTo( fun moveItemStackTo(
source: Slot, source: Slot,
slots: Collection<Slot> slots: Collection<Slot>,
onlyFiltered: Boolean = false
): Boolean { ): Boolean {
val remainder = moveItemStackToSlots(source.item, slots) val remainder = moveItemStackToSlots(source.item, slots, onlyFiltered = onlyFiltered)
if (remainder.count == source.item.count) { if (remainder.count == source.item.count) {
return false return false
@ -540,15 +569,25 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return true return true
} }
fun moveItemStackToSlots(item: ItemStack, slots: Collection<Slot>, simulate: Boolean = false): ItemStack { fun moveItemStackToSlots(item: ItemStack, slots: Collection<Slot>, simulate: Boolean = false, onlyFiltered: Boolean = false): ItemStack {
if (item.isEmpty) {
return ItemStack.EMPTY
}
val copy = item.copy() val copy = item.copy()
// 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)) {
continue
} else if (!onlyFiltered && slot is InventorySlot && slot.filter != null && slot.filter.get() != null && slot.filter.get() != item.item) {
continue
}
val limit = slot.getMaxStackSize(copy) val limit = slot.getMaxStackSize(copy)
if (limit > slot.item.count && ItemStack.isSameItemSameTags(slot.item, copy) && slot.mayPlace(item)) { if (limit > slot.item.count && slot.mayPlace(item) && ItemStack.isSameItemSameTags(slot.item, copy)) {
val newCount = (slot.item.count + copy.count).coerceAtMost(limit) val newCount = (slot.item.count + copy.count).coerceAtMost(limit)
val diff = newCount - slot.item.count val diff = newCount - slot.item.count
copy.count -= diff copy.count -= diff
@ -567,6 +606,12 @@ 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)) {
continue
} else if (!onlyFiltered && slot is InventorySlot && slot.filter != null && slot.filter.get() != null && slot.filter.get() != item.item) {
continue
}
val limit = slot.getMaxStackSize(copy) val limit = slot.getMaxStackSize(copy)
if (!slot.hasItem() && slot.mayPlace(item)) { if (!slot.hasItem() && slot.mayPlace(item)) {
@ -597,9 +642,19 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
require(finalSlot < slots.size) { "Final slot $finalSlot is bigger than total size of array of ${slots.size}" } require(finalSlot < slots.size) { "Final slot $finalSlot is bigger than total size of array of ${slots.size}" }
val slots = ArrayList<Slot>(finalSlot - initialSlot + 1) val slots = ArrayList<Slot>(finalSlot - initialSlot + 1)
var filters = false
for (i in (if (inverse) finalSlot downTo initialSlot else initialSlot .. finalSlot)) { for (i in (if (inverse) finalSlot downTo initialSlot else initialSlot .. finalSlot)) {
slots.add(slots[i]) val slot = slots[i]
slots.add(slot)
if (slot is InventorySlot && slot.filter != null) {
filters = true
}
}
if (filters) {
return moveItemStackToSlots(moveItemStackToSlots(item, slots, simulate, onlyFiltered = true), slots, simulate, onlyFiltered = false)
} }
return moveItemStackToSlots(item, slots, simulate) return moveItemStackToSlots(item, slots, simulate)

View File

@ -6,7 +6,9 @@ import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.game.ClientboundSetCarriedItemPacket import net.minecraft.network.protocol.game.ClientboundSetCarriedItemPacket
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
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.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT import net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT
import net.minecraftforge.network.NetworkDirection.PLAY_TO_SERVER import net.minecraftforge.network.NetworkDirection.PLAY_TO_SERVER
import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.NetworkEvent
@ -27,6 +29,8 @@ import ru.dbotthepony.mc.otm.client.render.ShockwaveRenderer
import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.container.set import ru.dbotthepony.mc.otm.container.set
import ru.dbotthepony.mc.otm.core.math.Vector import ru.dbotthepony.mc.otm.core.math.Vector
import ru.dbotthepony.mc.otm.core.readItemType
import ru.dbotthepony.mc.otm.core.writeItemType
import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu
import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.AndroidFeatures
@ -510,8 +514,56 @@ object HideExosuitPacket : MatteryPacket {
} }
} }
class SetInventoryFilterPacket(val type: Type, val slot: Int, val item: Item?) : MatteryPacket {
enum class Type {
INVENTORY,
EXOPACK
}
override fun write(buff: FriendlyByteBuf) {
buff.writeEnum(type)
buff.writeVarInt(slot)
if (item == null) {
buff.writeByte(0)
} else {
buff.writeByte(1)
buff.writeItemType(item)
}
}
override fun play(context: Supplier<NetworkEvent.Context>) {
context.packetHandled = true
val player = context.sender?.matteryPlayer ?: return
when (type) {
Type.INVENTORY -> {
if (slot in 0 until player.regularSlotFilters.size) {
player.regularSlotFilters[slot].value = item
}
}
Type.EXOPACK -> {
if (slot in 0 until player.exoPackSlotCount) {
if (item == null) {
player.exoPackSlotFilters.remove(slot)
} else {
player.exoPackSlotFilters[slot] = item
}
}
}
}
}
companion object {
fun read(buff: FriendlyByteBuf): SetInventoryFilterPacket {
return SetInventoryFilterPacket(buff.readEnum(Type::class.java), buff.readVarInt(), if (buff.readByte() > 0) buff.readItemType() else null)
}
}
}
object MatteryPlayerNetworkChannel : MatteryNetworkChannel( object MatteryPlayerNetworkChannel : MatteryNetworkChannel(
version = "2", version = "3",
name = "player" name = "player"
) { ) {
fun register() { fun register() {
@ -543,5 +595,7 @@ object MatteryPlayerNetworkChannel : MatteryNetworkChannel(
add(DisplayExosuitPacket::class, { DisplayExosuitPacket }, PLAY_TO_SERVER) add(DisplayExosuitPacket::class, { DisplayExosuitPacket }, PLAY_TO_SERVER)
add(HideExosuitPacket::class, { HideExosuitPacket }, PLAY_TO_SERVER) add(HideExosuitPacket::class, { HideExosuitPacket }, PLAY_TO_SERVER)
add(SetInventoryFilterPacket::class, SetInventoryFilterPacket.Companion::read, PLAY_TO_SERVER)
} }
} }

View File

@ -2,6 +2,8 @@
public-f net.minecraft.client.gui.screens.Screen f_96539_ # title public-f net.minecraft.client.gui.screens.Screen f_96539_ # title
public net.minecraft.server.MinecraftServer f_129744_ # storageSource public net.minecraft.server.MinecraftServer f_129744_ # storageSource
public net.minecraft.world.entity.player.Inventory f_150070_ # SELECTION_SIZE
# for accessing and setting from MatteryScreen class # for accessing and setting from MatteryScreen class
public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169600_ public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169600_
public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169605_ public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169605_

View File

@ -533,46 +533,6 @@ function injectAtTail(path, instructions) {
}) })
} }
function injectInventoryInsertHook(
instructions,
determinedOffset,
hookName,
hookDesc,
pre
) {
var last = instructions.get(determinedOffset)
// print('Patching Inventory#add at instruction ' + determinedOffset + ' to add ' + hookName + hookDesc)
// loading this (Inventory) to stack
if (pre != undefined) {
instructions.insert(last, pre)
last = pre
}
var next = new VarInsnNode(opcodesRemapped.aload, 0)
instructions.insert(last, next)
last = next
// loading itemstack to stack
next = new VarInsnNode(opcodesRemapped.aload, 2)
instructions.insert(last, next)
last = next
// dispatching hook method
next = new MethodInsnNode(
opcodesRemapped.invokestatic,
'ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability',
hookName,
hookDesc,
false
)
instructions.insert(last, next)
}
function patchBlendFunc(node) { function patchBlendFunc(node) {
var last = new MethodInsnNode( var last = new MethodInsnNode(
opcodesRemapped.invokestatic, opcodesRemapped.invokestatic,
@ -795,128 +755,6 @@ function initializeCoreMod() {
return node return node
}), }),
'Inventory#add patch': {
'target': {
'type': 'METHOD',
'class': 'net.minecraft.world.entity.player.Inventory',
'methodName': ASMAPI.mapMethod('m_36040_'), // add
'methodDesc': '(ILnet/minecraft/world/item/ItemStack;)Z'
},
'transformer': function(node) {
// If item is not "damaged":
// 113: invokevirtual #144 // Method addResource:(ILnet/minecraft/world/item/ItemStack;)I
// 116: invokevirtual #158 // Method net/minecraft/world/item/ItemStack.setCount:(I)V
// 119: aload_2
// 120: invokevirtual #57 // Method net/minecraft/world/item/ItemStack.isEmpty:()Z
// 123: ifne 134
// 126: aload_2
// 127: invokevirtual #68 // Method net/minecraft/world/item/ItemStack.getCount:()I
// 130: iload_3
// 131: if_icmplt 87
// <-- our target
// 134: aload_2
// 135: invokevirtual #68 // Method net/minecraft/world/item/ItemStack.getCount:()I
// 138: iload_3
// 139: if_icmpne 162
// 142: aload_0
// If item returned that it is damaged:
// 10: invokevirtual #97 // Method net/minecraft/world/item/ItemStack.isDamaged:()Z
// 13: ifeq 87
// 16: iload_1
// 17: iconst_m1
// 18: if_icmpne 26
// 21: aload_0
// 22: invokevirtual #86 // Method getFreeSlot:()I
// 25: istore_1
// <-- our target
// 26: iload_1
// 27: iflt 65
// 30: aload_0
// 31: getfield #19 // Field items:Lnet/minecraft/core/NonNullList;
// 34: iload_1
// 35: aload_2
var i
// 83: iconst_1
// 84: ireturn
// 85: iconst_0
// 86: ireturn
// 87: aload_2
// 88: invokevirtual #68 // Method net/minecraft/world/item/ItemStack.getCount:()I
// patch "not damaged" before loop
for (i = 0; i < node.instructions.size(); i++) {
var determinedOffset = test([
opcodesRemapped.iconst_1,
opcodesRemapped.ireturn,
opcodesRemapped.iconst_0,
opcodesRemapped.ireturn,
opcodesRemapped.aload,
opcodesRemapped.invokevirtual,
], node.instructions, i)
if (determinedOffset != -1) {
injectInventoryInsertHook(node.instructions, determinedOffset - 1,
'inventoryAddItemHookPre',
'(Lnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/item/ItemStack;)V')
break
}
}
// patch "not damaged" after loop
for (i = 0; i < node.instructions.size(); i++) {
var determinedOffset = test([
opcodesRemapped.invokevirtual,
opcodesRemapped.invokevirtual,
opcodesRemapped.aload,
opcodesRemapped.invokevirtual,
opcodesRemapped.ifne,
opcodesRemapped.aload,
opcodesRemapped.invokevirtual,
opcodesRemapped.iload,
opcodesRemapped.if_icmplt,
], node.instructions, i)
if (determinedOffset != -1) {
injectInventoryInsertHook(node.instructions, determinedOffset,
'inventoryAddItemHook',
'(Lnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/item/ItemStack;)V')
break
}
}
// patch "damaged"
for (i = 0; i < node.instructions.size(); i++) {
var determinedOffset = test([
opcodesRemapped.invokevirtual,
opcodesRemapped.ifeq,
opcodesRemapped.iload,
opcodesRemapped.iconst_m1,
opcodesRemapped.if_icmpne,
opcodesRemapped.aload,
opcodesRemapped.invokevirtual,
opcodesRemapped.istore,
], node.instructions, i)
if (determinedOffset != -1) {
injectInventoryInsertHook(node.instructions, determinedOffset,
'inventoryAddDamagedItemHook',
'(ILnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/item/ItemStack;)V',
new VarInsnNode(opcodesRemapped.iload, node.instructions.get(determinedOffset - 1)['var']))
break
}
}
return node
}
},
'GameRenderer#render hook': { 'GameRenderer#render hook': {
'target': { 'target': {
'type': 'METHOD', 'type': 'METHOD',

View File

@ -9,6 +9,7 @@
"MixinPatchProjectileFinder", "MixinPatchProjectileFinder",
"MixinLivingEntity", "MixinLivingEntity",
"MixinAnvilBlock", "MixinAnvilBlock",
"MixinInventory",
"MixinAbstractHurtingProjectile" "MixinAbstractHurtingProjectile"
] ]
} }