Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@ object PylonCore : JavaPlugin(), PylonAddon {
Bukkit.getPluginManager().registerEvents(BlockListener, this)
Bukkit.getPluginManager().registerEvents(PylonItemListener, this)
Bukkit.getScheduler().runTaskTimer(this, PylonInventoryTicker(), 0, PylonConfig.inventoryTickerBaseRate)
Bukkit.getPluginManager().registerEvents(TickManager, this)
Bukkit.getPluginManager().registerEvents(MultiblockCache, this)
Bukkit.getPluginManager().registerEvents(EntityStorage, this)
Bukkit.getPluginManager().registerEvents(EntityListener, this)
Bukkit.getPluginManager().registerEvents(Research, this)
Bukkit.getPluginManager().registerEvents(PylonGuiBlock, this)
Bukkit.getPluginManager().registerEvents(PylonEntityHolderBlock, this)
Bukkit.getPluginManager().registerEvents(PylonSimpleMultiblock, this)
Bukkit.getPluginManager().registerEvents(PylonProcessor, this)
Bukkit.getPluginManager().registerEvents(PylonRecipeProcessor, this)
Bukkit.getPluginManager().registerEvents(PylonFluidBufferBlock, this)
Bukkit.getPluginManager().registerEvents(PylonFluidTank, this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,9 @@ internal object BlockListener : Listener {
blockErrMap[block] = blockErrMap[block]?.plus(1) ?: 1
if (blockErrMap[block]!! > PylonConfig.allowedBlockErrors) {
BlockStorage.makePhantom(block)
TickManager.stopTicking(block)
if (block is PylonTickingBlock) {
PylonTickingBlock.stopTicking(block)
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package io.github.pylonmc.pylon.core.block.base

import io.github.pylonmc.pylon.core.datatypes.PylonSerializers
import io.github.pylonmc.pylon.core.event.PylonBlockDeserializeEvent
import io.github.pylonmc.pylon.core.event.PylonBlockLoadEvent
import io.github.pylonmc.pylon.core.event.PylonBlockSerializeEvent
import io.github.pylonmc.pylon.core.event.PylonBlockUnloadEvent
import io.github.pylonmc.pylon.core.util.gui.ProgressItem
import io.github.pylonmc.pylon.core.util.pylonKey
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.jetbrains.annotations.ApiStatus
import java.util.IdentityHashMap

/**
* An interface that tracks progress of some kind of process, such as processing a
* recipe, burning a piece of fuel, enchanting an item, etc
*
* This interface overrides [PylonTickingBlock.tick], meaning the rate at which the
* block progresses is determined by [PylonTickingBlock.setTickInterval].
*/
interface PylonProcessor {

@ApiStatus.Internal
data class ProcessorData(
var processTimeTicks: Int?,
var processTicksRemaining: Int?,
var progressItem: ProgressItem?,
)
private val processorData: ProcessorData
get() = processorBlocks.getOrPut(this) { ProcessorData(null, null, null)}

val processTimeTicks: Int?
@ApiStatus.NonExtendable
get() = processorData.processTimeTicks

val processTicksRemaining: Int?
@ApiStatus.NonExtendable
get() = processorData.processTicksRemaining

val isProcessing: Boolean
@ApiStatus.NonExtendable
get() = processTimeTicks != null

/**
* Set the progress item that should be updated as the process progresses. Optional.
*
* Does not persist; you must call this whenever the block is initialised (e.g.
* in [io.github.pylonmc.pylon.core.block.PylonBlock.postInitialise])
*/
fun setProgressItem(item: ProgressItem) {
processorData.progressItem = item
}

/**
* Starts a new process with duration [ticks], with [ticks] being the number of server
* ticks the process will take.
*/
fun startProcess(ticks: Int) {
processorData.processTimeTicks = ticks
processorData.processTicksRemaining = ticks
processorData.progressItem?.setTotalTimeTicks(ticks)
processorData.progressItem?.setRemainingTimeTicks(ticks)
}

fun stopProcess() {
val data = processorData
data.processTimeTicks = null
data.processTicksRemaining = null
data.progressItem?.totalTime = null
}

fun finishProcess() {
check(isProcessing) {
"Cannot finish process because there is no process ongoing"
}
onProcessFinished()
stopProcess()
}

fun onProcessFinished() {}

@ApiStatus.Internal
fun progressProcess(ticks: Int) {
val data = processorData
if (data.processTimeTicks == null) {
return
}

data.processTicksRemaining = data.processTicksRemaining!! - ticks
data.progressItem?.setRemainingTimeTicks(data.processTicksRemaining!!)
if (data.processTicksRemaining!! <= 0) {
finishProcess()
}
}

@ApiStatus.Internal
companion object : Listener {

private val processorKey = pylonKey("processor_data")

private val processorBlocks = IdentityHashMap<PylonProcessor, ProcessorData>()

@EventHandler
private fun onDeserialize(event: PylonBlockDeserializeEvent) {
val block = event.pylonBlock
if (block !is PylonProcessor) {
return
}

val data = event.pdc.get(processorKey, PylonSerializers.PROCESSOR_DATA)
?: error("Processor data not found for ${block.key}")
processorBlocks[block] = data
}

@EventHandler
private fun onLoad(event: PylonBlockLoadEvent) {
// This separate listener is needed because when [PylonBlockDeserializeEvent] fires, then the
// block may not have been fully initialised yet (e.g. postInitialise may not have been called)
// which means progressItem may not have been set yet
val block = event.pylonBlock
if (block is PylonProcessor) {
val data = processorBlocks[block]!!
data.progressItem?.setTotalTimeTicks(data.processTimeTicks)
data.processTicksRemaining?.let { data.progressItem?.setRemainingTimeTicks(it) }
}
}

@EventHandler
private fun onSerialize(event: PylonBlockSerializeEvent) {
val block = event.pylonBlock
if (block is PylonProcessor) {
val data = processorBlocks[block] ?: error {
"No recipe processor data found for ${block.key}"
}
event.pdc.set(processorKey, PylonSerializers.PROCESSOR_DATA, data)
}
}

@EventHandler
private fun onUnload(event: PylonBlockUnloadEvent) {
val block = event.pylonBlock
if (block is PylonProcessor) {
processorBlocks.remove(block)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.pylonmc.pylon.core.block.base

import com.google.common.base.Preconditions
import io.github.pylonmc.pylon.core.datatypes.PylonSerializers
import io.github.pylonmc.pylon.core.event.PylonBlockDeserializeEvent
import io.github.pylonmc.pylon.core.event.PylonBlockLoadEvent
Expand All @@ -17,33 +18,36 @@ import java.util.IdentityHashMap
/**
* An interface that stores and progresses a recipe.
*
* This does not actually handle the recipe inputs and outputs. Instead, it simply
* tracks a recipe that is being processed and how much time is left on it, ticking
* automatically.
*
* This interface overrides [PylonTickingBlock.tick], meaning the rate at which the
* recipe ticks is determined by [PylonTickingBlock.setTickInterval].
* @see PylonProcessor
*/
interface PylonRecipeProcessor<T: PylonRecipe> : PylonTickingBlock {
interface PylonRecipeProcessor<T: PylonRecipe> {

@ApiStatus.Internal
data class RecipeProcessorData(
var recipeType: RecipeType<*>?,
var currentRecipe: PylonRecipe?,
var totalRecipeTicks: Int?,
var recipeTimeTicks: Int?,
var recipeTicksRemaining: Int?,
var progressItem: ProgressItem?,
)

private val recipeProcessorData: RecipeProcessorData
@ApiStatus.NonExtendable
get() = recipeProcessorBlocks.getOrPut(this) { RecipeProcessorData(null, null, null, null, null)}

val currentRecipe: T?
@ApiStatus.NonExtendable
// cast should always be safe due to type restriction when starting recipe
get() = recipeProcessorData.currentRecipe as T?

val recipeTicksRemaining: Int?
@ApiStatus.NonExtendable
get() = recipeProcessorData.recipeTicksRemaining

val isProcessingRecipe: Boolean
@ApiStatus.NonExtendable
get() = currentRecipe != null

/**
* Set the progress item that should be updated as the recipe progresses. Optional.
*
Expand Down Expand Up @@ -71,37 +75,43 @@ interface PylonRecipeProcessor<T: PylonRecipe> : PylonTickingBlock {
*/
fun startRecipe(recipe: T, ticks: Int) {
recipeProcessorData.currentRecipe = recipe
recipeProcessorData.totalRecipeTicks = ticks
recipeProcessorData.recipeTimeTicks = ticks
recipeProcessorData.recipeTicksRemaining = ticks
recipeProcessorData.progressItem?.setTotalTimeTicks(ticks)
recipeProcessorData.progressItem?.setRemainingTimeTicks(ticks)
}

fun stopRecipe() {
val data = recipeProcessorData
data.currentRecipe = null
data.recipeTimeTicks = null
data.recipeTicksRemaining = null
data.progressItem?.totalTime = null
}

fun finishRecipe() {
check(isProcessingRecipe) {
"Cannot finish recipe because there is no recipe being processed"
}
@Suppress("UNCHECKED_CAST") // cast should always be safe due to type restriction when starting recipe
onRecipeFinished(recipeProcessorData.currentRecipe as T)
stopRecipe()
}

fun onRecipeFinished(recipe: T)

override fun tick(deltaSeconds: Double) {
fun progressRecipe(ticks: Int) {
val data = recipeProcessorData

if (data.currentRecipe != null && data.recipeTicksRemaining != null) {
data.recipeTicksRemaining = data.recipeTicksRemaining!! - ticks
data.progressItem?.setRemainingTimeTicks(data.recipeTicksRemaining!!)

// tick recipe
if (data.recipeTicksRemaining!! > 0) {
data.recipeTicksRemaining = data.recipeTicksRemaining!! - tickInterval
return
if (data.recipeTicksRemaining!! <= 0) {
finishRecipe()
}

// finish recipe
onRecipeFinished(data.currentRecipe as T)
data.currentRecipe = null
data.totalRecipeTicks = null
data.recipeTicksRemaining = null
data.progressItem?.totalTime = null
// cast should always be safe due to type restriction when starting recipe
return
}
}

@ApiStatus.Internal
companion object : Listener {

private val recipeProcessorKey = pylonKey("recipe_processor_data")
Expand All @@ -126,7 +136,7 @@ interface PylonRecipeProcessor<T: PylonRecipe> : PylonTickingBlock {
val block = event.pylonBlock
if (block is PylonRecipeProcessor<*>) {
val data = recipeProcessorBlocks[block]!!
data.progressItem?.setTotalTimeTicks(data.totalRecipeTicks)
data.progressItem?.setTotalTimeTicks(data.recipeTimeTicks)
data.recipeTicksRemaining?.let { data.progressItem?.setRemainingTimeTicks(it) }
}
}
Expand All @@ -135,7 +145,11 @@ interface PylonRecipeProcessor<T: PylonRecipe> : PylonTickingBlock {
private fun onSerialize(event: PylonBlockSerializeEvent) {
val block = event.pylonBlock
if (block is PylonRecipeProcessor<*>) {
event.pdc.set(recipeProcessorKey, PylonSerializers.RECIPE_PROCESSOR_DATA, recipeProcessorBlocks[block]!!)
val data = recipeProcessorBlocks[block] ?: error {
"No recipe processor data found for ${block.key}"
}
event.pdc.set(recipeProcessorKey, PylonSerializers.RECIPE_PROCESSOR_DATA, data)
check(data.recipeType != null) { "No recipe type set for ${event.pylonBlock.key}; did you forget to call setRecipeType in your place constructor?" }
}
}

Expand Down
Loading