Skip to content

Commit 7514f99

Browse files
authored
Merge pull request #169 from pylonmc/migrate-fluids-to-core
2 parents 8040c2b + af84ba5 commit 7514f99

57 files changed

Lines changed: 1458 additions & 158 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@ import io.github.pylonmc.pylon.core.block.base.PylonGuiBlock
88
import io.github.pylonmc.pylon.core.block.base.PylonSimpleMultiblock
99
import io.github.pylonmc.pylon.core.block.waila.Waila
1010
import io.github.pylonmc.pylon.core.command.PylonCommand
11-
import io.github.pylonmc.pylon.core.debug.DebugWaxedWeatheredCutCopperStairs
11+
import io.github.pylonmc.pylon.core.content.debug.DebugWaxedWeatheredCutCopperStairs
12+
import io.github.pylonmc.pylon.core.content.fluid.FluidPipeConnector
13+
import io.github.pylonmc.pylon.core.content.fluid.FluidPipeDisplay
14+
import io.github.pylonmc.pylon.core.content.fluid.FluidPipeMarker
15+
import io.github.pylonmc.pylon.core.content.fluid.FluidPointDisplay
16+
import io.github.pylonmc.pylon.core.content.fluid.FluidPointInteraction
1217
import io.github.pylonmc.pylon.core.entity.EntityListener
1318
import io.github.pylonmc.pylon.core.entity.EntityStorage
1419
import io.github.pylonmc.pylon.core.entity.PylonEntity
15-
import io.github.pylonmc.pylon.core.guide.PylonGuide
20+
import io.github.pylonmc.pylon.core.fluid.connecting.ConnectingService
21+
import io.github.pylonmc.pylon.core.content.guide.PylonGuide
1622
import io.github.pylonmc.pylon.core.i18n.AddonTranslator
1723
import io.github.pylonmc.pylon.core.i18n.MinecraftTranslator
1824
import io.github.pylonmc.pylon.core.item.PylonItem
@@ -26,6 +32,8 @@ import org.bukkit.Bukkit
2632
import org.bukkit.Material
2733
import org.bukkit.NamespacedKey
2834
import org.bukkit.entity.BlockDisplay
35+
import org.bukkit.entity.Interaction
36+
import org.bukkit.entity.ItemDisplay
2937
import org.bukkit.plugin.java.JavaPlugin
3038
import xyz.xenondevs.invui.InvUI
3139
import java.util.Locale
@@ -60,6 +68,7 @@ object PylonCore : JavaPlugin(), PylonAddon {
6068
Bukkit.getPluginManager().registerEvents(PylonEntityHolderBlock, this)
6169
Bukkit.getPluginManager().registerEvents(PylonSimpleMultiblock, this)
6270
Bukkit.getPluginManager().registerEvents(PylonRecipeListener, this)
71+
Bukkit.getPluginManager().registerEvents(ConnectingService, this)
6372

6473
Bukkit.getScheduler().runTaskTimer(
6574
this,
@@ -79,25 +88,31 @@ object PylonCore : JavaPlugin(), PylonAddon {
7988

8089
manager.registerCommand(PylonCommand())
8190

82-
PylonItem.register(DebugWaxedWeatheredCutCopperStairs::class.java, DebugWaxedWeatheredCutCopperStairs.STACK)
91+
PylonItem.register<DebugWaxedWeatheredCutCopperStairs>(DebugWaxedWeatheredCutCopperStairs.STACK)
8392
PylonGuide.hideItem(DebugWaxedWeatheredCutCopperStairs.KEY)
8493

85-
PylonItem.register(PhantomBlock.ErrorItem::class.java, PhantomBlock.ErrorItem.STACK)
94+
PylonItem.register<PhantomBlock.ErrorItem>(PhantomBlock.ErrorItem.STACK)
8695
PylonGuide.hideItem(PhantomBlock.ErrorItem.KEY)
8796

88-
PylonItem.register(PylonGuide::class.java, PylonGuide.STACK)
97+
PylonItem.register<PylonGuide>(PylonGuide.STACK)
8998
PylonGuide.hideItem(PylonGuide.KEY)
9099

91-
PylonEntity.register(
100+
PylonEntity.register<BlockDisplay, PylonSimpleMultiblock.MultiblockGhostBlock>(
92101
PylonSimpleMultiblock.MultiblockGhostBlock.KEY,
93-
BlockDisplay::class.java,
94-
PylonSimpleMultiblock.MultiblockGhostBlock::class.java
95102
)
96103

104+
PylonEntity.register<ItemDisplay, FluidPointDisplay>(FluidPointDisplay.KEY)
105+
PylonEntity.register<Interaction, FluidPointInteraction>(FluidPointInteraction.KEY)
106+
PylonEntity.register<ItemDisplay, FluidPipeDisplay>(FluidPipeDisplay.KEY)
107+
108+
PylonBlock.register<FluidPipeMarker>(FluidPipeMarker.KEY, Material.STRUCTURE_VOID)
109+
PylonBlock.register<FluidPipeConnector>(FluidPipeConnector.KEY, Material.STRUCTURE_VOID)
110+
97111
RecipeType.addVanillaRecipes()
98112
}
99113

100114
override fun onDisable() {
115+
ConnectingService.cleanup()
101116
BlockStorage.cleanupEverything()
102117
EntityStorage.cleanupEverything()
103118
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,15 @@ object BlockStorage : Listener {
338338
chunkAutosaveTasks[event.chunk.position] = PylonCore.launch(PylonCore.minecraftDispatcher) {
339339

340340
// Wait a random delay before starting, this is to help smooth out lag from saving
341-
delay(Random.nextLong(PylonConfig.entityDataAutosaveIntervalSeconds * 1000))
341+
delay(Random.nextLong(PylonConfig.blockDataAutosaveIntervalSeconds * 1000))
342342

343343
while (true) {
344344
lockBlockRead {
345345
val blocksInChunk = blocksByChunk[event.chunk.position]
346346
check(blocksInChunk != null) { "Block autosave task was not cancelled properly" }
347347
save(event.chunk, blocksInChunk)
348348
}
349-
delay(PylonConfig.entityDataAutosaveIntervalSeconds * 1000)
349+
delay(PylonConfig.blockDataAutosaveIntervalSeconds * 1000)
350350
}
351351
}
352352
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ open class PylonBlock protected constructor(val block: Block) {
112112
PylonRegistry.BLOCKS.register(schema)
113113
}
114114

115+
@JvmSynthetic
116+
inline fun <reified T: PylonBlock> register(key: NamespacedKey, material: Material)
117+
= register(key, material, T::class.java)
118+
115119
@JvmSynthetic
116120
internal fun serialize(
117121
block: PylonBlock,

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@ interface PylonFluidBlock {
99

1010
/**
1111
* Returns a map of fluid types - and their corresponding amounts - that can be supplied by
12-
* a particular connection point. deltaSeconds is the time since the last fluid tick.
12+
* the block. deltaSeconds is the time since the last fluid tick.
1313
*
1414
* If you have a machine that can supply up to 100 fluid per second, it should supply
1515
* 100*deltaSeconds of that fluid
1616
*
17-
* Any implementation of this method must NEVER call the same method for another connection
18-
* point, otherwise you risk creating infinite loops.
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.
19+
*
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.
1923
*/
20-
fun getSuppliedFluids(connectionPoint: String, deltaSeconds: Double): Map<PylonFluid, Double> = mapOf()
24+
fun getSuppliedFluids(deltaSeconds: Double): Map<PylonFluid, Double> = mapOf()
2125

2226
/**
2327
* Returns a map of fluid types - and their corresponding amounts - that can be *taken in* by
@@ -27,24 +31,28 @@ interface PylonFluidBlock {
2731
* If you have a machine that consumes 100 fluid per second, it should request
2832
* 100*deltaSeconds of that fluid
2933
*
30-
* Any implementation of this method must NEVER call the same method for another connection
31-
* point, otherwise you risk creating infinite loops.
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.
36+
*
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.
3240
*/
33-
fun getRequestedFluids(connectionPoint: String, deltaSeconds: Double): Map<PylonFluid, Double> = mapOf()
41+
fun getRequestedFluids(deltaSeconds: Double): Map<PylonFluid, Double> = mapOf()
3442

3543
/**
3644
* `amount` is always at most `getRequestedFluids(connectionPoint).get(fluid)` and will never
3745
* be zero or less.
3846
*/
39-
fun addFluid(connectionPoint: String, fluid: PylonFluid, amount: Double) {
47+
fun addFluid(fluid: PylonFluid, amount: Double) {
4048
error("Block requested fluids, but does not implement addFluid")
4149
}
4250

4351
/**
4452
* `amount` is always at least `getSuppliedFluids(connectionPoint).get(fluid)` and will never
4553
* be zero or less.
4654
*/
47-
fun removeFluid(connectionPoint: String, fluid: PylonFluid, amount: Double) {
55+
fun removeFluid(fluid: PylonFluid, amount: Double) {
4856
error("Block supplied fluids, but does not implement removeFluid")
4957
}
5058
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import com.github.shynixn.mccoroutine.bukkit.launch
77
import io.github.pylonmc.pylon.core.PylonCore
88
import io.github.pylonmc.pylon.core.block.BlockStorage
99
import io.github.pylonmc.pylon.core.block.waila.Waila.Companion.wailaEnabled
10-
import io.github.pylonmc.pylon.core.debug.DebugWaxedWeatheredCutCopperStairs
11-
import io.github.pylonmc.pylon.core.guide.PylonGuide
10+
import io.github.pylonmc.pylon.core.content.debug.DebugWaxedWeatheredCutCopperStairs
11+
import io.github.pylonmc.pylon.core.content.guide.PylonGuide
1212
import io.github.pylonmc.pylon.core.i18n.PylonArgument
1313
import io.github.pylonmc.pylon.core.item.PylonItem
1414
import io.github.pylonmc.pylon.core.item.research.Research

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ object PylonConfig {
2828
@JvmStatic
2929
val researchesEnabled: Boolean = config.getOrThrow("research.enabled")
3030

31-
@JvmStatic
32-
val researchCheckInterval: Int = config.getOrThrow("research.interval")
31+
val PIPE_PLACEMENT_TASK_INTERVAL_TICKS: Long = config.getOrThrow<Int>("pipe-placement.task-interval-ticks").toLong()
32+
33+
val PIPE_PLACEMENT_MAX_DISTANCE: Long = config.getOrThrow<Int>("pipe-placement.max-distance").toLong()
34+
3335

3436
@JvmStatic
3537
val translationWrapLimit: Int by config

pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/debug/DebugWaxedWeatheredCutCopperStairs.kt renamed to pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/debug/DebugWaxedWeatheredCutCopperStairs.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.github.pylonmc.pylon.core.debug
1+
package io.github.pylonmc.pylon.core.content.debug
22

33
import io.github.pylonmc.pylon.core.block.BlockStorage
44
import io.github.pylonmc.pylon.core.block.TickManager
@@ -104,4 +104,4 @@ class DebugWaxedWeatheredCutCopperStairs(stack: ItemStack)
104104

105105
private fun Audience.sendDebug(subkey: String, vararg args: TranslationArgument) {
106106
return sendMessage(Component.translatable("pylon.pyloncore.message.debug.$subkey", *args))
107-
}
107+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package io.github.pylonmc.pylon.core.content.fluid
2+
3+
import io.github.pylonmc.pylon.core.block.BlockStorage
4+
import io.github.pylonmc.pylon.core.entity.EntityStorage
5+
import io.github.pylonmc.pylon.core.fluid.FluidManager
6+
import io.github.pylonmc.pylon.core.fluid.PylonFluid
7+
import io.github.pylonmc.pylon.core.fluid.connecting.ConnectingPointInteraction
8+
import io.github.pylonmc.pylon.core.fluid.connecting.ConnectingPointNewBlock
9+
import io.github.pylonmc.pylon.core.fluid.connecting.ConnectingPointPipeConnector
10+
import io.github.pylonmc.pylon.core.fluid.connecting.ConnectingPointPipeMarker
11+
import io.github.pylonmc.pylon.core.fluid.connecting.ConnectingService
12+
import io.github.pylonmc.pylon.core.fluid.tags.FluidTemperature
13+
import io.github.pylonmc.pylon.core.item.PylonItem
14+
import io.github.pylonmc.pylon.core.item.base.PylonInteractor
15+
import io.github.pylonmc.pylon.core.item.base.PylonItemEntityInteractor
16+
import io.github.pylonmc.pylon.core.util.gui.unit.UnitFormat
17+
import io.github.pylonmc.pylon.core.util.position.BlockPosition
18+
import net.kyori.adventure.text.Component
19+
import net.kyori.adventure.text.ComponentLike
20+
import net.kyori.adventure.text.JoinConfiguration
21+
import org.bukkit.Material
22+
import org.bukkit.block.Block
23+
import org.bukkit.entity.Player
24+
import org.bukkit.event.block.Action
25+
import org.bukkit.event.player.PlayerInteractEntityEvent
26+
import org.bukkit.event.player.PlayerInteractEvent
27+
import org.bukkit.inventory.EquipmentSlot
28+
import org.bukkit.inventory.ItemStack
29+
import java.util.function.Predicate
30+
31+
open class FluidPipe(stack: ItemStack) : PylonItem(stack), PylonItemEntityInteractor, PylonInteractor {
32+
val material = Material.valueOf(getSettings().getOrThrow<String>("material").uppercase())
33+
val fluidPerSecond = getSettings().getOrThrow<Double>("fluid-per-second")
34+
val allowedFluids = getSettings().getOrThrow<List<String>>("allow-fluids")
35+
.map { s -> FluidTemperature.valueOf(s.uppercase()) }
36+
37+
override fun getPlaceholders(): Map<String, ComponentLike>
38+
= mapOf<String, ComponentLike>(
39+
"fluid_per_second" to UnitFormat.MILLIBUCKETS_PER_SECOND.format(fluidPerSecond),
40+
"fluids" to Component.join(
41+
JoinConfiguration.separator(Component.text(", ")),
42+
allowedFluids.map(FluidTemperature::valueText)
43+
)
44+
)
45+
46+
fun getPredicate(): Predicate<PylonFluid>
47+
= Predicate<PylonFluid> { fluid ->
48+
fluid.hasTag<FluidTemperature>() && allowedFluids.contains(fluid.getTag<FluidTemperature>())
49+
}
50+
51+
override fun onUsedToRightClickEntity(event: PlayerInteractEntityEvent) {
52+
if (event.hand != EquipmentSlot.HAND) {
53+
return
54+
}
55+
56+
val interaction = EntityStorage.get(event.rightClicked)
57+
if (interaction is FluidPointInteraction) {
58+
if (!ConnectingService.isConnecting(event.getPlayer())) {
59+
ConnectingService.startConnection(event.getPlayer(), ConnectingPointInteraction(interaction), this)
60+
return
61+
}
62+
}
63+
64+
if (ConnectingService.isConnecting(event.getPlayer())) {
65+
val segment = ConnectingService.placeConnection(event.getPlayer())
66+
if (segment != null) {
67+
FluidManager.setFluidPerSecond(segment, fluidPerSecond)
68+
FluidManager.setFluidPredicate(segment, this.getPredicate())
69+
}
70+
}
71+
}
72+
73+
override fun onUsedToRightClick(event: PlayerInteractEvent) {
74+
if (event.hand != EquipmentSlot.HAND) {
75+
return
76+
}
77+
78+
val action = event.getAction()
79+
val block: Block? = event.clickedBlock
80+
val player: Player = event.getPlayer()
81+
82+
if (block != null && action == Action.RIGHT_CLICK_BLOCK && !ConnectingService.isConnecting(player)) {
83+
if (!tryStartConnection(player, block)) {
84+
tryStartConnection(player, block.getRelative(event.getBlockFace()))
85+
}
86+
}
87+
88+
if ((action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK) && ConnectingService.isConnecting(player)) {
89+
val segment = ConnectingService.placeConnection(player)
90+
if (segment != null) {
91+
FluidManager.setFluidPerSecond(segment, fluidPerSecond)
92+
FluidManager.setFluidPredicate(segment, this.getPredicate())
93+
}
94+
}
95+
}
96+
97+
private fun tryStartConnection(player: Player, block: Block): Boolean {
98+
val pylonBlock = BlockStorage.get(block)
99+
if (pylonBlock is FluidPipeConnector) {
100+
if (pylonBlock.pipe != this) {
101+
player.sendActionBar(Component.translatable("pylon.pylonbase.message.pipe.not_of_same_type"))
102+
return true
103+
}
104+
val connectingPoint = ConnectingPointPipeConnector(pylonBlock)
105+
ConnectingService.startConnection(player, connectingPoint, this)
106+
return true
107+
}
108+
109+
if (pylonBlock is FluidPipeMarker) {
110+
val pipeDisplay = pylonBlock.getPipeDisplay()!!
111+
if (pipeDisplay.pipe != this) {
112+
player.sendActionBar(Component.translatable("pylon.pylonbase.message.pipe.not_of_same_type"))
113+
return true
114+
}
115+
val connectingPoint = ConnectingPointPipeMarker(pylonBlock)
116+
ConnectingService.startConnection(player, connectingPoint, this)
117+
return true
118+
}
119+
120+
if (block.type.isAir()) {
121+
val connectingPoint = ConnectingPointNewBlock(BlockPosition(block))
122+
ConnectingService.startConnection(player, connectingPoint, this)
123+
return true
124+
}
125+
126+
return false
127+
}
128+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.github.pylonmc.pylon.core.content.fluid
2+
3+
import io.github.pylonmc.pylon.core.block.PylonBlock
4+
import io.github.pylonmc.pylon.core.block.base.PylonEntityHolderBlock
5+
import io.github.pylonmc.pylon.core.block.context.BlockBreakContext
6+
import io.github.pylonmc.pylon.core.block.context.BlockBreakContext.PlayerBreak
7+
import io.github.pylonmc.pylon.core.block.context.BlockCreateContext
8+
import io.github.pylonmc.pylon.core.block.waila.WailaConfig
9+
import io.github.pylonmc.pylon.core.entity.EntityStorage
10+
import io.github.pylonmc.pylon.core.fluid.FluidPointType
11+
import io.github.pylonmc.pylon.core.item.PylonItem
12+
import io.github.pylonmc.pylon.core.util.pylonKey
13+
import org.bukkit.block.Block
14+
import org.bukkit.entity.Player
15+
import org.bukkit.inventory.ItemStack
16+
import org.bukkit.persistence.PersistentDataContainer
17+
18+
class FluidPipeConnector : PylonBlock, PylonEntityHolderBlock {
19+
20+
@Suppress("unused")
21+
constructor(block: Block, context: BlockCreateContext) : super(block)
22+
23+
@Suppress("unused")
24+
constructor(block: Block, pdc: PersistentDataContainer) : super(block)
25+
26+
override fun createEntities(context: BlockCreateContext)
27+
= mutableMapOf(
28+
"connector" to FluidPointInteraction.make(context, FluidPointType.CONNECTOR)
29+
)
30+
31+
val fluidPointInteraction
32+
get() = getHeldEntityOrThrow(FluidPointInteraction::class.java, "connector")
33+
34+
override fun onBreak(drops: MutableList<ItemStack>, context: BlockBreakContext) {
35+
var player: Player? = null
36+
if (context is PlayerBreak) {
37+
player = context.event.player
38+
}
39+
40+
// Clone to prevent ConcurrentModificationException if pipeDisplay.delete modified connectedPipeDisplays
41+
for (pipeDisplayId in fluidPointInteraction.connectedPipeDisplays.toSet()) {
42+
val pipeDisplay = EntityStorage.getAs<FluidPipeDisplay>(pipeDisplayId)
43+
// can be null if called from two different location (eg two different connection points removing the display)
44+
pipeDisplay?.delete(true, player)
45+
}
46+
47+
super<PylonEntityHolderBlock>.onBreak(drops, context)
48+
}
49+
50+
override fun getWaila(player: Player): WailaConfig
51+
= WailaConfig(name, mapOf("pipe" to this.pipe.stack.effectiveName()))
52+
53+
val pipe: PylonItem
54+
get() {
55+
check(fluidPointInteraction.connectedPipeDisplays.isNotEmpty())
56+
val uuid = fluidPointInteraction.connectedPipeDisplays.iterator().next()
57+
return EntityStorage.getAs<FluidPipeDisplay?>(uuid)!!.pipe
58+
}
59+
60+
companion object {
61+
val KEY = pylonKey("fluid_pipe_connector")
62+
}
63+
}

0 commit comments

Comments
 (0)