diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt index 5c6dde744..68785c657 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt @@ -207,6 +207,7 @@ class PlayerPacketHandler(private val player: ServerPlayer, private val handler: val item = PylonItem.fromStack(bukkitStack) ?: return stack val prototype = item.schema.getItemStack() prototype.copyDataFrom(bukkitStack) { it != DataComponentTypes.ITEM_NAME && it != DataComponentTypes.LORE } + prototype.amount = bukkitStack.amount val translatedPrototype = prototype.clone() try { handler.handleItem(translatedPrototype) diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt index d89c089e8..d13c67191 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlock.kt @@ -65,6 +65,8 @@ open class PylonBlock internal constructor(val block: Block) { val key = schema.key + val nameTranslationKey = schema.nameTranslationKey + val loreTranslationKey = schema.loreTranslationKey val defaultWailaTranslationKey = schema.defaultWailaTranslationKey /** diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlockSchema.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlockSchema.kt index 884b0d74f..7a3cfd9c1 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlockSchema.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PylonBlockSchema.kt @@ -32,14 +32,20 @@ class PylonBlockSchema( val addon = getAddon(key) + val nameTranslationKey: TranslatableComponent + val loreTranslationKey: TranslatableComponent val defaultWailaTranslationKey: TranslatableComponent init { val prefix = "pylon.${key.namespace}.item.${key.key}" + nameTranslationKey = Component.translatable("$prefix.name") + loreTranslationKey = Component.translatable("$prefix.lore") val default = "$prefix.waila" - defaultWailaTranslationKey = Component.translatable( - if (addon.translator.languages.any { addon.translator.canTranslate(default, it) }) default else "$prefix.name" - ) + defaultWailaTranslationKey = if (addon.translator.languages.any { addon.translator.canTranslate(default, it) }) { + Component.translatable(default) + } else { + nameTranslationKey + } } private val createConstructor: MethodHandle = blockClass.findConstructorMatching( diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonGuiBlock.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonGuiBlock.kt index 89caa37d2..286602011 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonGuiBlock.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/base/PylonGuiBlock.kt @@ -12,7 +12,6 @@ import net.kyori.adventure.text.Component import org.bukkit.event.Event import org.bukkit.event.EventHandler import org.bukkit.event.Listener -import org.bukkit.event.inventory.InventoryOpenEvent import org.bukkit.event.player.PlayerInteractEvent import org.bukkit.inventory.EquipmentSlot import org.bukkit.inventory.ItemStack @@ -46,7 +45,7 @@ interface PylonGuiBlock : PylonBreakHandler, PylonInteractBlock, PylonNoVanillaC * The title of the GUI */ val guiTitle: Component - get() = (this as PylonBlock).defaultWailaTranslationKey + get() = (this as PylonBlock).nameTranslationKey /** * The GUI associated with this block. diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/PylonTranslator.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/PylonTranslator.kt index af7f7d068..d52fa747f 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/PylonTranslator.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/PylonTranslator.kt @@ -25,6 +25,8 @@ import net.kyori.adventure.key.Key import net.kyori.adventure.text.Component import net.kyori.adventure.text.TextReplacementConfig import net.kyori.adventure.text.TranslatableComponent +import net.kyori.adventure.text.TranslationArgument +import net.kyori.adventure.text.TranslationArgumentLike import net.kyori.adventure.text.VirtualComponent import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.Style @@ -225,7 +227,11 @@ class PylonTranslator private constructor(private val addon: PylonAddon) : Trans editData(DataComponentTypes.LORE) { lore -> val newLore = lore.lines().flatMap { line -> if (!isPylon(line)) return@flatMap listOf(line) - val translated = GlobalTranslator.render(line.withArguments(arguments), locale) + val concatenatedArguments: MutableList = arguments.toMutableList() + if (line is TranslatableComponent) { + concatenatedArguments.addAll(line.arguments()) + } + val translated = GlobalTranslator.render(line.withArguments(concatenatedArguments), locale) if (translated.plainText.isBlank()) return@flatMap emptyList() val encoded = LineWrapEncoder.encode(translated) val wrapped = encoded.copy( diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/builder/ItemStackBuilder.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/builder/ItemStackBuilder.kt index 77631e74c..8646439b2 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/builder/ItemStackBuilder.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/builder/ItemStackBuilder.kt @@ -41,6 +41,7 @@ import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.ItemMeta import org.bukkit.persistence.PersistentDataContainer import org.jetbrains.annotations.ApiStatus +import xyz.xenondevs.invui.item.Item import xyz.xenondevs.invui.item.ItemProvider import java.util.function.Consumer import kotlin.collections.forEach @@ -69,6 +70,8 @@ open class ItemStackBuilder internal constructor(val stack: ItemStack) : ItemPro stack.setData(type) } + fun get(type: DataComponentType.Valued) = stack.getData(type) + /** * @see ItemStack.unsetData */ @@ -134,6 +137,10 @@ open class ItemStackBuilder internal constructor(val stack: ItemStack) : ItemPro fun lore(vararg lore: String) = lore(*lore.map(::fromMiniMessage).toTypedArray()) + fun clearLore() = apply { + stack.setData(DataComponentTypes.LORE, ItemLore.lore()) + } + fun hideFromTooltip(componentType: DataComponentType) = apply { val tooltipDisplay = stack.getData(DataComponentTypes.TOOLTIP_DISPLAY) val hidden = tooltipDisplay?.hiddenComponents()?.toMutableSet() ?: mutableSetOf() @@ -317,6 +324,10 @@ open class ItemStackBuilder internal constructor(val stack: ItemStack) : ItemPro fun useCooldown(cooldownTicks: Int, cooldownGroup: Key?) = set(DataComponentTypes.USE_COOLDOWN, UseCooldown.useCooldown(cooldownTicks / 20.0f).cooldownGroup(cooldownGroup)) + public override fun clone(): ItemStackBuilder { + return of(stack.clone()) + } + fun build(): ItemStack = stack.clone() /** @@ -364,6 +375,11 @@ open class ItemStackBuilder internal constructor(val stack: ItemStack) : ItemPro return of(ItemStack(material)) } + @JvmStatic + fun of(item: Item): ItemStackBuilder { + return of(ItemStack(item.itemProvider.get())) + } + /** * Creates a new [ItemStack] for a GUI item, sets its pdc key and adds * a custom model data string for resource packs. diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/GuiItems.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/GuiItems.kt index 874ce2091..d30aa2b84 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/GuiItems.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/gui/GuiItems.kt @@ -47,6 +47,24 @@ object GuiItems { .set(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay().hideTooltip(true)) ) + /** + * A lime glass pane named 'Input' + */ + @JvmStatic + fun input(): Item = SimpleItem( + ItemStackBuilder.gui(Material.LIME_STAINED_GLASS_PANE, pylonKey("input")) + .name(Component.translatable("pylon.pyloncore.gui.input")) + ) + + /** + * An orange glass pane named 'Output' + */ + @JvmStatic + fun output(): Item = SimpleItem( + ItemStackBuilder.gui(Material.ORANGE_STAINED_GLASS_PANE, pylonKey("output")) + .name(Component.translatable("pylon.pyloncore.gui.output")) + ) + /** * Item that automatically cycles through durability to represent processing time. * Intended for use in recipe displays. 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 bbbadeeb7..87d58ac80 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 @@ -6,6 +6,8 @@ import io.github.pylonmc.pylon.core.util.gui.unit.UnitFormat import io.papermc.paper.datacomponent.DataComponentTypes import io.papermc.paper.datacomponent.item.TooltipDisplay import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor +import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.entity.Player import org.bukkit.event.inventory.ClickType @@ -13,45 +15,121 @@ import org.bukkit.event.inventory.InventoryClickEvent import xyz.xenondevs.invui.item.ItemProvider import xyz.xenondevs.invui.item.impl.AbstractItem import java.time.Duration +import kotlin.math.min /** - * An item that counts down (by default) from a maximum durability, symbolising progress. + * An item that counts down (or up if [countDown] is true) from a maximum durability, symbolising progress. * * For example, this might be used in a custom furnace to show how much burn time is left on - * the current fuel. In this case, you'd want override [totalTime] and set it to the the total + * the current fuel. In this case, you'd want override [setTotalTimeTicks] and set it to the the total * burn time of the fuel (eg: 80 seconds for coal), and then gradually increase [progress] as the * remaining burn time decreased. * - * @param material The material to use for the item - * @param inverse If true, the progress bar will be inverted, meaning that 0.0 is full and 1.0 is empty. + * Using any `set` methods on here will automatically update the item in any windows that contain it. + * + * @param builder The item stack builder to use for the item + * @param countDown If true, the progress bar will be inverted, meaning that 0.0 is full and 1.0 is empty. */ -abstract class ProgressItem @JvmOverloads constructor( - private val material: Material, - private val inverse: Boolean = false +open class ProgressItem @JvmOverloads constructor( + builder: ItemStackBuilder, + private val countDown: Boolean = true ) : AbstractItem() { + @JvmOverloads constructor(material: Material, inverse: Boolean = true) : this(ItemStackBuilder.of(material), inverse) + + /** + * The item to be displayed + */ + var itemStackBuilder: ItemStackBuilder = builder + set(value) { + field = value + notifyWindows() + } + + /** + * The total time of whatever process this item is representing + */ + var totalTime: Duration? = null + set(value) { + field = value + notifyWindows() + if (field == null) { + progress = 0.0 + } + } + + /** + * How far through the [totalTime] we are + */ var progress: Double = 0.0 set(value) { field = value.coerceIn(0.0, 1.0) notifyWindows() } - open val totalTime: Duration? = null + /** + * Sets how far through the [totalTime] we are + */ + fun setRemainingTime(time: Duration) { + check(totalTime != null) { "Remaining time can only be set if total time is not null" } + progress = 1.0 - time.toNanos().toDouble() / totalTime!!.toNanos().toDouble() + } + + /** + * Sets how far through the [totalTime] we are + */ + fun setRemainingTimeSeconds(seconds: Int) { + setRemainingTime(Duration.ofSeconds(seconds.toLong())) + } + + /** + * Sets how far through the [totalTime] we are + */ + fun setRemainingTimeTicks(ticks: Int) { + setRemainingTime(Duration.ofMillis((ticks * 1000.0 / 20.0).toLong())) + } + + fun setTotalTimeSeconds(seconds: Int?) { + totalTime = seconds?.let { Duration.ofSeconds(it.toLong()) } + } + + fun setTotalTimeTicks(ticks: Int?) { + totalTime = ticks?.let { Duration.ofMillis((it * 1000.0 / 20.0).toLong()) } + } @Suppress("UnstableApiUsage") override fun getItemProvider(): ItemProvider { var progressValue = progress - if (!inverse) { + if (!countDown) { progressValue = 1 - progressValue } - val builder = ItemStackBuilder.of(material) - .set(DataComponentTypes.MAX_STACK_SIZE, 1) + + val builder = itemStackBuilder + .clone() .set(DataComponentTypes.MAX_DAMAGE, MAX_DURABILITY) - .set(DataComponentTypes.DAMAGE, (progressValue * MAX_DURABILITY).toInt()) - .set( - DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay() - .addHiddenComponents(DataComponentTypes.DAMAGE, DataComponentTypes.MAX_DAMAGE) - ) + + if (totalTime != null) { + // +1 so the durability bar is always visible + builder.set(DataComponentTypes.DAMAGE, min(MAX_DURABILITY, (progressValue * MAX_DURABILITY + 1).toInt())) + } + + // Hide damage and max damage text + val tooltipDisplay = builder.get(DataComponentTypes.TOOLTIP_DISPLAY) + val newTooltipDisplay = TooltipDisplay.tooltipDisplay() + if (tooltipDisplay != null) { + // clone existing tooltip because modifying it does not work for some godforesaken reason + newTooltipDisplay.hideTooltip(tooltipDisplay.hideTooltip()) + for (tooltip in tooltipDisplay.hiddenComponents()) { + newTooltipDisplay.addHiddenComponents(tooltip) + } + } + newTooltipDisplay.addHiddenComponents(DataComponentTypes.DAMAGE, DataComponentTypes.MAX_DAMAGE) + builder.set( + DataComponentTypes.TOOLTIP_DISPLAY, + newTooltipDisplay + ) + + // Set time in lore totalTime?.let { val remaining = it - it * progress builder.lore( @@ -61,17 +139,10 @@ abstract class ProgressItem @JvmOverloads constructor( ) ) } - completeItem(builder) + return builder } - /** - * This is used to modify the item being displayed. - * - * @param builder The item builder representing the progress item - */ - protected abstract fun completeItem(builder: ItemStackBuilder) - override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) {} companion object { @@ -79,4 +150,4 @@ abstract class ProgressItem @JvmOverloads constructor( } } -private operator fun Duration.times(value: Double): Duration = Duration.ofMillis((toMillis() * value).toLong()) \ No newline at end of file +private operator fun Duration.times(value: Double): Duration = Duration.ofMillis((toMillis() * value).toLong()) diff --git a/pylon-core/src/main/resources/lang/en.yml b/pylon-core/src/main/resources/lang/en.yml index ad6eef8a9..b3303b703 100644 --- a/pylon-core/src/main/resources/lang/en.yml +++ b/pylon-core/src/main/resources/lang/en.yml @@ -109,7 +109,9 @@ gui: next: "Next page (%current%/%total%)" previous: "Previous page (%current%/%total%)" - time_left: "Time left: %time%" + time_left: " Time left: <#d6d1b3>%time%" + input: "Input" + output: "Output" guide: title: "Pylon Guide"