diff --git a/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java b/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java index a3d03a8d5..255f14a95 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java +++ b/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java @@ -8,6 +8,7 @@ import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.network.simple.SimpleChannel; import ru.dbotthepony.mc.otm.OverdriveThatMatters; import ru.dbotthepony.mc.otm.block.entity.EnergyCounterPacket; +import ru.dbotthepony.mc.otm.container.ItemFilterSlotPacket; import ru.dbotthepony.mc.otm.item.weapon.WeaponScopePacket; import ru.dbotthepony.mc.otm.item.weapon.WeaponFireInputPacket; import ru.dbotthepony.mc.otm.matter.RegistryPacketClear; @@ -312,5 +313,14 @@ public class MatteryNetworking { WeaponFireInputPacket::play, Optional.of(NetworkDirection.PLAY_TO_SERVER) ); + + CHANNEL.registerMessage( + next_network_id++, + ItemFilterSlotPacket.class, + ItemFilterSlotPacket::write, + ItemFilterSlotPacket.Companion::read, + ItemFilterSlotPacket::play + // Optional.of(NetworkDirection.) + ); } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageBusBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageBusBlockEntity.kt index 0462428c9..6515312cb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageBusBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageBusBlockEntity.kt @@ -3,6 +3,8 @@ package ru.dbotthepony.mc.otm.block.entity import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import net.minecraft.core.BlockPos import net.minecraft.core.Direction +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag import net.minecraft.network.chat.Component import net.minecraft.network.chat.TranslatableComponent import net.minecraft.server.level.ServerLevel @@ -20,10 +22,12 @@ import ru.dbotthepony.mc.otm.* import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.core.ImpreciseFraction import ru.dbotthepony.mc.otm.core.plus import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph +import ru.dbotthepony.mc.otm.menu.StorageBusMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MNames import ru.dbotthepony.mc.otm.storage.* @@ -53,13 +57,17 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter override val defaultDisplayName: Component get() = MACHINE_NAME - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? { - return null + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return StorageBusMenu(containerID, inventory, this) } override val energy = WorkerEnergyStorage(this, maxBatteryLevel = MAX_POWER) val cell = BasicStorageGraphNode(energy) + val filter = ItemFilter(MAX_FILTERS) { _, _, _ -> + component?.scan() + } + override fun setLevel(p_155231_: Level) { super.setLevel(p_155231_) @@ -105,6 +113,18 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter valid = false } + override fun saveAdditional(nbt: CompoundTag) { + super.saveAdditional(nbt) + + nbt["filter"] = filter.serializeNBT() + } + + override fun load(nbt: CompoundTag) { + super.load(nbt) + + nbt.ifHas("filter", ListTag::class.java, filter::deserializeNBT) + } + fun checkSurroundings() { if (isRemoved) return @@ -139,6 +159,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter companion object { private val MACHINE_NAME = TranslatableComponent("block.${OverdriveThatMatters.MOD_ID}.${MNames.STORAGE_BUS}") private val MAX_POWER = ImpreciseFraction(10_000) + const val MAX_FILTERS = 6 * 3 } private inner class ItemHandlerComponent(private val parent: IItemHandler) : IStorageComponent { @@ -272,7 +293,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } fun scan(slot: Int) { - val current = parent[slot].let { if (it.isEmpty) null else it } + val current = parent[slot].let { if (it.isEmpty || !filter.match(it)) null else it } val last = scanned[slot] if (current == null && last != null) { @@ -298,7 +319,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } override fun insertStack(stack: ItemStackWrapper, simulate: Boolean): ItemStackWrapper { - if (energy.batteryLevel.isZero) + if (energy.batteryLevel.isZero || !filter.match(stack.item)) return stack val maxPossibleDemand = stack.count * ITEM_STORAGE.energyPerOperation diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt index 61da21f11..b438fb865 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt @@ -3,5 +3,5 @@ package ru.dbotthepony.mc.otm.client import net.minecraft.client.Minecraft import net.minecraft.client.gui.Font -val minecraft: Minecraft get() = Minecraft.getInstance() -val font: Font get() = minecraft.font +inline val minecraft: Minecraft get() = Minecraft.getInstance() +inline val font: Font get() = minecraft.font diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageBusScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageBusScreen.kt new file mode 100644 index 000000000..454d3c515 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageBusScreen.kt @@ -0,0 +1,28 @@ +package ru.dbotthepony.mc.otm.client.screen + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.panels.FilterSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel + +import ru.dbotthepony.mc.otm.menu.StorageBusMenu + +class StorageBusScreen(menu: StorageBusMenu, inventory: Inventory, title: Component) : + MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel { + val frame = super.makeMainFrame()!! + + PowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + SlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) + + for (row in 0 .. 2) { + for (column in 0 .. 5) { + FilterSlotPanel(this, frame, menu.busFilterSlots[row + column * 3], 55f + 18f * column, 17f + 18f * row) + } + } + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FilterSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FilterSlotPanel.kt new file mode 100644 index 000000000..e94071b30 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FilterSlotPanel.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot +import ru.dbotthepony.mc.otm.container.ItemFilterSlotPacket +import ru.dbotthepony.mc.otm.network.MatteryNetworking + +open class FilterSlotPanel( + screen: MatteryScreen<*>, + parent: EditablePanel?, + val slot: ItemFilterNetworkSlot, + x: Float = 0f, + y: Float = 0f, + width: Float = REGULAR_DIMENSIONS, + height: Float = REGULAR_DIMENSIONS +) : AbstractSlotPanel(screen, parent, x, y, width, height) { + override fun getItemStack(): ItemStack { + return slot.get() + } + + override fun mouseClickedInner(mouse_x: Double, mouse_y: Double, mouse_click_type: Int): Boolean { + if (screen.menu.carried.isEmpty) { + MatteryNetworking.CHANNEL.sendToServer(ItemFilterSlotPacket(slot.networkID, ItemStack.EMPTY)) + } else { + MatteryNetworking.CHANNEL.sendToServer(ItemFilterSlotPacket(slot.networkID, screen.menu.carried)) + } + + return true + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt new file mode 100644 index 000000000..dbc5855a8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt @@ -0,0 +1,163 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.util.INBTSerializable +import net.minecraftforge.network.NetworkEvent +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import java.util.LinkedList +import java.util.function.Supplier + +data class ItemFilterSlotPacket(val slotID: Int, val newValue: ItemStack) { + fun write(buff: FriendlyByteBuf) { + buff.writeInt(slotID) + buff.writeItemStack(newValue, true) + } + + fun play(context: Supplier) { + context.get().packetHandled = true + + if (context.get().sender != null) { + context.get().enqueueWork { + playServer(context.get().sender!!) + } + } else { + context.get().enqueueWork { + playClient() + } + } + } + + fun playClient() { + val menu = minecraft.player?.containerMenu as? MatteryMenu ?: throw IllegalStateException("No MatteryMenu is open right now, can't handle ItemFilterSlotReplicaPacket") + menu.filterSlots[slotID].set(newValue) + } + + fun playServer(player: ServerPlayer) { + val menu = player.containerMenu as? MatteryMenu ?: return LOGGER.error("No MatteryMenu is open right now, can't handle ItemFilterSlotReplicaPacket from $player") + menu.filterSlots.getOrNull(slotID)?.set(newValue) //?: LOGGER.error("ItemFilterSlotReplicaPacket: unknown slot $slotID from $player in $menu!") + } + + companion object { + fun read(buff: FriendlyByteBuf): ItemFilterSlotPacket { + return ItemFilterSlotPacket(buff.readInt(), buff.readItem()) + } + + private val LOGGER = LogManager.getLogger() + } +} + +data class ItemFilterNetworkSlot(val slotID: Int, val networkID: Int, val filter: ItemFilter?) { + fun get() = filter?.get(slotID) ?: remote + fun set(value: ItemStack) { + if (filter != null) + filter[slotID] = value + else + remote = value + } + + private var remote: ItemStack = ItemStack.EMPTY + + fun sendChanges(full: Boolean = false): ItemFilterSlotPacket? { + requireNotNull(filter) { "Invalid side" } + + if (full || !ItemStack.isSameItemSameTags(remote, get())) { + remote = get() + return ItemFilterSlotPacket(networkID, get()) + } + + return null + } +} + +class ItemFilter( + val size: Int, + private val modified: (slot: Int?, oldValue: ItemStack?, newValue: ItemStack?) -> Unit +) : INBTSerializable { + private val filter = Array(size) { ItemStack.EMPTY } + private val linkedFilter = LinkedList() + + var isWhitelist = false + set(value) { + if (value != field) { + field = value + modified.invoke(null, null, null) + } + } + + operator fun set(index: Int, value: ItemStack) { + if (value.isEmpty && filter[index].isEmpty) { + return + } + + val old = filter[index] + filter[index] = value.let { if (!it.isEmpty) it.copy().also { it.count = 1 } else it } + + if (!old.isEmpty) + linkedFilter.remove(old) + + if (!filter[index].isEmpty) + linkedFilter.add(filter[index]) + + modified.invoke(index, old, filter[index]) + + } + + operator fun get(index: Int): ItemStack = filter[index].copy() + + fun match(value: ItemStack): Boolean { + if (value.isEmpty) { + return false + } + + if (linkedFilter.isEmpty()) { + return !isWhitelist + } + + if (isWhitelist) { + for (item in linkedFilter) { + if (item.`is`(value.item)) { + return true + } + } + + return false + } + + + for (item in linkedFilter) { + if (item.`is`(value.item)) { + return false + } + } + + return true + } + + override fun serializeNBT(): ListTag { + return ListTag().also { + for (value in filter) { + it.add(value.serializeNBT()) + } + } + } + + override fun deserializeNBT(nbt: ListTag?) { + for (i in filter.indices) + filter[i] = ItemStack.EMPTY + + if (nbt == null) + return + + for ((i, value) in nbt.withIndex()) { + if (value is CompoundTag) { + filter[i] = ItemStack.of(value) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterBottlerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterBottlerMenu.kt index 63f565304..19a2c9089 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterBottlerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterBottlerMenu.kt @@ -34,7 +34,7 @@ class MatterBottlerMenu @JvmOverloads constructor( } else { progressWidget = ProgressGaugeWidget(this) { tile.getWorkProgress() } matterWidget = LevelGaugeWidget(this, tile.matter) - workFlow = BooleanPlayerInputWidget(this).withSupplier { tile.workFlow }.withClicker { tile.workFlow = it } + workFlow = BooleanPlayerInputWidget(this, tile::workFlow) } this.container = Array(6) { index -> diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt index e8c7989de..65f05f634 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -7,9 +7,12 @@ import net.minecraft.world.inventory.* import net.minecraft.world.item.ItemStack import net.minecraft.world.level.block.entity.BlockEntity import net.minecraftforge.network.PacketDistributor +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot import ru.dbotthepony.mc.otm.menu.data.MultiByteDataContainer import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget import ru.dbotthepony.mc.otm.network.MatteryNetworking +import java.util.Collections @JvmRecord data class MoveResult(val mergeOccurred: Boolean, val remaining: ItemStack, val changed_slots: Set) @@ -37,6 +40,29 @@ abstract class MatteryMenu protected @JvmOverloads constructor( @JvmField protected var _synchronizer: ContainerSynchronizer? = null + private val _filterSlots = ArrayList() + val filterSlots = Collections.unmodifiableList(_filterSlots) + + fun addFilterSlots(slots: ItemFilter): List { + val result = ArrayList(slots.size) + + for (i in 0 until slots.size) { + _filterSlots.add(ItemFilterNetworkSlot(i, _filterSlots.size, slots).also(result::add)) + } + + return result + } + + fun addFilterSlots(amount: Int): List { + val result = ArrayList(amount) + + for (i in 0 until amount) { + _filterSlots.add(ItemFilterNetworkSlot(i, _filterSlots.size, null).also(result::add)) + } + + return result + } + override fun setSynchronizer(p_150417_: ContainerSynchronizer) { _synchronizer = p_150417_ super.setSynchronizer(p_150417_) @@ -143,6 +169,16 @@ abstract class MatteryMenu protected @JvmOverloads constructor( } super.broadcastChanges() + + val consumer = PacketDistributor.PLAYER.with { ply as ServerPlayer } + + for (slot in _filterSlots) { + val packet = slot.sendChanges() + + if (packet != null) { + MatteryNetworking.CHANNEL.send(consumer, packet) + } + } } override fun broadcastFullState() { @@ -155,6 +191,16 @@ abstract class MatteryMenu protected @JvmOverloads constructor( } super.broadcastFullState() + + val consumer = PacketDistributor.PLAYER.with { ply as ServerPlayer } + + for (slot in _filterSlots) { + val packet = slot.sendChanges(true) + + if (packet != null) { + MatteryNetworking.CHANNEL.send(consumer, packet) + } + } } override fun stillValid(player: Player): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/PatternStorageMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/PatternStorageMenu.kt index ca8c23ad2..6e47d66f5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/PatternStorageMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/PatternStorageMenu.kt @@ -9,10 +9,10 @@ import ru.dbotthepony.mc.otm.registry.MMenus class PatternStorageMenu @JvmOverloads constructor( p_38852_: Int, - inventory: Inventory?, + inventory: Inventory, tile: PatternStorageBlockEntity? = null ) : MatteryMenu( - MMenus.PATTERN_STORAGE, p_38852_, inventory!!, tile + MMenus.PATTERN_STORAGE, p_38852_, inventory, tile ) { val patternSlots = arrayOfNulls(2 * 4) val storedThis: LevelGaugeWidget diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageBusMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageBusMenu.kt new file mode 100644 index 000000000..3dfa84d90 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageBusMenu.kt @@ -0,0 +1,33 @@ +package ru.dbotthepony.mc.otm.menu + +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.StorageBusBlockEntity +import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot +import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class StorageBusMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: StorageBusBlockEntity? = null +) : MatteryPoweredMenu( + MMenus.STORAGE_BUS, p_38852_, inventory, tile +) { + val busFilterSlots: List + val busFilterState: BooleanPlayerInputWidget + + init { + if (tile != null) { + busFilterSlots = addFilterSlots(tile.filter) + busFilterState = BooleanPlayerInputWidget(this, tile.filter::isWhitelist) + } else { + busFilterSlots = addFilterSlots(StorageBusBlockEntity.MAX_FILTERS) + busFilterState = BooleanPlayerInputWidget(this).asClient() + } + + addInventorySlots() + } + + override fun getWorkingSlotStart() = 0 + override fun getWorkingSlotEnd() = 1 +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/BooleanPlayerInputWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/BooleanPlayerInputWidget.kt index 437d6388f..8afd2c9e7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/BooleanPlayerInputWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/BooleanPlayerInputWidget.kt @@ -6,6 +6,7 @@ import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.data.BooleanDataContainer import ru.dbotthepony.mc.otm.network.MatteryNetworking import java.util.function.Supplier +import kotlin.reflect.KMutableProperty0 class BooleanPlayerInputPacket(val id: Int, val value: Boolean) { fun play(context: Supplier) { @@ -35,6 +36,11 @@ class BooleanPlayerInputWidget(menu: MatteryMenu) : AbstractWidget(menu) { addDataSlots(container) } + constructor(menu: MatteryMenu, state: KMutableProperty0) : this(menu) { + withClicker { state.set(it) } + withSupplier { state.get() } + } + var supplier: (() -> Boolean)? = null var clicker: ((Boolean) -> Unit)? = null var value by container::value diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt index cc7460574..033d01202 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt @@ -31,11 +31,14 @@ object MMenus { val PLATE_PRESS: MenuType<*> by registry.register(MNames.PLATE_PRESS) { MenuType(::PlatePressMenu) } val MATTER_RECYCLER: MenuType<*> by registry.register(MNames.MATTER_RECYCLER) { MenuType(::MatterRecyclerMenu) } + val STORAGE_BUS: MenuType<*> by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu) } + internal fun register() { registry.register(FMLJavaModLoadingContext.get().modEventBus) FMLJavaModLoadingContext.get().modEventBus.addListener(this::registerClient) } + @Suppress("unchecked_cast") private fun registerClient(event: FMLClientSetupEvent) { event.enqueueWork { MenuScreens.register(ANDROID_STATION as MenuType, ::AndroidStationScreen) @@ -55,6 +58,7 @@ object MMenus { MenuScreens.register(CHEMICAL_GENERATOR as MenuType, ::ChemicalGeneratorScreen) MenuScreens.register(PLATE_PRESS as MenuType, ::PlatePressScreen) MenuScreens.register(MATTER_RECYCLER as MenuType, ::MatterRecyclerScreen) + MenuScreens.register(STORAGE_BUS as MenuType, ::StorageBusScreen) } } }