Skip to content

Commit f26be72

Browse files
authored
Merge pull request #181 from pylonmc/add-fluid-buffer-to-pylon-fluid-block
Fluid refactor
2 parents 8d0b599 + 7510d91 commit f26be72

20 files changed

Lines changed: 665 additions & 220 deletions

File tree

pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ package io.github.pylonmc.pylon.core
33
import co.aikar.commands.PaperCommandManager
44
import io.github.pylonmc.pylon.core.addon.PylonAddon
55
import io.github.pylonmc.pylon.core.block.*
6-
import io.github.pylonmc.pylon.core.block.base.PylonEntityHolderBlock
7-
import io.github.pylonmc.pylon.core.block.base.PylonGuiBlock
8-
import io.github.pylonmc.pylon.core.block.base.PylonSimpleMultiblock
6+
import io.github.pylonmc.pylon.core.block.base.*
97
import io.github.pylonmc.pylon.core.block.waila.Waila
108
import io.github.pylonmc.pylon.core.command.PylonCommand
119
import io.github.pylonmc.pylon.core.content.debug.DebugWaxedWeatheredCutCopperStairs
@@ -63,6 +61,8 @@ object PylonCore : JavaPlugin(), PylonAddon {
6361
Bukkit.getPluginManager().registerEvents(PylonGuiBlock, this)
6462
Bukkit.getPluginManager().registerEvents(PylonEntityHolderBlock, this)
6563
Bukkit.getPluginManager().registerEvents(PylonSimpleMultiblock, this)
64+
Bukkit.getPluginManager().registerEvents(PylonFluidBufferBlock, this)
65+
Bukkit.getPluginManager().registerEvents(PylonFluidTank, this)
6666
Bukkit.getPluginManager().registerEvents(PylonRecipeListener, this)
6767
Bukkit.getPluginManager().registerEvents(ConnectingService, this)
6868

pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonFluidBlock.kt

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,59 @@ package io.github.pylonmc.pylon.core.block.base
33
import io.github.pylonmc.pylon.core.fluid.PylonFluid
44

55
/**
6-
* A block that can supply or request fluids through fluid connections.
6+
* PylonFluidBlock must be implemented by any block that has fluid inputs/outputs.
7+
* This interface allowed you to request fluids from input points, supply fluids to
8+
* output points, and specify how to add/remove fluids from your block.
9+
*
10+
* At this time, having multiple inputs/outputs is not supported.
711
*/
12+
// TODO 'Confused? [Here is an explanation on how this works.](link)'
813
interface PylonFluidBlock {
914

1015
/**
1116
* Returns a map of fluid types - and their corresponding amounts - that can be supplied by
12-
* the block. deltaSeconds is the time since the last fluid tick.
17+
* the block for this fluid tick. deltaSeconds is the time since the last fluid tick.
1318
*
1419
* If you have a machine that can supply up to 100 fluid per second, it should supply
1520
* 100*deltaSeconds of that fluid
1621
*
17-
* WARNING 1: Any implementation of this method must NEVER call the same method for another
18-
* connection point, otherwise you risk creating infinite loops.
22+
* Any implementation of this method must NEVER call the same method for any other connection
23+
* point, otherwise you risk creating infinite loops.
1924
*
20-
* WARNING 2: This method is called for EVERY output on the machine. This means that the fluid
21-
* output is multiplied by however many output points you have. If you want to change this, just
22-
* divide every supplied fluid by the number of output points you have.
25+
* Called exactly one per fluid tick.
2326
*/
2427
fun getSuppliedFluids(deltaSeconds: Double): Map<PylonFluid, Double> = mapOf()
2528

2629
/**
27-
* Returns a map of fluid types - and their corresponding amounts - that can be *taken in* by
28-
* a particular connection point. For example, a tank should request enough fluid to fill up
29-
* to capacity.
30+
* Returns the amount of the given fluid that the machine wants to receive next tick.
3031
*
31-
* If you have a machine that consumes 100 fluid per second, it should request
32-
* 100*deltaSeconds of that fluid
32+
* If you have a machine that consumes 100 water per second, it should request
33+
* 100*deltaSeconds of water, and return 0 for every other fluid.
3334
*
34-
* WARNING 1: Any implementation of this method must NEVER call the same method for another
35-
* connection point, otherwise you risk creating infinite loops.
35+
* Any implementation of this method must NEVER call the same method for any other connection
36+
* point, otherwise you risk creating infinite loops.
3637
*
37-
* WARNING 2: This method is called for EVERY input on the machine. This means that the fluid
38-
* input is multiplied by however many input points you have. If you want to change this, just
39-
* divide every supplied fluid by the number of input points you have.
38+
* Called at most once for any given fluid type per tick.
4039
*/
41-
fun getRequestedFluids(deltaSeconds: Double): Map<PylonFluid, Double> = mapOf()
40+
fun fluidAmountRequested(fluid: PylonFluid, deltaSeconds: Double): Double = 0.0
4241

4342
/**
44-
* `amount` is always at most `getRequestedFluids(connectionPoint).get(fluid)` and will never
43+
* `amount` is always at most `getRequestedFluids().get(fluid)` and will never
4544
* be zero or less.
45+
*
46+
* Called at most once per fluid tick.
4647
*/
47-
fun addFluid(fluid: PylonFluid, amount: Double) {
48-
error("Block requested fluids, but does not implement addFluid")
48+
fun onFluidAdded(fluid: PylonFluid, amount: Double) {
49+
error("Block requested fluids, but does not implement onFluidAdded")
4950
}
5051

5152
/**
52-
* `amount` is always at least `getSuppliedFluids(connectionPoint).get(fluid)` and will never
53+
* `amount` is always at least `getSuppliedFluids().get(fluid)` and will never
5354
* be zero or less.
55+
*
56+
* Called at most once per fluid tick.
5457
*/
55-
fun removeFluid(fluid: PylonFluid, amount: Double) {
58+
fun onFluidRemoved(fluid: PylonFluid, amount: Double) {
5659
error("Block supplied fluids, but does not implement removeFluid")
5760
}
58-
}
61+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package io.github.pylonmc.pylon.core.block.base
2+
3+
import io.github.pylonmc.pylon.core.datatypes.PylonSerializers
4+
import io.github.pylonmc.pylon.core.event.PylonBlockDeserializeEvent
5+
import io.github.pylonmc.pylon.core.event.PylonBlockSerializeEvent
6+
import io.github.pylonmc.pylon.core.event.PylonBlockUnloadEvent
7+
import io.github.pylonmc.pylon.core.fluid.PylonFluid
8+
import io.github.pylonmc.pylon.core.util.pylonKey
9+
import org.bukkit.event.EventHandler
10+
import org.bukkit.event.Listener
11+
import org.jetbrains.annotations.ApiStatus
12+
import java.util.*
13+
import kotlin.math.max
14+
15+
/**
16+
* Usually, fluid machines store fluids in internal fluids. For example,
17+
* the press has an internal buffer used to store plant oil, of size
18+
* 1000mB by default. This is a common enough thing that we created a new
19+
* interface to handle it: PylonFluidBufferBlock. This interface allows
20+
* your block to easily manage fluid buffers.
21+
*
22+
* You will need to call `createFluidBuffer` when your block is placed
23+
* and specify the buffer's fluid type, capacity, whether it can take in
24+
* fluids from input points, and whether it can supply fluids to output
25+
* points.
26+
*
27+
* You do not need to handle saving buffers or implement any of the
28+
* `PylonFluidBlock` methods for this; this is all done automatically.
29+
*/
30+
interface PylonFluidBufferBlock : PylonFluidBlock {
31+
@get:ApiStatus.NonExtendable
32+
val fluidBuffers: MutableMap<PylonFluid, FluidBufferData>
33+
get() = bufferFluidBlocks.getOrPut(this, ::mutableMapOf)
34+
35+
@ApiStatus.NonExtendable
36+
fun fluidData(fluid: PylonFluid)
37+
= fluidBuffers[fluid] ?: error("Block does not contain ${fluid.key}")
38+
39+
/**
40+
* Creates a fluid buffer with the specified fluid type and capacity
41+
*
42+
* @param input whether this buffer can be added to by fluid input points
43+
* @param output whether this buffer can be taken from by fluid output points
44+
*/
45+
fun createFluidBuffer(fluid: PylonFluid, capacity: Double, input: Boolean, output: Boolean)
46+
= fluidBuffers.put(fluid, FluidBufferData(0.0, capacity, input, output))
47+
48+
/**
49+
* Deletes a fluid buffer
50+
*/
51+
fun deleteFluidBuffer(fluid: PylonFluid)
52+
= fluidBuffers.remove(fluid)
53+
54+
/**
55+
* Returns whether a buffer exists for this fluid
56+
*/
57+
fun hasFluid(fluid: PylonFluid): Boolean
58+
= fluid in fluidBuffers
59+
60+
/**
61+
* Returns the amount of fluid stored in a buffer
62+
*/
63+
fun fluidAmount(fluid: PylonFluid): Double
64+
= fluidData(fluid).amount
65+
66+
/**
67+
* Returns the capacity of a buffer
68+
*/
69+
fun fluidCapacity(fluid: PylonFluid): Double
70+
= fluidData(fluid).capacity
71+
72+
/**
73+
* Returns the amount of space remaining in a fluid buffer
74+
*/
75+
fun fluidSpaceRemaining(fluid: PylonFluid): Double
76+
= fluidCapacity(fluid) - fluidAmount(fluid)
77+
78+
/**
79+
* Sets the new capacity of a buffer. Any existing fluid will not be
80+
* removed, so you may end up with a buffer containing more fluid
81+
* than it technically has capacity for.
82+
*/
83+
fun setFluidCapacity(fluid: PylonFluid, capacity: Double) {
84+
check(capacity > -1.0e6)
85+
fluidData(fluid).capacity = max(0.0, capacity)
86+
}
87+
88+
/**
89+
* Checks if a new amount of fluid is greater than zero and fits inside
90+
* the corresponding buffer.
91+
*/
92+
fun canSetFluid(fluid: PylonFluid, amount: Double): Boolean
93+
= amount >= 0 && amount <= fluidData(fluid).capacity + 1.0e-6
94+
95+
/**
96+
* Sets a fluid buffer only if the new amount of fluid is greater
97+
* than zero and fits in the buffer.
98+
*
99+
* @return true only if the buffer was set successfully
100+
*/
101+
fun setFluid(fluid: PylonFluid, amount: Double): Boolean {
102+
if (canSetFluid(fluid, amount)) {
103+
fluidData(fluid).amount = amount
104+
return true
105+
}
106+
return false
107+
}
108+
109+
/**
110+
* Adds to a fluid buffer only if the new amount of fluid is greater
111+
* than zero and fits in the buffer.
112+
*
113+
* @return true only if the buffer was added to successfully
114+
*/
115+
fun addFluid(fluid: PylonFluid, amount: Double): Boolean {
116+
return setFluid(fluid, fluidData(fluid).amount + amount)
117+
}
118+
119+
/**
120+
* Removes from a fluid buffer only if the new amount of fluid is greater
121+
* than zero and fits in the buffer.
122+
*
123+
* @return true only if the buffer was added to successfully
124+
*/
125+
fun removeFluid(fluid: PylonFluid, amount: Double): Boolean {
126+
return setFluid(fluid, fluidData(fluid).amount - amount)
127+
}
128+
129+
override fun fluidAmountRequested(fluid: PylonFluid, deltaSeconds: Double): Double
130+
= if (hasFluid(fluid)) fluidSpaceRemaining(fluid) else 0.0
131+
132+
override fun getSuppliedFluids(deltaSeconds: Double): Map<PylonFluid, Double>
133+
= fluidBuffers.filter { it.value.output }.mapValues { it.value.amount }
134+
135+
override fun onFluidAdded(fluid: PylonFluid, amount: Double) {
136+
addFluid(fluid, amount)
137+
}
138+
139+
override fun onFluidRemoved(fluid: PylonFluid, amount: Double) {
140+
removeFluid(fluid, amount)
141+
}
142+
143+
@ApiStatus.Internal
144+
data class FluidBufferData(
145+
var amount: Double,
146+
var capacity: Double,
147+
var input: Boolean,
148+
var output: Boolean,
149+
)
150+
151+
companion object : Listener {
152+
153+
private val fluidBuffersKey = pylonKey("buffer_fluid_block_fluid_buffers")
154+
private val fluidBuffersType = PylonSerializers.MAP.mapTypeFrom(PylonSerializers.PYLON_FLUID, PylonSerializers.FLUID_BUFFER_DATA)
155+
156+
private val bufferFluidBlocks = IdentityHashMap<PylonFluidBufferBlock, MutableMap<PylonFluid, FluidBufferData>>()
157+
158+
@EventHandler
159+
private fun onDeserialize(event: PylonBlockDeserializeEvent) {
160+
val block = event.pylonBlock
161+
if (block is PylonFluidBufferBlock) {
162+
bufferFluidBlocks[block] = event.pdc.get(fluidBuffersKey, fluidBuffersType)?.toMutableMap()
163+
?: error("Fluid buffers not found for ${block.key}")
164+
}
165+
}
166+
167+
@EventHandler
168+
private fun onSerialize(event: PylonBlockSerializeEvent) {
169+
val block = event.pylonBlock
170+
if (block is PylonFluidBufferBlock) {
171+
event.pdc.set(fluidBuffersKey, fluidBuffersType, bufferFluidBlocks[block]!!)
172+
}
173+
}
174+
175+
@EventHandler
176+
private fun onUnload(event: PylonBlockUnloadEvent) {
177+
val block = event.pylonBlock
178+
if (block is PylonFluidBufferBlock) {
179+
bufferFluidBlocks.remove(block)
180+
}
181+
}
182+
}
183+
}

0 commit comments

Comments
 (0)