diff --git a/build.gradle.kts b/build.gradle.kts index c97d0d0..e62cec2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,7 @@ allprojects { } dependencies { - compileOnly("com.willfp:eco:6.55.0") + compileOnly("com.willfp:eco:6.56.0") compileOnly("org.jetbrains:annotations:23.0.0") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.0") } 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 ca65a26..46371c2 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 @@ -140,7 +140,7 @@ fun OfflinePlayer.resetJob(job: Job) { /** * Get the experience required to advance to the next level. */ -fun OfflinePlayer.getJobXPRequired(job: Job) = job.getExpForLevel(this.getJobLevel(job) + 1) +fun OfflinePlayer.getJobXPRequired(job: Job) = job.getFormattedExpForLevel(this.getJobLevel(job) + 1) /** * Get progress to next level between 0 and 1, where 0 is none and 1 is complete. 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 775628d..752f8b9 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 @@ -9,10 +9,12 @@ import com.willfp.eco.core.items.builder.ItemStackBuilder import com.willfp.eco.core.placeholder.PlayerPlaceholder import com.willfp.eco.core.placeholder.PlayerStaticPlaceholder import com.willfp.eco.core.placeholder.PlayerlessPlaceholder +import com.willfp.eco.core.placeholder.context.placeholderContext import com.willfp.eco.core.price.ConfiguredPrice import com.willfp.eco.core.price.impl.PriceEconomy import com.willfp.eco.core.registry.Registrable 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.ecojobs.EcoJobsPlugin @@ -24,6 +26,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.LevelInjectable import com.willfp.libreforge.ViolationContext import com.willfp.libreforge.conditions.ConditionList import com.willfp.libreforge.conditions.Conditions @@ -32,12 +35,11 @@ import com.willfp.libreforge.effects.EffectList import com.willfp.libreforge.effects.Effects import org.bukkit.Bukkit import org.bukkit.OfflinePlayer +import org.bukkit.configuration.InvalidConfigurationException import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import java.time.Duration import java.util.Objects -import java.util.concurrent.TimeUnit -import kotlin.math.max class Job( val id: String, @@ -49,8 +51,11 @@ class Job( .build() val name = config.getFormattedString("name") + val description = config.getFormattedString("description") + val isUnlockedByDefault = config.getBool("unlocked-by-default") + val resetsOnQuit = config.getBool("reset-on-quit") val joinPrice = ConfiguredPrice.create(config.getSubsection("join-price")) ?: ConfiguredPrice( @@ -71,9 +76,11 @@ class Job( EcoJobsPlugin.instance.namespacedKeyFactory.create("${id}_xp"), PersistentDataKeyType.DOUBLE, 0.0 ) - private val levelXpRequirements = listOf(0) + config.getInts("level-xp-requirements") + private val xpFormula = config.getStringOrNull("xp-formula") - val maxLevel = levelXpRequirements.size + private val levelXpRequirements = config.getDoublesOrNull("level-xp-requirements") + + val maxLevel = config.getIntOrNull("max-level") ?: levelXpRequirements?.size ?: Int.MAX_VALUE val levelGUI = JobLevelGUI(plugin, this) @@ -106,6 +113,10 @@ class Job( } init { + if (xpFormula == null && levelXpRequirements == null) { + throw InvalidConfigurationException("Skill $id has no requirements or xp formula") + } + config.injectPlaceholders(PlayerStaticPlaceholder( "level" ) { p -> @@ -275,13 +286,8 @@ class Job( 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("%required_xp%", this.getFormattedExpForLevel(player.getJobLevel(this) + 1)) + .replace("%description%", this.description).replace("%job%", this.name) .replace("%level%", (forceLevel ?: player.getJobLevel(this)).toString()) .replace("%level_numeral%", NumberUtils.toNumeral(forceLevel ?: player.getJobLevel(this))) .replace("%join_price%", this.joinPrice.getDisplay(player)) @@ -347,12 +353,33 @@ class Job( }.build() } - fun getExpForLevel(level: Int): Int { - if (level < 1 || level > maxLevel) { - return Int.MAX_VALUE + /** + * Get the XP required to reach the next level, if currently at [level]. + */ + fun getExpForLevel(level: Int): Double { + if (xpFormula != null) { + return evaluateExpression( + xpFormula, + placeholderContext( + injectable = LevelInjectable(level) + ) + ) } - return levelXpRequirements[level - 1] + if (levelXpRequirements != null) { + return levelXpRequirements.getOrNull(level) ?: Double.POSITIVE_INFINITY + } + + return Double.POSITIVE_INFINITY + } + + fun getFormattedExpForLevel(level: Int): String { + val required = getExpForLevel(level) + return if (required.isInfinite()) { + plugin.langYml.getFormattedString("infinity") + } else { + required.toNiceString() + } } fun executeLevelCommands(player: Player, level: Int) { @@ -409,43 +436,3 @@ private fun Collection.format(string: String, level: Int): Str fun OfflinePlayer.getJobLevelObject(job: Job): JobLevel = job.getLevel(this.getJobLevel(job)) -private val expMultiplierCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build { - it.cacheJobExperienceMultiplier() -} - -val Player.jobExperienceMultiplier: Double - get() = expMultiplierCache.get(this) - -private fun Player.cacheJobExperienceMultiplier(): Double { - if (this.hasPermission("ecojobs.xpmultiplier.quadruple")) { - return 4.0 - } - - if (this.hasPermission("ecojobs.xpmultiplier.triple")) { - return 3.0 - } - - if (this.hasPermission("ecojobs.xpmultiplier.double")) { - return 2.0 - } - - if (this.hasPermission("ecojobs.xpmultiplier.50percent")) { - return 1.5 - } - - 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 perm = permissionAttachmentInfo.permission - if (perm.startsWith(permission)) { - val found = perm.substring(perm.lastIndexOf(".") + 1).toDoubleOrNull() ?: continue - highest = max(highest ?: Double.MIN_VALUE, found) - } - } - - return highest ?: default -} diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobXPAccumulator.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobXPAccumulator.kt index cf2cd6a..47e5e47 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobXPAccumulator.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/jobs/JobXPAccumulator.kt @@ -1,9 +1,12 @@ package com.willfp.ecojobs.jobs +import com.github.benmanes.caffeine.cache.Caffeine import com.willfp.ecojobs.api.giveJobExperience import com.willfp.ecojobs.api.hasJobActive import com.willfp.libreforge.counters.Accumulator import org.bukkit.entity.Player +import java.util.concurrent.TimeUnit +import kotlin.math.max class JobXPAccumulator( private val job: Job @@ -16,3 +19,44 @@ class JobXPAccumulator( player.giveJobExperience(job, count) } } + +private val expMultiplierCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build { + it.cacheJobExperienceMultiplier() +} + +val Player.jobExperienceMultiplier: Double + get() = expMultiplierCache.get(this) + +private fun Player.cacheJobExperienceMultiplier(): Double { + if (this.hasPermission("ecojobs.xpmultiplier.quadruple")) { + return 4.0 + } + + if (this.hasPermission("ecojobs.xpmultiplier.triple")) { + return 3.0 + } + + if (this.hasPermission("ecojobs.xpmultiplier.double")) { + return 2.0 + } + + if (this.hasPermission("ecojobs.xpmultiplier.50percent")) { + return 1.5 + } + + 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 perm = permissionAttachmentInfo.permission + if (perm.startsWith(permission)) { + val found = perm.substring(perm.lastIndexOf(".") + 1).toDoubleOrNull() ?: continue + highest = max(highest ?: Double.MIN_VALUE, found) + } + } + + return highest ?: default +} diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/util/LevelInjectable.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/util/LevelInjectable.kt new file mode 100644 index 0000000..606607c --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/ecojobs/util/LevelInjectable.kt @@ -0,0 +1,27 @@ +package com.willfp.ecojobs.util + +import com.willfp.eco.core.placeholder.InjectablePlaceholder +import com.willfp.eco.core.placeholder.PlaceholderInjectable +import com.willfp.eco.core.placeholder.StaticPlaceholder + +class LevelInjectable( + level: Int +) : PlaceholderInjectable { + private val placeholders = listOf( + StaticPlaceholder( + "level" + ) { level.toString() } + ) + + override fun getPlaceholderInjections(): List { + return placeholders + } + + override fun addInjectablePlaceholder(p0: Iterable) { + return + } + + override fun clearInjectedPlaceholders() { + return + } +} diff --git a/eco-core/core-plugin/src/main/resources/jobs/_example.yml b/eco-core/core-plugin/src/main/resources/jobs/_example.yml index cfb11c5..b37ec7e 100644 --- a/eco-core/core-plugin/src/main/resources/jobs/_example.yml +++ b/eco-core/core-plugin/src/main/resources/jobs/_example.yml @@ -39,7 +39,15 @@ leave-price: leave-lore: - " &8ยป This will cost %leave_price%" -# The xp requirements for each job level - add new levels by adding more to this list +# There are two ways to specify level XP requirements: +# 1. A formula to calculate for infinite levels +# 2. A list of XP requirements for each level + +# Formula +# xp-formula: (2 ^ %level%) * 25 +# max-level: 100 # The max level of the job + +# List level-xp-requirements: - 100 - 120