9
0
mirror of https://github.com/Auxilor/EcoJobs.git synced 2025-12-19 23:19:16 +00:00

Compare commits

...

9 Commits

Author SHA1 Message Date
Will FP
e67717cf84 Removed ConfigUpdater import 2025-10-26 13:38:50 +00:00
Will FP
16798df570 Fixed GUI 2025-10-26 13:38:27 +00:00
Will FP
d0eaee3ad0 Updated to 3.77.1 2025-10-06 10:44:46 +01:00
Will FP
4e076680aa Hotfix 2025-10-06 10:44:38 +01:00
Will FP
4257c45920 libreforge-updater 2025-10-06 08:56:29 +01:00
Will FP
8a05885940 Merge remote-tracking branch 'origin/develop' 2025-09-26 16:16:04 +01:00
Will FP
42033f31b6 libreforge-updater 2025-09-11 09:57:55 +01:00
Exanthiax
662e978cbf Improved EcoJobs top placeholder and added new /jobs top command 2025-08-27 18:57:43 +01:00
Exanthiax
ec8ddba84c fixed level-xp-requirements ignoring first few values 2025-08-27 18:51:51 +01:00
12 changed files with 218 additions and 75 deletions

View File

@@ -39,14 +39,14 @@ allprojects {
} }
dependencies { dependencies {
compileOnly("com.willfp:eco:6.56.0") compileOnly("com.willfp:eco:6.77.0")
compileOnly("org.jetbrains:annotations:23.0.0") compileOnly("org.jetbrains:annotations:23.0.0")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.0") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.0")
} }
java { java {
withSourcesJar() withSourcesJar()
toolchain.languageVersion.set(JavaLanguageVersion.of(17)) toolchain.languageVersion.set(JavaLanguageVersion.of(21))
} }
tasks { tasks {
@@ -57,7 +57,7 @@ allprojects {
compileKotlin { compileKotlin {
compilerOptions { compilerOptions {
jvmTarget.set(JvmTarget.JVM_17) jvmTarget.set(JvmTarget.JVM_21)
} }
} }

View File

@@ -10,8 +10,10 @@ import com.willfp.ecojobs.api.getJobLevel
import com.willfp.ecojobs.api.jobLimit import com.willfp.ecojobs.api.jobLimit
import com.willfp.ecojobs.commands.CommandEcoJobs import com.willfp.ecojobs.commands.CommandEcoJobs
import com.willfp.ecojobs.commands.CommandJobs import com.willfp.ecojobs.commands.CommandJobs
import com.willfp.ecojobs.jobs.EcoJobsJobTopPlaceholder
import com.willfp.ecojobs.jobs.JobLevelListener import com.willfp.ecojobs.jobs.JobLevelListener
import com.willfp.ecojobs.jobs.Jobs import com.willfp.ecojobs.jobs.Jobs
import com.willfp.ecojobs.jobs.JobsGUI
import com.willfp.ecojobs.jobs.PriceHandler import com.willfp.ecojobs.jobs.PriceHandler
import com.willfp.ecojobs.jobs.ResetOnQuitListener import com.willfp.ecojobs.jobs.ResetOnQuitListener
import com.willfp.ecojobs.libreforge.ConditionHasActiveJob import com.willfp.ecojobs.libreforge.ConditionHasActiveJob
@@ -64,6 +66,8 @@ class EcoJobsPlugin : LibreforgePlugin() {
} }
} }
EcoJobsJobTopPlaceholder(this).register()
PlayerPlaceholder( PlayerPlaceholder(
this, this,
"limit" "limit"
@@ -84,28 +88,10 @@ class EcoJobsPlugin : LibreforgePlugin() {
} }
level.toString() level.toString()
}.register() }.register()
}
DynamicPlaceholder( override fun handleReload() {
this, JobsGUI.update(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<PluginCommand> { override fun loadPluginCommands(): List<PluginCommand> {

View File

@@ -12,6 +12,7 @@ class CommandJobs(plugin: EcoPlugin) : PluginCommand(plugin, "jobs", "ecojobs.co
init { init {
this.addSubcommand(CommandJoin(plugin)) this.addSubcommand(CommandJoin(plugin))
.addSubcommand(CommandLeave(plugin)) .addSubcommand(CommandLeave(plugin))
.addSubcommand(CommandTop(plugin))
} }
override fun onExecute(player: Player, args: List<String>) { override fun onExecute(player: Player, args: List<String>) {

View File

@@ -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<String>) {
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<String>()
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<String>): List<String> {
val completions = mutableListOf<String>()
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()
}
}

View File

@@ -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
}
}
}

View File

@@ -27,6 +27,7 @@ import com.willfp.ecojobs.api.getJobXP
import com.willfp.ecojobs.api.getJobXPRequired import com.willfp.ecojobs.api.getJobXPRequired
import com.willfp.ecojobs.api.hasJobActive import com.willfp.ecojobs.api.hasJobActive
import com.willfp.ecojobs.api.jobLimit import com.willfp.ecojobs.api.jobLimit
import com.willfp.ecojobs.util.LeaderboardCacheEntry
import com.willfp.ecojobs.util.LevelInjectable import com.willfp.ecojobs.util.LevelInjectable
import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.ViolationContext
import com.willfp.libreforge.conditions.ConditionList import com.willfp.libreforge.conditions.ConditionList
@@ -42,6 +43,7 @@ import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import java.time.Duration import java.time.Duration
import java.util.Objects import java.util.Objects
import java.util.UUID
class Job( class Job(
val id: String, val id: String,
@@ -80,7 +82,7 @@ class Job(
private val xpFormula = config.getStringOrNull("xp-formula") private val xpFormula = config.getStringOrNull("xp-formula")
private val levelXpRequirements = config.getDoublesOrNull("level-xp-requirements") private val levelXpRequirements = listOf(0) + config.getInts("level-xp-requirements")
val maxLevel = config.getIntOrNull("max-level") ?: levelXpRequirements?.size ?: Int.MAX_VALUE val maxLevel = config.getIntOrNull("max-level") ?: levelXpRequirements?.size ?: Int.MAX_VALUE
@@ -184,6 +186,14 @@ class Job(
) { ) {
Bukkit.getOfflinePlayers().count { this in it.activeJobs }.toString() Bukkit.getOfflinePlayers().count { this in it.activeJobs }.toString()
}.register() }.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") @Deprecated("Use level-up-effects instead")
@@ -216,7 +226,7 @@ class Job(
NormalExecutorFactory.create(), NormalExecutorFactory.create(),
ViolationContext(plugin, "Job $id level-up-effects") ViolationContext(plugin, "Job $id level-up-effects")
) )
val joinEffects = Effects.compileChain( val joinEffects = Effects.compileChain(
config.getSubsections("join-effects"), config.getSubsections("join-effects"),
NormalExecutorFactory.create(), NormalExecutorFactory.create(),
@@ -321,6 +331,7 @@ class Job(
.replace("%level_numeral%", NumberUtils.toNumeral(forceLevel ?: player.getJobLevel(this))) .replace("%level_numeral%", NumberUtils.toNumeral(forceLevel ?: player.getJobLevel(this)))
.replace("%join_price%", this.joinPrice.getDisplay(player)) .replace("%join_price%", this.joinPrice.getDisplay(player))
.replace("%leave_price%", this.leavePrice.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 level = forceLevel ?: player.getJobLevel(this)
val regex = Regex("%level_(-?\\d+)(_numeral)?%") val regex = Regex("%level_(-?\\d+)(_numeral)?%")
@@ -400,20 +411,20 @@ class Job(
* Get the XP required to reach the next level, if currently at [level]. * Get the XP required to reach the next level, if currently at [level].
*/ */
fun getExpForLevel(level: Int): Double { fun getExpForLevel(level: Int): Double {
if (level < 1 || level > maxLevel) {
return Double.MAX_VALUE
}
if (xpFormula != null) { if (xpFormula != null) {
return evaluateExpression( return evaluateExpression(
xpFormula, xpFormula,
placeholderContext( placeholderContext(
injectable = LevelInjectable(level) injectable = LevelInjectable(level - 1)
) )
) )
} }
if (levelXpRequirements != null) { return levelXpRequirements[level - 1].toDouble()
return levelXpRequirements.getOrNull(level) ?: Double.POSITIVE_INFINITY
}
return Double.POSITIVE_INFINITY
} }
fun getFormattedExpForLevel(level: Int): String { fun getFormattedExpForLevel(level: Int): String {
@@ -442,6 +453,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 { override fun getID(): String {
return this.id return this.id
} }
@@ -465,11 +484,6 @@ private class LevelPlaceholder(
operator fun invoke(level: Int) = function(level) operator fun invoke(level: Int) = function(level)
} }
data class LeaderboardCacheEntry(
val player: OfflinePlayer,
val amount: Int
)
private fun Collection<LevelPlaceholder>.format(string: String, level: Int): String { private fun Collection<LevelPlaceholder>.format(string: String, level: Int): String {
var process = string var process = string
for (placeholder in this) { for (placeholder in this) {
@@ -479,4 +493,3 @@ private fun Collection<LevelPlaceholder>.format(string: String, level: Int): Str
} }
fun OfflinePlayer.getJobLevelObject(job: Job): JobLevel = job.getLevel(this.getJobLevel(job)) fun OfflinePlayer.getJobLevelObject(job: Job): JobLevel = job.getLevel(this.getJobLevel(job))

View File

@@ -1,5 +1,6 @@
package com.willfp.ecojobs.jobs package com.willfp.ecojobs.jobs
import com.willfp.eco.util.SoundUtils
import com.willfp.ecojobs.EcoJobsPlugin import com.willfp.ecojobs.EcoJobsPlugin
import com.willfp.ecojobs.api.event.PlayerJobLevelUpEvent import com.willfp.ecojobs.api.event.PlayerJobLevelUpEvent
import com.willfp.libreforge.toDispatcher import com.willfp.libreforge.toDispatcher
@@ -21,15 +22,17 @@ class JobLevelListener(
job.executeLevelCommands(player, level) job.executeLevelCommands(player, level)
if (this.plugin.configYml.getBool("level-up.sound.enabled")) { if (this.plugin.configYml.getBool("level-up.sound.enabled")) {
val sound = Sound.valueOf(this.plugin.configYml.getString("level-up.sound.id").uppercase()) val sound = SoundUtils.getSound(this.plugin.configYml.getString("level-up.sound.id"))
val pitch = this.plugin.configYml.getDouble("level-up.sound.pitch") val pitch = this.plugin.configYml.getDouble("level-up.sound.pitch")
player.playSound( if (sound != null) {
player.location, player.playSound(
sound, player.location,
100f, sound,
pitch.toFloat() 100f,
) pitch.toFloat()
)
}
} }
if (this.plugin.configYml.getBool("level-up.message.enabled")) { if (this.plugin.configYml.getBool("level-up.message.enabled")) {

View File

@@ -2,7 +2,6 @@ package com.willfp.ecojobs.jobs
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.willfp.eco.core.config.interfaces.Config import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.config.updating.ConfigUpdater
import com.willfp.eco.core.registry.Registry import com.willfp.eco.core.registry.Registry
import com.willfp.ecojobs.EcoJobsPlugin import com.willfp.ecojobs.EcoJobsPlugin
import com.willfp.ecojobs.api.getJobLevel import com.willfp.ecojobs.api.getJobLevel
@@ -30,8 +29,8 @@ object Jobs : ConfigCategory("job", "jobs") {
* @return The matching [Job], or null if not found. * @return The matching [Job], or null if not found.
*/ */
@JvmStatic @JvmStatic
fun getByID(name: String): Job? { fun getByID(name: String?): Job? {
return registry[name] return name?.let { registry[it] }
} }
override fun clear(plugin: LibreforgePlugin) { override fun clear(plugin: LibreforgePlugin) {

View File

@@ -1,6 +1,5 @@
package com.willfp.ecojobs.jobs package com.willfp.ecojobs.jobs
import com.willfp.eco.core.config.updating.ConfigUpdater
import com.willfp.eco.core.gui.menu import com.willfp.eco.core.gui.menu
import com.willfp.eco.core.gui.menu.Menu import com.willfp.eco.core.gui.menu.Menu
import com.willfp.eco.core.gui.onLeftClick import com.willfp.eco.core.gui.onLeftClick
@@ -13,6 +12,7 @@ import com.willfp.eco.core.gui.slot.MaskItems
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.SkullBuilder import com.willfp.eco.core.items.builder.SkullBuilder
import com.willfp.eco.util.SoundUtils
import com.willfp.eco.util.formatEco import com.willfp.eco.util.formatEco
import com.willfp.ecojobs.EcoJobsPlugin import com.willfp.ecojobs.EcoJobsPlugin
import com.willfp.ecojobs.api.activeJobs import com.willfp.ecojobs.api.activeJobs
@@ -27,16 +27,12 @@ import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.SkullMeta import org.bukkit.inventory.meta.SkullMeta
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
object JobsGUI { object JobsGUI {
private lateinit var menu: Menu private lateinit var menu: Menu
private val jobAreaSlots = mutableListOf<Pair<Int, Int>>() private val jobAreaSlots = mutableListOf<Pair<Int, Int>>()
@JvmStatic internal fun update(plugin: EcoJobsPlugin) {
@ConfigUpdater
fun update(plugin: EcoJobsPlugin) {
val topLeftRow = plugin.configYml.getInt("gui.job-area.top-left.row") val topLeftRow = plugin.configYml.getInt("gui.job-area.top-left.row")
val topLeftColumn = plugin.configYml.getInt("gui.job-area.top-left.column") val topLeftColumn = plugin.configYml.getInt("gui.job-area.top-left.column")
val bottomRightRow = plugin.configYml.getInt("gui.job-area.bottom-right.row") val bottomRightRow = plugin.configYml.getInt("gui.job-area.bottom-right.row")
@@ -150,12 +146,16 @@ object JobsGUI {
} }
} }
player.playSound( val sound = SoundUtils.getSound(plugin.configYml.getString("gui.job-icon.click.sound"))
player.location,
Sound.valueOf(plugin.configYml.getString("gui.job-icon.click.sound").uppercase()), if (sound != null) {
1f, player.playSound(
plugin.configYml.getDouble("gui.job-icon.click.pitch").toFloat() player.location,
) sound,
1f,
plugin.configYml.getDouble("gui.job-icon.click.pitch").toFloat()
)
}
} }
onRightClick { player, _, _, menu -> onRightClick { player, _, _, menu ->
@@ -170,12 +170,16 @@ object JobsGUI {
if (player.hasJobActive(job)) { if (player.hasJobActive(job)) {
job.leaveGUI.open(player) job.leaveGUI.open(player)
player.playSound( val sound = SoundUtils.getSound(plugin.configYml.getString("gui.job-icon.click.sound"))
player.location,
Sound.valueOf(plugin.configYml.getString("gui.job-icon.click.sound").uppercase()), if (sound != null) {
1f, player.playSound(
plugin.configYml.getDouble("gui.job-icon.click.pitch").toFloat() player.location,
) sound,
1f,
plugin.configYml.getDouble("gui.job-icon.click.pitch").toFloat()
)
}
} }
} }
}) })
@@ -204,12 +208,14 @@ object JobsGUI {
) )
maxPages { player -> maxPages { player ->
ceil(Jobs.values() ceil(
.filter { player.getJobLevel(it) > 0 } Jobs.values()
.size.toDouble() / jobAreaSlots.size).toInt() .filter { player.getJobLevel(it) > 0 }
.size.toDouble() / jobAreaSlots.size).toInt()
} }
setSlot(plugin.configYml.getInt("gui.close.location.row"), setSlot(
plugin.configYml.getInt("gui.close.location.row"),
plugin.configYml.getInt("gui.close.location.column"), plugin.configYml.getInt("gui.close.location.column"),
slot( slot(
ItemStackBuilder(Items.lookup(plugin.configYml.getString("gui.close.item"))) ItemStackBuilder(Items.lookup(plugin.configYml.getString("gui.close.item")))

View File

@@ -0,0 +1,8 @@
package com.willfp.ecojobs.util
import org.bukkit.OfflinePlayer
data class LeaderboardCacheEntry(
val player: OfflinePlayer,
val level: Int
)

View File

@@ -34,6 +34,9 @@ menu:
infinity: "∞" infinity: "∞"
top-line-format: "%rank%. %player% - %level%"
top: top:
name-empty: "&cEmpty" empty-position: "&cN/A"
amount-empty: "0" format:
- "---- Jobs Leaderboard ----"
- "%lines%"

View File

@@ -1,5 +1,5 @@
#libreforge-updater #libreforge-updater
#Fri Aug 01 10:03:49 BST 2025 #Mon Oct 06 08:56:29 BST 2025
kotlin.code.style=official kotlin.code.style=official
libreforge-version=4.77.0 libreforge-version=4.79.0
version=3.75.0 version=3.77.2