9
0
mirror of https://github.com/Auxilor/EcoQuests.git synced 2025-12-19 15:09:22 +00:00

Improved GUI, fixed bugs, added API events, added quest complete display

This commit is contained in:
Auxilor
2023-08-08 20:13:55 +01:00
parent 051eb37dd6
commit 1d3326a5f6
20 changed files with 347 additions and 29 deletions

View File

@@ -1,16 +1,24 @@
package com.willfp.ecoquests
import com.sun.tools.javac.jvm.ByteCodes.ret
import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.ecoquests.commands.CommandEcoQuests
import com.willfp.ecoquests.commands.CommandQuests
import com.willfp.ecoquests.gui.QuestsGUI
import com.willfp.ecoquests.quests.QuestCompleteDisplay
import com.willfp.ecoquests.quests.Quests
import com.willfp.ecoquests.tasks.Tasks
import com.willfp.libreforge.loader.LibreforgePlugin
import com.willfp.libreforge.loader.configs.ConfigCategory
import org.bukkit.Bukkit
import org.bukkit.event.Listener
class EcoQuestsPlugin : LibreforgePlugin() {
override fun handleReload() {
QuestsGUI.reload(this)
}
override fun createTasks() {
val scanInterval = this.configYml.getInt("scan-interval").toLong()
this.scheduler.runTimer(scanInterval, scanInterval) {
@@ -24,6 +32,12 @@ class EcoQuestsPlugin : LibreforgePlugin() {
}
}
override fun loadListeners(): List<Listener> {
return listOf(
QuestCompleteDisplay(this)
)
}
override fun loadPluginCommands(): List<PluginCommand> {
return listOf(
CommandEcoQuests(this),

View File

@@ -0,0 +1,24 @@
package com.willfp.ecoquests.api.event
import com.willfp.ecoquests.quests.Quest
import org.bukkit.entity.Player
import org.bukkit.event.HandlerList
import org.bukkit.event.player.PlayerEvent
class PlayerCompleteQuestEvent(
who: Player,
val quest: Quest
): PlayerEvent(who) {
override fun getHandlers(): HandlerList {
return HANDLERS
}
companion object {
private val HANDLERS = HandlerList()
@JvmStatic
fun getHandlerList(): HandlerList {
return HANDLERS
}
}
}

View File

@@ -0,0 +1,24 @@
package com.willfp.ecoquests.api.event
import com.willfp.ecoquests.tasks.Task
import org.bukkit.entity.Player
import org.bukkit.event.HandlerList
import org.bukkit.event.player.PlayerEvent
class PlayerCompleteTaskEvent(
who: Player,
val task: Task
): PlayerEvent(who) {
override fun getHandlers(): HandlerList {
return HANDLERS
}
companion object {
private val HANDLERS = HandlerList()
@JvmStatic
fun getHandlerList(): HandlerList {
return HANDLERS
}
}
}

View File

@@ -0,0 +1,24 @@
package com.willfp.ecoquests.api.event
import com.willfp.ecoquests.quests.Quest
import org.bukkit.entity.Player
import org.bukkit.event.HandlerList
import org.bukkit.event.player.PlayerEvent
class PlayerStartQuestEvent(
who: Player,
val quest: Quest
): PlayerEvent(who) {
override fun getHandlers(): HandlerList {
return HANDLERS
}
companion object {
private val HANDLERS = HandlerList()
@JvmStatic
fun getHandlerList(): HandlerList {
return HANDLERS
}
}
}

View File

@@ -12,6 +12,7 @@ class CommandEcoQuests(plugin: EcoPlugin) : PluginCommand(
) {
init {
this.addSubcommand(CommandReload(plugin))
.addSubcommand(CommandStart(plugin))
}
override fun onExecute(sender: CommandSender, args: List<String>) {

View File

@@ -0,0 +1,29 @@
package com.willfp.ecoquests.commands
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.ecoquests.gui.QuestsGUI
import com.willfp.ecoquests.quests.Quests
import com.willfp.libreforge.commands.CommandReload
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class CommandStart(plugin: EcoPlugin) : PluginCommand(
plugin,
"start",
"ecoquests.command.start",
false
) {
override fun onExecute(sender: CommandSender, args: List<String>) {
val player = notifyPlayerRequired(args.getOrNull(0), "invalid-player")
val quest = notifyNull(Quests[args.getOrNull(1)], "invalid-quest")
if (quest.hasStarted(player) || quest.hasCompleted(player)) {
sender.sendMessage(plugin.langYml.getMessage("already-started"))
return
}
quest.start(player)
}
}

View File

@@ -9,6 +9,7 @@ import com.willfp.eco.core.gui.slot.FillerMask
import com.willfp.eco.core.gui.slot.MaskItems
import com.willfp.eco.core.items.Items
import com.willfp.ecoquests.gui.components.CloseButton
import com.willfp.ecoquests.gui.components.GUIInfoComponent
import com.willfp.ecoquests.gui.components.PositionedPageChanger
import com.willfp.ecoquests.gui.components.QuestAreaComponent
import com.willfp.ecoquests.gui.components.addComponent
@@ -18,9 +19,13 @@ object QuestsGUI {
private lateinit var menu: Menu
fun reload(plugin: EcoPlugin) {
val questAreaComponent = QuestAreaComponent(plugin.configYml.getSubsection("gui.quest-area"))
menu = menu(plugin.configYml.getInt("gui.rows")) {
title = plugin.configYml.getFormattedString("gui.title")
maxPages { questAreaComponent.getPages(it) }
setMask(
FillerMask(
MaskItems.fromItemNames(plugin.configYml.getStrings("gui.mask.materials")),
@@ -28,19 +33,21 @@ object QuestsGUI {
)
)
addComponent(GUIInfoComponent(plugin.configYml.getSubsection("gui.gui-info")))
addComponent(CloseButton(plugin.configYml.getSubsection("gui.close")))
addComponent(PositionedPageChanger(
plugin.configYml.getSubsection("gui.prev-page.item"),
plugin.configYml.getSubsection("gui.prev-page"),
PageChanger.Direction.BACKWARDS)
)
addComponent(PositionedPageChanger(
plugin.configYml.getSubsection("gui.next-page.item"),
plugin.configYml.getSubsection("gui.next-page"),
PageChanger.Direction.FORWARDS)
)
addComponent(QuestAreaComponent(plugin.configYml.getSubsection("gui.quest-area")))
addComponent(questAreaComponent)
for (config in plugin.configYml.getSubsections("gui.custom-slots")) {
setSlot(

View File

@@ -0,0 +1,24 @@
package com.willfp.ecoquests.gui.components
import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.gui.slot
import com.willfp.eco.core.items.Items
import com.willfp.eco.core.items.builder.ItemStackBuilder
class GUIInfoComponent(
config: Config
) : PositionedComponent {
private val slot = slot(
ItemStackBuilder(Items.lookup(config.getString("item")))
.setDisplayName(config.getFormattedString("name"))
.addLoreLines(config.getFormattedStrings("lore"))
.build()
) {
onLeftClick { event, _ -> event.whoClicked.closeInventory() }
}
override val row: Int = config.getInt("location.row")
override val column: Int = config.getInt("location.column")
override fun getSlotAt(row: Int, column: Int) = slot
}

View File

@@ -16,11 +16,18 @@ class QuestAreaComponent(
override val rowSize = config.getInt("bottom-right.row") - row + 1
override val columnSize = config.getInt("bottom-right.column") - column + 1
override fun getSlotAt(row: Int, column: Int, player: Player, menu: Menu): Slot? {
val index = MenuUtils.rowColumnToSlot(row, column, columnSize)
private val pageSize = rowSize * columnSize
return Quests.values()
.filter { it.hasStarted(player) }
fun getPages(player: Player): Int {
return Quests.getCurrentlyActiveQuests(player).size.floorDiv(pageSize) + 1
}
override fun getSlotAt(row: Int, column: Int, player: Player, menu: Menu): Slot? {
val page = menu.getPage(player)
val index = MenuUtils.rowColumnToSlot(row, column, columnSize) + ((page - 1) * pageSize)
return Quests.getCurrentlyActiveQuests(player)
.getOrNull(index)
?.slot
}

View File

@@ -7,18 +7,20 @@ import com.willfp.eco.core.data.keys.PersistentDataKeyType
import com.willfp.eco.core.data.profile
import com.willfp.eco.core.gui.slot
import com.willfp.eco.core.items.Items
import com.willfp.eco.core.items.builder.ItemStackBuilder
import com.willfp.eco.core.items.builder.modify
import com.willfp.eco.core.placeholder.context.placeholderContext
import com.willfp.eco.core.registry.KRegistrable
import com.willfp.eco.util.formatEco
import com.willfp.ecoquests.api.event.PlayerCompleteQuestEvent
import com.willfp.ecoquests.api.event.PlayerStartQuestEvent
import com.willfp.ecoquests.tasks.Tasks
import com.willfp.libreforge.EmptyProvidedHolder
import com.willfp.libreforge.ViolationContext
import com.willfp.libreforge.conditions.Conditions
import com.willfp.libreforge.effects.Effects
import com.willfp.libreforge.effects.executors.impl.NormalExecutorFactory
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
class Quest(
private val plugin: EcoPlugin,
@@ -34,7 +36,7 @@ class Quest(
addLoreLines(
plugin.configYml.getStrings("gui.quest-area.quest-icon.lore")
.flatMap {
if (it == "%tasks%") tasks.map { task -> task.getCompletedDescription(player) }
if (it == "%tasks%") tasks.flatMap { task -> task.getCompletedDescription(player) }
else listOf(it)
}.formatEco(player)
)
@@ -45,18 +47,20 @@ class Quest(
val tasks = config.getStrings("tasks").mapNotNull { Tasks[it] }
val hasStartedKey: PersistentDataKey<Boolean> = PersistentDataKey(
private val hasStartedKey: PersistentDataKey<Boolean> = PersistentDataKey(
plugin.createNamespacedKey("quest_${id}_has_started"),
PersistentDataKeyType.BOOLEAN,
false
)
val hasCompletedKey: PersistentDataKey<Boolean> = PersistentDataKey(
private val hasCompletedKey: PersistentDataKey<Boolean> = PersistentDataKey(
plugin.createNamespacedKey("quest_${id}_has_completed"),
PersistentDataKeyType.BOOLEAN,
false
)
private val rewardMessages = config.getStrings("reward-message")
val rewards = Effects.compileChain(
config.getSubsections("rewards"),
NormalExecutorFactory.create(),
@@ -74,6 +78,10 @@ class Quest(
ViolationContext(plugin, "quest $id start-conditions")
)
fun hasCompleted(player: Player): Boolean {
return player.profile.read(hasCompletedKey)
}
fun shouldStart(player: Player): Boolean {
return startConditions.areMet(player, EmptyProvidedHolder)
&& !hasStarted(player)
@@ -90,6 +98,8 @@ class Quest(
startEffects?.trigger(player)
player.profile.write(hasStartedKey, true)
Bukkit.getPluginManager().callEvent(PlayerStartQuestEvent(player, this))
}
fun checkCompletion(player: Player): Boolean {
@@ -102,9 +112,45 @@ class Quest(
player.profile.write(hasCompletedKey, true)
rewards?.trigger(player)
Bukkit.getPluginManager().callEvent(PlayerCompleteQuestEvent(player, this))
return true
}
return false
}
private fun List<String>.addMargin(margin: Int): List<String> {
return this.map { s -> " ".repeat(margin) + s }
}
fun addPlaceholdersInto(
strings: List<String>,
player: Player
): List<String> {
val quest = this // I just hate the @ notation kotlin uses
fun String.addPlaceholders() = this
.replace("%quest%", quest.name)
// Replace placeholders in the strings with their actual values.
val withPlaceholders = strings.map { it.addPlaceholders() }
// Replace multi-line placeholders.
val processed = withPlaceholders.flatMap { s ->
val margin = s.length - s.trimStart().length
if (s.contains("%rewards%")) {
rewardMessages
.addMargin(margin)
} else {
listOf(s)
}
}.map { it.addPlaceholders() }
return processed.formatEco(
placeholderContext(
player = player
)
)
}
}

View File

@@ -0,0 +1,61 @@
package com.willfp.ecoquests.quests
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.sound.PlayableSound
import com.willfp.eco.util.toComponent
import com.willfp.ecoquests.api.event.PlayerCompleteQuestEvent
import net.kyori.adventure.title.Title
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import java.time.Duration
class QuestCompleteDisplay(
private val plugin: EcoPlugin
) : Listener {
private val sound = if (plugin.configYml.getBool("quests.complete.sound.enabled")) {
PlayableSound.create(
plugin.configYml.getSubsection("quests.complete.sound")
)
} else null
@EventHandler
fun handle(event: PlayerCompleteQuestEvent) {
val player = event.player
val quest = event.quest
if (plugin.configYml.getBool("quests.complete.message.enabled")) {
val rawMessage = plugin.configYml.getStrings("quests.complete.message.message")
val formatted = quest.addPlaceholdersInto(
rawMessage,
player
)
formatted.forEach { player.sendMessage(it) }
}
if (plugin.configYml.getBool("quests.complete.title.enabled")) {
val rawTitle = plugin.configYml.getString("quests.complete.title.title")
val rawSubtitle = plugin.configYml.getString("quests.complete.title.subtitle")
val formatted = quest.addPlaceholdersInto(
listOf(rawTitle, rawSubtitle),
player
)
player.showTitle(
Title.title(
formatted[0].toComponent(),
formatted[1].toComponent(),
Title.Times.times(
Duration.ofMillis((plugin.configYml.getDouble("quests.complete.title.fade-in") * 1000).toLong()),
Duration.ofMillis((plugin.configYml.getDouble("quests.complete.title.stay") * 1000).toLong()),
Duration.ofMillis((plugin.configYml.getDouble("quests.complete.title.fade-out") * 1000).toLong())
)
)
)
}
sound?.playTo(player)
}
}

View File

@@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.registry.Registry
import com.willfp.libreforge.loader.LibreforgePlugin
import com.willfp.libreforge.loader.configs.ConfigCategory
import org.bukkit.entity.Player
object Quests : ConfigCategory("quest", "quests") {
private val registry = Registry<Quest>()
@@ -19,5 +20,11 @@ object Quests : ConfigCategory("quest", "quests") {
operator fun get(id: String?) = registry[id ?: ""]
fun values(): Collection<Quest> = registry.values()
fun getCurrentlyActiveQuests(player: Player): List<Quest> {
return values()
.filter { it.hasStarted(player) }
.filterNot { it.hasCompleted(player) }
}
}

View File

@@ -7,15 +7,14 @@ import com.willfp.eco.core.data.keys.PersistentDataKeyType
import com.willfp.eco.core.data.profile
import com.willfp.eco.core.registry.KRegistrable
import com.willfp.eco.util.formatEco
import com.willfp.eco.util.lineWrap
import com.willfp.eco.util.toNiceString
import com.willfp.ecoquests.api.event.PlayerCompleteTaskEvent
import com.willfp.ecoquests.quests.Quests
import com.willfp.libreforge.BlankHolder.conditions
import com.willfp.libreforge.EmptyProvidedHolder
import com.willfp.libreforge.ViolationContext
import com.willfp.libreforge.conditions.Conditions
import com.willfp.libreforge.counters.Accumulator
import com.willfp.libreforge.counters.Counters
import org.bukkit.OfflinePlayer
import org.bukkit.Bukkit
import org.bukkit.entity.Player
class Task(
@@ -35,7 +34,7 @@ class Task(
false
)
private val tasks = config.getSubsections("tasks").mapNotNull {
private val xpGainMethods = config.getSubsections("xp-gain-methods").mapNotNull {
Counters.compile(it, ViolationContext(plugin, "task $id tasks"))
}
@@ -46,14 +45,14 @@ class Task(
}
override fun onRegister() {
for (task in tasks) {
task.bind(accumulator)
for (counter in xpGainMethods) {
counter.bind(accumulator)
}
}
override fun onRemove() {
for (task in tasks) {
task.unbind()
for (counter in xpGainMethods) {
counter.unbind()
}
}
@@ -64,13 +63,15 @@ class Task(
.formatEco(player)
}
fun getCompletedDescription(player: Player): String {
fun getCompletedDescription(player: Player): List<String> {
return if (hasCompleted(player)) {
plugin.configYml.getString("tasks.completed")
.replace("%description%", getDescription(player))
.lineWrap(plugin.configYml.getInt("tasks.line-wrap"), true)
} else {
plugin.configYml.getString("tasks.not-completed")
.replace("%description%", getDescription(player))
.lineWrap(plugin.configYml.getInt("tasks.line-wrap"), true)
}
}
@@ -95,6 +96,8 @@ class Task(
if (newXp >= requiredXp) {
player.profile.write(hasCompletedKey, true)
Bukkit.getPluginManager().callEvent(PlayerCompleteTaskEvent(player, this))
// Then check if any quests are now completed
for (quest in Quests.values()) {
quest.checkCompletion(player)

View File

@@ -33,7 +33,8 @@ gui:
- "111111111"
gui-info:
item: writable_book name:"&aQuest Book"
item: writable_book
name: "&aQuest Book"
lore:
- "&7View your active quests,"
- "&7progress, and rewards."
@@ -78,5 +79,37 @@ gui:
custom-slots: [ ]
tasks:
line-wrap: 32
completed: " &a&l✓ &r&f%description%"
not-completed: " &c&l✘ &r&f%description%"
quests:
complete:
message:
enabled: true
message:
- "&f"
- " &#eacda3&lQUEST COMPLETE: &f%quest%"
- "&f"
- " &#eacda3&lREWARDS:"
- "%rewards%"
- "&f"
title:
enabled: false
# Durations are in seconds
fade-in: 0.5
stay: 2
fade-out: 0.5
title: "&#eacda3&lQuest Complete!"
subtitle: "&f%quest%"
sound:
# If a sound should be played
enabled: true
# The sound that should be played
sound: ui_toast_challange_complete
# Pitch between 0.5 and 2
pitch: 1.5
# The volume
volume: 1.0

View File

@@ -1,6 +1,10 @@
messages:
prefix: "&#eacda3&lEcoQuests &f» "
prefix: "&#eacda3&lEcoQuests &8» &f"
no-permission: "&cYou don't have permission to do this!"
not-player: "&cThis command must be run by a player"
invalid-command: "&cUnknown subcommand!"
reloaded: "Reloaded!"
invalid-player: "&cInvalid player!"
invalid-quest: "&cInvalid quest!"
already-started: "&cThe player has already started this quest!"

View File

@@ -32,6 +32,7 @@ permissions:
children:
ecoquests.command.reload: true
ecoquests.command.quests: true
ecoquests.command.start: op
ecoquests.command.reload:
description: Allows reloading the config
@@ -41,4 +42,7 @@ permissions:
default: true
ecoquests.command.quests:
description: Allows the use of /quests.
default: true
default: true
ecoquests.command.start:
description: Allows using /ecoquests start.
default: op

View File

@@ -7,6 +7,9 @@ gui:
tasks:
- break_100_stone
reward-messages:
- " &8» &r&f+2 %ecoskills_defense_name%"
# A list of effects to run when the quest is completed.
# Read https://plugins.auxilor.io/effects/configuring-an-effect
rewards: [ ]

View File

@@ -7,6 +7,9 @@ gui:
tasks:
- break_100_stone
reward-messages:
- " &8» &r&f+2 %ecoskills_defense_name%"
# A list of effects to run when the quest is completed.
# Read https://plugins.auxilor.io/effects/configuring-an-effect
rewards: [ ]

View File

@@ -7,10 +7,10 @@ description: "&fBreak stone blocks (&a%xp%&8/&a%required-xp%&f)"
# If the task is just one action, set this to 1.
required-xp: 100
# A task takes a trigger, a multiplier, conditions, and filters.
# An XP gain method takes a trigger, a multiplier, conditions, and filters.
# The multiplier takes the value produced by the trigger and multiplies it
# by some value to calculate the experience that should be given.
tasks:
xp-gain-methods:
- trigger: mine_block
multiplier: 1
filters:

View File

@@ -7,10 +7,10 @@ description: "&fBreak stone blocks (&a%xp%&8/&a%required-xp%&f)"
# If the task is just one action, set this to 1.
required-xp: 100
# A task takes a trigger, a multiplier, conditions, and filters.
# An XP gain method takes a trigger, a multiplier, conditions, and filters.
# The multiplier takes the value produced by the trigger and multiplies it
# by some value to calculate the experience that should be given.
tasks:
xp-gain-methods:
- trigger: mine_block
multiplier: 1
filters: