From 068e6ba3f9ea9c286dd676283b945c369503d6be Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 31 May 2023 14:09:12 +0700 Subject: [PATCH] Configurable Fluid Handler --- .../mc/otm/datagen/lang/English.kt | 1 + .../mc/otm/datagen/lang/Russian.kt | 1 + .../block/entity/MatteryDeviceBlockEntity.kt | 220 ++++++++++++++++-- .../entity/decorative/FluidTankBlockEntity.kt | 3 +- .../mc/otm/client/render/Widgets18.kt | 1 + .../screen/decorative/FluidTankScreen.kt | 2 +- .../client/screen/panels/button/Buttons.kt | 66 +++++- .../mc/otm/core/util/Savetables.kt | 2 +- .../mc/otm/menu/decorative/FluidTankMenu.kt | 2 + .../otm/menu/input/FluidConfigPlayerInput.kt | 68 ++++++ .../textures/gui/widgets/side_controls.png | Bin 2020 -> 2050 bytes .../textures/gui/widgets/side_controls.xcf | Bin 36864 -> 37629 bytes 12 files changed, 338 insertions(+), 28 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/FluidConfigPlayerInput.kt diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index 1817ae2ee..2f178dfb4 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -659,6 +659,7 @@ private fun gui(provider: MatteryLanguageProvider) { gui("sides.item_config", "Item Configuration") gui("sides.energy_config", "Energy Configuration") + gui("sides.fluid_config", "Fluid Configuration") gui("sides.top", "Top") gui("sides.bottom", "Bottom") diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt index 1b957ba61..c450a4a45 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt @@ -664,6 +664,7 @@ private fun gui(provider: MatteryLanguageProvider) { gui("sides.item_config", "Настройка предметов") gui("sides.energy_config", "Настройка энергии") + gui("sides.fluid_config", "Настройка жидкости") gui("sides.top", "Верхняя сторона") gui("sides.bottom", "Нижняя сторона") diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt index dcef9b156..44ed68d03 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt @@ -13,6 +13,8 @@ import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler import net.minecraftforge.items.IItemHandler import ru.dbotthepony.mc.otm.capability.CombinedItemHandler import ru.dbotthepony.mc.otm.capability.EmptyItemHandler @@ -21,6 +23,7 @@ import ru.dbotthepony.mc.otm.capability.UnmodifiableItemHandler import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.moveBetweenSlots import ru.dbotthepony.mc.otm.capability.moveEnergy +import ru.dbotthepony.mc.otm.capability.moveFluid import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.getValue import ru.dbotthepony.mc.otm.core.ifPresentK @@ -84,6 +87,153 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo } } + inner class ConfigurableFluidHandler( + val capability: T, + + val possibleModes: FlowDirection = FlowDirection.BI_DIRECTIONAL, + + val frontDefault: FlowDirection = possibleModes, + val backDefault: FlowDirection = possibleModes, + val leftDefault: FlowDirection = possibleModes, + val rightDefault: FlowDirection = possibleModes, + val topDefault: FlowDirection = possibleModes, + val bottomDefault: FlowDirection = possibleModes, + ) { + init { + exposeSideless(ForgeCapabilities.FLUID_HANDLER, capability) + } + + val front = Piece(RelativeSide.FRONT).also { it.flow = frontDefault } + val back = Piece(RelativeSide.BACK).also { it.flow = backDefault } + val left = Piece(RelativeSide.LEFT).also { it.flow = leftDefault } + val right = Piece(RelativeSide.RIGHT).also { it.flow = rightDefault } + val top = Piece(RelativeSide.TOP).also { it.flow = topDefault } + val bottom = Piece(RelativeSide.BOTTOM).also { it.flow = bottomDefault } + + val pieces = immutableMap { + put(RelativeSide.FRONT, front) + put(RelativeSide.BACK, back) + put(RelativeSide.LEFT, left) + put(RelativeSide.RIGHT, right) + put(RelativeSide.TOP, top) + put(RelativeSide.BOTTOM, bottom) + } + + val defaults = immutableMap { + put(RelativeSide.FRONT, frontDefault) + put(RelativeSide.BACK, backDefault) + put(RelativeSide.LEFT, leftDefault) + put(RelativeSide.RIGHT, rightDefault) + put(RelativeSide.TOP, topDefault) + put(RelativeSide.BOTTOM, bottomDefault) + } + + inner class Piece(val side: RelativeSide) : IFluidHandler, ITickable { + init { + tickList.always(this) + + // https://tenor.com/view/simp-metal-gear-liquid-snake-running-gif-16717852 + savetables.enum(::flow, "fluid_${side}_flow", FlowDirection::valueOf) + savetables.bool(::automatePull, "fluid_${side}_pull") + savetables.bool(::automatePush, "fluid_${side}_push") + } + + private val controller = sides[side]!!.Cap(ForgeCapabilities.FLUID_HANDLER, this) + private val neighbour by sides[side]!!.track(ForgeCapabilities.FLUID_HANDLER) + + var flow by synchronizer.enum(possibleModes, setter = { value, access, setByRemote -> + require(possibleModes.isSupertype(value)) { "Energy mode $value is not allowed (allowed modes: ${possibleModes.family})" } + + if (access.read() != value) { + access.write(value) + + if (value == FlowDirection.NONE) { + controller.close() + } else { + controller.close() + controller.expose() + } + } + }) + + // var automatePull by synchronizer.bool().property + var automatePull = false + // var automatePush by synchronizer.bool().property + var automatePush = false + + override fun tick() { + if (flow == FlowDirection.NONE || !automatePull && !automatePush || redstoneControl.isBlockedByRedstone) + return + + neighbour.ifPresentK { + if (flow.input && automatePull) { + moveFluid(source = it, destination = capability) + } + + if (flow.output && automatePush) { + moveFluid(source = capability, destination = it) + } + } + } + + override fun getTanks(): Int { + if (flow == FlowDirection.NONE) { + return 0 + } else { + return capability.getTanks() + } + } + + override fun getFluidInTank(tank: Int): FluidStack { + if (flow == FlowDirection.NONE) { + return FluidStack.EMPTY + } else { + return capability.getFluidInTank(tank) + } + } + + override fun getTankCapacity(tank: Int): Int { + if (flow == FlowDirection.NONE) { + return 0 + } else { + return capability.getTankCapacity(tank) + } + } + + override fun isFluidValid(tank: Int, stack: FluidStack): Boolean { + if (flow.input) { + return capability.isFluidValid(tank, stack) + } else { + return false + } + } + + override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int { + if (flow.input) { + return capability.fill(resource, action) + } else { + return 0 + } + } + + override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack { + if (flow.output) { + return capability.drain(resource, action) + } else { + return FluidStack.EMPTY + } + } + + override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack { + if (flow.output) { + return capability.drain(maxDrain, action) + } else { + return FluidStack.EMPTY + } + } + } + } + inner class ConfigurableEnergy( val capability: T, @@ -129,6 +279,25 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo private val capControllers = exposeEnergy(side, this@Piece) private val neighbour by sides[side]!!.track(ForgeCapabilities.ENERGY) + override var batteryLevel: Decimal by capability::batteryLevel + override val maxBatteryLevel: Decimal by capability::maxBatteryLevel + override val missingPower: Decimal by capability::missingPower + + override val canSetBatteryLevel: Boolean by capability::canSetBatteryLevel + + // var automatePull by synchronizer.bool().property + var automatePull = false + // var automatePush by synchronizer.bool().property + var automatePush = false + + init { + tickList.always(this) + + savetables.enum(::energyFlow, "energy_${side}_flow", FlowDirection::valueOf) + savetables.bool(::automatePull, "energy_${side}_pull") + savetables.bool(::automatePush, "energy_${side}_push") + } + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { return capability.extractEnergy(howMuch, simulate) } @@ -151,12 +320,6 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo return Decimal.ZERO } - override var batteryLevel: Decimal by capability::batteryLevel - override val maxBatteryLevel: Decimal by capability::maxBatteryLevel - override val missingPower: Decimal by capability::missingPower - - override val canSetBatteryLevel: Boolean by capability::canSetBatteryLevel - override fun drainBattery(): Boolean { return capability.drainBattery() } @@ -180,10 +343,6 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo } } - init { - tickList.always(this) - } - override var energyFlow by synchronizer.enum(possibleModes, setter = { value, access, setByRemote -> require(possibleModes.isSupertype(value)) { "Energy mode $value is not allowed (allowed modes: ${possibleModes.family})" } @@ -201,15 +360,6 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo } } }) - - var automatePull by synchronizer.bool().property - var automatePush by synchronizer.bool().property - - init { - savetables.enum(::energyFlow, "energy_${side}_flow", FlowDirection::valueOf) - savetables.bool(::automatePull, "energy_${side}_pull") - savetables.bool(::automatePush, "energy_${side}_push") - } } } @@ -311,9 +461,7 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo put(RelativeSide.BOTTOM, bottomDefault) } - inner class Piece( - val side: RelativeSide, - ) : IItemHandler, ITickable { + inner class Piece(val side: RelativeSide) : IItemHandler, ITickable { private var currentHandler: IItemHandler = EmptyItemHandler private val capController = sides[side]!!.Cap(ForgeCapabilities.ITEM_HANDLER, this) private val neighbour by sides[side]!!.track(ForgeCapabilities.ITEM_HANDLER) @@ -346,7 +494,7 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo } }) - var automatePull by synchronizer.bool(setter = { value, access, _ -> + /*var automatePull by synchronizer.bool(setter = { value, access, _ -> if (access.readBoolean() != value) { access.write(value) @@ -366,7 +514,31 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo outerSlotPush = 0 } } - }).property + }).property*/ + + var automatePull = false + set(value) { + if (field != value) { + field = value + + if (value) { + innerSlotPush = 0 + outerSlotPush = 0 + } + } + } + + var automatePush = false + set(value) { + if (field != value) { + field = value + + if (value) { + innerSlotPush = 0 + outerSlotPush = 0 + } + } + } init { savetables.bool(::automatePull, "itemhandler_${side}_automatePull") diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt index f10dc1c0a..5b184133e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt @@ -62,8 +62,9 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery bottomDefault = ItemHandlerMode.INPUT_OUTPUT, ) + val fluidConfig = ConfigurableFluidHandler(fluid) + init { - exposeGlobally(ForgeCapabilities.FLUID_HANDLER, fluid) savetables.stateful(::fluid, FLUID_KEY) savetables.stateful(::fillInput) savetables.stateful(::drainInput) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt index 75194029f..a34697f1c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt @@ -106,6 +106,7 @@ object Widgets18 { val BATTERY_ONLY = controlsGrid.next() val ITEMS_CONFIGURATION = controlsGrid.next() val ENERGY_CONFIGURATION = controlsGrid.next() + val FLUID_CONFIGURATION = controlsGrid.next() val LEFT_CONTROLS_ITEMS = controls2(LEFT_CONTROLS) val RIGHT_CONTROLS_ITEMS = controls2(RIGHT_CONTROLS) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt index 4f9f9f00e..9edcc6387 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt @@ -30,7 +30,7 @@ class FluidTankScreen(menu: FluidTankMenu, inventory: Inventory, title: Componen SlotPanel(this, frame, menu.output, x = 30f + s.width + 4f + 20f, y = 53f) - makeDeviceControls(this, frame, itemConfig = menu.itemConfig, redstoneConfig = menu.redstoneConfig) + makeDeviceControls(this, frame, itemConfig = menu.itemConfig, redstoneConfig = menu.redstoneConfig, fluidConfig = menu.fluidConfig) makeCuriosPanel(this, frame, menu.equipment.curiosSlots, autoAlign = true) PlayerEquipmentPanel(this, frame, armorSlots = menu.equipment.armorSlots).also { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt index 0d9c8f7f2..d89dbc847 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt @@ -13,6 +13,7 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.FluidConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import java.util.function.Predicate @@ -66,6 +67,18 @@ private fun > makeEnergyModeButton(screen: S, parent: Frame return button } +private fun > makeFluidModeButton(screen: S, parent: FramePanel, input: FluidConfigPlayerInput.Piece, side: RelativeSide): LargeEnumRectangleButtonPanel { + val button = LargeEnumRectangleButtonPanel(screen, parent, enum = FlowDirection::class.java, prop = input.input, defaultValue = input.default) + + for (v in FlowDirection.values()) { + button.add(v, skinElement = Widgets18.CONTROLS[side]!![v]!!, tooltip = TranslatableComponent(v.translationKey)) + } + + button.finish() + + return button +} + private fun moveButtons( front: EditablePanel<*>, back: EditablePanel<*>, @@ -191,6 +204,35 @@ private fun > makeEnergyConfigPanel( return frame } +private fun > makeFluidConfigPanel( + screen: S, + inputs: FluidConfigPlayerInput +): FramePanel { + val frame = object : FramePanel(screen, 78f, 80f, TranslatableComponent("otm.gui.sides.fluid_config")) { + override fun tickInner() { + super.tickInner() + + if (!isEverFocused()) { + remove() + } + } + } + + val front = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.FRONT]!!, RelativeSide.FRONT).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + val back = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.BACK]!!, RelativeSide.BACK).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + val left = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.LEFT]!!, RelativeSide.LEFT).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + val right = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.RIGHT]!!, RelativeSide.RIGHT).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + val top = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.TOP]!!, RelativeSide.TOP).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + val bottom = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.BOTTOM]!!, RelativeSide.BOTTOM).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + + pullPush(frame, inputs.pull, inputs.push) + moveButtons(front, back, left, right, top, bottom) + screen.addPanel(frame) + frame.requestFocus() + + return frame +} + class DeviceControls>( screen: S, parent: FramePanel, @@ -198,9 +240,11 @@ class DeviceControls>( val redstoneConfig: IPlayerInputWithFeedback? = null, val itemConfig: ItemConfigPlayerInput? = null, val energyConfig: EnergyConfigPlayerInput? = null, + val fluidConfig: FluidConfigPlayerInput? = null, ) : EditablePanel(screen, parent, x = parent.width + 3f, height = 0f, width = 0f) { val itemConfigButton: LargeRectangleButtonPanel? val energyConfigButton: LargeRectangleButtonPanel? + val fluidConfigButton: LargeRectangleButtonPanel? val redstoneControlsButton: LargeEnumRectangleButtonPanel? private var nextY = 0f @@ -262,6 +306,25 @@ class DeviceControls>( } else { energyConfigButton = null } + + if (fluidConfig != null) { + fluidConfigButton = addButton(object : LargeRectangleButtonPanel(screen, this@DeviceControls, y = nextY, skinElement = Widgets18.FLUID_CONFIGURATION) { + init { + tooltip = TranslatableComponent("otm.gui.sides.fluid_config") + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + val frame = makeFluidConfigPanel(screen, fluidConfig) + + frame.x = absoluteX + width / 2f - frame.width / 2f + frame.y = absoluteY + height + 8f + } + } + }) + } else { + fluidConfigButton = null + } } override fun tickInner() { @@ -278,6 +341,7 @@ fun > makeDeviceControls( redstoneConfig: IPlayerInputWithFeedback? = null, itemConfig: ItemConfigPlayerInput? = null, energyConfig: EnergyConfigPlayerInput? = null, + fluidConfig: FluidConfigPlayerInput? = null, ): DeviceControls { - return DeviceControls(screen, parent, extra = extra, redstoneConfig = redstoneConfig, itemConfig = itemConfig, energyConfig = energyConfig) + return DeviceControls(screen, parent, extra = extra, redstoneConfig = redstoneConfig, itemConfig = itemConfig, energyConfig = energyConfig, fluidConfig = fluidConfig) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt index 670a2a580..ce92d4f66 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt @@ -27,7 +27,7 @@ import kotlin.reflect.KProperty0 class Savetables : INBTSerializable { private val entries = ArrayList>() - interface Entry : INBTSerializable { + sealed interface Entry : INBTSerializable { val name: String val type: Class fun validate() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt index e7e1b4693..cce696644 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt @@ -13,6 +13,7 @@ import ru.dbotthepony.mc.otm.menu.MachineOutputSlot import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatterySlot import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.FluidConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.widget.FluidGaugeWidget import ru.dbotthepony.mc.otm.registry.MMenus @@ -22,6 +23,7 @@ class FluidTankMenu(containerId: Int, inventory: Inventory, tile: FluidTankBlock val equipment = makeEquipmentSlots(true) val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) val redstoneConfig = EnumInputWithFeedback(this) + val fluidConfig = FluidConfigPlayerInput(this, tile?.fluidConfig) val drainInput = object : MatterySlot(tile?.drainInput ?: SimpleContainer(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/FluidConfigPlayerInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/FluidConfigPlayerInput.kt new file mode 100644 index 000000000..aa0afad4e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/FluidConfigPlayerInput.kt @@ -0,0 +1,68 @@ +package ru.dbotthepony.mc.otm.menu.input + +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.menu.MatteryMenu + +/** + * [allowPull] and [allowPush] controls whenever player is allowed to change these options + */ +class FluidConfigPlayerInput(val menu: MatteryMenu, config: MatteryDeviceBlockEntity.ConfigurableFluidHandler<*>? = null, val allowPull: Boolean = false, val allowPush: Boolean = false) { + var possibleModes by menu.mSynchronizer.enum(FlowDirection::class.java) + private set + + inner class Piece(val side: RelativeSide) { + val pull = BooleanInputWithFeedback(menu) + val push = BooleanInputWithFeedback(menu) + val input = EnumInputWithFeedback(menu) + + var default by menu.mSynchronizer.enum(FlowDirection.NONE) + + init { + pull.filter { allowPull } + push.filter { allowPush } + } + + fun with(config: MatteryDeviceBlockEntity.ConfigurableFluidHandler<*>.Piece, parent: MatteryDeviceBlockEntity.ConfigurableFluidHandler<*>) { + pull.with(config::automatePull) + push.with(config::automatePush) + input.withSupplier { config.flow }.withConsumer { if (parent.possibleModes.isSupertype(it)) config.flow = it } + } + } + + val pieces = immutableMap { for (side in RelativeSide.values()) put(side, Piece(side)) } + + // TODO + val pull = BooleanInputWithFeedback(menu) + + // TODO + val push = BooleanInputWithFeedback(menu) + + init { + pull.filter { allowPull } + push.filter { allowPush } + } + + fun with(config: MatteryDeviceBlockEntity.ConfigurableFluidHandler<*>) { + possibleModes = config.possibleModes + + for ((side, v) in config.pieces) { + pieces[side]!!.with(v, config) + pieces[side]!!.default = config.defaults[side]!! + } + + pull.withSupplier { pieces.values.all { it.pull.value } } + push.withSupplier { pieces.values.all { it.push.value } } + + pull.withConsumer { v -> pieces.values.forEach { it.pull.input.invoke(v) } } + push.withConsumer { v -> pieces.values.forEach { it.push.input.invoke(v) } } + } + + init { + if (config != null) { + with(config) + } + } +} diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.png index 30dfc45a74927934ac1da9d45c7a56a12abc9060..b9553b67fcfaa477c3745da08c68d80db3bd8028 100644 GIT binary patch delta 2011 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