Item Filter and storage bus menu

This commit is contained in:
DBotThePony 2022-05-14 20:58:36 +07:00
parent 5b245ec564
commit aa6f28977b
Signed by: DBot
GPG Key ID: DCC23B5715498507
12 changed files with 351 additions and 9 deletions

View File

@ -8,6 +8,7 @@ import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.simple.SimpleChannel;
import ru.dbotthepony.mc.otm.OverdriveThatMatters; import ru.dbotthepony.mc.otm.OverdriveThatMatters;
import ru.dbotthepony.mc.otm.block.entity.EnergyCounterPacket; 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.WeaponScopePacket;
import ru.dbotthepony.mc.otm.item.weapon.WeaponFireInputPacket; import ru.dbotthepony.mc.otm.item.weapon.WeaponFireInputPacket;
import ru.dbotthepony.mc.otm.matter.RegistryPacketClear; import ru.dbotthepony.mc.otm.matter.RegistryPacketClear;
@ -312,5 +313,14 @@ public class MatteryNetworking {
WeaponFireInputPacket::play, WeaponFireInputPacket::play,
Optional.of(NetworkDirection.PLAY_TO_SERVER) Optional.of(NetworkDirection.PLAY_TO_SERVER)
); );
CHANNEL.registerMessage(
next_network_id++,
ItemFilterSlotPacket.class,
ItemFilterSlotPacket::write,
ItemFilterSlotPacket.Companion::read,
ItemFilterSlotPacket::play
// Optional.of(NetworkDirection.)
);
} }
} }

View File

@ -3,6 +3,8 @@ package ru.dbotthepony.mc.otm.block.entity
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.Direction 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.Component
import net.minecraft.network.chat.TranslatableComponent import net.minecraft.network.chat.TranslatableComponent
import net.minecraft.server.level.ServerLevel 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.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage 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.ImpreciseFraction
import ru.dbotthepony.mc.otm.core.plus import ru.dbotthepony.mc.otm.core.plus
import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode
import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph 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.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MNames import ru.dbotthepony.mc.otm.registry.MNames
import ru.dbotthepony.mc.otm.storage.* import ru.dbotthepony.mc.otm.storage.*
@ -53,13 +57,17 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
override val defaultDisplayName: Component override val defaultDisplayName: Component
get() = MACHINE_NAME get() = MACHINE_NAME
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? { override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
return null return StorageBusMenu(containerID, inventory, this)
} }
override val energy = WorkerEnergyStorage(this, maxBatteryLevel = MAX_POWER) override val energy = WorkerEnergyStorage(this, maxBatteryLevel = MAX_POWER)
val cell = BasicStorageGraphNode(energy) val cell = BasicStorageGraphNode(energy)
val filter = ItemFilter(MAX_FILTERS) { _, _, _ ->
component?.scan()
}
override fun setLevel(p_155231_: Level) { override fun setLevel(p_155231_: Level) {
super.setLevel(p_155231_) super.setLevel(p_155231_)
@ -105,6 +113,18 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
valid = false 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() { fun checkSurroundings() {
if (isRemoved) if (isRemoved)
return return
@ -139,6 +159,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
companion object { companion object {
private val MACHINE_NAME = TranslatableComponent("block.${OverdriveThatMatters.MOD_ID}.${MNames.STORAGE_BUS}") private val MACHINE_NAME = TranslatableComponent("block.${OverdriveThatMatters.MOD_ID}.${MNames.STORAGE_BUS}")
private val MAX_POWER = ImpreciseFraction(10_000) private val MAX_POWER = ImpreciseFraction(10_000)
const val MAX_FILTERS = 6 * 3
} }
private inner class ItemHandlerComponent(private val parent: IItemHandler) : IStorageComponent<ItemStackWrapper> { private inner class ItemHandlerComponent(private val parent: IItemHandler) : IStorageComponent<ItemStackWrapper> {
@ -272,7 +293,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
} }
fun scan(slot: Int) { 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] val last = scanned[slot]
if (current == null && last != null) { if (current == null && last != null) {
@ -298,7 +319,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
} }
override fun insertStack(stack: ItemStackWrapper, simulate: Boolean): ItemStackWrapper { override fun insertStack(stack: ItemStackWrapper, simulate: Boolean): ItemStackWrapper {
if (energy.batteryLevel.isZero) if (energy.batteryLevel.isZero || !filter.match(stack.item))
return stack return stack
val maxPossibleDemand = stack.count * ITEM_STORAGE.energyPerOperation val maxPossibleDemand = stack.count * ITEM_STORAGE.energyPerOperation

View File

@ -3,5 +3,5 @@ package ru.dbotthepony.mc.otm.client
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.client.gui.Font import net.minecraft.client.gui.Font
val minecraft: Minecraft get() = Minecraft.getInstance() inline val minecraft: Minecraft get() = Minecraft.getInstance()
val font: Font get() = minecraft.font inline val font: Font get() = minecraft.font

View File

@ -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<StorageBusMenu>(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
}
}

View File

@ -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
}
}

View File

@ -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<NetworkEvent.Context>) {
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<ListTag> {
private val filter = Array<ItemStack>(size) { ItemStack.EMPTY }
private val linkedFilter = LinkedList<ItemStack>()
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)
}
}
}
}

View File

@ -34,7 +34,7 @@ class MatterBottlerMenu @JvmOverloads constructor(
} else { } else {
progressWidget = ProgressGaugeWidget(this) { tile.getWorkProgress() } progressWidget = ProgressGaugeWidget(this) { tile.getWorkProgress() }
matterWidget = LevelGaugeWidget(this, tile.matter) 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 -> this.container = Array(6) { index ->

View File

@ -7,9 +7,12 @@ import net.minecraft.world.inventory.*
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraftforge.network.PacketDistributor 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.data.MultiByteDataContainer
import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget
import ru.dbotthepony.mc.otm.network.MatteryNetworking import ru.dbotthepony.mc.otm.network.MatteryNetworking
import java.util.Collections
@JvmRecord @JvmRecord
data class MoveResult(val mergeOccurred: Boolean, val remaining: ItemStack, val changed_slots: Set<Slot>) data class MoveResult(val mergeOccurred: Boolean, val remaining: ItemStack, val changed_slots: Set<Slot>)
@ -37,6 +40,29 @@ abstract class MatteryMenu protected @JvmOverloads constructor(
@JvmField @JvmField
protected var _synchronizer: ContainerSynchronizer? = null protected var _synchronizer: ContainerSynchronizer? = null
private val _filterSlots = ArrayList<ItemFilterNetworkSlot>()
val filterSlots = Collections.unmodifiableList(_filterSlots)
fun addFilterSlots(slots: ItemFilter): List<ItemFilterNetworkSlot> {
val result = ArrayList<ItemFilterNetworkSlot>(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<ItemFilterNetworkSlot> {
val result = ArrayList<ItemFilterNetworkSlot>(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) { override fun setSynchronizer(p_150417_: ContainerSynchronizer) {
_synchronizer = p_150417_ _synchronizer = p_150417_
super.setSynchronizer(p_150417_) super.setSynchronizer(p_150417_)
@ -143,6 +169,16 @@ abstract class MatteryMenu protected @JvmOverloads constructor(
} }
super.broadcastChanges() 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() { override fun broadcastFullState() {
@ -155,6 +191,16 @@ abstract class MatteryMenu protected @JvmOverloads constructor(
} }
super.broadcastFullState() 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 { override fun stillValid(player: Player): Boolean {

View File

@ -9,10 +9,10 @@ import ru.dbotthepony.mc.otm.registry.MMenus
class PatternStorageMenu @JvmOverloads constructor( class PatternStorageMenu @JvmOverloads constructor(
p_38852_: Int, p_38852_: Int,
inventory: Inventory?, inventory: Inventory,
tile: PatternStorageBlockEntity? = null tile: PatternStorageBlockEntity? = null
) : MatteryMenu( ) : MatteryMenu(
MMenus.PATTERN_STORAGE, p_38852_, inventory!!, tile MMenus.PATTERN_STORAGE, p_38852_, inventory, tile
) { ) {
val patternSlots = arrayOfNulls<PatternSlot>(2 * 4) val patternSlots = arrayOfNulls<PatternSlot>(2 * 4)
val storedThis: LevelGaugeWidget val storedThis: LevelGaugeWidget

View File

@ -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<ItemFilterNetworkSlot>
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
}

View File

@ -6,6 +6,7 @@ import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.data.BooleanDataContainer import ru.dbotthepony.mc.otm.menu.data.BooleanDataContainer
import ru.dbotthepony.mc.otm.network.MatteryNetworking import ru.dbotthepony.mc.otm.network.MatteryNetworking
import java.util.function.Supplier import java.util.function.Supplier
import kotlin.reflect.KMutableProperty0
class BooleanPlayerInputPacket(val id: Int, val value: Boolean) { class BooleanPlayerInputPacket(val id: Int, val value: Boolean) {
fun play(context: Supplier<NetworkEvent.Context>) { fun play(context: Supplier<NetworkEvent.Context>) {
@ -35,6 +36,11 @@ class BooleanPlayerInputWidget(menu: MatteryMenu) : AbstractWidget(menu) {
addDataSlots(container) addDataSlots(container)
} }
constructor(menu: MatteryMenu, state: KMutableProperty0<Boolean>) : this(menu) {
withClicker { state.set(it) }
withSupplier { state.get() }
}
var supplier: (() -> Boolean)? = null var supplier: (() -> Boolean)? = null
var clicker: ((Boolean) -> Unit)? = null var clicker: ((Boolean) -> Unit)? = null
var value by container::value var value by container::value

View File

@ -31,11 +31,14 @@ object MMenus {
val PLATE_PRESS: MenuType<*> by registry.register(MNames.PLATE_PRESS) { MenuType(::PlatePressMenu) } 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 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() { internal fun register() {
registry.register(FMLJavaModLoadingContext.get().modEventBus) registry.register(FMLJavaModLoadingContext.get().modEventBus)
FMLJavaModLoadingContext.get().modEventBus.addListener(this::registerClient) FMLJavaModLoadingContext.get().modEventBus.addListener(this::registerClient)
} }
@Suppress("unchecked_cast")
private fun registerClient(event: FMLClientSetupEvent) { private fun registerClient(event: FMLClientSetupEvent) {
event.enqueueWork { event.enqueueWork {
MenuScreens.register(ANDROID_STATION as MenuType<AndroidStationMenu>, ::AndroidStationScreen) MenuScreens.register(ANDROID_STATION as MenuType<AndroidStationMenu>, ::AndroidStationScreen)
@ -55,6 +58,7 @@ object MMenus {
MenuScreens.register(CHEMICAL_GENERATOR as MenuType<ChemicalGeneratorMenu>, ::ChemicalGeneratorScreen) MenuScreens.register(CHEMICAL_GENERATOR as MenuType<ChemicalGeneratorMenu>, ::ChemicalGeneratorScreen)
MenuScreens.register(PLATE_PRESS as MenuType<PlatePressMenu>, ::PlatePressScreen) MenuScreens.register(PLATE_PRESS as MenuType<PlatePressMenu>, ::PlatePressScreen)
MenuScreens.register(MATTER_RECYCLER as MenuType<MatterRecyclerMenu>, ::MatterRecyclerScreen) MenuScreens.register(MATTER_RECYCLER as MenuType<MatterRecyclerMenu>, ::MatterRecyclerScreen)
MenuScreens.register(STORAGE_BUS as MenuType<StorageBusMenu>, ::StorageBusScreen)
} }
} }
} }