diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageInterfaces.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageInterfaces.kt index 6b5602095..b60098a41 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageInterfaces.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/StorageInterfaces.kt @@ -2,6 +2,8 @@ package ru.dbotthepony.mc.otm.block.entity 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 @@ -15,21 +17,24 @@ import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.util.LazyOptional import net.minecraftforge.items.CapabilityItemHandler import net.minecraftforge.items.IItemHandler -import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.* import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.capability.* +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.core.toImpreciseFraction import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph -import ru.dbotthepony.mc.otm.orThrow +import ru.dbotthepony.mc.otm.menu.StorageExporterMenu +import ru.dbotthepony.mc.otm.menu.StorageImporterMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.storage.IStorageTuple -import ru.dbotthepony.mc.otm.storage.ITEM_STORAGE -import ru.dbotthepony.mc.otm.storage.ItemStackWrapper -import ru.dbotthepony.mc.otm.unaryMinus +import ru.dbotthepony.mc.otm.storage.* +import java.util.* +import java.util.stream.Stream +import kotlin.collections.HashMap +import kotlin.collections.HashSet abstract class AbstractStorageImportExport( blockType: BlockEntityType<*>, @@ -99,8 +104,12 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : A override val defaultDisplayName: Component get() = MACHINE_NAME - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? { - return null + val filter = ItemFilter(MAX_FILTERS) { _, _, _ -> + + } + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return StorageImporterMenu(containerID, inventory, this) } private var lastSlot = 0 @@ -111,6 +120,18 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : A override val targetCapability: Capability get() = CapabilityItemHandler.ITEM_HANDLER_CAPABILITY + 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 tick() { batteryChargeLoop() cell.tickEnergyDemanding() @@ -130,7 +151,7 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : A val maxMove = energy.extractStepInner(ITEM_STORAGE.energyPerOperation, MAX_MOVE_PER_OPERATION, true) var extracted = resolved.extractItem(lastSlot, maxMove, true) - if (extracted.isEmpty) { + if (extracted.isEmpty || !filter.match(extracted)) { lastSlot++ } else { val leftOver = items.insertStack(ItemStackWrapper(extracted), true) @@ -154,17 +175,48 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : A const val MAX_MOVE_PER_OPERATION = 4 private val MACHINE_NAME = TranslatableComponent("block.${OverdriveThatMatters.MOD_ID}.${MNames.STORAGE_IMPORTER}") private const val INTERVAL = 5 + const val MAX_FILTERS = 6 * 3 } } -class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : AbstractStorageImportExport(MBlockEntities.STORAGE_EXPORTER, blockPos, blockState) { +class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : + AbstractStorageImportExport(MBlockEntities.STORAGE_EXPORTER, blockPos, blockState), + IStorageListener { 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 StorageExporterMenu(containerID, inventory, this) } + private val relevantTuples = HashSet() + + override fun addStack(stack: ItemStackWrapper, id: UUID, provider: IStorageView) { + if (!filter.match(stack.item)) { + return + } + + relevantTuples.add(id) + } + + override fun changeStack(stack: ItemStackWrapper, id: UUID, oldCount: ImpreciseFraction) { + // no-op + } + + override fun removeStack(stack: ItemStackWrapper, id: UUID) { + relevantTuples.remove(id) + } + + val filter = ItemFilter(MAX_FILTERS) { _, _, _ -> + relevantTuples.clear() + + val component = cell.storageGraph?.getVirtualComponent(ITEM_STORAGE) ?: return@ItemFilter + + for (tuple in component.getStacks()) { + addStack(tuple, component) + } + }.also { it.isWhitelist = true } + private var lastSlot = 0 private var nextTick = INTERVAL private val enoughEnergy get() = energy.batteryLevel >= ITEM_STORAGE.energyPerOperation @@ -172,8 +224,23 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : A override val targetCapability: Capability get() = CapabilityItemHandler.ITEM_HANDLER_CAPABILITY - private val exportStacks: List> - get() = cell.storageGraph?.getVirtualComponent(ITEM_STORAGE)?.getStacks() ?: emptyList() + private val exportStacks: Stream> + get() { + val view = cell.storageGraph?.getVirtualComponent(ITEM_STORAGE) ?: return Stream.empty() + return relevantTuples.stream().map { it to view.getStack(it) } + } + + 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 tick() { batteryChargeLoop() @@ -194,7 +261,7 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : A var hit = false for (stack in exportStacks) { - val exportAmountA = items.extractStack(stack.id, stack.stack.count.toInt().coerceAtMost(MAX_MOVE_PER_OPERATION).toImpreciseFraction(), true).count.toInt() + val exportAmountA = items.extractStack(stack.first, stack.second.count.toInt().coerceAtMost(MAX_MOVE_PER_OPERATION).toImpreciseFraction(), true).count.toInt() if (exportAmountA == 0) { continue @@ -206,12 +273,12 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : A break } - val leftover = resolved.insertItem(lastSlot, stack.stack.stack.also { it.count = exportAmount }, true) + val leftover = resolved.insertItem(lastSlot, stack.second.stack.also { it.count = exportAmount }, true) if (leftover.count != exportAmount) { hit = true - exportAmount = items.extractStack(stack.id, ImpreciseFraction(exportAmount - leftover.count), false).count.toInt() - resolved.insertItem(lastSlot, stack.stack.stack.also { it.count = exportAmount }, false) + exportAmount = items.extractStack(stack.first, ImpreciseFraction(exportAmount - leftover.count), false).count.toInt() + resolved.insertItem(lastSlot, stack.second.stack.also { it.count = exportAmount }, false) energy.extractStepInner(ITEM_STORAGE.energyPerOperation, exportAmount, false) break } @@ -231,5 +298,6 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : A const val MAX_MOVE_PER_OPERATION = 4 private val MACHINE_NAME = TranslatableComponent("block.${OverdriveThatMatters.MOD_ID}.${MNames.STORAGE_EXPORTER}") private const val INTERVAL = 5 + const val MAX_FILTERS = 6 * 3 } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageExporterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageExporterScreen.kt new file mode 100644 index 000000000..ff4327a5a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageExporterScreen.kt @@ -0,0 +1,32 @@ +package ru.dbotthepony.mc.otm.client.screen + +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.TranslatableComponent +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.panels.CheckBoxLabelInputPanel +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.StorageExporterMenu + +class StorageExporterScreen(menu: StorageExporterMenu, 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) + } + } + + CheckBoxLabelInputPanel(this, frame, menu.busFilterState, TranslatableComponent("otm.gui.filter.is_whitelist"), 59f, 78f, width = 170f) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageImporterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageImporterScreen.kt new file mode 100644 index 000000000..d54e451df --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageImporterScreen.kt @@ -0,0 +1,32 @@ +package ru.dbotthepony.mc.otm.client.screen + +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.TranslatableComponent +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.panels.CheckBoxLabelInputPanel +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.StorageImporterMenu + +class StorageImporterScreen(menu: StorageImporterMenu, 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) + } + } + + CheckBoxLabelInputPanel(this, frame, menu.busFilterState, TranslatableComponent("otm.gui.filter.is_whitelist"), 59f, 78f, width = 170f) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageExporterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageExporterMenu.kt new file mode 100644 index 000000000..ad66bec08 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageExporterMenu.kt @@ -0,0 +1,34 @@ +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.block.entity.StorageExporterBlockEntity +import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot +import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class StorageExporterMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: StorageExporterBlockEntity? = null +) : MatteryPoweredMenu( + MMenus.STORAGE_EXPORTER, 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(StorageExporterBlockEntity.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/StorageImporterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageImporterMenu.kt new file mode 100644 index 000000000..f0263f334 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageImporterMenu.kt @@ -0,0 +1,35 @@ +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.block.entity.StorageExporterBlockEntity +import ru.dbotthepony.mc.otm.block.entity.StorageImporterBlockEntity +import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot +import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class StorageImporterMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: StorageImporterBlockEntity? = null +) : MatteryPoweredMenu( + MMenus.STORAGE_IMPORTER, 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(StorageImporterBlockEntity.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/registry/MMenus.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt index 033d01202..c0c926f20 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt @@ -32,6 +32,8 @@ object MMenus { val MATTER_RECYCLER: MenuType<*> by registry.register(MNames.MATTER_RECYCLER) { MenuType(::MatterRecyclerMenu) } val STORAGE_BUS: MenuType<*> by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu) } + val STORAGE_EXPORTER: MenuType<*> by registry.register(MNames.STORAGE_EXPORTER) { MenuType(::StorageExporterMenu) } + val STORAGE_IMPORTER: MenuType<*> by registry.register(MNames.STORAGE_IMPORTER) { MenuType(::StorageImporterMenu) } internal fun register() { registry.register(FMLJavaModLoadingContext.get().modEventBus) @@ -59,6 +61,8 @@ object MMenus { MenuScreens.register(PLATE_PRESS as MenuType, ::PlatePressScreen) MenuScreens.register(MATTER_RECYCLER as MenuType, ::MatterRecyclerScreen) MenuScreens.register(STORAGE_BUS as MenuType, ::StorageBusScreen) + MenuScreens.register(STORAGE_EXPORTER as MenuType, ::StorageExporterScreen) + MenuScreens.register(STORAGE_IMPORTER as MenuType, ::StorageImporterScreen) } } }