diff --git a/plugin/build.gradle b/plugin/build.gradle index c68367d..d4dfab0 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -25,6 +25,10 @@ repositories { name 'TimeAPI' url 'https://repo.piggypiglet.me/releases' } + + maven { + url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' + } } dependencies { @@ -34,6 +38,9 @@ dependencies { compileOnly 'me.gabytm.util:actions-core:1.0.0-SNAPSHOT' implementation('me.gabytm.util:actions-spigot:1.0.0-SNAPSHOT') { exclude group: 'com.google.guava' } + // Requirements + implementation 'me.gabytm.minecraft.util:requirements-bukkit:1.0.0-SNAPSHOT' + compileOnly('com.github.MilkBowl:VaultAPI:1.7') { transitive false } // NBT @@ -62,6 +69,9 @@ dependencies { // Other dependencies that don't have a repository or the release is old compileOnly fileTree(dir: './libs', includes: ['*.jar']) + + // PAPI + compileOnly 'me.clip:placeholderapi:2.10.10' } compileKotlin { diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/ArcaneVouchers.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/ArcaneVouchers.kt index b5f25d2..39a56f4 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/ArcaneVouchers.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/ArcaneVouchers.kt @@ -12,6 +12,7 @@ import me.gabytm.minecraft.arcanevouchers.listeners.VoucherUseListener import me.gabytm.minecraft.arcanevouchers.message.Lang import me.gabytm.minecraft.arcanevouchers.other.ResourcesHandler import me.gabytm.minecraft.arcanevouchers.voucher.VoucherManager +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.ArcaneRequirementProcessor import net.kyori.adventure.platform.bukkit.BukkitAudiences import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin @@ -29,6 +30,7 @@ class ArcaneVouchers : JavaPlugin() { lateinit var actionManager: ArcaneActionManager private set lateinit var itemCreator: ItemCreator private set lateinit var voucherManager: VoucherManager private set + lateinit var requirementProcessor: ArcaneRequirementProcessor private set private fun sendLogo() { with (description) { @@ -62,6 +64,7 @@ class ArcaneVouchers : JavaPlugin() { this.actionManager = ArcaneActionManager(this) this.itemCreator = ItemCreator(this) this.voucherManager = VoucherManager(this) + this.requirementProcessor = ArcaneRequirementProcessor(actionManager) reload() CommandManager(this) // register the commands diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/Constant.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/Constant.kt index 457f6f7..8409a58 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/Constant.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/Constant.kt @@ -54,6 +54,15 @@ object Constant { } + object Requirement { + + const val TYPE: String = "type" + const val OPTIONAL: String = "optional" + const val NEGATION: Char = '!' + const val FAIL_ACTIONS: String = "failActions" + + } + object Separator { val COLON = Regex(":") diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/actions/implementations/message/MessageAction.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/actions/implementations/message/MessageAction.kt index e7f7908..03a1149 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/actions/implementations/message/MessageAction.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/actions/implementations/message/MessageAction.kt @@ -19,6 +19,7 @@ class MessageAction(meta: ActionMeta, handler: PermissionHandler) : Arca private val messageType: MessageType = meta.getProperty("type", MessageType.CHAT) { MessageType.find(it) } private val broadcast: Broadcast = Broadcast.parse(meta.properties["broadcast"]) + @Transient private val times: Title.Times = Title.Times.times( parseDuration("fadeIn", Title.DEFAULT_TIMES.fadeIn()), parseDuration("stay", Title.DEFAULT_TIMES.stay()), diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/commands/commands/DebugCommand.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/commands/commands/DebugCommand.kt index 7c91016..16a3636 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/commands/commands/DebugCommand.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/commands/commands/DebugCommand.kt @@ -6,6 +6,9 @@ import me.gabytm.minecraft.arcanevouchers.ArcaneVouchers import me.gabytm.minecraft.arcanevouchers.Constant import me.gabytm.minecraft.arcanevouchers.commands.ArcaneCommand import me.gabytm.minecraft.arcanevouchers.functions.exception +import me.gabytm.minecraft.arcanevouchers.io.serializers.java.PatternSerializer +import me.gabytm.minecraft.arcanevouchers.io.serializers.adventure.TextComponentSerializer +import me.gabytm.minecraft.arcanevouchers.io.serializers.bukkit.LocationSerializer import me.gabytm.minecraft.arcanevouchers.message.implementations.ActionBarMessage import me.gabytm.minecraft.arcanevouchers.message.implementations.ChatMessage import me.gabytm.minecraft.arcanevouchers.message.implementations.TitleMessage @@ -14,8 +17,8 @@ import me.gabytm.minecraft.arcanevouchers.voucher.settings.VoucherSettings import me.mattstudios.mf.annotations.Permission import me.mattstudios.mf.annotations.SubCommand import net.kyori.adventure.text.TextComponent -import net.kyori.adventure.text.minimessage.MiniMessage import org.bukkit.Bukkit +import org.bukkit.Location import org.bukkit.command.CommandSender import java.io.IOException import java.io.InputStreamReader @@ -42,6 +45,7 @@ class DebugCommand(plugin: ArcaneVouchers) : ArcaneCommand(plugin) { .registerTypeAdapter(TextComponent::class.java, TextComponentSerializer()) .registerTypeAdapter(Voucher::class.java, VoucherSerializer.INSTANCE) .registerTypeAdapter(Pattern::class.java, PatternSerializer()) + .registerTypeAdapter(Location::class.java, LocationSerializer.INSTANCE) // Messages .registerTypeAdapter(ActionBarMessage::class.java, ActionBarMessage.Serializer.INSTANCE) .registerTypeAdapter(ChatMessage::class.java, ChatMessage.Serializer.INSTANCE) @@ -156,20 +160,4 @@ class DebugCommand(plugin: ArcaneVouchers) : ArcaneCommand(plugin) { } - internal class TextComponentSerializer : JsonSerializer { - - override fun serialize(src: TextComponent?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { - return if (src == null) JsonNull.INSTANCE else JsonPrimitive(MiniMessage.miniMessage().serialize(src)) - } - - } - - internal class PatternSerializer : JsonSerializer { - - override fun serialize(src: Pattern?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { - return JsonPrimitive(src?.pattern()) - } - - } - } \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/config/Config.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/config/Config.kt index 912a10b..a6c19cd 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/config/Config.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/config/Config.kt @@ -10,6 +10,7 @@ import java.nio.file.Files class Config(plugin: ArcaneVouchers, path: String, isResource: Boolean = true) { + @Transient private val file = File(plugin.dataFolder, path) var yaml: YamlConfiguration private set diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/functions/Strings.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/functions/Strings.kt index 8bea16a..e7bcd9f 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/functions/Strings.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/functions/Strings.kt @@ -2,12 +2,15 @@ package me.gabytm.minecraft.arcanevouchers.functions import com.google.common.base.Enums import com.google.common.primitives.Ints +import me.clip.placeholderapi.PlaceholderAPI import me.gabytm.minecraft.arcanevouchers.Constant import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.TextDecoration import net.md_5.bungee.api.ChatColor import org.apache.commons.lang.StringUtils +import org.bukkit.Bukkit import org.bukkit.Color +import org.bukkit.OfflinePlayer import sh.okx.timeapi.TimeAPI import java.util.concurrent.TimeUnit import java.util.regex.Pattern @@ -36,6 +39,14 @@ fun String.mini(removeItalic: Boolean = false): Component { } } +fun String.papi(player: OfflinePlayer?): String { + return if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { + PlaceholderAPI.setPlaceholders(player, this) + } else { + this + } +} + fun Array.toArgsMap(): MutableMap { if (isEmpty()) { return mutableMapOf() diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/io/serializers/adventure/TextComponentSerializer.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/io/serializers/adventure/TextComponentSerializer.kt new file mode 100644 index 0000000..ec3c424 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/io/serializers/adventure/TextComponentSerializer.kt @@ -0,0 +1,14 @@ +package me.gabytm.minecraft.arcanevouchers.io.serializers.adventure + +import com.google.gson.* +import net.kyori.adventure.text.TextComponent +import net.kyori.adventure.text.minimessage.MiniMessage +import java.lang.reflect.Type + +class TextComponentSerializer : JsonSerializer { + + override fun serialize(src: TextComponent?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { + return if (src == null) JsonNull.INSTANCE else JsonPrimitive(MiniMessage.miniMessage().serialize(src)) + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/io/serializers/bukkit/LocationSerializer.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/io/serializers/bukkit/LocationSerializer.kt new file mode 100644 index 0000000..31212b6 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/io/serializers/bukkit/LocationSerializer.kt @@ -0,0 +1,28 @@ +package me.gabytm.minecraft.arcanevouchers.io.serializers.bukkit + +import com.google.gson.* +import org.bukkit.Location +import java.lang.reflect.Type + +class LocationSerializer : JsonSerializer { + + override fun serialize(src: Location?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { + if (src == null) { + return JsonNull.INSTANCE + } + + val element = JsonObject() + element.addProperty("world", src.world?.name) + element.addProperty("x", src.x) + element.addProperty("y", src.y) + element.addProperty("z", src.z) + return element + } + + companion object { + + val INSTANCE = LocationSerializer() + + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/io/serializers/java/PatternSerializer.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/io/serializers/java/PatternSerializer.kt new file mode 100644 index 0000000..fc31a15 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/io/serializers/java/PatternSerializer.kt @@ -0,0 +1,16 @@ +package me.gabytm.minecraft.arcanevouchers.io.serializers.java + +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import java.lang.reflect.Type +import java.util.regex.Pattern + +class PatternSerializer : JsonSerializer { + + override fun serialize(src: Pattern?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { + return JsonPrimitive(src?.pattern()) + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/listeners/VoucherUseListener.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/listeners/VoucherUseListener.kt index 3aaa561..a8f349e 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/listeners/VoucherUseListener.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/listeners/VoucherUseListener.kt @@ -8,6 +8,7 @@ import me.gabytm.minecraft.arcanevouchers.ServerVersion import me.gabytm.minecraft.arcanevouchers.functions.* import me.gabytm.minecraft.arcanevouchers.limit.LimitType import me.gabytm.minecraft.arcanevouchers.voucher.Voucher +import me.gabytm.minecraft.util.requirements.Arguments import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.entity.Player @@ -203,6 +204,10 @@ class VoucherUseListener(private val plugin: ArcaneVouchers) : Listener { return } + if (!settings.requirementsList.check(player, Arguments.of(emptyMap()))) { + return + } + val isBulk = settings.bulkOpen.enabled && player.isSneaking if (settings.confirmationEnabled) { diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/other/ResourcesHandler.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/other/ResourcesHandler.kt index 384e5a3..9d3e4b9 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/other/ResourcesHandler.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/other/ResourcesHandler.kt @@ -9,6 +9,7 @@ import me.gabytm.minecraft.arcanevouchers.functions.warning import net.kyori.adventure.bossbar.BossBar import net.kyori.adventure.sound.Sound import org.bukkit.Bukkit +import org.bukkit.DyeColor import org.bukkit.Material import org.bukkit.block.banner.PatternType import org.bukkit.configuration.file.YamlConfiguration @@ -47,7 +48,7 @@ class ResourcesHandler(plugin: ArcaneVouchers) { } create("DyeColors") { - it["list"] = Constant.NAMED_COLORS.keys.toList().sorted() + it["list"] = DyeColor.values().map { it.name }.sorted() } // For some reason, on versions <= 1.12.2, the first value of the array is null (???) diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/Voucher.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/Voucher.kt index 35179b8..d9838aa 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/Voucher.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/Voucher.kt @@ -11,6 +11,7 @@ import me.gabytm.minecraft.arcanevouchers.functions.debug import me.gabytm.minecraft.arcanevouchers.functions.item import me.gabytm.minecraft.arcanevouchers.items.ItemCreator import me.gabytm.minecraft.arcanevouchers.limit.LimitType +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.ArcaneRequirementProcessor import me.gabytm.minecraft.arcanevouchers.voucher.settings.VoucherSettings import org.bukkit.Material import org.bukkit.configuration.ConfigurationSection @@ -179,9 +180,12 @@ class Voucher private constructor( companion object { - fun from(config: ConfigurationSection, actionManager: ArcaneActionManager, itemCreator: ItemCreator): Voucher { + fun from( + config: ConfigurationSection, actionManager: ArcaneActionManager, + itemCreator: ItemCreator, requirementProcessor: ArcaneRequirementProcessor + ): Voucher { val id = config.name - val settings = VoucherSettings.from(config.getConfigurationSection("settings")) + val settings = VoucherSettings.from(config.getConfigurationSection("settings"), requirementProcessor) val item = itemCreator.create(true, config.getConfigurationSection("item"), Material.PAPER).apply { val nbtItem = NBTItem(this, true) val compound = nbtItem.getOrCreateCompound(NBT.VOUCHER_COMPOUND) diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/VoucherManager.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/VoucherManager.kt index 362076e..42f4d5f 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/VoucherManager.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/VoucherManager.kt @@ -36,7 +36,7 @@ class VoucherManager(private val plugin: ArcaneVouchers) { } val section = vouchersSection.getConfigurationSection(it) ?: continue - this.loadedVouchers[it] = Voucher.from(section, plugin.actionManager, plugin.itemCreator) + this.loadedVouchers[it] = Voucher.from(section, plugin.actionManager, plugin.itemCreator, plugin.requirementProcessor) } info("Loaded ${this.loadedVouchers.size} voucher(s): ${this.loadedVouchers.keys.joinToString(", ")}") diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/ArcaneRequirement.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/ArcaneRequirement.kt new file mode 100644 index 0000000..4564978 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/ArcaneRequirement.kt @@ -0,0 +1,27 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements + +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneAction +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneActionManager +import me.gabytm.minecraft.util.requirements.Requirement +import org.bukkit.entity.Player + +abstract class ArcaneRequirement( + name: String, + optional: Boolean, + negated: Boolean, + @Transient private val failActions: List, + @Transient private val actionManager: ArcaneActionManager + // A requirement can not be 'optional' and 'required' at the same time + // So we consider a requirement 'required' if it is not 'optional' +) : Requirement(name, !optional, optional, negated) { + + @Suppress("unused") + protected val requirementType: String = this::class.java.simpleName + + override fun onFail(player: Player?) { + if (player != null && failActions.isNotEmpty()) { + actionManager.executeActions(player, failActions) + } + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/ArcaneRequirementFactory.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/ArcaneRequirementFactory.kt new file mode 100644 index 0000000..3f39b50 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/ArcaneRequirementFactory.kt @@ -0,0 +1,13 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements + +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneActionManager +import me.gabytm.minecraft.util.requirements.RequirementFactory +import org.bukkit.configuration.ConfigurationSection + +abstract class ArcaneRequirementFactory : RequirementFactory { + + override fun create(source: ConfigurationSection): R? = null + + abstract fun create(source: ConfigurationSection, actionManager: ArcaneActionManager): R? + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/ArcaneRequirementProcessor.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/ArcaneRequirementProcessor.kt new file mode 100644 index 0000000..b757fa3 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/ArcaneRequirementProcessor.kt @@ -0,0 +1,50 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements + +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneActionManager +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.location.DistanceRequirementFactory +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.number.NumberRequirementFactory +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.string.StringRequirementFactory +import me.gabytm.minecraft.util.requirements.RequirementsList +import me.gabytm.minecraft.util.requirements.bukkit.BukkitRequirementProcessor +import org.bukkit.configuration.ConfigurationSection +import org.bukkit.entity.Player + +class ArcaneRequirementProcessor(private val actionManager: ArcaneActionManager) : BukkitRequirementProcessor() { + + private val requirementFactories = mutableSetOf>() + + init { + // Location + registerFactory(DistanceRequirementFactory()) + + registerFactory(NumberRequirementFactory()) + registerFactory(StringRequirementFactory()) + } + + private fun createRequirement(source: ConfigurationSection): ArcaneRequirement? { + return requirementFactories.firstOrNull { it.matches(source) }?.create(source, actionManager) + } + + fun registerFactory(factory: ArcaneRequirementFactory) { + requirementFactories.add(factory) + } + + fun processRequirements(root: ConfigurationSection?): RequirementsList { + if (root == null) { + return RequirementsList(emptyList()) + } + + val minimumRequirements = root.getInt("minimumRequirements", RequirementsList.ALL_REQUIREMENTS) + val requirementsSection = root.getConfigurationSection("list") ?: return RequirementsList(emptyList()) + val requirements = mutableListOf() + + for (name in requirementsSection.getKeys(false)) { + val section = requirementsSection.getConfigurationSection(name) ?: continue + val requirement = createRequirement(section) ?: continue + requirements.add(requirement) + } + + return RequirementsList(requirements, minimumRequirements) + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/BooleanVariable.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/BooleanVariable.kt new file mode 100644 index 0000000..a5dc530 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/BooleanVariable.kt @@ -0,0 +1,17 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.common.variable + +import me.gabytm.minecraft.arcanevouchers.functions.papi +import me.gabytm.minecraft.arcanevouchers.functions.warning +import org.bukkit.entity.Player + +data class BooleanVariable( + private val string: String +) : Variable( + { player -> string.toBooleanStrictOrNull() ?: string.papi(player).toBooleanStrictOrNull() } +) { + + override fun warn(player: Player?, requirement: String) { + warning("[$requirement] '$string' (${string.papi(player)}) is not a valid boolean (accepted values: true, false)") + } + +} diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/DoubleVariable.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/DoubleVariable.kt new file mode 100644 index 0000000..aada795 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/DoubleVariable.kt @@ -0,0 +1,17 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.common.variable + +import me.gabytm.minecraft.arcanevouchers.functions.papi +import me.gabytm.minecraft.arcanevouchers.functions.warning +import org.bukkit.entity.Player + +data class DoubleVariable( + private val string: String +) : Variable( + { player -> string.toDoubleOrNull() ?: string.papi(player).toDoubleOrNull() } +) { + + override fun warn(player: Player?, requirement: String) { + warning("[$requirement] '$string' (${string.papi(player)}) is not a valid Double") + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/LocationVariable.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/LocationVariable.kt new file mode 100644 index 0000000..e4e4ace --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/LocationVariable.kt @@ -0,0 +1,33 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.common.variable + +import me.gabytm.minecraft.arcanevouchers.Constant +import me.gabytm.minecraft.arcanevouchers.functions.papi +import me.gabytm.minecraft.arcanevouchers.functions.warning +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.entity.Player + +data class LocationVariable( + val string: String +) : Variable( + transformer@{ player -> + // world;x;y;z + val parts = string.papi(player).split(Constant.Separator.SEMICOLON) + + if (parts.size != 4) { + return@transformer null + } + + val world = Bukkit.getWorld(parts[0]) ?: return@transformer null + val x = parts[1].toDoubleOrNull() ?: return@transformer null + val y = parts[2].toDoubleOrNull() ?: return@transformer null + val z = parts[3].toDoubleOrNull() ?: return@transformer null + return@transformer Location(world, x, y, z) + } +) { + + override fun warn(player: Player?, requirement: String) { + warning("[$requirement] '$string' (${string.papi(player)}) is not a valid location (expected format: world;x;y;z)") + } + +} diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/Variable.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/Variable.kt new file mode 100644 index 0000000..aeb79ee --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/common/variable/Variable.kt @@ -0,0 +1,17 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.common.variable + +import org.bukkit.entity.Player + +abstract class Variable( + @Transient private val transformer: (player: Player?) -> T? +) { + + @Suppress("unused") + private val variableType = this::class.java.simpleName + private val parsed: T? = transformer(null) + + fun get(player: Player?): T? = parsed ?: transformer(player) + + abstract fun warn(player: Player?, requirement: String) + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/location/DistanceRequirement.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/location/DistanceRequirement.kt new file mode 100644 index 0000000..8e42432 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/location/DistanceRequirement.kt @@ -0,0 +1,51 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.location + +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneAction +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneActionManager +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.ArcaneRequirement +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.common.variable.DoubleVariable +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.common.variable.LocationVariable +import me.gabytm.minecraft.util.requirements.Arguments +import org.bukkit.entity.Player +import kotlin.math.pow + +class DistanceRequirement( + name: String, + optional: Boolean, + negated: Boolean, + failActions: List, + actionManager: ArcaneActionManager, + private val locationVariable: LocationVariable, + private val distanceVariable: DoubleVariable +) : ArcaneRequirement( + name, optional, negated, failActions, actionManager +) { + + override fun check(player: Player?, arguments: Arguments): Boolean { + if (player == null) { + return false + } + + val distance = distanceVariable.get(player) ?: kotlin.run { + distanceVariable.warn(player, getName()) + return false + } + val location = locationVariable.get(player) ?: kotlin.run { + locationVariable.warn(player, getName()) + return false + } + + if (player.world != location.world) { + return false + } + + return (player.location.distanceSquared(location) <= distance.pow(2)) == !negated + } + + companion object { + + const val TYPE: String = "distance" + + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/location/DistanceRequirementFactory.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/location/DistanceRequirementFactory.kt new file mode 100644 index 0000000..6ff3e6f --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/location/DistanceRequirementFactory.kt @@ -0,0 +1,48 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.location + +import me.gabytm.minecraft.arcanevouchers.Constant.Requirement +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneActionManager +import me.gabytm.minecraft.arcanevouchers.functions.warning +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.ArcaneRequirementFactory +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.common.variable.DoubleVariable +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.common.variable.LocationVariable +import org.bukkit.configuration.ConfigurationSection + +class DistanceRequirementFactory : ArcaneRequirementFactory() { + + private fun warnMissingProperty(source: ConfigurationSection, property: String) { + warning("Could not load '${DistanceRequirement.TYPE}' requirement from ${source.currentPath}: missing required property '${property}'") + } + + override fun matches(source: ConfigurationSection): Boolean { + var type = source.getString(Requirement.TYPE)?.trim() ?: return false + + if (type.startsWith(Requirement.NEGATION)) { + type = type.substring(1) + } + + return type.equals(DistanceRequirement.TYPE, true) + } + + override fun create(source: ConfigurationSection, actionManager: ArcaneActionManager): DistanceRequirement? { + val type = source.getString(Requirement.TYPE)?.trim() ?: return null + val negated = type.startsWith(Requirement.NEGATION) + val optional = source.getBoolean(Requirement.OPTIONAL) + + val location = LocationVariable( + source.getString("location") ?: kotlin.run { + warnMissingProperty(source, "location") + return null + } + ) + val distance = DoubleVariable( + source.getString("distance") ?: kotlin.run { + warnMissingProperty(source, "distance") + return null + } + ) + val failActions = actionManager.parseActions(source.getStringList(Requirement.FAIL_ACTIONS)) + return DistanceRequirement(source.name, optional, negated, failActions, actionManager, location, distance) + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/number/NumberRequirement.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/number/NumberRequirement.kt new file mode 100644 index 0000000..ad36185 --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/number/NumberRequirement.kt @@ -0,0 +1,84 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.number + +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneAction +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneActionManager +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.ArcaneRequirement +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.common.variable.DoubleVariable +import me.gabytm.minecraft.util.requirements.Arguments +import org.bukkit.entity.Player +import java.util.* + +class NumberRequirement( + name: String, + optional: Boolean, + negated: Boolean, + failActions: List, + actionManager: ArcaneActionManager, + private val left: DoubleVariable, + private val right: DoubleVariable, + private val operation: Operation +) : ArcaneRequirement( + name, optional, negated, failActions, actionManager +) { + + override fun check(player: Player?, arguments: Arguments): Boolean { + val leftNumber = left.get(player) ?: kotlin.run { + left.warn(player, getName()) + return false + } + val rightNumber = right.get(player) ?: kotlin.run { + right.warn(player, getName()) + return false + } + + return operation.check(leftNumber, rightNumber, !negated) + } + + @Suppress("unused") + enum class Operation(val identifier: String) { + + EQUAL("==") { + override fun check(left: Double, right: Double, negated: Boolean): Boolean { + return (left == right) == negated + } + }, + + GREATER(">") { + override fun check(left: Double, right: Double, negated: Boolean): Boolean { + return (left > right) == negated + } + }, + + GREATER_OR_EQUAL(">=") { + override fun check(left: Double, right: Double, negated: Boolean): Boolean { + return (left >= right) == negated + } + }, + + SMALLER("<") { + override fun check(left: Double, right: Double, negated: Boolean): Boolean { + return (left < right) == negated + } + }, + + SMALLER_OR_EQUAL("<=") { + override fun check(left: Double, right: Double, negated: Boolean): Boolean { + return (left <= right) == negated + } + }; + + abstract fun check(left: Double, right: Double, negated: Boolean): Boolean + + companion object { + + private val VALUES = EnumSet.allOf(Operation::class.java) + + fun find(string: String): Operation? { + return VALUES.firstOrNull { it.identifier == string } + } + + } + + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/number/NumberRequirementFactory.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/number/NumberRequirementFactory.kt new file mode 100644 index 0000000..870da5b --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/number/NumberRequirementFactory.kt @@ -0,0 +1,57 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.number + +import me.gabytm.minecraft.arcanevouchers.Constant.Requirement +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneActionManager +import me.gabytm.minecraft.arcanevouchers.functions.warning +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.ArcaneRequirementFactory +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.common.variable.DoubleVariable +import org.bukkit.configuration.ConfigurationSection + +class NumberRequirementFactory : ArcaneRequirementFactory() { + + private fun warnMissingProperty( + operation: NumberRequirement.Operation, + source: ConfigurationSection, + property: String + ) { + warning("Could not load '${operation.identifier}' requirement from ${source.currentPath}: missing required property '${property}'") + } + + override fun matches(source: ConfigurationSection): Boolean { + var type = source.getString(Requirement.TYPE)?.trim() ?: return false + + if (type.startsWith(Requirement.NEGATION)) { + type = type.substring(1) + } + + return NumberRequirement.Operation.find(type) != null + } + + override fun create(source: ConfigurationSection, actionManager: ArcaneActionManager): NumberRequirement? { + var type = source.getString(Requirement.TYPE)?.trim() ?: return null + val negated = type.startsWith(Requirement.NEGATION) + val optional = source.getBoolean(Requirement.OPTIONAL) + + if (negated) { + type = type.substring(1) + } + + // Not adding a warning for this because it won't get to this point if the factory couldn't find an Operation + val operation = NumberRequirement.Operation.find(type) ?: return null + val left = DoubleVariable( + source.getString("left") ?: kotlin.run { + warnMissingProperty(operation, source, "left") + return null + } + ) + val right = DoubleVariable( + source.getString("right") ?: kotlin.run { + warnMissingProperty(operation, source, "right") + return null + } + ) + val failActions = actionManager.parseActions(source.getStringList(Requirement.FAIL_ACTIONS)) + return NumberRequirement(source.name, optional, negated, failActions, actionManager, left, right, operation) + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/string/StringRequirement.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/string/StringRequirement.kt new file mode 100644 index 0000000..778bc1d --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/string/StringRequirement.kt @@ -0,0 +1,69 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.string + +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneAction +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneActionManager +import me.gabytm.minecraft.arcanevouchers.functions.papi +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.ArcaneRequirement +import me.gabytm.minecraft.util.requirements.Arguments +import org.bukkit.entity.Player +import java.util.* + +class StringRequirement( + name: String, + optional: Boolean, + negated: Boolean, + failActions: List, + actionManager: ArcaneActionManager, + private val left: String, + private val right: String, + private val operation: Operation +) : ArcaneRequirement( + name, optional, negated, failActions, actionManager +) { + + override fun check(player: Player?, arguments: Arguments): Boolean { + return operation.check(left.papi(player), right.papi(player), !negated) + } + + @Suppress("unused") + enum class Operation(val identifier: String) { + + EQUALS("string equals") { + override fun check(left: String?, right: String?, negated: Boolean): Boolean { + return left?.equals(right) == negated + } + }, + + EQUALS_IGNORE_CASE("string equals ignore case") { + override fun check(left: String?, right: String?, negated: Boolean): Boolean { + return left?.equals(right, true) == negated + } + }, + + CONTAINS("string contains") { + override fun check(left: String?, right: String?, negated: Boolean): Boolean { + return right != null && left?.contains(right) == negated + } + }, + + CONTAINS_IGNORE_CASE("string contains ignore case") { + override fun check(left: String?, right: String?, negated: Boolean): Boolean { + return right != null && left?.contains(right, true) == negated + } + }; + + abstract fun check(left: String?, right: String?, negated: Boolean): Boolean + + companion object { + + private val VALUES = EnumSet.allOf(Operation::class.java) + + fun find(string: String): Operation? { + return VALUES.firstOrNull { it.identifier.equals(string, true) } + } + + } + + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/string/StringRequirementFactory.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/string/StringRequirementFactory.kt new file mode 100644 index 0000000..92b4e6d --- /dev/null +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/requirements/implementations/string/StringRequirementFactory.kt @@ -0,0 +1,51 @@ +package me.gabytm.minecraft.arcanevouchers.voucher.requirements.implementations.string + +import me.gabytm.minecraft.arcanevouchers.Constant.Requirement +import me.gabytm.minecraft.arcanevouchers.actions.ArcaneActionManager +import me.gabytm.minecraft.arcanevouchers.functions.warning +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.ArcaneRequirementFactory +import org.bukkit.configuration.ConfigurationSection + +class StringRequirementFactory : ArcaneRequirementFactory() { + + private fun warnMissingProperty( + operation: StringRequirement.Operation, + source: ConfigurationSection, + property: String + ) { + warning("Could not load '${operation.identifier}' requirement from ${source.currentPath}: missing required property '${property}'") + } + + override fun matches(source: ConfigurationSection): Boolean { + var type = source.getString(Requirement.TYPE)?.trim() ?: return false + + if (type.startsWith(Requirement.NEGATION)) { + type = type.substring(1) + } + + return StringRequirement.Operation.find(type) != null + } + + override fun create(source: ConfigurationSection, actionManager: ArcaneActionManager): StringRequirement? { + var type = source.getString(Requirement.TYPE)?.trim() ?: return null + val negated = type.startsWith(Requirement.TYPE) + val optional = source.getBoolean(Requirement.OPTIONAL) + + if (negated) { + type = type.substring(1) + } + + val operation = StringRequirement.Operation.find(type) ?: return null + val left = source.getString("left") ?: kotlin.run { + warnMissingProperty(operation, source, "left") + return null + } + val right = source.getString("right") ?: kotlin.run { + warnMissingProperty(operation, source, "right") + return null + } + val failActions = actionManager.parseActions(source.getStringList(Requirement.FAIL_ACTIONS)) + return StringRequirement(source.name, optional, negated, failActions, actionManager, left, right, operation) + } + +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/settings/VoucherSettings.kt b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/settings/VoucherSettings.kt index 150acd0..5645890 100644 --- a/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/settings/VoucherSettings.kt +++ b/plugin/src/main/kotlin/me/gabytm/minecraft/arcanevouchers/voucher/settings/VoucherSettings.kt @@ -4,6 +4,9 @@ import me.gabytm.minecraft.arcanevouchers.functions.parseTime import me.gabytm.minecraft.arcanevouchers.functions.replace import me.gabytm.minecraft.arcanevouchers.limit.LimitType import me.gabytm.minecraft.arcanevouchers.message.Message +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.ArcaneRequirement +import me.gabytm.minecraft.arcanevouchers.voucher.requirements.ArcaneRequirementProcessor +import me.gabytm.minecraft.util.requirements.RequirementsList import org.bukkit.World import org.bukkit.configuration.ConfigurationSection import org.bukkit.entity.Player @@ -20,7 +23,8 @@ class VoucherSettings( val permissions: Permissions = Permissions(), val worlds: Worlds = Worlds(), val regions: Regions = Regions(), - val bindToReceiver: BindToReceiver = BindToReceiver() + val bindToReceiver: BindToReceiver = BindToReceiver(), + val requirementsList: RequirementsList = RequirementsList(emptyList()) ) { data class BulkOpen( @@ -142,7 +146,7 @@ class VoucherSettings( companion object { - fun from(config: ConfigurationSection?): VoucherSettings { + fun from(config: ConfigurationSection?, requirementProcessor: ArcaneRequirementProcessor): VoucherSettings { if (config == null) { return VoucherSettings() } @@ -173,7 +177,7 @@ class VoucherSettings( ) val cooldown = Cooldown( - config.getBoolean("cooldown.allowBulkOpen", true), + config.getBoolean("cooldown.enabled", true), (config.getString("cooldown.cooldown") ?: "").parseTime(TimeUnit.MILLISECONDS), Message.create(config.getString("cooldown.message") ?: ""), SoundWrapper.from(config.getConfigurationSection("cooldown.sound")) @@ -212,8 +216,10 @@ class VoucherSettings( SoundWrapper.from(config.getConfigurationSection("bindToReceiver.sound")) ) + val requirementsList = requirementProcessor.processRequirements(config.getConfigurationSection("requirements")) + return VoucherSettings(bulkOpen, messages, sounds, confirmationEnabled, limit, cooldown,permissions, worlds, - regions, bindToReceiver) + regions, bindToReceiver, requirementsList) } } diff --git a/plugin/src/main/resources/vouchers.yml b/plugin/src/main/resources/vouchers.yml index 4f65207..ce66953 100644 --- a/plugin/src/main/resources/vouchers.yml +++ b/plugin/src/main/resources/vouchers.yml @@ -77,4 +77,37 @@ vouchers: - '{type=TITLE} [message] +@gold@ gold' settings: bulkOpen: - enabled: true \ No newline at end of file + enabled: true + # + # If the built-in requirements don't fulfill your needs, you can create your own requirements using PlaceholderAPI + # placeholders + # + # ▪ Requirements: https://wiki.gabytm.me/resources/arcane/vouchers/requirements + # + requirementsExample: + item: + material: PLAYER_HEAD + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjg2OWJkZDlhOGY3N2VlZmY3NWQ4ZjY3ZWQwMzIyYmQ5YzE2ZGQ0OTQ5NzIzMTRlZDcwN2RkMTBhMzEzOWE1OCJ9fX0= + name: 'Golden Heart' + lore: + - 'Right click to consume and fill your health bar' + actions: + - '[console] heal %player_name%' + settings: + requirements: + list: + # + # Each requirement must have a unique name + # + health: + # + # Check if player has ≤ 10 HP + # + left: '%player_health%' + type: '<=' + right: 10 + # + # If the player has more than 10 HP, send a message + # + failActions: + - '[message] You can only consume a Golden Heart if you have <= 10 HP' \ No newline at end of file diff --git a/plugin/src/main/resources/vouchers_legacy.yml b/plugin/src/main/resources/vouchers_legacy.yml index e1a91d8..c2742b5 100644 --- a/plugin/src/main/resources/vouchers_legacy.yml +++ b/plugin/src/main/resources/vouchers_legacy.yml @@ -80,4 +80,39 @@ vouchers: - '{type=TITLE} [message] +@gold@ gold' settings: bulkOpen: - enabled: true \ No newline at end of file + enabled: true + + # + # If the built-in requirements don't fulfill your needs, you can create your own requirements using PlaceholderAPI + # placeholders + # + # ▪ Requirements: https://wiki.gabytm.me/resources/arcane/vouchers/requirements + # + requirementsExample: + item: + material: SKULL_ITEM + damage: 3 + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjg2OWJkZDlhOGY3N2VlZmY3NWQ4ZjY3ZWQwMzIyYmQ5YzE2ZGQ0OTQ5NzIzMTRlZDcwN2RkMTBhMzEzOWE1OCJ9fX0= + name: 'Golden Heart' + lore: + - 'Right click to consume and fill your health bar' + actions: + - '[console] heal %player_name%' + settings: + requirements: + list: + # + # Each requirement must have a unique name + # + health: + # + # Check if player has ≤ 10 HP + # + left: '%player_health%' + type: '<=' + right: 10 + # + # If the player has more than 10 HP, send a message + # + failActions: + - '[message] You can only consume a Golden Heart if you have <= 10 HP' \ No newline at end of file