diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PhantomBlock.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PhantomBlock.kt index 82191ab34..71f0030d3 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PhantomBlock.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/block/PhantomBlock.kt @@ -85,7 +85,7 @@ class PhantomBlock( companion object { val KEY = pylonKey("error_item") val BLOCK_KEY = pylonKey("block") - val STACK = ItemStackBuilder.pylonItem(Material.BARRIER, KEY) + val STACK = ItemStackBuilder.pylon(Material.BARRIER, KEY) .build() } } diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/debug/DebugWaxedWeatheredCutCopperStairs.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/debug/DebugWaxedWeatheredCutCopperStairs.kt index ee49cad1b..e660d75d1 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/debug/DebugWaxedWeatheredCutCopperStairs.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/debug/DebugWaxedWeatheredCutCopperStairs.kt @@ -148,7 +148,7 @@ internal class DebugWaxedWeatheredCutCopperStairs(stack: ItemStack) companion object { val KEY = pylonKey("debug_waxed_weathered_cut_copper_stairs") - val STACK = ItemStackBuilder.pylonItem(Material.BRICK, KEY) + val STACK = ItemStackBuilder.pylon(Material.BRICK, KEY) .set(DataComponentTypes.ITEM_MODEL, Material.WAXED_WEATHERED_CUT_COPPER_STAIRS.key) .set(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true) .build() diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/guide/PylonGuide.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/guide/PylonGuide.kt index 94ffd9073..4dfda8e41 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/guide/PylonGuide.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/content/guide/PylonGuide.kt @@ -44,7 +44,7 @@ class PylonGuide(stack: ItemStack) : PylonItem(stack), PylonInteractor { val KEY = pylonKey("guide") @JvmField - val STACK = ItemStackBuilder.pylonItem(Material.BOOK, KEY) + val STACK = ItemStackBuilder.pylon(Material.BOOK, KEY) .set(DataComponentTypes.ITEM_MODEL, Key.key("knowledge_book")) .set(DataComponentTypes.MAX_STACK_SIZE, 1) .build() diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/fluid/PylonFluid.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/fluid/PylonFluid.kt index ffa9bee91..d569b4f01 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/fluid/PylonFluid.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/fluid/PylonFluid.kt @@ -1,14 +1,17 @@ package io.github.pylonmc.pylon.core.fluid import io.github.pylonmc.pylon.core.PylonCore +import io.github.pylonmc.pylon.core.datatypes.PylonSerializers import io.github.pylonmc.pylon.core.i18n.PylonTranslator.Companion.translator import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder import io.github.pylonmc.pylon.core.registry.PylonRegistry import io.github.pylonmc.pylon.core.util.getAddon +import io.github.pylonmc.pylon.core.util.pylonKey import net.kyori.adventure.text.Component import org.bukkit.Keyed import org.bukkit.Material import org.bukkit.NamespacedKey +import org.bukkit.inventory.ItemStack /** * Fluids aren't necessarily just liquids, they can also be gases or other substances that can flow. @@ -29,8 +32,9 @@ open class PylonFluid( private val internalItem by lazy { val builder = ItemStackBuilder.of(material) - .name(Component.translatable("pylon.${key.namespace}.fluid.${key.key}")) + .editPdc { it.set(pylonFluidKeyKey, PylonSerializers.NAMESPACED_KEY, key) } .addCustomModelDataString(key.toString()) + .name(name) for (tag in tags) { builder.lore(tag.displayText) @@ -94,4 +98,18 @@ open class PylonFluid( override fun equals(other: Any?): Boolean = other is PylonFluid && key == other.key override fun hashCode(): Int = key.hashCode() override fun toString(): String = key.toString() + + companion object { + val pylonFluidKeyKey = pylonKey("pylon_fluid_key") + + /** + * Get the fluid represented by the given item stack, or null if the stack is null, empty or does not represent a fluid. + * See [item] for how to get an item stack that represents this fluid. + */ + fun fromStack(stack: ItemStack?): PylonFluid? { + if (stack == null || stack.isEmpty) return null + val id = stack.persistentDataContainer.get(pylonFluidKeyKey, PylonSerializers.NAMESPACED_KEY) ?: return null + return PylonRegistry.FLUIDS[id] + } + } } \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/BackButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/BackButton.kt index 00b58f972..24f5104b1 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/BackButton.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/BackButton.kt @@ -2,6 +2,7 @@ package io.github.pylonmc.pylon.core.guide.button import io.github.pylonmc.pylon.core.content.guide.PylonGuide import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder +import io.github.pylonmc.pylon.core.util.pylonKey import net.kyori.adventure.text.Component import org.bukkit.Material import org.bukkit.entity.Player @@ -14,7 +15,7 @@ import xyz.xenondevs.invui.item.impl.AbstractItem */ open class BackButton() : AbstractItem() { - override fun getItemProvider() = ItemStackBuilder.of(Material.ENCHANTED_BOOK) + override fun getItemProvider() = ItemStackBuilder.gui(Material.ENCHANTED_BOOK, pylonKey("guide_back")) .name(Component.translatable("pylon.pyloncore.guide.button.back.name")) .lore(Component.translatable("pylon.pyloncore.guide.button.back.lore")) diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/CullingPresetButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/CullingPresetButton.kt index d0602fbb6..9b3b7bff8 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/CullingPresetButton.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/CullingPresetButton.kt @@ -1,10 +1,10 @@ package io.github.pylonmc.pylon.core.guide.button import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine.cullingPreset -import io.github.pylonmc.pylon.core.config.PylonConfig import io.github.pylonmc.pylon.core.i18n.PylonArgument import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureConfig +import io.github.pylonmc.pylon.core.util.pylonKey import net.kyori.adventure.text.Component import org.bukkit.entity.Player import org.bukkit.event.inventory.ClickType @@ -15,7 +15,7 @@ import xyz.xenondevs.invui.item.impl.AbstractItem class CullingPresetButton : AbstractItem() { override fun getItemProvider(player: Player): ItemProvider? { val preset = player.cullingPreset - return ItemStackBuilder.of(preset.material) + return ItemStackBuilder.gui(preset.material, pylonKey("guide_culling_preset_${preset.id}")) .name(Component.translatable("pylon.pyloncore.guide.button.culling-preset.${preset.id}.name")) .lore( Component.translatable("pylon.pyloncore.guide.button.culling-preset.${preset.id}.lore") diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ResearchButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ResearchButton.kt index 68e77b1d3..4ecf5d049 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ResearchButton.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ResearchButton.kt @@ -6,6 +6,8 @@ import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder import io.github.pylonmc.pylon.core.item.research.Research import io.github.pylonmc.pylon.core.item.research.Research.Companion.researchPoints import io.github.pylonmc.pylon.core.util.getAddon +import io.github.pylonmc.pylon.core.util.gui.GuiItems +import io.github.pylonmc.pylon.core.util.pylonKey import net.kyori.adventure.text.Component import org.bukkit.Material import org.bukkit.entity.Player @@ -21,7 +23,7 @@ open class ResearchButton(val research: Research) : AbstractItem() { override fun getItemProvider(player: Player): ItemProvider = try { val playerHasResearch = Research.getResearches(player).contains(research) - val item = ItemStackBuilder.of(if (playerHasResearch) Material.LIME_STAINED_GLASS_PANE else research.material) + val item = ItemStackBuilder.gui(if (playerHasResearch) Material.LIME_STAINED_GLASS_PANE else research.material, "${pylonKey("research")}:${research.key}:$playerHasResearch") .name(research.name) if (playerHasResearch) { diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleArmorTexturesButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleArmorTexturesButton.kt index d404f4c88..8c4b1e646 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleArmorTexturesButton.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleArmorTexturesButton.kt @@ -2,6 +2,7 @@ package io.github.pylonmc.pylon.core.guide.button import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder import io.github.pylonmc.pylon.core.resourcepack.armor.ArmorTextureEngine.hasCustomArmorTextures +import io.github.pylonmc.pylon.core.util.pylonKey import net.kyori.adventure.text.Component import org.bukkit.Material import org.bukkit.entity.Player @@ -10,9 +11,10 @@ import org.bukkit.event.inventory.InventoryClickEvent import xyz.xenondevs.invui.item.impl.AbstractItem class ToggleArmorTexturesButton : AbstractItem() { - override fun getItemProvider(player: Player) = ItemStackBuilder.of(if (player.hasCustomArmorTextures) Material.LIME_CONCRETE else Material.RED_CONCRETE) - .name(Component.translatable("pylon.pyloncore.guide.button.toggle-armor-textures.name")) - .lore(Component.translatable("pylon.pyloncore.guide.button.toggle-armor-textures.lore")) + override fun getItemProvider(player: Player) + = ItemStackBuilder.gui(if (player.hasCustomArmorTextures) Material.LIME_CONCRETE else Material.RED_CONCRETE, pylonKey("toggle_armor_textures")) + .name(Component.translatable("pylon.pyloncore.guide.button.toggle-armor-textures.name")) + .lore(Component.translatable("pylon.pyloncore.guide.button.toggle-armor-textures.lore")) override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) { player.hasCustomArmorTextures = !player.hasCustomArmorTextures diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleBlockTexturesButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleBlockTexturesButton.kt index f15a5fcc1..b6a1dd480 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleBlockTexturesButton.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleBlockTexturesButton.kt @@ -3,6 +3,7 @@ package io.github.pylonmc.pylon.core.guide.button import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine.hasCustomBlockTextures import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder import io.github.pylonmc.pylon.core.resourcepack.block.BlockTextureEngine +import io.github.pylonmc.pylon.core.util.pylonKey import net.kyori.adventure.text.Component import org.bukkit.Material import org.bukkit.entity.Player @@ -11,9 +12,10 @@ import org.bukkit.event.inventory.InventoryClickEvent import xyz.xenondevs.invui.item.impl.AbstractItem class ToggleBlockTexturesButton : AbstractItem() { - override fun getItemProvider(player: Player) = ItemStackBuilder.of(if (player.hasCustomBlockTextures) Material.LIME_CONCRETE else Material.RED_CONCRETE) - .name(Component.translatable("pylon.pyloncore.guide.button.toggle-block-textures.name")) - .lore(Component.translatable("pylon.pyloncore.guide.button.toggle-block-textures.lore")) + override fun getItemProvider(player: Player) + = ItemStackBuilder.gui(if (player.hasCustomBlockTextures) Material.LIME_CONCRETE else Material.RED_CONCRETE, pylonKey("toggle_block_textures")) + .name(Component.translatable("pylon.pyloncore.guide.button.toggle-block-textures.name")) + .lore(Component.translatable("pylon.pyloncore.guide.button.toggle-block-textures.lore")) override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) { player.hasCustomBlockTextures = !player.hasCustomBlockTextures diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleWailaButton.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleWailaButton.kt index 3bebde6d8..8bbbe50ee 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleWailaButton.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/button/ToggleWailaButton.kt @@ -2,6 +2,7 @@ package io.github.pylonmc.pylon.core.guide.button import io.github.pylonmc.pylon.core.block.waila.Waila.Companion.wailaEnabled import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder +import io.github.pylonmc.pylon.core.util.pylonKey import net.kyori.adventure.text.Component import org.bukkit.Material import org.bukkit.entity.Player @@ -14,9 +15,10 @@ import xyz.xenondevs.invui.item.impl.AbstractItem */ class ToggleWailaButton : AbstractItem() { - override fun getItemProvider(player: Player) = ItemStackBuilder.of(if (player.wailaEnabled) Material.LIME_CONCRETE else Material.RED_CONCRETE) - .name(Component.translatable("pylon.pyloncore.guide.button.toggle-waila.name")) - .lore(Component.translatable("pylon.pyloncore.guide.button.toggle-waila.lore")) + override fun getItemProvider(player: Player) + = ItemStackBuilder.gui(if (player.wailaEnabled) Material.LIME_CONCRETE else Material.RED_CONCRETE, pylonKey("toggle_waila")) + .name(Component.translatable("pylon.pyloncore.guide.button.toggle-waila.name")) + .lore(Component.translatable("pylon.pyloncore.guide.button.toggle-waila.lore")) override fun handleClick(clickType: ClickType, player: Player, event: InventoryClickEvent) { player.wailaEnabled = !player.wailaEnabled diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/base/SimpleDynamicGuidePage.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/base/SimpleDynamicGuidePage.kt index e237d0383..8297903eb 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/base/SimpleDynamicGuidePage.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/base/SimpleDynamicGuidePage.kt @@ -5,6 +5,7 @@ import io.github.pylonmc.pylon.core.guide.button.BackButton import io.github.pylonmc.pylon.core.guide.button.PageButton import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder import io.github.pylonmc.pylon.core.util.gui.GuiItems +import io.github.pylonmc.pylon.core.util.pylonKey import net.kyori.adventure.text.Component import org.bukkit.Material import org.bukkit.NamespacedKey @@ -41,7 +42,7 @@ open class SimpleDynamicGuidePage( override fun getKey() = key - override val item = ItemStackBuilder.of(material) + override val item = ItemStackBuilder.gui(material, "${pylonKey("guide_page")}:$key") .name(Component.translatable("pylon.${key.namespace}.guide.page.${key.key}")) /** diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/research/AddonResearchesPage.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/research/AddonResearchesPage.kt index 0594f0458..cbbd5d5b4 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/research/AddonResearchesPage.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/guide/pages/research/AddonResearchesPage.kt @@ -24,7 +24,7 @@ class AddonResearchesPage(val addon: PylonAddon) : SimpleDynamicGuidePage( } ) { override val item: ItemStackBuilder - get() = ItemStackBuilder.of(addon.material) + get() = ItemStackBuilder.gui(addon.material, addon.key) .name(addon.displayName) override val title: Component diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/PylonItem.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/PylonItem.kt index 2944a5043..7066e3b8d 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/PylonItem.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/item/PylonItem.kt @@ -82,7 +82,7 @@ open class PylonItem(val stack: ItemStack) : Keyed { var isNameValid = true if (name == null || name.key() != ItemStackBuilder.nameKey(schema.key)) { - PylonCore.logger.warning("Item ${schema.key}'s name is not a translation key; check your item uses ItemStackBuilder.pylonItem(...)") + PylonCore.logger.warning("Item ${schema.key}'s name is not a translation key; check your item uses PylonItemStackBuilder.of(...)") isNameValid = false } 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 1f7b25703..77631e74c 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 @@ -1,37 +1,58 @@ package io.github.pylonmc.pylon.core.item.builder +import io.github.pylonmc.pylon.core.config.Config +import io.github.pylonmc.pylon.core.config.Settings +import io.github.pylonmc.pylon.core.config.adapter.ConfigAdapter import io.github.pylonmc.pylon.core.datatypes.PylonSerializers +import io.github.pylonmc.pylon.core.item.PylonItem import io.github.pylonmc.pylon.core.item.PylonItemSchema -import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder.Companion.pylonItem import io.github.pylonmc.pylon.core.util.editData +import io.github.pylonmc.pylon.core.util.editDataOrDefault +import io.github.pylonmc.pylon.core.util.editDataOrSet import io.github.pylonmc.pylon.core.util.fromMiniMessage +import io.github.pylonmc.pylon.core.util.gui.GuiItems +import io.github.pylonmc.pylon.core.util.pickaxeMineable import io.github.pylonmc.pylon.core.util.pylonKey import io.papermc.paper.datacomponent.DataComponentBuilder import io.papermc.paper.datacomponent.DataComponentType import io.papermc.paper.datacomponent.DataComponentTypes import io.papermc.paper.datacomponent.item.CustomModelData +import io.papermc.paper.datacomponent.item.ItemAttributeModifiers import io.papermc.paper.datacomponent.item.ItemLore +import io.papermc.paper.datacomponent.item.TooltipDisplay +import io.papermc.paper.datacomponent.item.Tool +import io.papermc.paper.datacomponent.item.UseCooldown +import io.papermc.paper.datacomponent.item.Weapon +import io.papermc.paper.registry.keys.tags.BlockTypeTagKeys +import io.papermc.paper.registry.set.RegistryKeySet +import net.kyori.adventure.key.Key import net.kyori.adventure.text.Component import net.kyori.adventure.text.ComponentLike +import net.kyori.adventure.util.TriState import org.bukkit.Color import org.bukkit.Material import org.bukkit.NamespacedKey +import org.bukkit.Registry +import org.bukkit.attribute.Attribute +import org.bukkit.attribute.AttributeModifier +import org.bukkit.block.BlockType +import org.bukkit.inventory.EquipmentSlotGroup 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.ItemProvider import java.util.function.Consumer +import kotlin.collections.forEach /** - * Helper class for creating an [ItemStack], including utilities for creating Pylon - * items specifically. + * Helper class for creating an [ItemStack] with various properties. Includes + * methods for creating [PylonItem] stacks, and gui items. * * Implements InvUI's [ItemProvider], so can be used instead of an [ItemStack] in GUIs. */ @Suppress("UnstableApiUsage") -open class ItemStackBuilder private constructor(val stack: ItemStack) : ItemProvider { - +open class ItemStackBuilder internal constructor(val stack: ItemStack) : ItemProvider { fun amount(amount: Int) = apply { stack.amount = amount } @@ -74,6 +95,14 @@ open class ItemStackBuilder private constructor(val stack: ItemStack) : ItemProv stack.editData(type, block) } + fun editDataOrDefault(type: DataComponentType.Valued, block: (T) -> T) = apply { + stack.editDataOrDefault(type, block) + } + + fun editDataOrSet(type: DataComponentType.Valued, block: (T?) -> T) = apply { + stack.editDataOrSet(type, block) + } + fun name(name: Component) = set(DataComponentTypes.ITEM_NAME, name) fun name(name: String) = name(fromMiniMessage(name)) @@ -82,7 +111,7 @@ open class ItemStackBuilder private constructor(val stack: ItemStack) : ItemProv * Sets the item's name to the default language file key (for example * `pylon.pyloncore.item.my_dumb_item.name`), based on the item [key] given. * - * Use [pylonItem] instead of this to create a stack for a [io.github.pylonmc.pylon.core.item.PylonItem]. + * Use [pylon] instead of this to create a stack for a [PylonItem]. */ fun defaultTranslatableName(key: NamespacedKey) = name(Component.translatable(nameKey(key))) @@ -105,11 +134,20 @@ open class ItemStackBuilder private constructor(val stack: ItemStack) : ItemProv fun lore(vararg lore: String) = lore(*lore.map(::fromMiniMessage).toTypedArray()) + fun hideFromTooltip(componentType: DataComponentType) = apply { + val tooltipDisplay = stack.getData(DataComponentTypes.TOOLTIP_DISPLAY) + val hidden = tooltipDisplay?.hiddenComponents()?.toMutableSet() ?: mutableSetOf() + hidden.add(componentType) + stack.setData(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay() + .hideTooltip(tooltipDisplay?.hideTooltip() == true) + .hiddenComponents(hidden)) + } + /** * Sets the item's lore to the default language file key (for example * `pylon.pyloncore.item.my_dumb_item.lore`), based on the item [key] given. * - * Use [pylonItem] instead of this to create a stack for a [io.github.pylonmc.pylon.core.item.PylonItem]. + * Use [pylon] instead of this to create a stack for a [PylonItem]. */ fun defaultTranslatableLore(key: NamespacedKey) = lore(Component.translatable(loreKey(key), "")) @@ -154,6 +192,131 @@ open class ItemStackBuilder private constructor(val stack: ItemStack) : ItemProv fun addCustomModelDataColor(color: Color) = editCustomModelData { it.addColor(color) } + @JvmOverloads + fun addAttributeModifier( + attribute: Attribute, + modifier: AttributeModifier, + replaceExisting: Boolean = true + ) = apply { + editDataOrSet(DataComponentTypes.ATTRIBUTE_MODIFIERS) { modifiers -> + val copying = modifiers?.modifiers()?.filter { !replaceExisting || it.modifier().key != modifier.key } + ItemAttributeModifiers.itemAttributes().copy(copying) + .addModifier(attribute, modifier) + .build() + } + } + + fun removeAttributeModifier( + attribute: Attribute, + modifierKey: NamespacedKey + ) = removeAttributeModifiers(attribute) { it.key == modifierKey } + + fun removeAttributeModifiers( + attribute: Attribute + ) = removeAttributeModifiers(attribute) { true } + + fun removeAttributeModifiers( + attribute: Attribute, + predicate: (AttributeModifier) -> Boolean + ) = apply { + editDataOrSet(DataComponentTypes.ATTRIBUTE_MODIFIERS) { modifiers -> + val copying = modifiers?.modifiers()?.filter { it.attribute() != attribute || !predicate(it.modifier()) } + ItemAttributeModifiers.itemAttributes().copy(copying).build() + } + } + + fun helmet( + armor: Double, + armorToughness: Double + ) = armor(EquipmentSlotGroup.HEAD, armor, armorToughness) + + fun chestplate( + armor: Double, + armorToughness: Double + ) = armor(EquipmentSlotGroup.CHEST, armor, armorToughness) + + fun leggings( + armor: Double, + armorToughness: Double + ) = armor(EquipmentSlotGroup.LEGS, armor, armorToughness) + + fun boots( + armor: Double, + armorToughness: Double + ) = armor(EquipmentSlotGroup.FEET, armor, armorToughness) + + fun armor( + slot: EquipmentSlotGroup, + armor: Double, + armorToughness: Double + ) = apply { + removeAttributeModifiers(Attribute.ARMOR) + removeAttributeModifiers(Attribute.ARMOR_TOUGHNESS) + addAttributeModifier(Attribute.ARMOR, AttributeModifier(baseArmor, armor, AttributeModifier.Operation.ADD_NUMBER, slot)) + addAttributeModifier(Attribute.ARMOR_TOUGHNESS, AttributeModifier(baseArmorToughness, armorToughness, AttributeModifier.Operation.ADD_NUMBER, slot)) + } + + fun axe( + miningSpeed: Float, + miningDurabilityDamage: Int + ) = tool(Registry.BLOCK.getTag(BlockTypeTagKeys.MINEABLE_AXE), miningSpeed, miningDurabilityDamage) + + fun pickaxe( + miningSpeed: Float, + miningDurabilityDamage: Int + ) = tool(Registry.BLOCK.getTag(BlockTypeTagKeys.MINEABLE_PICKAXE), miningSpeed, miningDurabilityDamage) + + fun shovel( + miningSpeed: Float, + miningDurabilityDamage: Int + ) = tool(Registry.BLOCK.getTag(BlockTypeTagKeys.MINEABLE_SHOVEL), miningSpeed, miningDurabilityDamage) + + fun hoe( + miningSpeed: Float, + miningDurabilityDamage: Int + ) = tool(Registry.BLOCK.getTag(BlockTypeTagKeys.MINEABLE_HOE), miningSpeed, miningDurabilityDamage) + + fun tool( + blocks: RegistryKeySet, + miningSpeed: Float, + miningDurabilityDamage: Int + ) = apply { + set(DataComponentTypes.TOOL, Tool.tool() + .defaultMiningSpeed(miningSpeed) + .damagePerBlock(miningDurabilityDamage) + .addRule(Tool.rule(blocks, miningSpeed, TriState.TRUE))) + } + + fun noTool() = unset(DataComponentTypes.TOOL) + + fun weapon( + attackDamage: Double, + attackSpeed: Double, + attackDurabilityDamage: Int, + disableShieldSeconds: Float + ) = apply { + addAttributeModifier(Attribute.ATTACK_DAMAGE, AttributeModifier(baseAttackDamage, -1.0 + attackDamage, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlotGroup.MAINHAND)) + addAttributeModifier(Attribute.ATTACK_SPEED, AttributeModifier(baseAttackSpeed, -4.0 + attackSpeed, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlotGroup.MAINHAND)) + set(DataComponentTypes.WEAPON, Weapon.weapon() + .itemDamagePerAttack(attackDurabilityDamage) + .disableBlockingForSeconds(disableShieldSeconds)) + } + + fun attackKnockback(knockback: Double) = apply { + removeAttributeModifiers(Attribute.ATTACK_KNOCKBACK) + addAttributeModifier(Attribute.ATTACK_KNOCKBACK, AttributeModifier(baseAttackKnockback, knockback, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlotGroup.MAINHAND)) + } + + fun durability(durability: Int) = durability(durability, 0) + + fun durability(durability: Int, damage: Int) = apply { + set(DataComponentTypes.MAX_DAMAGE, durability) + set(DataComponentTypes.DAMAGE, damage) + } + + fun useCooldown(cooldownTicks: Int, cooldownGroup: Key?) + = set(DataComponentTypes.USE_COOLDOWN, UseCooldown.useCooldown(cooldownTicks / 20.0f).cooldownGroup(cooldownGroup)) + fun build(): ItemStack = stack.clone() /** @@ -164,6 +327,13 @@ open class ItemStackBuilder private constructor(val stack: ItemStack) : ItemProv companion object { + val baseArmor = NamespacedKey.minecraft("base_armor") + val baseArmorToughness = NamespacedKey.minecraft("base_armor_toughness") + + val baseAttackDamage = NamespacedKey.minecraft("base_attack_damage") + val baseAttackSpeed = NamespacedKey.minecraft("base_attack_speed") + val baseAttackKnockback = NamespacedKey.minecraft("base_attack_knockback") + val disableNameHacksKey = pylonKey("disable_name_hacks") /** @@ -195,38 +365,475 @@ open class ItemStackBuilder private constructor(val stack: ItemStack) : ItemProv } /** - * Creates a new [ItemStack] for a [io.github.pylonmc.pylon.core.item.PylonItem] by setting + * Creates a new [ItemStack] for a GUI item, sets its pdc key and adds + * a custom model data string for resource packs. + */ + @JvmStatic + fun gui(stack: ItemStack, key: String): ItemStackBuilder { + return ItemStackBuilder(stack) + .editPdc { it.set(GuiItems.pylonGuiItemKeyKey, PylonSerializers.STRING, key) } + .addCustomModelDataString(key.toString()) + } + + /** + * Creates a new [ItemStack] for a GUI item, sets its pdc key and adds + * a custom model data string for resource packs. + */ + @JvmStatic + fun gui(material: Material, key: String): ItemStackBuilder { + return gui(ItemStack(material), key) + } + + /** + * Creates a new [ItemStack] for a GUI item, sets its pdc key and adds + * a custom model data string for resource packs. + */ + @JvmStatic + fun gui(stack: ItemStack, key: NamespacedKey): ItemStackBuilder { + return gui(stack, key.toString()) + } + + /** + * Creates a new [ItemStack] for a GUI item, sets its pdc key and adds + * a custom model data string for resource packs. + */ + @JvmStatic + fun gui(material: Material, key: NamespacedKey): ItemStackBuilder { + return gui(ItemStack(material), key) + } + + /** + * Creates a new [ItemStack] for a [PylonItem] by setting * the name and lore to the default translation keys, and setting the item's Pylon ID to the * provided [key]. */ @JvmStatic - fun pylonItem(material: Material, key: NamespacedKey): ItemStackBuilder { - return of(material) - .editPdc { pdc -> pdc.set(PylonItemSchema.pylonItemKeyKey, PylonSerializers.NAMESPACED_KEY, key) } - .set(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelData.customModelData().addString(key.toString())) + fun pylon(stack: ItemStack, key: NamespacedKey): ItemStackBuilder { + return ItemStackBuilder(stack) + .editPdc { it.set(PylonItemSchema.pylonItemKeyKey, PylonSerializers.NAMESPACED_KEY, key) } + .addCustomModelDataString(key.toString()) .defaultTranslatableName(key) .defaultTranslatableLore(key) } /** - * Returns an [ItemStackBuilder] with name and lore set to the default translation keys, and with the item's ID set to [key] + * Creates a new [ItemStack] for a [PylonItem] by setting + * the name and lore to the default translation keys, and setting the item's Pylon ID to the + * provided [key]. + */ + @JvmStatic + fun pylon(material: Material, key: NamespacedKey): ItemStackBuilder { + return pylon(ItemStack(material), key) + } + + /** + * Creates a new [ItemStack] for a [PylonItem] by setting + * the name and lore to the default translation keys, and setting the item's Pylon ID to the + * provided [key]. + * + * The provided [consumer] is called with the created [ItemStackBuilder] and + * the [Settings][Settings.get] for the item, allowing you to further customize the item + * based on its config. */ @JvmStatic - fun pylonItem(stack: ItemStack, key: NamespacedKey): ItemStackBuilder { - return of(stack) - .editPdc { it.set(PylonItemSchema.pylonItemKeyKey, PylonSerializers.NAMESPACED_KEY, key) } - .let { - // Adds the pylon item key as the FIRST string in custom model data, but preserve any pre-existing data - val originalModelData = it.stack.getData(DataComponentTypes.CUSTOM_MODEL_DATA) - val modelData = CustomModelData.customModelData().addString(key.toString()) - if (originalModelData != null) { - modelData.addStrings(originalModelData.strings()).addColors(originalModelData.colors()) - .addFloats(originalModelData.floats()).addFlags(originalModelData.flags()) - } - it.set(DataComponentTypes.CUSTOM_MODEL_DATA, modelData) - } - .defaultTranslatableName(key) - .defaultTranslatableLore(key) + fun pylon(stack: ItemStack, key: NamespacedKey, consumer: (ItemStackBuilder, Config) -> Any): ItemStackBuilder { + val builder = pylon(stack, key) + val settings = Settings.get(key) + consumer(builder, settings) + return builder + } + + /** + * Creates a new [ItemStack] for a [PylonItem] by setting + * the name and lore to the default translation keys, and setting the item's Pylon ID to the + * provided [key]. + * + * The provided [consumer] is called with the created [ItemStackBuilder] and + * the [Settings][Settings.get] for the item, allowing you to further customize the item + * based on its config. + */ + @JvmStatic + fun pylon(material: Material, key: NamespacedKey, consumer: (ItemStackBuilder, Config) -> Any): ItemStackBuilder { + return pylon(ItemStack(material), key, consumer) + } + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be worn in the [helmet][EquipmentSlotGroup.HEAD] slot. + * You must provide a value for `armor` and `armor-toughness` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-helmet.yml` + * ```yml + * armor: 2.0 # double + * armor-toughness: 0.5 # double + * durability: 250 # integer + * ``` + */ + @JvmStatic + fun pylonHelmet(stack: ItemStack, key: NamespacedKey, hasDurability: Boolean) = pylonArmor(stack, key, EquipmentSlotGroup.HEAD, hasDurability) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be worn in the [helmet][EquipmentSlotGroup.HEAD] slot. + * You must provide a value for `armor` and `armor-toughness` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-helmet.yml` + * ```yml + * armor: 2.0 # double + * armor-toughness: 0.5 # double + * durability: 250 # integer + * ``` + */ + @JvmStatic + fun pylonHelmet(material: Material, key: NamespacedKey, hasDurability: Boolean) = pylonHelmet(ItemStack(material), key, hasDurability) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be worn in the [chestplate][EquipmentSlotGroup.CHEST] slot. + * You must provide a value for `armor` and `armor-toughness` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-chestplate.yml` + * ```yml + * armor: 6.0 # double + * armor-toughness: 1.0 # double + * durability: 400 # integer + * ``` + */ + @JvmStatic + fun pylonChestplate(stack: ItemStack, key: NamespacedKey, hasDurability: Boolean) = pylonArmor(stack, key, EquipmentSlotGroup.CHEST, hasDurability) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be worn in the [chestplate][EquipmentSlotGroup.CHEST] slot. + * You must provide a value for `armor` and `armor-toughness` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-chestplate.yml` + * ```yml + * armor: 6.0 # double + * armor-toughness: 1.0 # double + * durability: 400 # integer + * ``` + */ + @JvmStatic + fun pylonChestplate(material: Material, key: NamespacedKey, hasDurability: Boolean) = pylonChestplate(ItemStack(material), key, hasDurability) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be worn in the [leggings][EquipmentSlotGroup.LEGS] slot. + * You must provide a value for `armor` and `armor-toughness` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-leggings.yml` + * ```yml + * armor: 5.0 # double + * armor-toughness: 0.5 # double + * durability: 350 # integer + * ``` + */ + @JvmStatic + fun pylonLeggings(stack: ItemStack, key: NamespacedKey, hasDurability: Boolean) = pylonArmor(stack, key, EquipmentSlotGroup.LEGS, hasDurability) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be worn in the [leggings][EquipmentSlotGroup.LEGS] slot. + * You must provide a value for `armor` and `armor-toughness` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-leggings.yml` + * ```yml + * armor: 5.0 # double + * armor-toughness: 0.5 # double + * durability: 350 # integer + * ``` + */ + @JvmStatic + fun pylonLeggings(material: Material, key: NamespacedKey, hasDurability: Boolean) = pylonLeggings(ItemStack(material), key, hasDurability) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be worn in the [boots][EquipmentSlotGroup.FEET] slot. + * You must provide a value for `armor` and `armor-toughness` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-boots.yml` + * ```yml + * armor: 2.0 # double + * armor-toughness: 0.0 # double + * durability: 300 # integer + * ``` + */ + @JvmStatic + fun pylonBoots(stack: ItemStack, key: NamespacedKey, hasDurability: Boolean) = pylonArmor(stack, key, EquipmentSlotGroup.FEET, hasDurability) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be worn in the [boots][EquipmentSlotGroup.FEET] slot. + * You must provide a value for `armor` and `armor-toughness` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-boots.yml` + * ```yml + * armor: 2.0 # double + * armor-toughness: 0.0 # double + * durability: 300 # integer + * ``` + */ + @JvmStatic + fun pylonBoots(material: Material, key: NamespacedKey, hasDurability: Boolean) = pylonBoots(ItemStack(material), key, hasDurability) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be worn in the specified [slot]. + * You must provide a value for `armor` and `armor-toughness` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-armor.yml` + * ```yml + * armor: 2.0 # double + * armor-toughness: 0.5 # double + * durability: 250 # integer + * ``` + */ + @JvmStatic + fun pylonArmor(stack: ItemStack, key: NamespacedKey, slot: EquipmentSlotGroup, hasDurability: Boolean) = pylon(stack, key) { builder, settings -> + builder.armor( + slot, + settings.getOrThrow("armor", ConfigAdapter.DOUBLE), + settings.getOrThrow("armor-toughness", ConfigAdapter.DOUBLE) + ) + + if (hasDurability) { + builder.durability(settings.getOrThrow("durability", ConfigAdapter.INT)) + } else { + builder.unset(DataComponentTypes.DAMAGE).unset(DataComponentTypes.MAX_DAMAGE) + } + } + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be worn in the specified [slot]. + * You must provide a value for `armor` and `armor-toughness` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-armor.yml` + * ```yml + * armor: 2.0 # double + * armor-toughness: 0.5 # double + * durability: 250 # integer + * ``` + */ + @JvmStatic + fun pylonArmor(material: Material, key: NamespacedKey, slot: EquipmentSlotGroup, hasDurability: Boolean) + = pylonArmor(ItemStack(material), key, slot, hasDurability) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be used as a tool for the [mineable] blocks. (See [pickaxeMineable] + * and related for basic tools) You must provide a value for `mining-speed` and `mining-durability-damage` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-tool.yml` + * ```yml + * mining-speed: 8.0 # double + * mining-durability-damage: 1 # integer + * durability: 500 # integer + * ``` + */ + @JvmStatic + fun pylonTool(stack: ItemStack, key: NamespacedKey, mineable: RegistryKeySet, hasDurability: Boolean) = pylon(stack, key) { builder, settings -> + builder.tool( + mineable, + settings.getOrThrow("mining-speed", ConfigAdapter.FLOAT), + settings.getOrThrow("mining-durability-damage", ConfigAdapter.INT) + ) + + if (hasDurability) { + builder.durability(settings.getOrThrow("durability", ConfigAdapter.INT)) + } else { + builder.unset(DataComponentTypes.DAMAGE).unset(DataComponentTypes.MAX_DAMAGE) + } + } + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be used as a tool for the [mineable] blocks. (See [pickaxeMineable] + * and related for basic tools) You must provide a value for `mining-speed` and `mining-durability-damage` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * `settings/example-tool.yml` + * ```yml + * mining-speed: 8.0 # double + * mining-durability-damage: 1 # integer + * durability: 500 # integer + * ``` + */ + @JvmStatic + fun pylonTool(material: Material, key: NamespacedKey, mineable: RegistryKeySet, hasDurability: Boolean) + = pylonTool(ItemStack(material), key, mineable, hasDurability) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be used as a weapon. + * You must provide a value for `attack-damage`, `attack-speed` and `attack-durability-damage` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * If [hasKnockback] is true, gives the item knockback defined by `attack-knockback` in the [Settings][Settings.get]. + * Otherwise, removes any existing attack knockback. + * + * If `disablesShield` is true, gives the item a shield disable time defined by `disable-shield-seconds` in the [Settings][Settings.get]. + * If false, the item will not disable shields when used in attacking. + * + * `settings/example-weapon.yml` + * ```yml + * attack-damage: 6.0 # double + * attack-speed: 1.6 # double + * attack-durability-damage: 2 # integer + * attack-knockback: 0.4 # double + * disable-shield-seconds: 3.0 # float + * durability: 500 # integer + * ``` + */ + @JvmStatic + fun pylonWeapon(stack: ItemStack, key: NamespacedKey, hasDurability: Boolean, hasKnockback: Boolean, disablesShield: Boolean) = pylon(stack, key) { builder, settings -> + builder.weapon( + settings.getOrThrow("attack-damage", ConfigAdapter.DOUBLE), + settings.getOrThrow("attack-speed", ConfigAdapter.DOUBLE), + settings.getOrThrow("attack-durability-damage", ConfigAdapter.INT), + if (disablesShield) settings.getOrThrow("disable-shield-seconds", ConfigAdapter.FLOAT) else 0f + ) + + if (hasKnockback) { + builder.attackKnockback(settings.getOrThrow("attack-knockback", ConfigAdapter.DOUBLE)) + } else { + builder.removeAttributeModifiers(Attribute.ATTACK_KNOCKBACK) + } + + if (hasDurability) { + builder.durability(settings.getOrThrow("durability", ConfigAdapter.INT)) + } else { + builder.unset(DataComponentTypes.DAMAGE).unset(DataComponentTypes.MAX_DAMAGE) + } + } + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be used as a weapon. + * You must provide a value for `attack-damage`, `attack-speed` and `attack-durability-damage` in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * If [hasKnockback] is true, gives the item knockback defined by `attack-knockback` in the [Settings][Settings.get]. + * Otherwise, removes any existing attack knockback. + * + * If `disablesShield` is true, gives the item a shield disable time defined by `disable-shield-seconds` in the [Settings][Settings.get]. + * If false, the item will not disable shields when used in attacking. + * + * `settings/example-weapon.yml` + * ```yml + * attack-damage: 6.0 # double + * attack-speed: 1.6 # double + * attack-durability-damage: 2 # integer + * attack-knockback: 0.4 # double + * disable-shield-seconds: 3.0 # float + * durability: 500 # integer + * ``` + */ + @JvmStatic + fun pylonWeapon(material: Material, key: NamespacedKey, hasDurability: Boolean, hasKnockback: Boolean, disablesShield: Boolean) + = pylonWeapon(ItemStack(material), key, hasDurability, hasKnockback, disablesShield) + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be used as a weapon and tool for the [mineable] blocks. (See [pickaxeMineable] and related for basic tools) + * You must provide a value for `attack-damage`, `attack-speed`, `attack-durability-damage`, `mining-speed` and `mining-durability-damage` + * in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * If [hasDurability] is true, gives the item knockback defined by `attack-knockback` in the [Settings][Settings.get]. + * Otherwise, removes any existing attack knockback. + * + * If [hasKnockback] is true, gives the item a shield disable time defined by `disable-shield-seconds` in the [Settings][Settings.get]. + * If false, the item will not disable shields when used in attacking. + * + * `settings/example-tool-weapon.yml` + * ```yml + * attack-damage: 6.0 # double + * attack-speed: 1.6 # double + * attack-durability-damage: 2 # integer + * attack-knockback: 0.4 # double + * disable-shield-seconds: 1.0 # float + * + * mining-speed: 8.0 # double + * mining-durability-damage: 1 # integer + * + * durability: 500 # integer + * ``` + */ + @JvmStatic + fun pylonToolWeapon(stack: ItemStack, key: NamespacedKey, mineable: RegistryKeySet, hasDurability: Boolean, hasKnockback: Boolean, disablesShield: Boolean): ItemStackBuilder { + val settings = Settings.get(key) + return pylonWeapon(stack, key, hasDurability, hasKnockback, disablesShield).tool( + mineable, + settings.getOrThrow("mining-speed", ConfigAdapter.FLOAT), + settings.getOrThrow("mining-durability-damage", ConfigAdapter.INT) + ) + } + + /** + * Creates a new [ItemStack] for a [PylonItem] that can be used as a weapon and tool for the [mineable] blocks. (See [pickaxeMineable] and related for basic tools) + * You must provide a value for `attack-damage`, `attack-speed`, `attack-durability-damage`, `mining-speed` and `mining-durability-damage` + * in the [Settings][Settings.get] for the item. + * + * If [hasDurability] is true, gives the item a max durability defined by `durability` in the [Settings][Settings.get]. + * Otherwise, removes any existing durability. + * + * If [hasKnockback] is true, gives the item knockback defined by `attack-knockback` in the [Settings][Settings.get]. + * Otherwise, removes any existing attack knockback. + * + * If `disablesShield` is true, gives the item a shield disable time defined by `disable-shield-seconds` in the [Settings][Settings.get]. + * If false, the item will not disable shields when used in attacking. + * + * `settings/example-tool-weapon.yml` + * ```yml + * attack-damage: 6.0 # double + * attack-speed: 1.6 # double + * attack-durability-damage: 2 # integer + * attack-knockback: 0.4 # double + * disable-shield-seconds: 1.0 # float + * + * mining-speed: 8.0 # double + * mining-durability-damage: 1 # integer + * + * durability: 500 # integer + * ``` + */ + @JvmStatic + fun pylonToolWeapon(material: Material, key: NamespacedKey, mineable: RegistryKeySet, hasDurability: Boolean, hasKnockback: Boolean, disablesShield: Boolean) + = pylonToolWeapon(ItemStack(material), key, mineable, hasDurability, hasKnockback, disablesShield) + + fun ItemAttributeModifiers.Builder.copy(modifiers: List?) : ItemAttributeModifiers.Builder { + modifiers?.forEach { entry -> + this.addModifier(entry.attribute(), entry.modifier(), entry.group, entry.display()) + } + return this } } } diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/PylonUtils.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/PylonUtils.kt index 110f9f839..54cedbf71 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/PylonUtils.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/util/PylonUtils.kt @@ -10,12 +10,14 @@ import io.github.pylonmc.pylon.core.entity.display.transform.TransformUtil.yawTo import io.github.pylonmc.pylon.core.item.PylonItem import io.github.pylonmc.pylon.core.registry.PylonRegistry import io.papermc.paper.datacomponent.DataComponentType +import io.papermc.paper.registry.keys.tags.BlockTypeTagKeys import net.kyori.adventure.text.Component import net.kyori.adventure.text.TranslatableComponent import net.kyori.adventure.text.TranslationArgumentLike import net.kyori.adventure.text.minimessage.MiniMessage import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer import org.bukkit.NamespacedKey +import org.bukkit.Registry import org.bukkit.block.Block import org.bukkit.block.BlockFace import org.bukkit.configuration.file.YamlConfiguration @@ -291,6 +293,27 @@ inline fun ItemStack.editData( return this } +@JvmSynthetic +@Suppress("UnstableApiUsage") +inline fun ItemStack.editDataOrDefault( + type: DataComponentType.Valued, + block: (T) -> T +): ItemStack { + val data = getData(type) ?: this.type.getDefaultData(type) ?: return this + setData(type, block(data)) + return this +} + +@JvmSynthetic +@Suppress("UnstableApiUsage") +inline fun ItemStack.editDataOrSet( + type: DataComponentType.Valued, + block: (T?) -> T +): ItemStack { + setData(type, block(getData(type))) + return this +} + /** * Wrapper around [PersistentDataContainer.set] that allows nullable values to be passed * @@ -395,3 +418,8 @@ fun ItemStack.vanillaDisplayName(): Component val Component.plainText: String get() = PlainTextComponentSerializer.plainText().serialize(this) + +fun pickaxeMineable() = Registry.BLOCK.getTag(BlockTypeTagKeys.MINEABLE_PICKAXE) +fun axeMineable() = Registry.BLOCK.getTag(BlockTypeTagKeys.MINEABLE_AXE) +fun shovelMineable() = Registry.BLOCK.getTag(BlockTypeTagKeys.MINEABLE_SHOVEL) +fun hoeMineable() = Registry.BLOCK.getTag(BlockTypeTagKeys.MINEABLE_HOE) \ No newline at end of file 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 ba4f6fe3b..874ce2091 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 @@ -5,6 +5,7 @@ package io.github.pylonmc.pylon.core.util.gui import io.github.pylonmc.pylon.core.i18n.PylonArgument import io.github.pylonmc.pylon.core.item.builder.ItemStackBuilder import io.github.pylonmc.pylon.core.util.gui.GuiItems.background +import io.github.pylonmc.pylon.core.util.pylonKey import io.papermc.paper.datacomponent.DataComponentTypes import io.papermc.paper.datacomponent.item.TooltipDisplay import net.kyori.adventure.text.Component @@ -22,13 +23,15 @@ import xyz.xenondevs.invui.item.impl.controlitem.ScrollItem * A utility class containing items commonly used in GUIs. */ object GuiItems { + val pylonGuiItemKeyKey = pylonKey("gui_item_key") + /** * A gray glass pane with no name or lore. */ @JvmStatic @JvmOverloads fun background(name: String = ""): Item = SimpleItem( - ItemStackBuilder.of(Material.GRAY_STAINED_GLASS_PANE) + ItemStackBuilder.gui(Material.GRAY_STAINED_GLASS_PANE, pylonKey("background")) .name(name) .set(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay().hideTooltip(true)) ) @@ -39,7 +42,7 @@ object GuiItems { @JvmStatic @JvmOverloads fun backgroundBlack(name: String = ""): Item = SimpleItem( - ItemStackBuilder.of(Material.BLACK_STAINED_GLASS_PANE) + ItemStackBuilder.gui(Material.BLACK_STAINED_GLASS_PANE, pylonKey("background_black")) .name(name) .set(DataComponentTypes.TOOLTIP_DISPLAY, TooltipDisplay.tooltipDisplay().hideTooltip(true)) ) @@ -109,13 +112,13 @@ object GuiItems { fun pagePrevious(): Item = PylonPageItem(false) } -private class PylonScrollItem(private val direction: Int, key: String?) : ScrollItem(direction) { +private class PylonScrollItem(private val direction: Int, private val key: String?) : ScrollItem(direction) { private val name = Component.translatable("pylon.pyloncore.gui.scroll.$key") override fun getItemProvider(gui: ScrollGui<*>): ItemProvider { val material = if (gui.canScroll(direction)) Material.GREEN_STAINED_GLASS_PANE else Material.RED_STAINED_GLASS_PANE - return ItemStackBuilder.of(material).name(name) + return ItemStackBuilder.gui(material, pylonKey("scroll_$key")).name(name) } } @@ -127,7 +130,7 @@ private class PylonPageItem(private val forward: Boolean) : PageItem(forward) { if (gui.pageAmount < 2) return background val material = if (gui.canPage) Material.GREEN_STAINED_GLASS_PANE else Material.RED_STAINED_GLASS_PANE - return ItemStackBuilder.of(material) + return ItemStackBuilder.gui(material, pylonKey("page_${if (forward) "next" else "previous"}")) .name( name.arguments( PylonArgument.of("current", gui.currentPage + 1), diff --git a/test/src/main/java/io/github/pylonmc/pylon/test/item/Items.java b/test/src/main/java/io/github/pylonmc/pylon/test/item/Items.java index 19c66061c..da367c859 100644 --- a/test/src/main/java/io/github/pylonmc/pylon/test/item/Items.java +++ b/test/src/main/java/io/github/pylonmc/pylon/test/item/Items.java @@ -9,13 +9,12 @@ import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; - public final class Items { private Items() {} public static final NamespacedKey STICKY_STICK_KEY = PylonTest.key("sticky_stick"); - public static final ItemStack STICKY_STICK_STACK = ItemStackBuilder.pylonItem(Material.STICK, STICKY_STICK_KEY) + public static final ItemStack STICKY_STICK_STACK = ItemStackBuilder.pylon(Material.STICK, STICKY_STICK_KEY) .set(DataComponentTypes.ITEM_NAME, Component.text("Sticky Stick")) .build(); diff --git a/test/src/main/java/io/github/pylonmc/pylon/test/item/OminousBlazePower.java b/test/src/main/java/io/github/pylonmc/pylon/test/item/OminousBlazePower.java index 896c4ea9f..0fef400fb 100644 --- a/test/src/main/java/io/github/pylonmc/pylon/test/item/OminousBlazePower.java +++ b/test/src/main/java/io/github/pylonmc/pylon/test/item/OminousBlazePower.java @@ -14,7 +14,7 @@ public class OminousBlazePower extends PylonItem implements PylonBrewingStandFuel { public static final NamespacedKey KEY = PylonTest.key("ominous_blaze_powder"); - public static final ItemStack STACK = ItemStackBuilder.pylonItem(Material.DIAMOND_SWORD, KEY) + public static final ItemStack STACK = ItemStackBuilder.pylon(Material.DIAMOND_SWORD, KEY) .name("OMINOUS BLAZE POWDER") .lore("<#ff0000>VERY SCARY") .lore("<#222222>OH NO")