Configurable Fluid Handler

This commit is contained in:
DBotThePony 2023-05-31 14:09:12 +07:00
parent e373512a61
commit 068e6ba3f9
Signed by: DBot
GPG Key ID: DCC23B5715498507
12 changed files with 338 additions and 28 deletions

View File

@ -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")

View File

@ -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", "Нижняя сторона")

View File

@ -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<T : IFluidHandler>(
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<T : IMatteryEnergyStorage>(
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")

View File

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

View File

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

View File

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

View File

@ -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 <S : MatteryScreen<*>> makeEnergyModeButton(screen: S, parent: Frame
return button
}
private fun <S : MatteryScreen<*>> makeFluidModeButton(screen: S, parent: FramePanel<S>, input: FluidConfigPlayerInput.Piece, side: RelativeSide): LargeEnumRectangleButtonPanel<S, FlowDirection> {
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 <S : MatteryScreen<*>> makeEnergyConfigPanel(
return frame
}
private fun <S : MatteryScreen<*>> makeFluidConfigPanel(
screen: S,
inputs: FluidConfigPlayerInput
): FramePanel<S> {
val frame = object : FramePanel<S>(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<out S : MatteryScreen<*>>(
screen: S,
parent: FramePanel<S>,
@ -198,9 +240,11 @@ class DeviceControls<out S : MatteryScreen<*>>(
val redstoneConfig: IPlayerInputWithFeedback<RedstoneSetting>? = null,
val itemConfig: ItemConfigPlayerInput? = null,
val energyConfig: EnergyConfigPlayerInput? = null,
val fluidConfig: FluidConfigPlayerInput? = null,
) : EditablePanel<S>(screen, parent, x = parent.width + 3f, height = 0f, width = 0f) {
val itemConfigButton: LargeRectangleButtonPanel<S>?
val energyConfigButton: LargeRectangleButtonPanel<S>?
val fluidConfigButton: LargeRectangleButtonPanel<S>?
val redstoneControlsButton: LargeEnumRectangleButtonPanel<S, RedstoneSetting>?
private var nextY = 0f
@ -262,6 +306,25 @@ class DeviceControls<out S : MatteryScreen<*>>(
} else {
energyConfigButton = null
}
if (fluidConfig != null) {
fluidConfigButton = addButton(object : LargeRectangleButtonPanel<S>(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 <S : MatteryScreen<*>> makeDeviceControls(
redstoneConfig: IPlayerInputWithFeedback<RedstoneSetting>? = null,
itemConfig: ItemConfigPlayerInput? = null,
energyConfig: EnergyConfigPlayerInput? = null,
fluidConfig: FluidConfigPlayerInput? = null,
): DeviceControls<S> {
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)
}

View File

@ -27,7 +27,7 @@ import kotlin.reflect.KProperty0
class Savetables : INBTSerializable<CompoundTag?> {
private val entries = ArrayList<Entry<*, *>>()
interface Entry<V : Any?, T : Tag?> : INBTSerializable<T?> {
sealed interface Entry<V : Any?, T : Tag?> : INBTSerializable<T?> {
val name: String
val type: Class<T>
fun validate()

View File

@ -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<RedstoneSetting>(this)
val fluidConfig = FluidConfigPlayerInput(this, tile?.fluidConfig)
val drainInput = object : MatterySlot(tile?.drainInput ?: SimpleContainer(1), 0) {
override fun mayPlace(itemStack: ItemStack): Boolean {

View File

@ -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<FlowDirection>(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)
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB