diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonProcessor.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonProcessor.kt index 52c392ce3..868dae0c2 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonProcessor.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonProcessor.kt @@ -9,15 +9,17 @@ 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]. + * You can set a progress item with `setRecipeProgressItem`. This item + * will be automatically synchronized to the process progress, and will + * be persisted. + * + * @see PylonRecipeProcessor */ interface PylonProcessor { @@ -42,15 +44,11 @@ interface PylonProcessor { @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 - } + var processProgressItem: ProgressItem + get() = processorData.progressItem ?: error("No recipe progress item was set") + set(progressItem) { + processorData.progressItem = progressItem + } /** * Starts a new process with duration [ticks], with [ticks] being the number of server @@ -74,8 +72,8 @@ interface PylonProcessor { check(isProcessing) { "Cannot finish process because there is no process ongoing" } - onProcessFinished() stopProcess() + onProcessFinished() } fun onProcessFinished() {} diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonRecipeProcessor.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonRecipeProcessor.kt index 1b6c2df7e..b498f9aed 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonRecipeProcessor.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonRecipeProcessor.kt @@ -1,6 +1,5 @@ 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 @@ -18,6 +17,10 @@ import java.util.IdentityHashMap /** * An interface that stores and progresses a recipe. * + * You can set a progress item with `setRecipeProgressItem`. This item + * will be automatically synchronized to the recipe progress, and will + * be persisted. + * * @see PylonProcessor */ interface PylonRecipeProcessor { @@ -48,6 +51,12 @@ interface PylonRecipeProcessor { @ApiStatus.NonExtendable get() = currentRecipe != null + var recipeProgressItem: ProgressItem + get() = recipeProcessorData.progressItem ?: error("No recipe progress item was set") + set(progressItem) { + recipeProcessorData.progressItem = progressItem + } + /** * Set the progress item that should be updated as the recipe progresses. Optional. * @@ -58,17 +67,6 @@ interface PylonRecipeProcessor { recipeProcessorData.recipeType = type } - /** - * Set the progress item that should be updated as the recipe 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]) - */ - @ApiStatus.NonExtendable - fun setProgressItem(item: ProgressItem) { - recipeProcessorData.progressItem = item - } - /** * Starts a new recipe with duration [ticks], with [ticks] being the number of server * ticks the recipe will take. @@ -94,8 +92,9 @@ interface PylonRecipeProcessor { "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) + val currentRecipe = recipeProcessorData.currentRecipe as T stopRecipe() + onRecipeFinished(currentRecipe) } fun onRecipeFinished(recipe: T) diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/DurationPersistentDataType.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/DurationPersistentDataType.kt new file mode 100644 index 000000000..8e216f11f --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/DurationPersistentDataType.kt @@ -0,0 +1,34 @@ +package io.github.pylonmc.pylon.core.datatypes + +import io.github.pylonmc.pylon.core.util.position.BlockPosition +import io.github.pylonmc.pylon.core.util.pylonKey +import io.papermc.paper.command.brigadier.argument.ArgumentTypes.world +import org.bukkit.persistence.PersistentDataAdapterContext +import org.bukkit.persistence.PersistentDataContainer +import org.bukkit.persistence.PersistentDataType +import java.time.Duration + +object DurationPersistentDataType : PersistentDataType { + val secondsKey = pylonKey("seconds") + val nanosKey = pylonKey("nanos") + + override fun getPrimitiveType(): Class = PersistentDataContainer::class.java + + override fun getComplexType(): Class = Duration::class.java + + override fun fromPrimitive( + primitive: PersistentDataContainer, + context: PersistentDataAdapterContext + ): Duration { + val seconds = primitive.get(secondsKey, PersistentDataType.LONG)!! + val nanos = primitive.get(nanosKey, PylonSerializers.INTEGER)!! + return Duration.ofSeconds(seconds, nanos.toLong()) + } + + override fun toPrimitive(complex: Duration, context: PersistentDataAdapterContext): PersistentDataContainer { + val pdc = context.newPersistentDataContainer() + pdc.set(secondsKey, PersistentDataType.LONG, complex.seconds) + pdc.set(nanosKey, PersistentDataType.INTEGER, complex.nano) + return pdc + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/ProcessorDataPersistentDataType.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/ProcessorDataPersistentDataType.kt index 121c65ad5..001ff7297 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/ProcessorDataPersistentDataType.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/ProcessorDataPersistentDataType.kt @@ -1,8 +1,6 @@ package io.github.pylonmc.pylon.core.datatypes import io.github.pylonmc.pylon.core.block.base.PylonProcessor -import io.github.pylonmc.pylon.core.block.base.PylonRecipeProcessor -import io.github.pylonmc.pylon.core.registry.PylonRegistry import io.github.pylonmc.pylon.core.util.pylonKey import io.github.pylonmc.pylon.core.util.setNullable import org.bukkit.persistence.PersistentDataAdapterContext @@ -13,6 +11,7 @@ internal object ProcessorDataPersistentDataType : PersistentDataType = PersistentDataContainer::class.java @@ -22,7 +21,7 @@ internal object ProcessorDataPersistentDataType : PersistentDataType { + val itemStackKey = pylonKey("item_stack") + val countDownKey = pylonKey("count_down") + val totalTimeKey = pylonKey("total_time") + val progressKey = pylonKey("progress") + + override fun getPrimitiveType(): Class = PersistentDataContainer::class.java + + override fun getComplexType(): Class = ProgressItem::class.java + + override fun fromPrimitive( + primitive: PersistentDataContainer, + context: PersistentDataAdapterContext + ): ProgressItem { + val stack = primitive.get(itemStackKey, PylonSerializers.ITEM_STACK)!! + val countDown = primitive.get(countDownKey, PylonSerializers.BOOLEAN)!! + val item = ProgressItem(stack, countDown) + item.totalTime = primitive.get(totalTimeKey, PylonSerializers.DURATION) + item.progress = primitive.get(progressKey, PylonSerializers.DOUBLE)!! + return item + } + + override fun toPrimitive(complex: ProgressItem, context: PersistentDataAdapterContext): PersistentDataContainer { + val pdc = context.newPersistentDataContainer() + pdc.set(itemStackKey, PylonSerializers.ITEM_STACK, complex.itemStackBuilder.stack) + pdc.set(countDownKey, PylonSerializers.BOOLEAN, complex.countDown) + pdc.setNullable(totalTimeKey, PylonSerializers.DURATION, complex.totalTime) + pdc.set(progressKey, PylonSerializers.DOUBLE, complex.progress) + return pdc + } +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PylonSerializers.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PylonSerializers.kt index c3a209bf9..b57cc5345 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PylonSerializers.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/PylonSerializers.kt @@ -111,6 +111,27 @@ object PylonSerializers { @JvmField val PYLON_FLUID = KEYED.keyedTypeFrom(PylonRegistry.FLUIDS::getOrThrow) + @JvmField + val FLUID_CONNECTION_POINT = FluidConnectionPointPersistentDataType + + @JvmField + val LOGISTIC_POINT_TYPE = EnumPersistentDataType(LogisticSlotType::class.java) + + @JvmField + val DURATION = DurationPersistentDataType + + @JvmField + val PROGRESS_ITEM = ProgressItemPersistentDataType + + @JvmSynthetic + internal val CARGO_BLOCK_DATA = CargoBlockPersistentDataType + + @JvmSynthetic + internal val WAILA_TYPE = EnumPersistentDataType(Waila.Type::class.java) + + @JvmSynthetic + internal val PLAYER_WAILA_CONFIG = PlayerWailaConfigPersistentDataType + @JvmSynthetic internal val FLUID_BUFFER_DATA = FluidBufferDataPersistentDataType @@ -129,18 +150,4 @@ object PylonSerializers { @JvmSynthetic internal val TICKING_BLOCK_DATA = TickingBlockPersistentDataType - @JvmField - val FLUID_CONNECTION_POINT = FluidConnectionPointPersistentDataType - - @JvmField - val LOGISTIC_POINT_TYPE = EnumPersistentDataType(LogisticSlotType::class.java) - - @JvmSynthetic - internal val CARGO_BLOCK_DATA = CargoBlockPersistentDataType - - @JvmSynthetic - internal val WAILA_TYPE = EnumPersistentDataType(Waila.Type::class.java) - - @JvmSynthetic - internal val PLAYER_WAILA_CONFIG = PlayerWailaConfigPersistentDataType } diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/RecipeProcessorDataPersistentDataType.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/RecipeProcessorDataPersistentDataType.kt index e7281ab8b..e5025e61b 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/RecipeProcessorDataPersistentDataType.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/datatypes/RecipeProcessorDataPersistentDataType.kt @@ -14,6 +14,7 @@ internal object RecipeProcessorDataPersistentDataType : PersistentDataType PylonRegistry.RECIPE_TYPES.getOrThrow(key) } @@ -29,7 +30,7 @@ internal object RecipeProcessorDataPersistentDataType : PersistentDataType throw IllegalArgumentException("$face is not a horizontal cardinal direction") } +/** + * Rotates [vector] to face a direction + * + * Assumes north to be the default direction (i.e. supplying north will result in no rotation) + * + * @param face Must be a horizontal cardinal direction (north, east, south, west) + * @return The rotated vector + */ +fun rotateVectorToFace(vector: Vector3d, face: BlockFace) = when (face) { + BlockFace.NORTH -> vector + BlockFace.EAST -> Vector3d(-vector.z, vector.y, vector.x) + BlockFace.SOUTH -> Vector3d(-vector.x, vector.y, -vector.z) + BlockFace.WEST -> Vector3d(vector.z, vector.y, -vector.x) + else -> throw IllegalArgumentException("$face is not a horizontal cardinal direction") +} + /** * @return Whether [vector] is a cardinal direction */ @@ -549,3 +571,22 @@ fun damageItem(itemStack: ItemStack, amount: Int, world: World, onBreak: (Materi @JvmOverloads fun damageItem(itemStack: ItemStack, amount: Int, entity: LivingEntity, slot: EquipmentSlot? = null, force: Boolean = false) = NmsAccessor.instance.damageItem(itemStack, amount, entity, slot, force) + + +/** + * A shorthand for a commonly used [VirtualInventory] handler which prevents players + * from removing items from it. + * + * Usage: Call [VirtualInventory.setPreUpdateHandler] and supply this function to it + */ +@JvmField +val DISALLOW_PLAYERS_FROM_ADDING_ITEMS_HANDLER = Consumer { event: ItemPreUpdateEvent -> + if (!event.isRemove && event.updateReason is PlayerUpdateReason) { + event.isCancelled = true + } +} + +/** + * Indicates a machine has updated an inventory slot. + */ +class MachineUpdateReason : UpdateReason diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/ProgressItem.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/ProgressItem.kt index 2a1430b55..a76c8970a 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/ProgressItem.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/ProgressItem.kt @@ -10,6 +10,8 @@ import org.bukkit.Material import org.bukkit.entity.Player import org.bukkit.event.inventory.ClickType import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import xyz.xenondevs.invui.item.Item import xyz.xenondevs.invui.item.ItemProvider import xyz.xenondevs.invui.item.impl.AbstractItem import java.time.Duration @@ -30,11 +32,16 @@ import kotlin.math.min */ open class ProgressItem @JvmOverloads constructor( builder: ItemStackBuilder, - private val countDown: Boolean = true + @JvmSynthetic + internal val countDown: Boolean = true ) : AbstractItem() { @JvmOverloads constructor(material: Material, inverse: Boolean = true) : this(ItemStackBuilder.of(material), inverse) + @JvmOverloads constructor(stack: ItemStack, inverse: Boolean = true) : this(ItemStackBuilder.of(stack), inverse) + + @JvmOverloads constructor(item: Item, inverse: Boolean = true) : this(ItemStackBuilder.of(item), inverse) + /** * The item to be displayed */ diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/unit/UnitFormat.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/unit/UnitFormat.kt index 5609cd377..f4c2ace5f 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/unit/UnitFormat.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/unit/UnitFormat.kt @@ -315,6 +315,14 @@ class UnitFormat @JvmOverloads constructor( prefix = MetricPrefix.NONE ) + @JvmField + val CYCLES_PER_SECOND = UnitFormat( + "cycles_per_second", + TextColor.color(0xb672bf), + abbreviate = true, + prefix = MetricPrefix.NONE + ) + /** * Helper function that automatically formats a duration into days:hours:minutes:seconds */ diff --git a/pylon-core/src/main/resources/lang/en.yml b/pylon-core/src/main/resources/lang/en.yml index f8acd24fb..5c6c12abe 100644 --- a/pylon-core/src/main/resources/lang/en.yml +++ b/pylon-core/src/main/resources/lang/en.yml @@ -424,3 +424,7 @@ unit: singular: "item per second" plural: "items per second" abbr: "items/s" + cycles_per_second: + singular: "cycle per second" + plural: "cycles per second" + abbr: "cycles/s"