diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/EcoJobsAPIImpl.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/EcoJobsAPIImpl.kt deleted file mode 100644 index 1e28794..0000000 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/EcoJobsAPIImpl.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.willfp.ecojobs - -import com.willfp.ecojobs.api.EcoJobsAPI -import com.willfp.ecojobs.jobs.Job -import com.willfp.ecojobs.jobs.activeJob -import com.willfp.ecojobs.jobs.getJobLevel -import com.willfp.ecojobs.jobs.getJobProgress -import com.willfp.ecojobs.jobs.getJobXP -import com.willfp.ecojobs.jobs.getJobXPRequired -import com.willfp.ecojobs.jobs.giveJobExperience -import com.willfp.ecojobs.jobs.hasJob -import org.bukkit.OfflinePlayer -import org.bukkit.entity.Player - -internal object EcoJobsAPIImpl : EcoJobsAPI { - override fun hasJob(player: OfflinePlayer, job: Job) = player.hasJob(job) - - override fun getActiveJob(player: OfflinePlayer): Job? = player.activeJob - - override fun getJobLevel(player: OfflinePlayer, job: Job) = player.getJobLevel(job) - - override fun giveJobExperience(player: Player, job: Job, amount: Double) = - player.giveJobExperience(job, amount) - - override fun giveJobExperience(player: Player, job: Job, amount: Double, applyMultipliers: Boolean) = - player.giveJobExperience(job, amount, noMultiply = !applyMultipliers) - - override fun getJobProgress(player: OfflinePlayer, job: Job) = - player.getJobProgress(job) - - override fun getJobXPRequired(player: OfflinePlayer, job: Job) = - player.getJobXPRequired(job) - - override fun getJobXP(player: OfflinePlayer, job: Job) = - player.getJobXP(job) -} diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/api/EcoJobsAPI.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/api/EcoJobsAPI.kt index f3edfa2..ca65a26 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/api/EcoJobsAPI.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/api/EcoJobsAPI.kt @@ -1,117 +1,192 @@ +@file:JvmName("EcoJobsAPI") + package com.willfp.ecojobs.api -import com.willfp.ecojobs.EcoJobsAPIImpl +import com.willfp.eco.core.data.keys.PersistentDataKey +import com.willfp.eco.core.data.keys.PersistentDataKeyType +import com.willfp.eco.core.data.profile +import com.willfp.ecojobs.EcoJobsPlugin +import com.willfp.ecojobs.api.event.PlayerJobExpGainEvent +import com.willfp.ecojobs.api.event.PlayerJobJoinEvent +import com.willfp.ecojobs.api.event.PlayerJobLeaveEvent +import com.willfp.ecojobs.api.event.PlayerJobLevelUpEvent import com.willfp.ecojobs.jobs.Job +import com.willfp.ecojobs.jobs.Jobs +import com.willfp.ecojobs.jobs.getNumericalPermission +import com.willfp.ecojobs.jobs.jobExperienceMultiplier +import org.bukkit.Bukkit import org.bukkit.OfflinePlayer import org.bukkit.entity.Player +import kotlin.math.abs -interface EcoJobsAPI { - /** - * Get if a player has a job. - * - * @param player The player. - * @param job The job. - * @return If the player has the job unlocked. - */ - fun hasJob( - player: OfflinePlayer, - job: Job - ): Boolean +private val plugin = EcoJobsPlugin.instance - /** - * Get a player's active job. - * - * @param player The player. - * @return The active job. - */ - fun getActiveJob( - player: OfflinePlayer - ): Job? +/* - /** - * Get a player's level of a certain job. - * - * @param player The player. - * @param job The job. - * @return The level. - */ - fun getJobLevel( - player: OfflinePlayer, - job: Job - ): Int +The old key is around for backwards compatibility with 1.x.x. - /** - * Give job experience to a player. - * - * @param player The player. - * @param job The job. - * @param amount The amount of experience to give. - */ - fun giveJobExperience( - player: Player, - job: Job, - amount: Double - ) + */ - /** - * Give job experience to a player. - * - * @param player The player. - * @param job The job. - * @param amount The amount of experience to give. - * @param applyMultipliers If multipliers should be applied. - */ - fun giveJobExperience( - player: Player, - job: Job, - amount: Double, - applyMultipliers: Boolean - ) +private val legacyActiveJobKey: PersistentDataKey = PersistentDataKey( + plugin.namespacedKeyFactory.create("active_job"), PersistentDataKeyType.STRING, "" +) - /** - * Get progress to next level between 0 and 1, where 0 is none and 1 is complete. - * - * @param player The player. - * @param job The job. - * @return The progress. - */ - fun getJobProgress( - player: OfflinePlayer, - job: Job - ): Double +private val activeJobsKey: PersistentDataKey> = PersistentDataKey( + plugin.namespacedKeyFactory.create("active_jobs"), PersistentDataKeyType.STRING_LIST, listOf() +) - /** - * Get the experience required to advance to the next level. - * - * @param player The player. - * @param job The job. - * @return The experience required. - */ - fun getJobXPRequired( - player: OfflinePlayer, - job: Job - ): Int +/** + * The job limit. + */ +val Player.jobLimit: Int + get() { + return this.getNumericalPermission("ecojobs.limit", plugin.configYml.getDouble("jobs.limit")).toInt() + } - /** - * Get experience to the next level. - * - * @param player The player. - * @param job The job. - * @return The experience. - */ - fun getJobXP( - player: OfflinePlayer, - job: Job - ): Double +/** + * If a player can join a job. + */ +fun Player.canJoinJob(job: Job): Boolean { + return this.activeJobs.size < this.jobLimit && job !in this.activeJobs && this.hasJob(job) +} - companion object { - /** - * Get the instance of the API. - * - * @return The API. - */ - @JvmStatic - val instance: EcoJobsAPI - get() = EcoJobsAPIImpl +/** + * Get if a job is unlocked. + */ +fun OfflinePlayer.hasJob(job: Job) = this.getJobLevel(job) > 0 + +/** + * Get if a player has a job active. + */ +fun OfflinePlayer.hasJobActive(job: Job) = job in this.activeJobs + +/** + * Get a player's active jobs. + */ +val OfflinePlayer.activeJobs: Collection + get() { + if (this.profile.read(legacyActiveJobKey).isNotBlank()) { + this.profile.write(activeJobsKey, listOf(this.profile.read(legacyActiveJobKey))) + this.profile.write(legacyActiveJobKey, "") + } + + return this.profile.read(activeJobsKey).mapNotNull { Jobs.getByID(it) } + + } + +/** + * Join a job. + */ +fun OfflinePlayer.joinJob(job: Job) { + val event = PlayerJobJoinEvent(this, job) + Bukkit.getPluginManager().callEvent(event) + + if (!event.isCancelled) { + this.profile.write(activeJobsKey, this.activeJobs.plus(job).map { it.id }) + } +} + +/** + * Leave a job. + */ +fun OfflinePlayer.leaveJob(job: Job) { + if (job !in this.activeJobs) { + return + } + + val event = PlayerJobLeaveEvent(this, job) + Bukkit.getPluginManager().callEvent(event) + + if (!event.isCancelled) { + this.forceLeaveJob(job) + } +} + +/** + * Leave a job without checking. + */ +fun OfflinePlayer.forceLeaveJob(job: Job) { + this.profile.write(activeJobsKey, this.activeJobs.minus(job).map { it.id }) +} + +/** + * Get the level of a certain job. + */ +fun OfflinePlayer.getJobLevel(job: Job) = this.profile.read(job.levelKey) + +/** + * Set the level of a certain job. + */ +fun OfflinePlayer.setJobLevel(job: Job, level: Int) = this.profile.write(job.levelKey, level) + +/** + * Get current job experience. + */ +fun OfflinePlayer.getJobXP(job: Job) = this.profile.read(job.xpKey) + +/** + * Set current job experience. + */ +fun OfflinePlayer.setJobXP(job: Job, xp: Double) = this.profile.write(job.xpKey, xp) + +/** + * Reset a job. + */ +fun OfflinePlayer.resetJob(job: Job) { + this.setJobLevel(job, 1) + this.setJobXP(job, 0.0) +} + +/** + * Get the experience required to advance to the next level. + */ +fun OfflinePlayer.getJobXPRequired(job: Job) = job.getExpForLevel(this.getJobLevel(job) + 1) + +/** + * Get progress to next level between 0 and 1, where 0 is none and 1 is complete. + */ +fun OfflinePlayer.getJobProgress(job: Job): Double { + val currentXP = this.getJobXP(job) + val requiredXP = job.getExpForLevel(this.getJobLevel(job) + 1) + return currentXP / requiredXP +} + +/** + * Give job experience. + */ +@JvmOverloads +fun Player.giveJobExperience(job: Job, experience: Double, withMultipliers: Boolean = true) { + val exp = abs( + if (withMultipliers) experience * this.jobExperienceMultiplier + else experience + ) + + val gainEvent = PlayerJobExpGainEvent(this, job, exp, !withMultipliers) + Bukkit.getPluginManager().callEvent(gainEvent) + + if (gainEvent.isCancelled) { + return + } + + this.giveExactJobExperience(job, gainEvent.amount) +} + +/** + * Give exact job experience, without calling PlayerJobExpGainEvent. + */ +fun Player.giveExactJobExperience(job: Job, experience: Double) { + val level = this.getJobLevel(job) + + val progress = this.getJobXP(job) + experience + + if (progress >= job.getExpForLevel(level + 1) && level + 1 <= job.maxLevel) { + val overshoot = progress - job.getExpForLevel(level + 1) + this.setJobXP(job, 0.0) + this.setJobLevel(job, level + 1) + val levelUpEvent = PlayerJobLevelUpEvent(this, job, level + 1) + Bukkit.getPluginManager().callEvent(levelUpEvent) + this.giveExactJobExperience(job, overshoot) + } else { + this.setJobXP(job, progress) } } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/api/event/PlayerJobJoinEvent.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/api/event/PlayerJobJoinEvent.kt index 95ec4b1..a5b9a3a 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/api/event/PlayerJobJoinEvent.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/api/event/PlayerJobJoinEvent.kt @@ -8,8 +8,7 @@ import org.bukkit.event.HandlerList class PlayerJobJoinEvent( val player: OfflinePlayer, - override val job: Job, - val oldJob: Job? + override val job: Job ) : Event(), Cancellable, JobEvent { private var cancelled = false diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandGiveXP.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandGiveXP.kt index b19811f..8e43186 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandGiveXP.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandGiveXP.kt @@ -5,9 +5,9 @@ import com.willfp.eco.core.command.impl.Subcommand import com.willfp.eco.util.StringUtils import com.willfp.eco.util.savedDisplayName import com.willfp.eco.util.toNiceString +import com.willfp.ecojobs.api.giveExactJobExperience +import com.willfp.ecojobs.api.hasJob import com.willfp.ecojobs.jobs.Jobs -import com.willfp.ecojobs.jobs.giveExactJobExperience -import com.willfp.ecojobs.jobs.hasJob import org.bukkit.Bukkit import org.bukkit.command.CommandSender 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 8d1ffcf..239a6b9 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 @@ -2,9 +2,9 @@ package com.willfp.ecojobs.commands import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.command.impl.PluginCommand +import com.willfp.ecojobs.api.hasJob import com.willfp.ecojobs.jobs.Jobs import com.willfp.ecojobs.jobs.JobsGUI -import com.willfp.ecojobs.jobs.hasJob import org.bukkit.entity.Player import org.bukkit.util.StringUtil diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandJoin.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandJoin.kt index 4806df2..bff59dc 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandJoin.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandJoin.kt @@ -3,9 +3,11 @@ package com.willfp.ecojobs.commands import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.command.impl.Subcommand import com.willfp.eco.util.StringUtils +import com.willfp.ecojobs.api.canJoinJob +import com.willfp.ecojobs.api.hasJob +import com.willfp.ecojobs.api.hasJobActive +import com.willfp.ecojobs.api.joinJob import com.willfp.ecojobs.jobs.Jobs -import com.willfp.ecojobs.jobs.activeJob -import com.willfp.ecojobs.jobs.hasJob import org.bukkit.command.CommandSender import org.bukkit.entity.Player import org.bukkit.util.StringUtil @@ -28,12 +30,12 @@ class CommandJoin(plugin: EcoPlugin) : Subcommand(plugin, "join", "ecojobs.comma return } - if (player.activeJob == job) { + if (player.hasJobActive(job)) { player.sendMessage(plugin.langYml.getMessage("job-already-joined")) return } - if (player.activeJob != null) { + if (!player.canJoinJob(job)) { player.sendMessage(plugin.langYml.getMessage("leave-current-job")) return } @@ -42,7 +44,8 @@ class CommandJoin(plugin: EcoPlugin) : Subcommand(plugin, "join", "ecojobs.comma plugin.langYml.getMessage("joined-job", StringUtils.FormatOption.WITHOUT_PLACEHOLDERS) .replace("%job%", job.name) ) - player.activeJob = job + + player.joinJob(job) } override fun tabComplete(sender: CommandSender, args: List): List { @@ -51,6 +54,7 @@ class CommandJoin(plugin: EcoPlugin) : Subcommand(plugin, "join", "ecojobs.comma } val completions = mutableListOf() + if (args.isEmpty()) { return Jobs.values().filter { sender.hasJob(it) }.map { it.id } } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandLeave.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandLeave.kt index 9515d49..1ba4a73 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandLeave.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandLeave.kt @@ -2,24 +2,46 @@ package com.willfp.ecojobs.commands import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.command.impl.Subcommand -import com.willfp.ecojobs.jobs.activeJob +import com.willfp.ecojobs.api.activeJobs +import com.willfp.ecojobs.api.hasJob +import com.willfp.ecojobs.api.hasJobActive +import com.willfp.ecojobs.api.leaveJob +import com.willfp.ecojobs.jobs.Jobs import org.bukkit.command.CommandSender import org.bukkit.entity.Player +import org.bukkit.util.StringUtil class CommandLeave(plugin: EcoPlugin) : Subcommand(plugin, "leave", "ecojobs.command.leave", true) { override fun onExecute(player: CommandSender, args: List) { player as Player - if (player.activeJob == null) { + if (args.isEmpty()) { + player.sendMessage(plugin.langYml.getMessage("needs-job")) + return + } + + if (player.activeJobs.isEmpty()) { player.sendMessage(plugin.langYml.getMessage("no-job")) return } - val job = player.activeJob ?: return + val id = args[0] - player.activeJob = null + val job = Jobs.getByID(id) - if (player.activeJob == null) { + if (job == null || !player.hasJob(job)) { + player.sendMessage(plugin.langYml.getMessage("invalid-job")) + return + } + + if (!player.hasJobActive(job)) { + player.sendMessage(plugin.langYml.getMessage("not-in-job")) + return + } + + player.leaveJob(job) + + if (!player.hasJobActive(job)) { player.sendMessage( plugin.langYml.getMessage("left-job") .replace("%job%", job.name) @@ -31,4 +53,27 @@ class CommandLeave(plugin: EcoPlugin) : Subcommand(plugin, "leave", "ecojobs.com ) } } + + override fun tabComplete(sender: CommandSender, args: List): List { + if (sender !is Player) { + return emptyList() + } + + val completions = mutableListOf() + + if (args.isEmpty()) { + return Jobs.values().filter { sender.hasJobActive(it) }.map { it.id } + } + + if (args.size == 1) { + StringUtil.copyPartialMatches( + args[0], + Jobs.values().filter { sender.hasJobActive(it) }.map { it.id }, + completions + ) + return completions + } + + return emptyList() + } } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandReset.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandReset.kt index bb17bbe..a484c6f 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandReset.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandReset.kt @@ -4,10 +4,10 @@ import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.command.impl.Subcommand import com.willfp.eco.util.StringUtils import com.willfp.eco.util.savedDisplayName +import com.willfp.ecojobs.api.forceLeaveJob +import com.willfp.ecojobs.api.hasJob +import com.willfp.ecojobs.api.resetJob import com.willfp.ecojobs.jobs.Jobs -import com.willfp.ecojobs.jobs.activeJob -import com.willfp.ecojobs.jobs.hasJob -import com.willfp.ecojobs.jobs.resetJob import org.bukkit.Bukkit import org.bukkit.command.CommandSender @@ -44,9 +44,7 @@ class CommandReset(plugin: EcoPlugin) : Subcommand(plugin, "reset", "ecojobs.com return } - if (player.activeJob == job) { - player.activeJob = null - } + player.forceLeaveJob(job) player.resetJob(job) sender.sendMessage( diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandUnlock.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandUnlock.kt index f6579d7..29b448f 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandUnlock.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/commands/CommandUnlock.kt @@ -4,9 +4,9 @@ import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.command.impl.Subcommand import com.willfp.eco.util.StringUtils import com.willfp.eco.util.savedDisplayName +import com.willfp.ecojobs.api.hasJob +import com.willfp.ecojobs.api.setJobLevel import com.willfp.ecojobs.jobs.Jobs -import com.willfp.ecojobs.jobs.hasJob -import com.willfp.ecojobs.jobs.setJobLevel import org.bukkit.Bukkit import org.bukkit.command.CommandSender import org.bukkit.util.StringUtil 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 43e0b0b..063c8d9 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 @@ -16,10 +16,17 @@ import com.willfp.eco.util.NumberUtils import com.willfp.eco.util.formatEco import com.willfp.eco.util.toNiceString import com.willfp.ecojobs.EcoJobsPlugin +import com.willfp.ecojobs.api.activeJobs +import com.willfp.ecojobs.api.canJoinJob import com.willfp.ecojobs.api.event.PlayerJobExpGainEvent import com.willfp.ecojobs.api.event.PlayerJobJoinEvent import com.willfp.ecojobs.api.event.PlayerJobLeaveEvent import com.willfp.ecojobs.api.event.PlayerJobLevelUpEvent +import com.willfp.ecojobs.api.getJobLevel +import com.willfp.ecojobs.api.getJobProgress +import com.willfp.ecojobs.api.getJobXP +import com.willfp.ecojobs.api.getJobXPRequired +import com.willfp.ecojobs.api.jobLimit import com.willfp.libreforge.conditions.Conditions import com.willfp.libreforge.conditions.ConfiguredCondition import com.willfp.libreforge.effects.ConfiguredEffect @@ -30,14 +37,14 @@ import org.bukkit.Bukkit import org.bukkit.OfflinePlayer import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack +import java.util.DoubleSummaryStatistics import java.util.Objects import java.util.concurrent.TimeUnit import kotlin.math.abs +import kotlin.math.max class Job( - val id: String, - val config: Config, - private val plugin: EcoJobsPlugin + val id: String, val config: Config, private val plugin: EcoJobsPlugin ) { val name = config.getFormattedString("name") val description = config.getFormattedString("description") @@ -45,13 +52,11 @@ class Job( val resetsOnQuit = config.getBool("reset-on-quit") val joinPrice = ConfiguredPrice.create(config.getSubsection("join-price")) ?: ConfiguredPrice( - PriceEconomy(config.getDouble("join-price")), - "" + PriceEconomy(config.getDouble("join-price")), "" ) val leavePrice = ConfiguredPrice.create(config.getSubsection("leave-price")) ?: ConfiguredPrice( - PriceEconomy(config.getDouble("leave-price")), - "" + PriceEconomy(config.getDouble("leave-price")), "" ) val levelKey: PersistentDataKey = PersistentDataKey( @@ -61,9 +66,7 @@ class Job( ) val xpKey: PersistentDataKey = PersistentDataKey( - EcoJobsPlugin.instance.namespacedKeyFactory.create("${id}_xp"), - PersistentDataKeyType.DOUBLE, - 0.0 + EcoJobsPlugin.instance.namespacedKeyFactory.create("${id}_xp"), PersistentDataKeyType.DOUBLE, 0.0 ) private val levelXpRequirements = listOf(0) + config.getInts("level-xp-requirements") @@ -79,41 +82,33 @@ class Job( private val effects: Set private val conditions: Set - private val levels = Caffeine.newBuilder() - .build() - private val effectsDescription = Caffeine.newBuilder() - .build>() - private val rewardsDescription = Caffeine.newBuilder() - .build>() - private val levelUpMessages = Caffeine.newBuilder() - .build>() + private val levels = Caffeine.newBuilder().build() + private val effectsDescription = Caffeine.newBuilder().build>() + private val rewardsDescription = Caffeine.newBuilder().build>() + private val levelUpMessages = Caffeine.newBuilder().build>() private val levelCommands = mutableMapOf>() - private val levelPlaceholders = config.getSubsections("level-placeholders") - .map { sub -> - LevelPlaceholder( - sub.getString("id") - ) { - NumberUtils.evaluateExpression( - sub.getString("value") - .replace("%level%", it.toString()) - ).toNiceString() - } + private val levelPlaceholders = config.getSubsections("level-placeholders").map { sub -> + LevelPlaceholder( + sub.getString("id") + ) { + NumberUtils.evaluateExpression( + sub.getString("value").replace("%level%", it.toString()) + ).toNiceString() } + } private val jobXpGains = config.getSubsections("xp-gain-methods").mapNotNull { Counters.compile(it, "Job $id") } init { - config.injectPlaceholders( - PlayerStaticPlaceholder( - "level" - ) { p -> - p.getJobLevel(this).toString() - } - ) + config.injectPlaceholders(PlayerStaticPlaceholder( + "level" + ) { p -> + p.getJobLevel(this).toString() + }) effects = config.getSubsections("effects").mapNotNull { Effects.compile(it, "Job $id") @@ -143,52 +138,45 @@ class Job( } PlayerPlaceholder( - plugin, - "${id}_percentage_progress" + plugin, "${id}_percentage_progress" ) { (it.getJobProgress(this) * 100).toNiceString() }.register() PlayerPlaceholder( - plugin, - id + plugin, id ) { it.getJobLevel(this).toString() }.register() PlayerPlaceholder( - plugin, - "${id}_current_xp" + plugin, "${id}_current_xp" ) { NumberUtils.format(it.getJobXP(this)) }.register() PlayerPlaceholder( - plugin, - "${id}_required_xp" + plugin, "${id}_required_xp" ) { it.getJobXPRequired(this).toString() }.register() PlayerlessPlaceholder( - plugin, - "${id}_name" + plugin, "${id}_name" ) { this.name }.register() PlayerPlaceholder( - plugin, - "${id}_level" + plugin, "${id}_level" ) { it.getJobLevel(this).toString() }.register() PlayerPlaceholder( - plugin, - "${id}_total_players" + plugin, "${id}_total_players" ) { - Bukkit.getOfflinePlayers().count { it.activeJob == this }.toString() + Bukkit.getOfflinePlayers().count { this in it.activeJobs }.toString() }.register() } @@ -208,13 +196,11 @@ class Job( } } - this.config.getStrings("level-up-messages.$highestConfiguredLevel") - .map { - levelPlaceholders.format(it, level) - } - .map { - " ".repeat(whitespace) + it - } + this.config.getStrings("level-up-messages.$highestConfiguredLevel").map { + levelPlaceholders.format(it, level) + }.map { + " ".repeat(whitespace) + it + } } private fun getEffectsDescription(level: Int, whitespace: Int = 0): List = effectsDescription.get(level) { @@ -229,13 +215,11 @@ class Job( } } - this.config.getStrings("effects-description.$highestConfiguredLevel") - .map { - levelPlaceholders.format(it, level) - } - .map { - " ".repeat(whitespace) + it - } + this.config.getStrings("effects-description.$highestConfiguredLevel").map { + levelPlaceholders.format(it, level) + }.map { + " ".repeat(whitespace) + it + } } private fun getRewardsDescription(level: Int, whitespace: Int = 0): List = rewardsDescription.get(level) { @@ -250,53 +234,40 @@ class Job( } } - this.config.getStrings("rewards-description.$highestConfiguredLevel") - .map { - levelPlaceholders.format(it, level) - } - .map { - " ".repeat(whitespace) + it - } + this.config.getStrings("rewards-description.$highestConfiguredLevel").map { + levelPlaceholders.format(it, level) + }.map { + " ".repeat(whitespace) + it + } } - private fun getLeaveLore(level: Int, whitespace: Int = 0): List = - this.config.getStrings("leave-lore") - .map { - levelPlaceholders.format(it, level) - } - .map { - " ".repeat(whitespace) + it - } + private fun getLeaveLore(level: Int, whitespace: Int = 0): List = this.config.getStrings("leave-lore").map { + levelPlaceholders.format(it, level) + }.map { + " ".repeat(whitespace) + it + } - private fun getJoinLore(level: Int, whitespace: Int = 0): List = - this.config.getStrings("join-lore") - .map { - levelPlaceholders.format(it, level) - } - .map { - " ".repeat(whitespace) + it - } + private fun getJoinLore(level: Int, whitespace: Int = 0): List = this.config.getStrings("join-lore").map { + levelPlaceholders.format(it, level) + }.map { + " ".repeat(whitespace) + it + } fun injectPlaceholdersInto(lore: List, player: Player, forceLevel: Int? = null): List { - val withPlaceholders = lore - .map { - it.replace("%percentage_progress%", (player.getJobProgress(this) * 100).toNiceString()) - .replace("%current_xp%", player.getJobXP(this).toNiceString()) - .replace("%required_xp%", this.getExpForLevel(player.getJobLevel(this) + 1).let { req -> - if (req == Int.MAX_VALUE) { - plugin.langYml.getFormattedString("infinity") - } else { - req.toNiceString() - } + val withPlaceholders = lore.map { + it.replace("%percentage_progress%", (player.getJobProgress(this) * 100).toNiceString()) + .replace("%current_xp%", player.getJobXP(this).toNiceString()) + .replace("%required_xp%", this.getExpForLevel(player.getJobLevel(this) + 1).let { req -> + if (req == Int.MAX_VALUE) { + plugin.langYml.getFormattedString("infinity") + } else { + req.toNiceString() } - ) - .replace("%description%", this.description) - .replace("%job%", this.name) - .replace("%level%", (forceLevel ?: player.getJobLevel(this)).toString()) - .replace("%join_price%", this.joinPrice.getDisplay(player)) - .replace("%leave_price%", this.leavePrice.getDisplay(player)) - } - .toMutableList() + }).replace("%description%", this.description).replace("%job%", this.name) + .replace("%level%", (forceLevel ?: player.getJobLevel(this)).toString()) + .replace("%join_price%", this.joinPrice.getDisplay(player)) + .replace("%leave_price%", this.leavePrice.getDisplay(player)) + }.toMutableList() val processed = mutableListOf>() @@ -328,35 +299,30 @@ class Job( val level = player.getJobLevel(this) - return ItemStackBuilder(base) - .setDisplayName( - plugin.configYml.getFormattedString("gui.job-icon.name") - .replace("%level%", level.toString()) - .replace("%job%", this.name) - ) - .addLoreLines { - injectPlaceholdersInto(plugin.configYml.getStrings("gui.job-icon.lore"), player) + - when (player.activeJob) { - this -> plugin.configYml.getStrings("gui.job-icon.active-lore") - null -> plugin.configYml.getStrings("gui.job-icon.join-lore") - else -> emptyList() - } + return ItemStackBuilder(base).setDisplayName( + plugin.configYml.getFormattedString("gui.job-icon.name").replace("%level%", level.toString()) + .replace("%job%", this.name) + ).addLoreLines { + injectPlaceholdersInto( + plugin.configYml.getStrings("gui.job-icon.lore"), player + ) + if (player.canJoinJob(this)) { + plugin.configYml.getStrings("gui.job-icon.join-lore") + } else if (player.activeJobs.size == player.jobLimit) { + plugin.configYml.getStrings("gui.job-icon.too-many-jobs-lore") + } else { + emptyList() } - .build() + }.build() } fun getJobInfoIcon(player: Player): ItemStack { val base = baseItem.clone() - return ItemStackBuilder(base) - .setDisplayName( - plugin.configYml.getFormattedString("gui.job-info.active.name") - .replace("%level%", player.getJobLevel(this).toString()) - .replace("%job%", this.name) - ) - .addLoreLines { - injectPlaceholdersInto(plugin.configYml.getStrings("gui.job-info.active.lore"), player) - } - .build() + return ItemStackBuilder(base).setDisplayName( + plugin.configYml.getFormattedString("gui.job-info.active.name") + .replace("%level%", player.getJobLevel(this).toString()).replace("%job%", this.name) + ).addLoreLines { + injectPlaceholdersInto(plugin.configYml.getStrings("gui.job-info.active.lore"), player) + }.build() } fun getExpForLevel(level: Int): Int { @@ -393,8 +359,7 @@ class Job( } private class LevelPlaceholder( - val id: String, - private val function: (Int) -> String + val id: String, private val function: (Int) -> String ) { operator fun invoke(level: Int) = function(level) } @@ -407,86 +372,12 @@ private fun Collection.format(string: String, level: Int): Str return process } -private val activeJobKey: PersistentDataKey = PersistentDataKey( - EcoJobsPlugin.instance.namespacedKeyFactory.create("active_job"), - PersistentDataKeyType.STRING, - "" -) +fun OfflinePlayer.getJobLevelObject(job: Job): JobLevel = job.getLevel(this.getJobLevel(job)) -var OfflinePlayer.activeJob: Job? - get() = Jobs.getByID(this.profile.read(activeJobKey)) - set(job) { - val oldJob = this.activeJob - - if (oldJob != job) { - // Have to check for oldJob too to have null safety - if (job == null && oldJob != null) { - val event = PlayerJobLeaveEvent(this, oldJob) - Bukkit.getPluginManager().callEvent(event) - - if (event.isCancelled) { - return - } - } - - // Not using else because null safety as well - if (job != null) { - val event = PlayerJobJoinEvent(this, job, oldJob) - Bukkit.getPluginManager().callEvent(event) - - if (event.isCancelled) { - return - } - } - } - - this.profile.write(activeJobKey, job?.id ?: "") - } - -val OfflinePlayer.activeJobLevel: JobLevel? - get() { - val active = this.activeJob ?: return null - return this.getJobLevelObject(active) - } - -fun OfflinePlayer.getJobLevel(job: Job): Int = - this.profile.read(job.levelKey) - -fun OfflinePlayer.setJobLevel(job: Job, level: Int) = - this.profile.write(job.levelKey, level) - -fun OfflinePlayer.resetJob(job: Job) { - this.setJobLevel(job, 1) - this.setJobXP(job, 0.0) +private val expMultiplierCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build { + it.cacheJobExperienceMultiplier() } -fun OfflinePlayer.getJobProgress(job: Job): Double { - val currentXP = this.getJobXP(job) - val requiredXP = job.getExpForLevel(this.getJobLevel(job) + 1) - return currentXP / requiredXP -} - -fun OfflinePlayer.getJobLevelObject(job: Job): JobLevel = - job.getLevel(this.getJobLevel(job)) - -fun OfflinePlayer.hasJob(job: Job): Boolean = - this.getJobLevel(job) > 0 - -fun OfflinePlayer.getJobXP(job: Job): Double = - this.profile.read(job.xpKey) - -fun OfflinePlayer.setJobXP(job: Job, xp: Double) = - this.profile.write(job.xpKey, xp) - -fun OfflinePlayer.getJobXPRequired(job: Job): Int = - job.getExpForLevel(this.getJobLevel(job) + 1) - -private val expMultiplierCache = Caffeine.newBuilder() - .expireAfterWrite(10, TimeUnit.SECONDS) - .build { - it.cacheJobExperienceMultiplier() - } - val Player.jobExperienceMultiplier: Double get() = expMultiplierCache.get(this) @@ -507,43 +398,19 @@ private fun Player.cacheJobExperienceMultiplier(): Double { return 1.5 } - val prefix = "ecojobs.xpmultiplier." + return 1 + getNumericalPermission("ecojobs.xpmultiplier", 0.0) / 100 +} + +fun Player.getNumericalPermission(permission: String, default: Double): Double { + var highest: Double? = null + for (permissionAttachmentInfo in this.effectivePermissions) { - val permission = permissionAttachmentInfo.permission - if (permission.startsWith(prefix)) { - return ((permission.substring(permission.lastIndexOf(".") + 1).toDoubleOrNull() ?: 100.0) / 100) + 1 + val perm = permissionAttachmentInfo.permission + if (perm.startsWith(permission)) { + val found = perm.substring(perm.lastIndexOf(".") + 1).toDoubleOrNull() ?: continue + highest = max(highest ?: Double.MIN_VALUE, found) } } - return 1.0 -} - -fun Player.giveJobExperience(job: Job, experience: Double, noMultiply: Boolean = false) { - val exp = abs(if (noMultiply) experience else experience * this.jobExperienceMultiplier) - - val gainEvent = PlayerJobExpGainEvent(this, job, exp, !noMultiply) - Bukkit.getPluginManager().callEvent(gainEvent) - - if (gainEvent.isCancelled) { - return - } - - this.giveExactJobExperience(job, gainEvent.amount) -} - -fun Player.giveExactJobExperience(job: Job, experience: Double) { - val level = this.getJobLevel(job) - - val progress = this.getJobXP(job) + experience - - if (progress >= job.getExpForLevel(level + 1) && level + 1 <= job.maxLevel) { - val overshoot = progress - job.getExpForLevel(level + 1) - this.setJobXP(job, 0.0) - this.setJobLevel(job, level + 1) - val levelUpEvent = PlayerJobLevelUpEvent(this, job, level + 1) - Bukkit.getPluginManager().callEvent(levelUpEvent) - this.giveExactJobExperience(job, overshoot) - } else { - this.setJobXP(job, progress) - } + return highest ?: default } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobLeaveGUI.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobLeaveGUI.kt index f947f74..7e743bc 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobLeaveGUI.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobLeaveGUI.kt @@ -9,6 +9,8 @@ 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.util.formatEco +import com.willfp.ecojobs.api.hasJobActive +import com.willfp.ecojobs.api.leaveJob import org.bukkit.entity.Player class JobLeaveGUI( @@ -59,9 +61,9 @@ class JobLeaveGUI( .build() }) { onLeftClick { player, _, _, _ -> - player.activeJob = null + player.leaveJob(job) - if (player.activeJob == null) { + if (!player.hasJobActive(job)) { player.sendMessage( plugin.langYml.getMessage("left-job") .replace("%job%", job.name) diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobLevelGUI.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobLevelGUI.kt index 360cea1..63f7d19 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobLevelGUI.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobLevelGUI.kt @@ -13,6 +13,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.util.NumberUtils +import com.willfp.ecojobs.api.getJobLevel import com.willfp.ecomponent.components.LevelComponent import com.willfp.ecomponent.components.LevelState import org.bukkit.entity.Player diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobTriggerXPGainListener.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobTriggerXPGainListener.kt index daf14d0..0af2c2a 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobTriggerXPGainListener.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobTriggerXPGainListener.kt @@ -1,5 +1,7 @@ package com.willfp.ecojobs.jobs +import com.willfp.ecojobs.api.activeJobs +import com.willfp.ecojobs.api.giveJobExperience import com.willfp.libreforge.events.TriggerPreProcessEvent import org.bukkit.event.EventHandler import org.bukkit.event.Listener @@ -9,14 +11,16 @@ object JobTriggerXPGainListener : Listener { fun handle(event: TriggerPreProcessEvent) { val player = event.player - val job = event.player.activeJob ?: return + val jobs = event.player.activeJobs - val amount = job.getXP(event) + for (job in jobs) { + val amount = job.getXP(event) - if (amount <= 0.0) { - return + if (amount <= 0.0) { + return + } + + player.giveJobExperience(job, amount) } - - player.giveJobExperience(job, amount) } } 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 c191a7e..320354b 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 @@ -5,6 +5,7 @@ import com.google.common.collect.HashBiMap import com.google.common.collect.ImmutableList import com.willfp.eco.core.config.updating.ConfigUpdater import com.willfp.ecojobs.EcoJobsPlugin +import com.willfp.ecojobs.api.getJobLevel import org.bukkit.OfflinePlayer object Jobs { diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobsGUI.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobsGUI.kt index 4c9a19a..fe2e3a3 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobsGUI.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobsGUI.kt @@ -11,6 +11,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.ecojobs.EcoJobsPlugin +import com.willfp.ecojobs.api.getJobLevel import com.willfp.ecojobs.jobs.Jobs.unlockedJobs import org.bukkit.Material import org.bukkit.Sound diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/ResetOnQuitListener.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/ResetOnQuitListener.kt index 083307b..683d6f1 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/ResetOnQuitListener.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/ResetOnQuitListener.kt @@ -1,6 +1,7 @@ package com.willfp.ecojobs.jobs import com.willfp.ecojobs.api.event.PlayerJobLeaveEvent +import com.willfp.ecojobs.api.resetJob import org.bukkit.event.EventHandler import org.bukkit.event.Listener diff --git a/eco-core/core-plugin/src/main/resources/config.yml b/eco-core/core-plugin/src/main/resources/config.yml index 4127ec6..e1152d4 100644 --- a/eco-core/core-plugin/src/main/resources/config.yml +++ b/eco-core/core-plugin/src/main/resources/config.yml @@ -3,6 +3,10 @@ # by Auxilor # +jobs: + limit: 3 # The most jobs a player can have at once. + # You can set custom limits with the ecojobs.limit. permission + gui: rows: 6 @@ -80,6 +84,10 @@ gui: - "" - "&cYou've already joined this job!" + too-many-jobs-lore: + - "" + - "&cYou have too many jobs already!" + join-lore: - "" - "&eClick to join this job!" diff --git a/eco-core/core-plugin/src/main/resources/lang.yml b/eco-core/core-plugin/src/main/resources/lang.yml index bb82eb7..9d1007e 100644 --- a/eco-core/core-plugin/src/main/resources/lang.yml +++ b/eco-core/core-plugin/src/main/resources/lang.yml @@ -36,6 +36,7 @@ messages: leave-current-job: "&cYou must leave your current job before joining a new one!" cant-leave-job: "&cYou can't leave the %job%&f job!" dont-have-job: "&cYou don't have this job unlocked!" + not-in-job: "&cYou are not in this job!" menu: title: "Jobs"