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

Compare commits

...

14 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
Will FP
d5eea9549f libreforge-updater 2025-08-01 10:03:49 +01:00
Will FP
744f5bf4b0 Merge pull request #37 from Exanthiax/master
Update Job.kt
2025-08-01 09:40:03 +01:00
Exanthiax
1a6c879305 Update Job.kt 2025-07-16 18:32:52 +01:00
Will FP
682446aa90 libreforge-updater 2025-07-05 16:44:53 +01:00
Will FP
2e2f44a387 libreforge-updater 2025-07-04 09:49:51 +01:00
12 changed files with 236 additions and 77 deletions

View File

@@ -39,14 +39,14 @@ allprojects {
}
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.kotlin:kotlin-stdlib:2.1.0")
}
java {
withSourcesJar()
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
tasks {
@@ -57,7 +57,7 @@ allprojects {
compileKotlin {
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.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.JobsGUI
import com.willfp.ecojobs.jobs.PriceHandler
import com.willfp.ecojobs.jobs.ResetOnQuitListener
import com.willfp.ecojobs.libreforge.ConditionHasActiveJob
@@ -64,6 +66,8 @@ class EcoJobsPlugin : LibreforgePlugin() {
}
}
EcoJobsJobTopPlaceholder(this).register()
PlayerPlaceholder(
this,
"limit"
@@ -84,28 +88,10 @@ 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 handleReload() {
JobsGUI.update(this)
}
override fun loadPluginCommands(): List<PluginCommand> {

View File

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

@@ -17,6 +17,7 @@ import com.willfp.eco.util.NumberUtils
import com.willfp.eco.util.NumberUtils.evaluateExpression
import com.willfp.eco.util.formatEco
import com.willfp.eco.util.toNiceString
import com.willfp.eco.util.toNumeral
import com.willfp.ecojobs.EcoJobsPlugin
import com.willfp.ecojobs.api.activeJobs
import com.willfp.ecojobs.api.canJoinJob
@@ -26,6 +27,7 @@ import com.willfp.ecojobs.api.getJobXP
import com.willfp.ecojobs.api.getJobXPRequired
import com.willfp.ecojobs.api.hasJobActive
import com.willfp.ecojobs.api.jobLimit
import com.willfp.ecojobs.util.LeaderboardCacheEntry
import com.willfp.ecojobs.util.LevelInjectable
import com.willfp.libreforge.ViolationContext
import com.willfp.libreforge.conditions.ConditionList
@@ -41,6 +43,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,
@@ -79,7 +82,7 @@ class Job(
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
@@ -183,6 +186,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")
@@ -215,7 +226,7 @@ class Job(
NormalExecutorFactory.create(),
ViolationContext(plugin, "Job $id level-up-effects")
)
val joinEffects = Effects.compileChain(
config.getSubsections("join-effects"),
NormalExecutorFactory.create(),
@@ -310,8 +321,9 @@ class Job(
}
fun injectPlaceholdersInto(lore: List<String>, player: Player, forceLevel: Int? = null): List<String> {
val withPlaceholders = lore.map {
it.replace("%percentage_progress%", (player.getJobProgress(this) * 100).toNiceString())
val withPlaceholders = lore.map { line ->
var result = line
.replace("%percentage_progress%", (player.getJobProgress(this) * 100).toNiceString())
.replace("%current_xp%", player.getJobXP(this).toNiceString())
.replace("%required_xp%", this.getFormattedExpForLevel(player.getJobLevel(this) + 1))
.replace("%description%", this.description).replace("%job%", this.name)
@@ -319,6 +331,21 @@ 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)?%")
// Handle dynamic %level_X% and %level_X_numeral%
result = regex.replace(result) { match ->
val offset = match.groupValues[1].toIntOrNull() ?: return@replace match.value
val isNumeral = match.groupValues[2].isNotEmpty()
val newLevel = level + offset
if (isNumeral) newLevel.toNumeral() else newLevel.toString()
}
result
}.toMutableList()
val processed = mutableListOf<List<String>>()
@@ -384,20 +411,20 @@ class Job(
* Get the XP required to reach the next level, if currently at [level].
*/
fun getExpForLevel(level: Int): Double {
if (level < 1 || level > maxLevel) {
return Double.MAX_VALUE
}
if (xpFormula != null) {
return evaluateExpression(
xpFormula,
placeholderContext(
injectable = LevelInjectable(level)
injectable = LevelInjectable(level - 1)
)
)
}
if (levelXpRequirements != null) {
return levelXpRequirements.getOrNull(level) ?: Double.POSITIVE_INFINITY
}
return Double.POSITIVE_INFINITY
return levelXpRequirements[level - 1].toDouble()
}
fun getFormattedExpForLevel(level: Int): String {
@@ -426,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 {
return this.id
}
@@ -449,11 +484,6 @@ private class LevelPlaceholder(
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 {
var process = string
for (placeholder in this) {
@@ -463,4 +493,3 @@ private fun Collection<LevelPlaceholder>.format(string: String, level: Int): Str
}
fun OfflinePlayer.getJobLevelObject(job: Job): JobLevel = job.getLevel(this.getJobLevel(job))

View File

@@ -1,5 +1,6 @@
package com.willfp.ecojobs.jobs
import com.willfp.eco.util.SoundUtils
import com.willfp.ecojobs.EcoJobsPlugin
import com.willfp.ecojobs.api.event.PlayerJobLevelUpEvent
import com.willfp.libreforge.toDispatcher
@@ -21,15 +22,17 @@ class JobLevelListener(
job.executeLevelCommands(player, level)
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")
player.playSound(
player.location,
sound,
100f,
pitch.toFloat()
)
if (sound != null) {
player.playSound(
player.location,
sound,
100f,
pitch.toFloat()
)
}
}
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.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.config.updating.ConfigUpdater
import com.willfp.eco.core.registry.Registry
import com.willfp.ecojobs.EcoJobsPlugin
import com.willfp.ecojobs.api.getJobLevel
@@ -30,8 +29,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) {

View File

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

View File

@@ -1,5 +1,5 @@
#libreforge-updater
#Sat Mar 29 14:21:51 GMT 2025
#Mon Oct 06 08:56:29 BST 2025
kotlin.code.style=official
libreforge-version=4.75.0
version=3.73.0
libreforge-version=4.79.0
version=3.77.2