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

Compare commits

...

67 Commits

Author SHA1 Message Date
Will FP
ee39a180e1 libreforge-updater 2023-12-24 14:54:57 +01:00
Auxilor
da5cb60399 libreforge-updater 2023-12-20 15:57:20 +00:00
Will FP
d25ba604c3 libreforge-updater 2023-12-14 16:13:50 +00:00
Will FP
5810b4a7a9 libreforge-updater 2023-12-11 12:13:05 +00:00
Will FP
2ab6a42b37 libreforge-updater 2023-12-07 17:26:01 +00:00
Will FP
b496848143 libreforge-updater 2023-12-03 15:59:50 +00:00
Will FP
274102ac06 libreforge-updater 2023-11-30 14:27:46 +00:00
Will FP
08ef85f6d0 libreforge-updater 2023-11-26 23:24:49 +00:00
Will FP
29d0f8200e libreforge-updater 2023-11-23 13:21:45 +00:00
Auxilor
6496d5c7d1 libreforge-updater 2023-11-21 22:41:42 +00:00
Auxilor
35c89d7aa5 libreforge-updater 2023-11-19 14:14:25 +00:00
Auxilor
ab99f74626 libreforge-updater 2023-11-17 19:02:33 +00:00
Auxilor
ecfa40920f libreforge-updater 2023-11-11 17:58:59 +00:00
Auxilor
202528681a libreforge-updater 2023-11-10 13:59:29 +00:00
Auxilor
7aa02b3a91 libreforge-updater 2023-11-05 13:42:08 +00:00
Auxilor
070d25de67 libreforge-updater 2023-10-30 13:31:07 +00:00
Auxilor
3ab99c9237 libreforge-updater 2023-10-28 14:15:35 +01:00
Auxilor
3ee10633fb libreforge-updater 2023-10-24 15:39:35 +01:00
Auxilor
49c77396ea libreforge-updater 2023-10-19 12:52:36 +01:00
Auxilor
09fb8d2109 libreforge-updater 2023-10-14 14:20:33 +01:00
Auxilor
9623f1c427 libreforge-updater 2023-10-14 14:19:14 +01:00
Auxilor
39eaef3856 libreforge-updater 2023-10-02 11:54:25 +01:00
Auxilor
bf085b4ea5 libreforge-updater 2023-09-26 14:46:03 +01:00
Auxilor
1a1433f84f libreforge-updater 2023-09-20 15:33:56 +01:00
Auxilor
571b4c05e2 Fixed a small amount of stupidity 2023-09-17 11:29:16 +01:00
Auxilor
58aa2fd81d graddel 2023-09-17 11:28:35 +01:00
Auxilor
335524a2da libreforge-updater 2023-09-17 11:20:21 +01:00
Auxilor
73d748d445 libreforge-updater 2023-09-13 15:08:56 +01:00
Auxilor
e0e0804cce libreforge-updater 2023-09-07 16:01:57 +01:00
Auxilor
c00d5eb7d2 libreforge-updater 2023-09-02 17:34:44 +01:00
Will FP
f7fab37555 Fixed page changers 2023-09-01 12:32:17 +01:00
Auxilor
a93aac6b6f libreforge-updater 2023-08-31 16:59:46 +01:00
Auxilor
025a74048a Added on-complete 2023-08-31 16:57:13 +01:00
Auxilor
9db9f542c9 libreforge-updater 2023-08-30 11:31:41 +01:00
Auxilor
75a091624c Dynamic experience requirements now lock in when a task starts 2023-08-30 11:09:09 +01:00
Auxilor
17c81ea5b8 Added use-local-storage 2023-08-30 09:50:00 +01:00
Auxilor
81d4faaf0c libreforge-updater 2023-08-26 18:11:44 +01:00
Auxilor
03110e36b1 libreforge-updater 2023-08-23 15:31:59 +01:00
Auxilor
4b687e2c00 libreforge-updater 2023-08-19 15:32:52 +01:00
Auxilor
c01c86a592 libreforge-updater 2023-08-15 18:52:36 +01:00
Auxilor
438b118846 libreforge-updater 2023-08-13 14:42:52 +01:00
Auxilor
9c6cd8f467 Updated to 1.0.1 2023-08-11 15:01:54 +01:00
Auxilor
1db9b48579 Fixed quests being allowed on lrcdb (tasks only) 2023-08-11 15:01:36 +01:00
Auxilor
6b7380decd Added polymart / bstats IDs 2023-08-10 19:53:34 +01:00
Auxilor
3ecc004a7f Added publishing block 2023-08-10 19:41:05 +01:00
Auxilor
252163ba46 Added .github 2023-08-10 19:39:12 +01:00
Auxilor
2a612e84fc Improved config.yml 2023-08-10 18:24:37 +01:00
Auxilor
c9af764714 Improvements 2023-08-10 18:20:30 +01:00
Auxilor
5455db9fda Updated to 1.0.0 2023-08-10 18:04:35 +01:00
Auxilor
3250d33f8e Clarified not-met-lines 2023-08-10 18:04:26 +01:00
Auxilor
c0528c2c81 Examples 2023-08-10 18:03:26 +01:00
Auxilor
eecbe696a6 Improved GUI 2023-08-10 17:59:41 +01:00
Auxilor
ba3bc524ae Added option to disable auto start 2023-08-10 17:58:22 +01:00
Auxilor
b98dd0c6e6 Fixed bug with GUYI 2023-08-10 17:47:59 +01:00
Auxilor
93cfb2911a Fixed 2023-08-10 17:45:32 +01:00
Auxilor
d89a50b33b Fixed 2023-08-10 17:40:53 +01:00
Auxilor
ebec6b0615 Added %ecoquests_recent_quest_name% and improved lore 2023-08-10 17:38:52 +01:00
Auxilor
3ca42cb6d8 Updated to 0.4.0 2023-08-10 17:01:08 +01:00
Auxilor
cc3e5af684 Added %time_since% placeholder 2023-08-10 17:00:25 +01:00
Auxilor
ddc2659c62 Added permissions for paper 2023-08-10 15:37:54 +01:00
Auxilor
406ff65a6d Cleanup more 2023-08-10 15:36:13 +01:00
Auxilor
e7a16550ac Added %ecoquests_quests_percent_completed% 2023-08-10 15:30:29 +01:00
Auxilor
63b19f130e Added quest_xp_multipleir 2023-08-10 15:27:29 +01:00
Auxilor
215e4556cb Cleanup 2023-08-10 15:23:43 +01:00
Auxilor
5eeb00c083 Improved APi 2023-08-10 15:11:39 +01:00
Auxilor
0a333853d8 Resettable tasks now save properly 2023-08-09 16:07:36 +01:00
Auxilor
dd454bd447 Fixed tasks, added task pool 2023-08-09 14:50:48 +01:00
43 changed files with 604 additions and 123 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @WillFP

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.gg/ZcwpSsE/
about: Issues have moved to Discord, please join the server to get help!

11
.github/ISSUE_TEMPLATE/report-a-bug.md vendored Normal file
View File

@@ -0,0 +1,11 @@
---
name: Report a Bug
about: Report an issue with the plugin
title: ''
labels: bug
assignees: ''
---
# Please report bugs on the discord!
[Join by clicking here](https://discord.gg/ZcwpSsE/)

33
.github/workflows/publish-release.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Publish Packages
on:
workflow_dispatch:
release:
types: [ created ]
push:
tags:
- '*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout latest code
uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: 17
- name: Change wrapper permissions
run: chmod +x ./gradlew
- name: Publish package
uses: gradle/gradle-build-action@v2
with:
arguments: publish
env:
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}

View File

@@ -2,7 +2,7 @@ plugins {
java java
`java-library` `java-library`
`maven-publish` `maven-publish`
kotlin("jvm") version "1.7.10" kotlin("jvm") version "1.9.20"
id("com.github.johnrengelman.shadow") version "8.0.0" id("com.github.johnrengelman.shadow") version "8.0.0"
id("com.willfp.libreforge-gradle-plugin") version "1.0.0" id("com.willfp.libreforge-gradle-plugin") version "1.0.0"
} }
@@ -39,7 +39,7 @@ allprojects {
dependencies { dependencies {
compileOnly("com.willfp:eco:6.65.0") compileOnly("com.willfp:eco:6.65.0")
compileOnly("org.jetbrains:annotations:23.0.0") compileOnly("org.jetbrains:annotations:23.0.0")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.7.10") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
} }
java { java {

View File

@@ -9,9 +9,23 @@ dependencies {
publishing { publishing {
publications { publications {
register("maven", MavenPublication::class) { register<MavenPublication>("maven") {
from(components["java"]) groupId = project.group.toString()
version = project.version.toString()
artifactId = rootProject.name artifactId = rootProject.name
artifact(rootProject.tasks.shadowJar.get().archiveFile)
}
}
repositories {
maven {
name = "auxilor"
url = uri("https://repo.auxilor.io/repository/maven-releases/")
credentials {
username = System.getenv("MAVEN_USERNAME")
password = System.getenv("MAVEN_PASSWORD")
}
} }
} }
} }

View File

@@ -1,8 +1,12 @@
package com.willfp.ecoquests package com.willfp.ecoquests
import com.willfp.eco.core.command.impl.PluginCommand import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.eco.core.data.profile
import com.willfp.eco.core.placeholder.PlayerPlaceholder import com.willfp.eco.core.placeholder.PlayerPlaceholder
import com.willfp.eco.core.placeholder.PlayerlessPlaceholder import com.willfp.eco.core.placeholder.PlayerlessPlaceholder
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import com.willfp.eco.core.placeholder.templates.SimpleInjectablePlaceholder
import com.willfp.eco.util.toNiceString
import com.willfp.ecoquests.commands.CommandEcoQuests import com.willfp.ecoquests.commands.CommandEcoQuests
import com.willfp.ecoquests.commands.CommandQuests import com.willfp.ecoquests.commands.CommandQuests
import com.willfp.ecoquests.gui.PreviousQuestsGUI import com.willfp.ecoquests.gui.PreviousQuestsGUI
@@ -12,6 +16,7 @@ import com.willfp.ecoquests.libreforge.ConditionHasCompletedTask
import com.willfp.ecoquests.libreforge.ConditionHasQuestActive import com.willfp.ecoquests.libreforge.ConditionHasQuestActive
import com.willfp.ecoquests.libreforge.EffectGainTaskXp import com.willfp.ecoquests.libreforge.EffectGainTaskXp
import com.willfp.ecoquests.libreforge.EffectGiveTaskXp import com.willfp.ecoquests.libreforge.EffectGiveTaskXp
import com.willfp.ecoquests.libreforge.EffectQuestXpMultiplier
import com.willfp.ecoquests.libreforge.EffectStartQuest import com.willfp.ecoquests.libreforge.EffectStartQuest
import com.willfp.ecoquests.libreforge.FilterQuest import com.willfp.ecoquests.libreforge.FilterQuest
import com.willfp.ecoquests.libreforge.FilterTask import com.willfp.ecoquests.libreforge.FilterTask
@@ -40,6 +45,7 @@ class EcoQuestsPlugin : LibreforgePlugin() {
Conditions.register(ConditionHasQuestActive) Conditions.register(ConditionHasQuestActive)
Effects.register(EffectGainTaskXp) Effects.register(EffectGainTaskXp)
Effects.register(EffectGiveTaskXp) Effects.register(EffectGiveTaskXp)
Effects.register(EffectQuestXpMultiplier)
Effects.register(EffectStartQuest) Effects.register(EffectStartQuest)
Filters.register(FilterQuest) Filters.register(FilterQuest)
Filters.register(FilterTask) Filters.register(FilterTask)
@@ -56,9 +62,20 @@ class EcoQuestsPlugin : LibreforgePlugin() {
Quests.getCompletedQuests(it).size.toString() Quests.getCompletedQuests(it).size.toString()
}.register() }.register()
PlayerPlaceholder(this, "quests_percent_completed") {
val completed = Quests.getCompletedQuests(it).size
val total = Quests.values().size
val percent = completed.toDouble() / total.toDouble() * 100.0
percent.toNiceString()
}.register()
PlayerPlaceholder(this, "quests_active") { PlayerPlaceholder(this, "quests_active") {
Quests.getActiveQuests(it).size.toString() Quests.getActiveQuests(it).size.toString()
}.register() }.register()
PlayerPlaceholder(this, "recent_quest_name") {
Quests.getActiveQuests(it).minBy { quest -> quest.getTimeSinceStart(it) }.name
}.register()
} }
override fun handleReload() { override fun handleReload() {

View File

@@ -13,6 +13,7 @@ class CommandEcoQuests(plugin: EcoPlugin) : PluginCommand(
init { init {
this.addSubcommand(CommandReload(plugin)) this.addSubcommand(CommandReload(plugin))
.addSubcommand(CommandStart(plugin)) .addSubcommand(CommandStart(plugin))
.addSubcommand(CommandResetPlayer(plugin))
.addSubcommand(CommandReset(plugin)) .addSubcommand(CommandReset(plugin))
} }

View File

@@ -3,8 +3,6 @@ package com.willfp.ecoquests.commands
import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.command.impl.PluginCommand import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.ecoquests.gui.QuestsGUI import com.willfp.ecoquests.gui.QuestsGUI
import com.willfp.libreforge.commands.CommandReload
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player import org.bukkit.entity.Player
class CommandQuests(plugin: EcoPlugin) : PluginCommand( class CommandQuests(plugin: EcoPlugin) : PluginCommand(

View File

@@ -3,11 +3,8 @@ package com.willfp.ecoquests.commands
import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.command.impl.PluginCommand import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.eco.util.StringUtils import com.willfp.eco.util.StringUtils
import com.willfp.ecoquests.gui.QuestsGUI
import com.willfp.ecoquests.quests.Quests import com.willfp.ecoquests.quests.Quests
import com.willfp.libreforge.commands.CommandReload
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import org.bukkit.util.StringUtil import org.bukkit.util.StringUtil
class CommandReset(plugin: EcoPlugin) : PluginCommand( class CommandReset(plugin: EcoPlugin) : PluginCommand(
@@ -17,15 +14,21 @@ class CommandReset(plugin: EcoPlugin) : PluginCommand(
false false
) { ) {
override fun onExecute(sender: CommandSender, args: List<String>) { override fun onExecute(sender: CommandSender, args: List<String>) {
val player = notifyPlayerRequired(args.getOrNull(0), "invalid-player") val quest = notifyNull(Quests[args.getOrNull(0)], "invalid-quest")
val quest = notifyNull(Quests[args.getOrNull(1)], "invalid-quest")
quest.reset(player) if (!quest.isResettable) {
sender.sendMessage(
plugin.langYml.getMessage("quest-not-resettable", StringUtils.FormatOption.WITHOUT_PLACEHOLDERS)
.replace("%quest%", quest.name)
)
return
}
quest.reset()
sender.sendMessage( sender.sendMessage(
plugin.langYml.getMessage("reset-quest", StringUtils.FormatOption.WITHOUT_PLACEHOLDERS) plugin.langYml.getMessage("reset-quest", StringUtils.FormatOption.WITHOUT_PLACEHOLDERS)
.replace("%quest%", quest.name) .replace("%quest%", quest.name)
.replace("%player%", player.name)
) )
} }
@@ -35,14 +38,6 @@ class CommandReset(plugin: EcoPlugin) : PluginCommand(
if (args.size == 1) { if (args.size == 1) {
StringUtil.copyPartialMatches( StringUtil.copyPartialMatches(
args[0], args[0],
plugin.server.onlinePlayers.map { it.name },
completions
)
}
if (args.size == 2) {
StringUtil.copyPartialMatches(
args[1],
Quests.values().map { it.id }, Quests.values().map { it.id },
completions completions
) )

View File

@@ -0,0 +1,51 @@
package com.willfp.ecoquests.commands
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.eco.util.StringUtils
import com.willfp.ecoquests.quests.Quests
import org.bukkit.command.CommandSender
import org.bukkit.util.StringUtil
class CommandResetPlayer(plugin: EcoPlugin) : PluginCommand(
plugin,
"resetplayer",
"ecoquests.command.resetplayer",
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")
quest.reset(player)
sender.sendMessage(
plugin.langYml.getMessage("reset-quest-for-player", StringUtils.FormatOption.WITHOUT_PLACEHOLDERS)
.replace("%quest%", quest.name)
.replace("%player%", player.name)
)
}
override fun tabComplete(sender: CommandSender, args: List<String>): List<String> {
val completions = mutableListOf<String>()
if (args.size == 1) {
StringUtil.copyPartialMatches(
args[0],
plugin.server.onlinePlayers.map { it.name },
completions
)
}
if (args.size == 2) {
StringUtil.copyPartialMatches(
args[1],
Quests.values().map { it.id },
completions
)
}
return completions
}
}

View File

@@ -3,11 +3,8 @@ package com.willfp.ecoquests.commands
import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.command.impl.PluginCommand import com.willfp.eco.core.command.impl.PluginCommand
import com.willfp.eco.util.StringUtils import com.willfp.eco.util.StringUtils
import com.willfp.ecoquests.gui.QuestsGUI
import com.willfp.ecoquests.quests.Quests import com.willfp.ecoquests.quests.Quests
import com.willfp.libreforge.commands.CommandReload
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import org.bukkit.util.StringUtil import org.bukkit.util.StringUtil
class CommandStart(plugin: EcoPlugin) : PluginCommand( class CommandStart(plugin: EcoPlugin) : PluginCommand(

View File

@@ -12,7 +12,7 @@ class PositionedPageChanger(
direction: PageChanger.Direction direction: PageChanger.Direction
): PositionedComponent { ): PositionedComponent {
private val pageChanger = PageChanger( private val pageChanger = PageChanger(
Items.lookup("item").item, Items.lookup(config.getString("item")).item,
direction direction
) )

View File

@@ -5,17 +5,21 @@ import com.willfp.eco.core.gui.onLeftClick
import com.willfp.eco.core.gui.slot import com.willfp.eco.core.gui.slot
import com.willfp.eco.core.items.Items import com.willfp.eco.core.items.Items
import com.willfp.eco.core.items.builder.ItemStackBuilder import com.willfp.eco.core.items.builder.ItemStackBuilder
import com.willfp.eco.core.items.builder.modify
import com.willfp.eco.util.formatEco
import com.willfp.ecoquests.gui.PreviousQuestsGUI import com.willfp.ecoquests.gui.PreviousQuestsGUI
class QuestInfoComponent( class QuestInfoComponent(
config: Config config: Config
) : PositionedComponent { ) : PositionedComponent {
private val slot = slot( private val baseItem = Items.lookup(config.getString("item"))
ItemStackBuilder(Items.lookup(config.getString("item")))
.setDisplayName(config.getFormattedString("name")) private val slot = slot({ player, _ ->
.addLoreLines(config.getFormattedStrings("lore")) baseItem.item.clone().modify {
.build() setDisplayName(config.getString("name").formatEco(player, formatPlaceholders = true))
) { addLoreLines(config.getStrings("lore").formatEco(player, formatPlaceholders = true))
}
}) {
onLeftClick { player, _, _, _ -> onLeftClick { player, _, _, _ ->
PreviousQuestsGUI.open(player) PreviousQuestsGUI.open(player)
} }

View File

@@ -2,9 +2,12 @@ package com.willfp.ecoquests.libreforge
import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.config.interfaces.Config
import com.willfp.ecoquests.quests.Quests import com.willfp.ecoquests.quests.Quests
import com.willfp.libreforge.Dispatcher
import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.NoCompileData
import com.willfp.libreforge.ProvidedHolder
import com.willfp.libreforge.arguments import com.willfp.libreforge.arguments
import com.willfp.libreforge.conditions.Condition import com.willfp.libreforge.conditions.Condition
import com.willfp.libreforge.get
import org.bukkit.entity.Player import org.bukkit.entity.Player
object ConditionHasCompletedQuest : Condition<NoCompileData>("has_completed_quest") { object ConditionHasCompletedQuest : Condition<NoCompileData>("has_completed_quest") {
@@ -12,7 +15,14 @@ object ConditionHasCompletedQuest : Condition<NoCompileData>("has_completed_ques
require("quest", "You must specify the quest ID!") require("quest", "You must specify the quest ID!")
} }
override fun isMet(player: Player, config: Config, compileData: NoCompileData): Boolean { override fun isMet(
dispatcher: Dispatcher<*>,
config: Config,
holder: ProvidedHolder,
compileData: NoCompileData
): Boolean {
val player = dispatcher.get<Player>() ?: return false
val quest = Quests[config.getString("quest")] ?: return false val quest = Quests[config.getString("quest")] ?: return false
return quest.hasCompleted(player) return quest.hasCompleted(player)

View File

@@ -3,18 +3,31 @@ package com.willfp.ecoquests.libreforge
import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.config.interfaces.Config
import com.willfp.ecoquests.quests.Quests import com.willfp.ecoquests.quests.Quests
import com.willfp.ecoquests.tasks.Tasks import com.willfp.ecoquests.tasks.Tasks
import com.willfp.libreforge.Dispatcher
import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.NoCompileData
import com.willfp.libreforge.ProvidedHolder
import com.willfp.libreforge.arguments import com.willfp.libreforge.arguments
import com.willfp.libreforge.conditions.Condition import com.willfp.libreforge.conditions.Condition
import com.willfp.libreforge.get
import org.bukkit.entity.Player import org.bukkit.entity.Player
object ConditionHasCompletedTask : Condition<NoCompileData>("has_completed_task") { object ConditionHasCompletedTask : Condition<NoCompileData>("has_completed_task") {
override val arguments = arguments { override val arguments = arguments {
require("quest", "You must specify the quest ID!")
require("task", "You must specify the task ID!") require("task", "You must specify the task ID!")
} }
override fun isMet(player: Player, config: Config, compileData: NoCompileData): Boolean { override fun isMet(
val task = Tasks[config.getString("task")] ?: return false dispatcher: Dispatcher<*>,
config: Config,
holder: ProvidedHolder,
compileData: NoCompileData
): Boolean {
val player = dispatcher.get<Player>() ?: return false
val quest = Quests[config.getString("quest")] ?: return false
val template = Tasks[config.getString("task")] ?: return false
val task = quest.getTask(template) ?: return false
return task.hasCompleted(player) return task.hasCompleted(player)
} }

View File

@@ -2,9 +2,12 @@ package com.willfp.ecoquests.libreforge
import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.config.interfaces.Config
import com.willfp.ecoquests.quests.Quests import com.willfp.ecoquests.quests.Quests
import com.willfp.libreforge.Dispatcher
import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.NoCompileData
import com.willfp.libreforge.ProvidedHolder
import com.willfp.libreforge.arguments import com.willfp.libreforge.arguments
import com.willfp.libreforge.conditions.Condition import com.willfp.libreforge.conditions.Condition
import com.willfp.libreforge.get
import org.bukkit.entity.Player import org.bukkit.entity.Player
object ConditionHasQuestActive : Condition<NoCompileData>("has_quest_active") { object ConditionHasQuestActive : Condition<NoCompileData>("has_quest_active") {
@@ -12,7 +15,14 @@ object ConditionHasQuestActive : Condition<NoCompileData>("has_quest_active") {
require("quest", "You must specify the quest ID!") require("quest", "You must specify the quest ID!")
} }
override fun isMet(player: Player, config: Config, compileData: NoCompileData): Boolean { override fun isMet(
dispatcher: Dispatcher<*>,
config: Config,
holder: ProvidedHolder,
compileData: NoCompileData
): Boolean {
val player = dispatcher.get<Player>() ?: return false
val quest = Quests[config.getString("quest")] ?: return false val quest = Quests[config.getString("quest")] ?: return false
return quest.hasActive(player) return quest.hasActive(player)

View File

@@ -1,6 +1,7 @@
package com.willfp.ecoquests.libreforge package com.willfp.ecoquests.libreforge
import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.config.interfaces.Config
import com.willfp.ecoquests.quests.Quests
import com.willfp.ecoquests.tasks.Tasks import com.willfp.ecoquests.tasks.Tasks
import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.NoCompileData
import com.willfp.libreforge.arguments import com.willfp.libreforge.arguments
@@ -17,13 +18,17 @@ object EffectGainTaskXp : Effect<NoCompileData>("gain_task_xp") {
override val arguments = arguments { override val arguments = arguments {
require("task", "You must specify the task ID!") require("task", "You must specify the task ID!")
require("quest", "You must specify the quest ID!")
require("xp", "You must specify the amount of xp to gain!") require("xp", "You must specify the amount of xp to gain!")
} }
override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean { override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean {
val player = data.player ?: return false val player = data.player ?: return false
val task = Tasks[config.getString("task")] ?: return false val quest = Quests[config.getString("quest")] ?: return false
val template = Tasks[config.getString("task")] ?: return false
val task = quest.getTask(template) ?: return false
val xp = config.getDoubleFromExpression("xp", data) val xp = config.getDoubleFromExpression("xp", data)
task.gainExperience(player, xp) task.gainExperience(player, xp)

View File

@@ -1,6 +1,7 @@
package com.willfp.ecoquests.libreforge package com.willfp.ecoquests.libreforge
import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.config.interfaces.Config
import com.willfp.ecoquests.quests.Quests
import com.willfp.ecoquests.tasks.Tasks import com.willfp.ecoquests.tasks.Tasks
import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.NoCompileData
import com.willfp.libreforge.arguments import com.willfp.libreforge.arguments
@@ -17,13 +18,17 @@ object EffectGiveTaskXp : Effect<NoCompileData>("give_task_xp") {
override val arguments = arguments { override val arguments = arguments {
require("task", "You must specify the task ID!") require("task", "You must specify the task ID!")
require("quest", "You must specify the quest ID!")
require("xp", "You must specify the amount of xp to give!") require("xp", "You must specify the amount of xp to give!")
} }
override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean { override fun onTrigger(config: Config, data: TriggerData, compileData: NoCompileData): Boolean {
val player = data.player ?: return false val player = data.player ?: return false
val task = Tasks[config.getString("task")] ?: return false val quest = Quests[config.getString("quest")] ?: return false
val template = Tasks[config.getString("task")] ?: return false
val task = quest.getTask(template) ?: return false
val xp = config.getDoubleFromExpression("xp", data) val xp = config.getDoubleFromExpression("xp", data)
task.giveExperience(player, xp) task.giveExperience(player, xp)

View File

@@ -0,0 +1,25 @@
package com.willfp.ecoquests.libreforge
import com.willfp.ecoquests.api.event.PlayerTaskExpGainEvent
import com.willfp.ecoquests.quests.Quest
import com.willfp.ecoquests.quests.Quests
import com.willfp.libreforge.effects.templates.MultiMultiplierEffect
import com.willfp.libreforge.toDispatcher
import org.bukkit.event.EventHandler
object EffectQuestXpMultiplier : MultiMultiplierEffect<Quest>("quest_xp_multiplier") {
override val key = "quests"
override fun getElement(key: String): Quest? {
return Quests[key]
}
override fun getAllElements(): Collection<Quest> {
return Quests.values()
}
@EventHandler(ignoreCancelled = true)
fun handle(event: PlayerTaskExpGainEvent) {
event.amount *= getMultiplier(event.player.toDispatcher(), event.quest)
}
}

View File

@@ -1,6 +1,7 @@
package com.willfp.ecoquests.libreforge package com.willfp.ecoquests.libreforge
import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.util.containsIgnoreCase
import com.willfp.ecoquests.api.event.QuestEvent import com.willfp.ecoquests.api.event.QuestEvent
import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.NoCompileData
import com.willfp.libreforge.filters.Filter import com.willfp.libreforge.filters.Filter
@@ -14,8 +15,6 @@ object FilterQuest : Filter<NoCompileData, Collection<String>>("quest") {
override fun isMet(data: TriggerData, value: Collection<String>, compileData: NoCompileData): Boolean { override fun isMet(data: TriggerData, value: Collection<String>, compileData: NoCompileData): Boolean {
val event = data.event as? QuestEvent ?: return true val event = data.event as? QuestEvent ?: return true
return value.any { questID -> return value.containsIgnoreCase(event.quest.id)
questID.equals(event.quest.id, ignoreCase = true)
}
} }
} }

View File

@@ -1,6 +1,7 @@
package com.willfp.ecoquests.libreforge package com.willfp.ecoquests.libreforge
import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.util.containsIgnoreCase
import com.willfp.ecoquests.api.event.QuestEvent import com.willfp.ecoquests.api.event.QuestEvent
import com.willfp.ecoquests.api.event.TaskEvent import com.willfp.ecoquests.api.event.TaskEvent
import com.willfp.libreforge.NoCompileData import com.willfp.libreforge.NoCompileData
@@ -15,8 +16,6 @@ object FilterTask : Filter<NoCompileData, Collection<String>>("task") {
override fun isMet(data: TriggerData, value: Collection<String>, compileData: NoCompileData): Boolean { override fun isMet(data: TriggerData, value: Collection<String>, compileData: NoCompileData): Boolean {
val event = data.event as? TaskEvent ?: return true val event = data.event as? TaskEvent ?: return true
return value.any { taskID -> return value.containsIgnoreCase(event.task.id)
taskID.equals(event.task.id, ignoreCase = true)
}
} }
} }

View File

@@ -2,6 +2,7 @@ package com.willfp.ecoquests.libreforge
import com.willfp.ecoquests.api.event.PlayerQuestCompleteEvent import com.willfp.ecoquests.api.event.PlayerQuestCompleteEvent
import com.willfp.ecoquests.api.event.PlayerQuestStartEvent import com.willfp.ecoquests.api.event.PlayerQuestStartEvent
import com.willfp.libreforge.toDispatcher
import com.willfp.libreforge.triggers.Trigger import com.willfp.libreforge.triggers.Trigger
import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerData
import com.willfp.libreforge.triggers.TriggerParameter import com.willfp.libreforge.triggers.TriggerParameter
@@ -18,7 +19,7 @@ object TriggerCompleteQuest : Trigger("complete_quest") {
val player = event.player val player = event.player
this.dispatch( this.dispatch(
player, player.toDispatcher(),
TriggerData( TriggerData(
player = player, player = player,
event = event event = event

View File

@@ -1,6 +1,7 @@
package com.willfp.ecoquests.libreforge package com.willfp.ecoquests.libreforge
import com.willfp.ecoquests.api.event.PlayerTaskCompleteEvent import com.willfp.ecoquests.api.event.PlayerTaskCompleteEvent
import com.willfp.libreforge.toDispatcher
import com.willfp.libreforge.triggers.Trigger import com.willfp.libreforge.triggers.Trigger
import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerData
import com.willfp.libreforge.triggers.TriggerParameter import com.willfp.libreforge.triggers.TriggerParameter
@@ -17,7 +18,7 @@ object TriggerCompleteTask : Trigger("complete_task") {
val player = event.player val player = event.player
this.dispatch( this.dispatch(
player, player.toDispatcher(),
TriggerData( TriggerData(
player = player, player = player,
event = event event = event

View File

@@ -1,6 +1,7 @@
package com.willfp.ecoquests.libreforge package com.willfp.ecoquests.libreforge
import com.willfp.ecoquests.api.event.PlayerTaskExpGainEvent import com.willfp.ecoquests.api.event.PlayerTaskExpGainEvent
import com.willfp.libreforge.toDispatcher
import com.willfp.libreforge.triggers.Trigger import com.willfp.libreforge.triggers.Trigger
import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerData
import com.willfp.libreforge.triggers.TriggerParameter import com.willfp.libreforge.triggers.TriggerParameter
@@ -19,7 +20,7 @@ object TriggerGainTaskXp : Trigger("gain_task_xp") {
val player = event.player val player = event.player
this.dispatch( this.dispatch(
player, player.toDispatcher(),
TriggerData( TriggerData(
player = player, player = player,
event = event, event = event,

View File

@@ -1,6 +1,7 @@
package com.willfp.ecoquests.libreforge package com.willfp.ecoquests.libreforge
import com.willfp.ecoquests.api.event.PlayerQuestStartEvent import com.willfp.ecoquests.api.event.PlayerQuestStartEvent
import com.willfp.libreforge.toDispatcher
import com.willfp.libreforge.triggers.Trigger import com.willfp.libreforge.triggers.Trigger
import com.willfp.libreforge.triggers.TriggerData import com.willfp.libreforge.triggers.TriggerData
import com.willfp.libreforge.triggers.TriggerParameter import com.willfp.libreforge.triggers.TriggerParameter
@@ -17,7 +18,7 @@ object TriggerStartQuest : Trigger("start_quest") {
val player = event.player val player = event.player
this.dispatch( this.dispatch(
player, player.toDispatcher(),
TriggerData( TriggerData(
player = player, player = player,
event = event event = event

View File

@@ -14,20 +14,28 @@ import com.willfp.eco.core.placeholder.PlayerlessPlaceholder
import com.willfp.eco.core.placeholder.context.placeholderContext import com.willfp.eco.core.placeholder.context.placeholderContext
import com.willfp.eco.core.registry.KRegistrable import com.willfp.eco.core.registry.KRegistrable
import com.willfp.eco.util.formatEco import com.willfp.eco.util.formatEco
import com.willfp.eco.util.lineWrap
import com.willfp.eco.util.toNiceString import com.willfp.eco.util.toNiceString
import com.willfp.ecoquests.api.event.PlayerQuestCompleteEvent import com.willfp.ecoquests.api.event.PlayerQuestCompleteEvent
import com.willfp.ecoquests.api.event.PlayerQuestStartEvent import com.willfp.ecoquests.api.event.PlayerQuestStartEvent
import com.willfp.ecoquests.tasks.Task import com.willfp.ecoquests.tasks.Task
import com.willfp.ecoquests.tasks.TaskTemplate
import com.willfp.ecoquests.tasks.Tasks import com.willfp.ecoquests.tasks.Tasks
import com.willfp.ecoquests.util.formatDuration import com.willfp.ecoquests.util.formatDuration
import com.willfp.ecoquests.util.randomlyPick
import com.willfp.libreforge.EmptyProvidedHolder import com.willfp.libreforge.EmptyProvidedHolder
import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.ViolationContext
import com.willfp.libreforge.conditions.Conditions import com.willfp.libreforge.conditions.Conditions
import com.willfp.libreforge.effects.Effects import com.willfp.libreforge.effects.Effects
import com.willfp.libreforge.effects.executors.impl.NormalExecutorFactory import com.willfp.libreforge.effects.executors.impl.NormalExecutorFactory
import com.willfp.libreforge.toDispatcher
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
val currentTimeMinutes: Int
get() = (System.currentTimeMillis() / 1000 / 60).toInt()
class Quest( class Quest(
private val plugin: EcoPlugin, private val plugin: EcoPlugin,
override val id: String, override val id: String,
@@ -37,6 +45,8 @@ class Quest(
val announcesStart = config.getBool("announce-start") val announcesStart = config.getBool("announce-start")
val autoStarts = config.getBool("auto-start")
private val guiItem = Items.lookup(config.getString("gui.item")).item private val guiItem = Items.lookup(config.getString("gui.item")).item
val slot = slot({ player, _ -> val slot = slot({ player, _ ->
@@ -49,7 +59,14 @@ class Quest(
) )
addLoreLines( addLoreLines(
startConditions.getNotMetLines(player, EmptyProvidedHolder) startConditions.getNotMetLines(player.toDispatcher(), EmptyProvidedHolder)
)
setDisplayName(
addPlaceholdersInto(
listOf(plugin.configYml.getString("quests.icon.name")),
player
).first()
) )
} }
}) { }) {
@@ -60,22 +77,62 @@ class Quest(
val alwaysInGUI = config.getBool("gui.always") val alwaysInGUI = config.getBool("gui.always")
val tasks = config.getStrings("tasks") // The pool of available tasks to pick from
.mapNotNull { Tasks[it] } private val availableTasks = config.getSubsections("tasks")
.map { Task(plugin, it, this) } .mapNotNull {
Tasks[it.getString("task")]
?.create(this, it.getString("xp"))
}
private val hasStartedKey: PersistentDataKey<Boolean> = PersistentDataKey( // The amount of tasks to use from the pool
val taskAmount = config.getInt("task-amount").let {
if (it < 0) {
availableTasks.size
} else {
it.coerceAtMost(availableTasks.size)
}
}
private val savedTasksKey = PersistentDataKey(
plugin.createNamespacedKey("quest_${id}_tasks"),
PersistentDataKeyType.STRING_LIST,
emptyList()
)
// The tasks that are actually in use
var tasks = run {
if (isResettable) {
loadTasks() ?: availableTasks.randomlyPick(taskAmount)
} else {
availableTasks.randomlyPick(taskAmount)
}
}
private set
private val hasStartedKey = PersistentDataKey(
plugin.createNamespacedKey("quest_${id}_has_started"), plugin.createNamespacedKey("quest_${id}_has_started"),
PersistentDataKeyType.BOOLEAN, PersistentDataKeyType.BOOLEAN,
false false
) )
private val hasCompletedKey: PersistentDataKey<Boolean> = PersistentDataKey( private val startedTimeKey = PersistentDataKey(
plugin.createNamespacedKey("quest_${id}_started_time"),
PersistentDataKeyType.INT,
Int.MAX_VALUE
)
private val hasCompletedKey = PersistentDataKey(
plugin.createNamespacedKey("quest_${id}_has_completed"), plugin.createNamespacedKey("quest_${id}_has_completed"),
PersistentDataKeyType.BOOLEAN, PersistentDataKeyType.BOOLEAN,
false false
) )
private val completedTimeKey = PersistentDataKey(
plugin.createNamespacedKey("quest_${id}_completed_time"),
PersistentDataKeyType.INT,
Int.MAX_VALUE
)
private val rewardMessages = config.getStrings("reward-messages") private val rewardMessages = config.getStrings("reward-messages")
private val rewards = Effects.compileChain( private val rewards = Effects.compileChain(
@@ -103,14 +160,16 @@ class Quest(
private val resetTime = config.getInt("reset-time") private val resetTime = config.getInt("reset-time")
val isResettable: Boolean
get() = resetTime >= 0
val minutesUntilReset: Int val minutesUntilReset: Int
get() = if (resetTime < 0) { get() = if (resetTime < 0) {
Int.MAX_VALUE Int.MAX_VALUE
} else { } else {
val currentTime = (System.currentTimeMillis() / 1000 / 60).toInt()
val previousTime = ServerProfile.load().read(lastResetTimeKey) val previousTime = ServerProfile.load().read(lastResetTimeKey)
resetTime - currentTime + previousTime resetTime - currentTimeMinutes + previousTime
} }
init { init {
@@ -141,33 +200,76 @@ class Quest(
PlayerlessPlaceholder(plugin, "quest_${id}_time_until_reset") { PlayerlessPlaceholder(plugin, "quest_${id}_time_until_reset") {
formatDuration(this.minutesUntilReset) formatDuration(this.minutesUntilReset)
}.register() }.register()
PlayerPlaceholder(plugin, "quest_${id}_time_since_start") {
formatDuration(this.getTimeSinceStart(it))
}.register()
PlayerPlaceholder(plugin, "quest_${id}_time_since_completed") {
formatDuration(this.getTimeSinceCompletion(it))
}.register()
PlayerPlaceholder(plugin, "quest_${id}_time_since") {
if (hasCompleted(it)) {
plugin.langYml.getString("time-since.completed")
.replace("%time%", formatDuration(this.getTimeSinceCompletion(it)))
.formatEco(it)
} else if (hasStarted(it)) {
plugin.langYml.getString("time-since.started")
.replace("%time%", formatDuration(this.getTimeSinceStart(it)))
.formatEco(it)
} else {
plugin.langYml.getString("time-since.never")
.formatEco(it)
}
}.register()
}
override fun onRegister() {
for (task in tasks) {
task.bind()
}
}
override fun onRemove() {
if (isResettable) {
saveTasks()
}
for (task in tasks) {
task.unbind()
}
}
fun getTask(template: TaskTemplate): Task? {
return tasks.firstOrNull { it.template == template }
} }
fun getDescription(player: Player): List<String> { fun getDescription(player: Player): List<String> {
return addPlaceholdersInto(listOf(config.getString("description")), player) return addPlaceholdersInto(listOf(config.getString("description")), player)
} }
fun hasActive(player: Player): Boolean { fun hasActive(player: OfflinePlayer): Boolean {
return hasStarted(player) && !hasCompleted(player) return hasStarted(player) && !hasCompleted(player)
} }
fun hasCompleted(player: Player): Boolean { fun hasCompleted(player: OfflinePlayer): Boolean {
return player.profile.read(hasCompletedKey) return player.profile.read(hasCompletedKey)
} }
fun meetsStartConditions(player: Player): Boolean { fun meetsStartConditions(player: Player): Boolean {
return startConditions.areMet(player, EmptyProvidedHolder) return startConditions.areMet(player.toDispatcher(), EmptyProvidedHolder)
} }
fun shouldStart(player: Player): Boolean { fun shouldStart(player: Player): Boolean {
return meetsStartConditions(player) && !hasStarted(player) return meetsStartConditions(player) && !hasStarted(player) && autoStarts
} }
fun hasStarted(player: Player): Boolean { fun hasStarted(player: OfflinePlayer): Boolean {
return player.profile.read(hasStartedKey) return player.profile.read(hasStartedKey)
} }
fun reset(player: Player) { fun reset(player: OfflinePlayer) {
player.profile.write(hasStartedKey, false) player.profile.write(hasStartedKey, false)
player.profile.write(hasCompletedKey, false) player.profile.write(hasCompletedKey, false)
@@ -181,12 +283,41 @@ class Quest(
return return
} }
startEffects?.trigger(player) startEffects?.trigger(player.toDispatcher())
player.profile.write(hasStartedKey, true) player.profile.write(hasStartedKey, true)
player.profile.write(startedTimeKey, currentTimeMinutes)
// Reset tasks to generate new xp requirements
for (task in tasks) {
task.reset(player)
}
Bukkit.getPluginManager().callEvent(PlayerQuestStartEvent(player, this)) Bukkit.getPluginManager().callEvent(PlayerQuestStartEvent(player, this))
} }
fun getTimeSinceStart(player: OfflinePlayer): Int {
return currentTimeMinutes - player.profile.read(startedTimeKey)
}
fun getTimeSinceCompletion(player: OfflinePlayer): Int {
return currentTimeMinutes - player.profile.read(completedTimeKey)
}
fun getTimeSincePlaceholder(player: Player): String {
return if (hasCompleted(player)) {
plugin.langYml.getString("time-since.completed")
.replace("%time%", formatDuration(this.getTimeSinceCompletion(player)))
.formatEco(player)
} else if (hasStarted(player)) {
plugin.langYml.getString("time-since.started")
.replace("%time%", formatDuration(this.getTimeSinceStart(player)))
.formatEco(player)
} else {
plugin.langYml.getString("time-since.never")
.formatEco(player)
}
}
fun resetIfNeeded() { fun resetIfNeeded() {
if (resetTime < 0) { if (resetTime < 0) {
return return
@@ -196,11 +327,69 @@ class Quest(
return return
} }
reset()
}
fun reset() {
ServerProfile.load().write(lastResetTimeKey, (System.currentTimeMillis() / 1000 / 60).toInt()) ServerProfile.load().write(lastResetTimeKey, (System.currentTimeMillis() / 1000 / 60).toInt())
for (player in Bukkit.getOnlinePlayers()) { for (player in Bukkit.getOnlinePlayers()) {
reset(player) reset(player)
} }
// Offline players can be reset async
plugin.scheduler.runAsync {
for (player in Bukkit.getOfflinePlayers()) {
if (!player.isOnline) {
reset(player)
}
}
}
// Unbind old tasks
for (task in tasks) {
task.unbind()
}
tasks = availableTasks.randomlyPick(taskAmount)
// Bind new tasks
for (task in tasks) {
task.bind()
}
// Save new tasks
saveTasks()
}
private fun loadTasks(): List<Task>? {
val serialized = ServerProfile.load().read(savedTasksKey)
if (serialized.isEmpty()) {
return null
}
val savedTasks = mutableListOf<Task>()
for (s in serialized) {
val split = s.split(":")
val taskId = split[0]
val xpExpr = split[1]
val template = Tasks[taskId] ?: continue
savedTasks += template.create(this, xpExpr)
}
return savedTasks
}
private fun saveTasks() {
val serialized = tasks.map {
"${it.template.id}:${it.xpExpr}"
}
ServerProfile.load().write(savedTasksKey, serialized)
} }
fun checkCompletion(player: Player): Boolean { fun checkCompletion(player: Player): Boolean {
@@ -210,17 +399,21 @@ class Quest(
} }
if (tasks.all { it.hasCompleted(player) }) { if (tasks.all { it.hasCompleted(player) }) {
player.profile.write(hasCompletedKey, true) complete(player)
rewards?.trigger(player)
Bukkit.getPluginManager().callEvent(PlayerQuestCompleteEvent(player, this))
return true return true
} }
return false return false
} }
private fun complete(player: Player) {
player.profile.write(hasCompletedKey, true)
player.profile.write(completedTimeKey, currentTimeMinutes)
rewards?.trigger(player.toDispatcher())
Bukkit.getPluginManager().callEvent(PlayerQuestCompleteEvent(player, this))
}
private fun List<String>.addMargin(margin: Int): List<String> { private fun List<String>.addMargin(margin: Int): List<String> {
return this.map { s -> " ".repeat(margin) + s } return this.map { s -> " ".repeat(margin) + s }
} }
@@ -233,6 +426,7 @@ class Quest(
fun String.addPlaceholders() = this fun String.addPlaceholders() = this
.replace("%quest%", quest.name) .replace("%quest%", quest.name)
.replace("%time_until_reset%", formatDuration(quest.minutesUntilReset)) .replace("%time_until_reset%", formatDuration(quest.minutesUntilReset))
.replace("%time_since%", getTimeSincePlaceholder(player))
// Replace multi-line placeholders. // Replace multi-line placeholders.
val processed = strings.flatMap { s -> val processed = strings.flatMap { s ->
@@ -242,7 +436,7 @@ class Quest(
rewardMessages rewardMessages
.addMargin(margin) .addMargin(margin)
} else if (s.contains("%tasks%")) { } else if (s.contains("%tasks%")) {
tasks.flatMap { task -> task.getCompletedDescription(player) } tasks.map { task -> task.getCompletedDescription(player) }
.addMargin(margin) .addMargin(margin)
} else if (s.contains("%description%")) { } else if (s.contains("%description%")) {
getDescription(player) getDescription(player)
@@ -256,6 +450,6 @@ class Quest(
placeholderContext( placeholderContext(
player = player player = player
) )
) ).lineWrap(plugin.configYml.getInt("quests.icon.line-wrap"), true)
} }
} }

View File

@@ -7,6 +7,8 @@ import com.willfp.libreforge.loader.configs.ConfigCategory
import org.bukkit.entity.Player import org.bukkit.entity.Player
object Quests : ConfigCategory("quest", "quests") { object Quests : ConfigCategory("quest", "quests") {
override val supportsSharing = false
private val registry = Registry<Quest>() private val registry = Registry<Quest>()
override fun clear(plugin: LibreforgePlugin) { override fun clear(plugin: LibreforgePlugin) {

View File

@@ -5,7 +5,6 @@ import com.willfp.eco.core.data.keys.PersistentDataKey
import com.willfp.eco.core.data.keys.PersistentDataKeyType import com.willfp.eco.core.data.keys.PersistentDataKeyType
import com.willfp.eco.core.data.profile import com.willfp.eco.core.data.profile
import com.willfp.eco.core.placeholder.PlayerPlaceholder import com.willfp.eco.core.placeholder.PlayerPlaceholder
import com.willfp.eco.core.placeholder.PlayerlessPlaceholder
import com.willfp.eco.core.placeholder.context.placeholderContext import com.willfp.eco.core.placeholder.context.placeholderContext
import com.willfp.eco.util.evaluateExpression import com.willfp.eco.util.evaluateExpression
import com.willfp.eco.util.formatEco import com.willfp.eco.util.formatEco
@@ -16,7 +15,9 @@ import com.willfp.ecoquests.api.event.PlayerTaskExpGainEvent
import com.willfp.ecoquests.quests.Quest import com.willfp.ecoquests.quests.Quest
import com.willfp.ecoquests.quests.Quests import com.willfp.ecoquests.quests.Quests
import com.willfp.libreforge.counters.Accumulator import com.willfp.libreforge.counters.Accumulator
import com.willfp.libreforge.toDispatcher
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
import kotlin.math.min import kotlin.math.min
@@ -24,7 +25,7 @@ class Task(
private val plugin: EcoPlugin, private val plugin: EcoPlugin,
val template: TaskTemplate, val template: TaskTemplate,
val quest: Quest, val quest: Quest,
private val xpExpr: String internal val xpExpr: String
) { ) {
private val xpKey = PersistentDataKey( private val xpKey = PersistentDataKey(
plugin.createNamespacedKey("${quest.id}_task_${template.id}_xp"), plugin.createNamespacedKey("${quest.id}_task_${template.id}_xp"),
@@ -32,6 +33,12 @@ class Task(
0.0 0.0
) )
private val xpRequiredKey = PersistentDataKey(
plugin.createNamespacedKey("${quest.id}_task_${template.id}_xp_required"),
PersistentDataKeyType.DOUBLE,
0.0
)
private val hasCompletedKey = PersistentDataKey( private val hasCompletedKey = PersistentDataKey(
plugin.createNamespacedKey("${quest.id}_task_${template.id}_has_completed"), plugin.createNamespacedKey("${quest.id}_task_${template.id}_has_completed"),
PersistentDataKeyType.BOOLEAN, PersistentDataKeyType.BOOLEAN,
@@ -80,16 +87,16 @@ class Task(
} }
} }
fun reset(player: Player) { fun reset(player: OfflinePlayer) {
player.profile.write(xpKey, 0.0) player.profile.write(xpKey, 0.0)
player.profile.write(hasCompletedKey, false) player.profile.write(hasCompletedKey, false)
} }
fun hasCompleted(player: Player): Boolean { fun hasCompleted(player: OfflinePlayer): Boolean {
return player.profile.read(hasCompletedKey) return player.profile.read(hasCompletedKey)
} }
fun getExperienceRequired(player: Player): Double { private fun generateExperienceRequired(player: Player): Double {
return evaluateExpression( return evaluateExpression(
xpExpr, xpExpr,
placeholderContext( placeholderContext(
@@ -98,8 +105,26 @@ class Task(
) )
} }
fun getExperience(player: Player): Double { fun getExperienceRequired(player: Player): Double {
return min(player.profile.read(xpKey), getExperienceRequired(player)) val required = player.profile.read(xpRequiredKey)
return if (required <= 0) {
val newRequired = generateExperienceRequired(player)
if (newRequired <= 0) {
throw IllegalStateException("Invalid XP Required for task ${template.id} in quest ${quest.id}" +
"(Requirement was $newRequired, which is less than or equal to 0)")
}
player.profile.write(xpRequiredKey, newRequired)
newRequired
} else {
required
}
}
fun getExperience(player: OfflinePlayer): Double {
return player.profile.read(xpKey)
} }
/** /**
@@ -124,10 +149,11 @@ class Task(
val requiredXp = getExperienceRequired(player) val requiredXp = getExperienceRequired(player)
val newXp = player.profile.read(xpKey) + amount val newXp = player.profile.read(xpKey) + amount
player.profile.write(xpKey, newXp) player.profile.write(xpKey, min(newXp, requiredXp))
if (newXp >= requiredXp) { if (newXp >= requiredXp) {
player.profile.write(hasCompletedKey, true) player.profile.write(hasCompletedKey, true)
template.onComplete?.trigger(player.toDispatcher())
Bukkit.getPluginManager().callEvent(PlayerTaskCompleteEvent(player, template, quest)) Bukkit.getPluginManager().callEvent(PlayerTaskCompleteEvent(player, template, quest))
@@ -145,15 +171,13 @@ class Task(
.formatEco(player) .formatEco(player)
} }
fun getCompletedDescription(player: Player): List<String> { fun getCompletedDescription(player: Player): String {
return if (hasCompleted(player)) { return if (hasCompleted(player)) {
plugin.configYml.getString("tasks.completed") plugin.configYml.getString("tasks.completed")
.replace("%description%", getDescription(player)) .replace("%description%", getDescription(player))
.lineWrap(plugin.configYml.getInt("tasks.line-wrap"), true)
} else { } else {
plugin.configYml.getString("tasks.not-completed") plugin.configYml.getString("tasks.not-completed")
.replace("%description%", getDescription(player)) .replace("%description%", getDescription(player))
.lineWrap(plugin.configYml.getInt("tasks.line-wrap"), true)
} }
} }
} }

View File

@@ -3,8 +3,10 @@ package com.willfp.ecoquests.tasks
import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.registry.KRegistrable import com.willfp.eco.core.registry.KRegistrable
import com.willfp.ecoquests.quests.Quest
import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.ViolationContext
import com.willfp.libreforge.counters.Counters import com.willfp.libreforge.counters.Counters
import com.willfp.libreforge.effects.Effects
class TaskTemplate( class TaskTemplate(
private val plugin: EcoPlugin, private val plugin: EcoPlugin,
@@ -14,4 +16,12 @@ class TaskTemplate(
val xpGainMethods = config.getSubsections("xp-gain-methods").mapNotNull { val xpGainMethods = config.getSubsections("xp-gain-methods").mapNotNull {
Counters.compile(it, ViolationContext(plugin, "task $id tasks")) Counters.compile(it, ViolationContext(plugin, "task $id tasks"))
} }
val onComplete = Effects.compileChain(
config.getSubsections("on-complete"),
ViolationContext(plugin, "task $id on-complete")
)
fun create(quest: Quest, xpExpr: String) =
Task(plugin, this, quest, xpExpr)
} }

View File

@@ -0,0 +1,14 @@
package com.willfp.ecoquests.util
fun <T> Collection<T>.randomlyPick(amount: Int): List<T> {
val list = this.toMutableList()
val picked = mutableListOf<T>()
repeat(amount) {
val index = (0 until list.size).random()
picked.add(list[index])
list.removeAt(index)
}
return picked
}

View File

@@ -3,7 +3,12 @@
# by Auxilor # by Auxilor
# #
scan-interval: 20 # How often to scan for quest starting / completion (in ticks) # Even if eco is set up to use a database, you can
# force EcoQuests to save to local storage to disable
# cross-server sync.
use-local-storage: false
scan-interval: 20 # How often to scan for quests auto-starting (in ticks)
gui: gui:
title: "Quest Book" title: "Quest Book"
@@ -34,10 +39,13 @@ gui:
quest-info: quest-info:
item: writable_book item: writable_book
name: "&aQuest Book" name: "&fQuest Book"
lore: lore:
- "&eClick to view your" - ""
- "&eprevious quests!" - "&7Quests Completed: &f%ecoquests_quests_completed%"
- "&7Quests Active: &f%ecoquests_quests_active%"
- ""
- "&eClick to view past quests!"
location: location:
row: 1 row: 1
@@ -130,12 +138,15 @@ completed-gui:
custom-slots: [ ] custom-slots: [ ]
tasks: tasks:
line-wrap: 32 # The line to show when a task is completed
completed: "&a&l &r&f%description%" completed: "&a&l &r&f%description%"
not-completed: "&c&l✘ &r&f%description%" # The line to show when a task is not completed
not-completed: "&c&l❌ &r&f%description%"
quests: quests:
icon: icon:
name: "&e%quest%" # The name of the icon
line-wrap: 32 # Lore line-wrapping
lore: lore:
- "%description%" - "%description%"
- "" - ""
@@ -144,6 +155,8 @@ quests:
- "" - ""
- "&fRewards:" - "&fRewards:"
- " %rewards%" - " %rewards%"
- ""
- "%time_since%"
complete: complete:
message: message:

View File

@@ -4,6 +4,6 @@ environment:
options: options:
color: "&#eacda3" color: "&#eacda3"
resource-id: 0 resource-id: 4600
bstats-id: 0 bstats-id: 19455
uses-reflective-reload: false uses-reflective-reload: false

View File

@@ -10,4 +10,11 @@ messages:
already-started: "&cThe player has already started this quest!" already-started: "&cThe player has already started this quest!"
started-quest: "&fStarted the &a%quest% &fquest for &a%player%&f!" started-quest: "&fStarted the &a%quest% &fquest for &a%player%&f!"
reset-quest: "&fReset the &a%quest% &fquest for &a%player%&f!" reset-quest-for-player: "&fReset the &a%quest% &fquest for &a%player%&f!"
reset-quest: "&fReset the &a%quest% &fquest!"
quest-not-resettable: "&cThis quest is not resettable!"
time-since:
never: "&cNot started yet!"
started: "&7Started %time% ago"
completed: "&7Completed %time% ago"

View File

@@ -1,17 +0,0 @@
name: ${pluginName}
version: ${version}
main: com.willfp.ecoquests.EcoQuestsPlugin
api-version: 1.19
dependencies:
- name: eco
required: true
bootstrap: false
- name: libreforge
required: false
bootstrap: false
load-after:
- name: eco
bootstrap: false

View File

@@ -32,8 +32,9 @@ permissions:
children: children:
ecoquests.command.reload: true ecoquests.command.reload: true
ecoquests.command.quests: true ecoquests.command.quests: true
ecoquests.command.start: op ecoquests.command.start: true
ecoquests.command.reset: op ecoquests.command.reset: true
ecoquests.command.resetplayer: true
ecoquests.command.reload: ecoquests.command.reload:
description: Allows reloading the config description: Allows reloading the config
@@ -50,3 +51,6 @@ permissions:
ecoquests.command.reset: ecoquests.command.reset:
description: Allows using /ecoquests reset. description: Allows using /ecoquests reset.
default: op default: op
ecoquests.command.resetplayer:
description: Allows using /ecoquests resetplayer.
default: op

View File

@@ -1,26 +1,36 @@
name: "Resettable Quest" # The ID of the quest is the name of the .yml file,
# for example traveller.yml has the ID of traveller
# You can place quests anywhere in this folder,
# including in subfolders if you want to organize your quest configs
# _example.yml is not loaded.
description: "&fThis quest will reset in &a%time_until_reset%" name: "Traveller"
description: "&7Stretch your legs! Walk around Lumoria and find new places to explore."
# Options for the /quests GUI # Options for the /quests GUI
gui: gui:
enabled: true # If the quest should be shown in the GUI enabled: true # If the quest should be shown in the GUI
always: false # If the quest should always be in the GUI, even if it's not started always: false # If the quest should always be in the GUI, even if it's not started
# The item to show in the GUI, read https://plugins.auxilor.io/all-plugins/the-item-lookup-system # The item to show in the GUI, read https://plugins.auxilor.io/all-plugins/the-item-lookup-system
item: paper name:"&eExample Quest" item: paper
# How many minutes between this quest being reset (set to -1 to disable) # How many minutes between this quest being reset (set to -1 to disable)
# 1 Day: 1440 # 1 Day: 1440
# 1 Week: 10080 # 1 Week: 10080
# 1 Month: 43200 # 1 Month: 43200
reset-time: 10 reset-time: -1
# A list of tasks and their XP requirements to complete this quest. # A list of tasks and their XP requirements to complete this quest.
# If the task is one action, set XP to 1. # If the task is one action, set XP to 1.
# XP requirements can use placeholder math, for example %ecoskills_combat% * 100 # XP requirements can use placeholder math, for example %ecoskills_combat% * 100
tasks: tasks:
- task: break_stone - task: move
xp: 100 xp: 1000
# (For resettable tasks) The amount of tasks to select from the list above.
# Set to -1 to use all tasks.
task-amount: -1
# The messages for the %rewards% placeholder in icons, messages, etc. # The messages for the %rewards% placeholder in icons, messages, etc.
reward-messages: reward-messages:
@@ -40,4 +50,9 @@ start-effects: [ ]
# A list of conditions required to start the quest. # A list of conditions required to start the quest.
# The quest will be automatically started when these conditions are met. # The quest will be automatically started when these conditions are met.
# Read https://plugins.auxilor.io/conditions/configuring-a-condition # Read https://plugins.auxilor.io/conditions/configuring-a-condition
# If gui.always is true, then not-met-lines will show up on the GUI icon!
start-conditions: [ ] start-conditions: [ ]
# If the quest should auto start when all conditions are met
# If this is set to false, the quest can only be started with /ecoquests start
auto-start: true

View File

@@ -1,4 +1,4 @@
name: "Resettable Quest" name: "Daily Quest I"
description: "&fThis quest will reset in &a%time_until_reset%" description: "&fThis quest will reset in &a%time_until_reset%"
@@ -7,13 +7,13 @@ gui:
enabled: true # If the quest should be shown in the GUI enabled: true # If the quest should be shown in the GUI
always: false # If the quest should always be in the GUI, even if it's not started always: false # If the quest should always be in the GUI, even if it's not started
# The item to show in the GUI, read https://plugins.auxilor.io/all-plugins/the-item-lookup-system # The item to show in the GUI, read https://plugins.auxilor.io/all-plugins/the-item-lookup-system
item: paper name:"&eExample Quest" item: paper
# How many minutes between this quest being reset (set to -1 to disable) # How many minutes between this quest being reset (set to -1 to disable)
# 1 Day: 1440 # 1 Day: 1440
# 1 Week: 10080 # 1 Week: 10080
# 1 Month: 43200 # 1 Month: 43200
reset-time: 10 reset-time: 1440
# A list of tasks and their XP requirements to complete this quest. # A list of tasks and their XP requirements to complete this quest.
# If the task is one action, set XP to 1. # If the task is one action, set XP to 1.
@@ -22,6 +22,10 @@ tasks:
- task: break_stone - task: break_stone
xp: 100 xp: 100
# (For resettable tasks) The amount of tasks to select from the list above.
# Set to -1 to use all tasks.
task-amount: 1
# The messages for the %rewards% placeholder in icons, messages, etc. # The messages for the %rewards% placeholder in icons, messages, etc.
reward-messages: reward-messages:
- " &8» &r&f+2 %ecoskills_defense_name%" - " &8» &r&f+2 %ecoskills_defense_name%"
@@ -40,4 +44,7 @@ start-effects: [ ]
# A list of conditions required to start the quest. # A list of conditions required to start the quest.
# The quest will be automatically started when these conditions are met. # The quest will be automatically started when these conditions are met.
# Read https://plugins.auxilor.io/conditions/configuring-a-condition # Read https://plugins.auxilor.io/conditions/configuring-a-condition
# If gui.always is true, then not-met-lines will show up on the GUI icon!
start-conditions: [ ] start-conditions: [ ]
auto-start: true

View File

@@ -1,7 +1,7 @@
# The ID of the task is the name of the .yml file, # The ID of the task is the name of the .yml file,
# for example break_100_stone.yml has the ID of break_100_stone # for example break_100_stone.yml has the ID of break_100_stone
# You can place tasks anywhere in this folder, # You can place tasks anywhere in this folder,
# including in subfolders if you want to organize your quest task # including in subfolders if you want to organize your task configs
# _example.yml is not loaded. # _example.yml is not loaded.
# If multiple quests have the same task, gaining task XP for one quest # If multiple quests have the same task, gaining task XP for one quest
@@ -21,3 +21,10 @@ xp-gain-methods:
filters: filters:
blocks: blocks:
- stone - stone
# An optional list of effects to run when a player completes the task
# Read here: https://plugins.auxilor.io/effects/configuring-an-effect
on-complete:
- id: send_message
args:
message: "Task Completed!"

View File

@@ -0,0 +1,4 @@
description: "&fMove distance (&a%xp%&8/&a%required-xp%&f)"
xp-gain-methods:
- trigger: move

View File

@@ -1,5 +1,5 @@
#libreforge-updater #libreforge-updater
#Wed Aug 09 14:37:41 BST 2023 #Sun Dec 24 14:54:57 CET 2023
kotlin.code.style=official kotlin.code.style=official
libreforge-version=4.27.0 libreforge-version=4.51.0
version=0.3.0 version=1.23.0

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

0
gradlew vendored Normal file → Executable file
View File