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.PylonBlockLoadEvent
6+ import io.github.pylonmc.pylon.core.event.PylonBlockSerializeEvent
7+ import io.github.pylonmc.pylon.core.event.PylonBlockUnloadEvent
8+ import io.github.pylonmc.pylon.core.recipe.PylonRecipe
9+ import io.github.pylonmc.pylon.core.recipe.RecipeType
10+ import io.github.pylonmc.pylon.core.util.gui.ProgressItem
11+ import io.github.pylonmc.pylon.core.util.pylonKey
12+ import org.bukkit.event.EventHandler
13+ import org.bukkit.event.Listener
14+ import org.jetbrains.annotations.ApiStatus
15+ import java.util.IdentityHashMap
16+
17+ /* *
18+ * An interface that stores and progresses a recipe.
19+ *
20+ * This does not actually handle the recipe inputs and outputs. Instead, it simply
21+ * tracks a recipe that is being processed and how much time is left on it, ticking
22+ * automatically.
23+ *
24+ * This interface overrides [PylonTickingBlock.tick], meaning the rate at which the
25+ * recipe ticks is determined by [PylonTickingBlock.setTickInterval].
26+ */
27+ interface PylonRecipeProcessor <T : PylonRecipe > : PylonTickingBlock {
28+
29+ data class RecipeProcessorData (
30+ var recipeType : RecipeType <* >? ,
31+ var currentRecipe : PylonRecipe ? ,
32+ var totalRecipeTicks : Int? ,
33+ var recipeTicksRemaining : Int? ,
34+ var progressItem : ProgressItem ? ,
35+ )
36+
37+ private val recipeProcessorData: RecipeProcessorData
38+ get() = recipeProcessorBlocks.getOrPut(this ) { RecipeProcessorData (null , null , null , null , null )}
39+
40+ val currentRecipe: T ?
41+ // cast should always be safe due to type restriction when starting recipe
42+ get() = recipeProcessorData.currentRecipe as T ?
43+
44+ val recipeTicksRemaining: Int?
45+ get() = recipeProcessorData.recipeTicksRemaining
46+
47+ /* *
48+ * Set the progress item that should be updated as the recipe progresses. Optional.
49+ *
50+ * Set once in your place constructor.
51+ */
52+ @ApiStatus.NonExtendable
53+ fun setRecipeType (type : RecipeType <T >) {
54+ recipeProcessorData.recipeType = type
55+ }
56+
57+ /* *
58+ * Set the progress item that should be updated as the recipe progresses. Optional.
59+ *
60+ * Does not persist; you must call this whenever the block is initialised (e.g.
61+ * in [io.github.pylonmc.pylon.core.block.PylonBlock.postInitialise])
62+ */
63+ @ApiStatus.NonExtendable
64+ fun setProgressItem (item : ProgressItem ) {
65+ recipeProcessorData.progressItem = item
66+ }
67+
68+ /* *
69+ * Starts a new recipe with duration [ticks], with [ticks] being the number of server
70+ * ticks the recipe will take.
71+ */
72+ fun startRecipe (recipe : T , ticks : Int ) {
73+ recipeProcessorData.currentRecipe = recipe
74+ recipeProcessorData.totalRecipeTicks = ticks
75+ recipeProcessorData.recipeTicksRemaining = ticks
76+ recipeProcessorData.progressItem?.setTotalTimeTicks(ticks)
77+ recipeProcessorData.progressItem?.setRemainingTimeTicks(ticks)
78+ }
79+
80+ fun onRecipeFinished (recipe : T )
81+
82+ override fun tick (deltaSeconds : Double ) {
83+ val data = recipeProcessorData
84+
85+ if (data.currentRecipe != null && data.recipeTicksRemaining != null ) {
86+ data.progressItem?.setRemainingTimeTicks(data.recipeTicksRemaining!! )
87+
88+ // tick recipe
89+ if (data.recipeTicksRemaining!! > 0 ) {
90+ data.recipeTicksRemaining = data.recipeTicksRemaining!! - tickInterval
91+ return
92+ }
93+
94+ // finish recipe
95+ onRecipeFinished(data.currentRecipe as T )
96+ data.currentRecipe = null
97+ data.totalRecipeTicks = null
98+ data.recipeTicksRemaining = null
99+ data.progressItem?.totalTime = null
100+ // cast should always be safe due to type restriction when starting recipe
101+ return
102+ }
103+ }
104+
105+ companion object : Listener {
106+
107+ private val recipeProcessorKey = pylonKey(" recipe_processor_data" )
108+
109+ private val recipeProcessorBlocks = IdentityHashMap <PylonRecipeProcessor <* >, RecipeProcessorData > ()
110+
111+ @EventHandler
112+ private fun onDeserialize (event : PylonBlockDeserializeEvent ) {
113+ val block = event.pylonBlock
114+ if (block is PylonRecipeProcessor <* >) {
115+ val data = event.pdc.get(recipeProcessorKey, PylonSerializers .RECIPE_PROCESSOR_DATA )
116+ ? : error(" Recipe processor data not found for ${block.key} " )
117+ recipeProcessorBlocks[block] = data
118+ }
119+ }
120+
121+ @EventHandler
122+ private fun onLoad (event : PylonBlockLoadEvent ) {
123+ // This separate listener is needed because when [PylonBlockDeserializeEvent] fires, then the
124+ // block may not have been fully initialised yet (e.g. postInitialise may not have been called)
125+ // which means progressItem may not have been set yet
126+ val block = event.pylonBlock
127+ if (block is PylonRecipeProcessor <* >) {
128+ val data = recipeProcessorBlocks[block]!!
129+ data.progressItem?.setTotalTimeTicks(data.totalRecipeTicks)
130+ data.recipeTicksRemaining?.let { data.progressItem?.setRemainingTimeTicks(it) }
131+ }
132+ }
133+
134+ @EventHandler
135+ private fun onSerialize (event : PylonBlockSerializeEvent ) {
136+ val block = event.pylonBlock
137+ if (block is PylonRecipeProcessor <* >) {
138+ event.pdc.set(recipeProcessorKey, PylonSerializers .RECIPE_PROCESSOR_DATA , recipeProcessorBlocks[block]!! )
139+ }
140+ }
141+
142+ @EventHandler
143+ private fun onUnload (event : PylonBlockUnloadEvent ) {
144+ val block = event.pylonBlock
145+ if (block is PylonRecipeProcessor <* >) {
146+ recipeProcessorBlocks.remove(block)
147+ }
148+ }
149+ }
150+ }
0 commit comments