diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/EcoJobsPlugin.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/EcoJobsPlugin.kt index 003c514..eb3f4f9 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/EcoJobsPlugin.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/EcoJobsPlugin.kt @@ -10,6 +10,7 @@ import com.willfp.ecojobs.api.getJobLevel import com.willfp.ecojobs.api.jobLimit import com.willfp.ecojobs.commands.CommandEcoJobs import com.willfp.ecojobs.commands.CommandJobs +import com.willfp.ecojobs.jobs.EcoJobsJobTopPlaceholder import com.willfp.ecojobs.jobs.JobLevelListener import com.willfp.ecojobs.jobs.Jobs import com.willfp.ecojobs.jobs.PriceHandler @@ -64,6 +65,8 @@ class EcoJobsPlugin : LibreforgePlugin() { } } + EcoJobsJobTopPlaceholder(this).register() + PlayerPlaceholder( this, "limit" @@ -84,28 +87,6 @@ class EcoJobsPlugin : LibreforgePlugin() { } level.toString() }.register() - - DynamicPlaceholder( - this, - Pattern.compile("top_[a-z]+_[0-9]+_[a-z]+") - ) { - val split = it.split("_") - val jobId = split.getOrNull(1) ?: return@DynamicPlaceholder "You must specify the job id!" - val job = Jobs.getByID(jobId) ?: return@DynamicPlaceholder "Invalid job id!" - val placeString = split.getOrNull(2) ?: return@DynamicPlaceholder "You must specify the place!" - val place = placeString.toIntOrNull() ?: return@DynamicPlaceholder "Invalid place!" - val type = split.getOrNull(3) ?: return@DynamicPlaceholder "You must specify the top type!" - val topEntry = job.getTop(place) - return@DynamicPlaceholder when (type) { - "name" -> topEntry?.player?.savedDisplayName - ?: this.langYml.getFormattedString("top.name-empty") - - "amount" -> topEntry?.amount?.toNiceString() - ?: this.langYml.getFormattedString("top.amount-empty") - - else -> "Invalid type: $type! Available types: name/amount" - } - }.register() } override fun loadPluginCommands(): List { diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandJobs.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandJobs.kt index 239a6b9..173ac18 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandJobs.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandJobs.kt @@ -12,6 +12,7 @@ class CommandJobs(plugin: EcoPlugin) : PluginCommand(plugin, "jobs", "ecojobs.co init { this.addSubcommand(CommandJoin(plugin)) .addSubcommand(CommandLeave(plugin)) + .addSubcommand(CommandTop(plugin)) } override fun onExecute(player: Player, args: List) { diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandTop.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandTop.kt new file mode 100644 index 0000000..e53da91 --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandTop.kt @@ -0,0 +1,87 @@ +package com.willfp.ecojobs.commands + +import com.willfp.eco.core.EcoPlugin +import com.willfp.eco.core.command.impl.Subcommand +import com.willfp.eco.core.placeholder.context.placeholderContext +import com.willfp.eco.util.formatEco +import com.willfp.eco.util.savedDisplayName +import com.willfp.ecojobs.jobs.Jobs +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player +import org.bukkit.util.StringUtil + +class CommandTop(plugin: EcoPlugin) : Subcommand(plugin, "top", "ecojobs.command.top", false) { + override fun onExecute(sender: CommandSender, args: List) { + plugin.scheduler.runAsync { + val job = Jobs.getByID(args.getOrNull(0)) + + if (job == null) { + sender.sendMessage(plugin.langYml.getMessage("invalid-job")) + return@runAsync + } + + val page = args.getOrNull(1)?.toIntOrNull() ?: 1 + + if (args.getOrNull(1)?.let { it.isNotBlank() && !it.matches("\\d+".toRegex()) } == true) { + sender.sendMessage(plugin.langYml.getMessage("invalid-page")) + return@runAsync + } + + val offset = (page - 1) * 10 + val positions = (offset + 1..offset + 10).toList() + + val top = positions.mapNotNull { job.getTop(it) } + + val messages = plugin.langYml.getStrings("top.format").toMutableList() + val lines = mutableListOf() + + top.forEachIndexed { index, entry -> + val line = plugin.langYml.getString("top-line-format") + .replace("%rank%", (offset + index + 1).toString()) + .replace("%level%", entry.level.toString()) + .replace("%player%", entry.player.savedDisplayName) + lines.add(line) + } + + val linesIndex = messages.indexOf("%lines%") + if (linesIndex != -1) { + messages.removeAt(linesIndex) + messages.addAll(linesIndex, lines) + } + + messages.forEach { message -> + sender.sendMessage( + message.formatEco( + placeholderContext( + player = sender as? Player + ) + ) + ) + } + } + } + + override fun tabComplete(sender: CommandSender, args: List): List { + val completions = mutableListOf() + + if (args.size == 1) { + StringUtil.copyPartialMatches( + args[0], + Jobs.values().map { it.id }, + completions + ) + return completions + } + + if (args.size == 2 && Jobs.getByID(args[0]) != null) { + StringUtil.copyPartialMatches( + args[1], + listOf("1", "2", "3", "4", "5"), + completions + ) + return completions + } + + return emptyList() + } +} \ No newline at end of file diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/EcoJobsTopPlaceholder.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/EcoJobsTopPlaceholder.kt new file mode 100644 index 0000000..4d6140f --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/EcoJobsTopPlaceholder.kt @@ -0,0 +1,37 @@ +package com.willfp.ecojobs.jobs + +import com.willfp.eco.core.EcoPlugin +import com.willfp.eco.core.placeholder.RegistrablePlaceholder +import com.willfp.eco.core.placeholder.context.PlaceholderContext +import com.willfp.eco.util.savedDisplayName +import java.util.regex.Pattern + +class EcoJobsJobTopPlaceholder( + private val plugin: EcoPlugin +) : RegistrablePlaceholder { + private val pattern = Pattern.compile("top_([a-z0-9_]+)_(\\d+)_(name|level|amount)") + + override fun getPattern(): Pattern = pattern + override fun getPlugin(): EcoPlugin = plugin + + override fun getValue(params: String, ctx: PlaceholderContext): String? { + val emptyPosition: String = plugin.langYml.getString("top.empty-position") + + val matcher = pattern.matcher(params) + if (!matcher.matches()) { + return null + } + + val jobId = matcher.group(1) + val place = matcher.group(2).toIntOrNull() ?: return null + val type = matcher.group(3) + + val job = Jobs.getByID(jobId) ?: return null + + return when (type) { + "name" -> job.getTop(place)?.player?.savedDisplayName ?: emptyPosition + "level", "amount" -> job.getTop(place)?.level?.toString() ?: emptyPosition + else -> null + } + } +} \ No newline at end of file diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/Job.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/Job.kt index 4116c85..0bc0a92 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/Job.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/Job.kt @@ -42,6 +42,7 @@ import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import java.time.Duration import java.util.Objects +import java.util.UUID class Job( val id: String, @@ -184,6 +185,14 @@ class Job( ) { Bukkit.getOfflinePlayers().count { this in it.activeJobs }.toString() }.register() + + PlayerPlaceholder( + plugin, "${id}_leaderboard_rank" + ) { player -> + val emptyPosition = plugin.langYml.getString("top.empty-position") + val position = getPosition(player.uniqueId) + position?.toString() ?: emptyPosition + }.register() } @Deprecated("Use level-up-effects instead") @@ -321,6 +330,7 @@ class Job( .replace("%level_numeral%", NumberUtils.toNumeral(forceLevel ?: player.getJobLevel(this))) .replace("%join_price%", this.joinPrice.getDisplay(player)) .replace("%leave_price%", this.leavePrice.getDisplay(player)) + .replace("%rank%", this.getPosition(player.uniqueId)?.toString() ?: plugin.langYml.getString("top.empty-position")) val level = forceLevel ?: player.getJobLevel(this) val regex = Regex("%level_(-?\\d+)(_numeral)?%") @@ -442,6 +452,14 @@ class Job( } } + fun getPosition(uuid: UUID): Int? { + val leaderboard = Bukkit.getOfflinePlayers().sortedByDescending { it.getJobLevel(this) } + .map { it.uniqueId } + + val index = leaderboard.indexOf(uuid) + return if (index == -1) null else index + 1 + } + override fun getID(): String { return this.id } @@ -465,11 +483,6 @@ private class LevelPlaceholder( operator fun invoke(level: Int) = function(level) } -data class LeaderboardCacheEntry( - val player: OfflinePlayer, - val amount: Int -) - private fun Collection.format(string: String, level: Int): String { var process = string for (placeholder in this) { diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/Jobs.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/Jobs.kt index b907d67..a3f20cb 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/Jobs.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/Jobs.kt @@ -30,8 +30,8 @@ object Jobs : ConfigCategory("job", "jobs") { * @return The matching [Job], or null if not found. */ @JvmStatic - fun getByID(name: String): Job? { - return registry[name] + fun getByID(name: String?): Job? { + return name?.let { registry[it] } } override fun clear(plugin: LibreforgePlugin) { diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/util/LeaderboardCacheEntry.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/util/LeaderboardCacheEntry.kt new file mode 100644 index 0000000..8d74038 --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/util/LeaderboardCacheEntry.kt @@ -0,0 +1,8 @@ +package com.willfp.ecojobs.util + +import org.bukkit.OfflinePlayer + +data class LeaderboardCacheEntry( + val player: OfflinePlayer, + val level: Int +) \ No newline at end of file diff --git a/eco-core/core-plugin/src/main/resources/lang.yml b/eco-core/core-plugin/src/main/resources/lang.yml index 2b9d208..245d64c 100644 --- a/eco-core/core-plugin/src/main/resources/lang.yml +++ b/eco-core/core-plugin/src/main/resources/lang.yml @@ -34,6 +34,9 @@ menu: infinity: "∞" +top-line-format: "%rank%. %player% - %level%" top: - name-empty: "&cEmpty" - amount-empty: "0" + empty-position: "&cN/A" + format: + - "---- Jobs Leaderboard ----" + - "%lines%" \ No newline at end of file