@@ -16,33 +16,39 @@ import com.willfp.eco.core.recipe.Recipes
1616import com.willfp.eco.core.recipe.parts.EmptyTestableItem
1717import com.willfp.eco.core.registry.Registrable
1818import com.willfp.eco.util.NumberUtils
19+ import com.willfp.eco.util.NumberUtils.evaluateExpression
20+ import com.willfp.eco.core.placeholder.context.placeholderContext
1921import com.willfp.eco.util.formatEco
2022import com.willfp.eco.util.toNiceString
2123import com.willfp.ecopets.EcoPetsPlugin
2224import com.willfp.ecopets.api.event.PlayerPetExpGainEvent
2325import com.willfp.ecopets.api.event.PlayerPetLevelUpEvent
2426import com.willfp.ecopets.pets.entity.PetEntity
27+ import com.willfp.ecopets.util.LevelInjectable
2528import com.willfp.libreforge.ViolationContext
2629import com.willfp.libreforge.conditions.ConditionList
2730import com.willfp.libreforge.conditions.Conditions
2831import com.willfp.libreforge.counters.Counters
2932import com.willfp.libreforge.effects.EffectList
3033import com.willfp.libreforge.effects.Effects
34+ import com.willfp.libreforge.effects.executors.impl.NormalExecutorFactory
3135import org.bukkit.Bukkit
3236import org.bukkit.OfflinePlayer
37+ import org.bukkit.configuration.InvalidConfigurationException
3338import org.bukkit.entity.Player
3439import org.bukkit.inventory.ItemStack
3540import org.bukkit.persistence.PersistentDataType
3641import java.util.Objects
37- import java.util.concurrent.TimeUnit
3842import kotlin.math.abs
3943
4044class Pet (
4145 val id : String ,
4246 val config : Config ,
4347 private val plugin : EcoPetsPlugin
4448) : Registrable {
49+
4550 val name = config.getFormattedString(" name" )
51+
4652 val description = config.getFormattedString(" description" )
4753
4854 val levelKey: PersistentDataKey <Int > = PersistentDataKey (
@@ -52,9 +58,7 @@ class Pet(
5258 )
5359
5460 val xpKey: PersistentDataKey <Double > = PersistentDataKey (
55- EcoPetsPlugin .instance.namespacedKeyFactory.create(" ${id} _xp" ),
56- PersistentDataKeyType .DOUBLE ,
57- 0.0
61+ EcoPetsPlugin .instance.namespacedKeyFactory.create(" ${id} _xp" ), PersistentDataKeyType .DOUBLE , 0.0
5862 )
5963
6064 private val spawnEggBacker: ItemStack ? = run {
@@ -115,25 +119,27 @@ class Pet(
115119
116120 val entityTexture = config.getString(" entity-texture" )
117121
118- private val levelXpRequirements = listOf (0 ) + config.getInts(" level-xp-requirements" )
122+ private val xpFormula = config.getStringOrNull(" xp-formula" )
123+
124+ private val levelXpRequirements = config.getDoublesOrNull(" level-xp-requirements" )
119125
120- val maxLevel = levelXpRequirements.size
126+ val maxLevel = config.getIntOrNull( " max-level " ) ? : levelXpRequirements? .size ? : Int . MAX_VALUE
121127
122128 val levelGUI = PetLevelGUI (plugin, this )
123129
124130 private val baseItem: ItemStack = Items .lookup(config.getString(" icon" )).item
125131
126132 private val effects: EffectList
133+
127134 private val conditions: ConditionList
128135
129- private val levels = Caffeine .newBuilder()
130- .build<Int , PetLevel >()
131- private val effectsDescription = Caffeine .newBuilder()
132- .build<Int , List <String >>()
133- private val rewardsDescription = Caffeine .newBuilder()
134- .build<Int , List <String >>()
135- private val levelUpMessages = Caffeine .newBuilder()
136- .build<Int , List <String >>()
136+ private val levels = Caffeine .newBuilder().build<Int , PetLevel >()
137+
138+ private val effectsDescription = Caffeine .newBuilder().build<Int , List <String >>()
139+
140+ private val rewardsDescription = Caffeine .newBuilder().build<Int , List <String >>()
141+
142+ private val levelUpMessages = Caffeine .newBuilder().build<Int , List <String >>()
137143
138144 private val levelCommands = mutableMapOf<Int , MutableList <String >>()
139145
@@ -150,13 +156,14 @@ class Pet(
150156 }
151157
152158 private val petXpGains = config.getSubsections(" xp-gain-methods" ).mapNotNull {
153- Counters .compile(
154- it,
155- ViolationContext (plugin, " Pet $id XP Gain methods" )
156- )
159+ Counters .compile(it, ViolationContext (plugin, " Pet $id " ))
157160 }
158161
159162 init {
163+ if (xpFormula == null && levelXpRequirements == null ) {
164+ throw InvalidConfigurationException (" Pet $id has no requirements or xp formula" )
165+ }
166+
160167 config.injectPlaceholders(
161168 PlayerStaticPlaceholder (
162169 " level"
@@ -175,24 +182,7 @@ class Pet(
175182 ViolationContext (plugin, " Pet $id " )
176183 )
177184
178- for (string in config.getStrings(" level-commands" )) {
179- val split = string.split(" :" )
180-
181- if (split.size == 1 ) {
182- for (level in 1 .. maxLevel) {
183- val commands = levelCommands[level] ? : mutableListOf ()
184- commands.add(string)
185- levelCommands[level] = commands
186- }
187- } else {
188- val level = split[0 ].toInt()
189-
190- val command = string.removePrefix(" $level :" )
191- val commands = levelCommands[level] ? : mutableListOf ()
192- commands.add(command)
193- levelCommands[level] = commands
194- }
195- }
185+ manageLevelCommands(config)
196186
197187 PlayerPlaceholder (
198188 plugin,
@@ -237,6 +227,37 @@ class Pet(
237227 }.register()
238228 }
239229
230+ @Deprecated(" Use level-up-effects instead" )
231+ private fun manageLevelCommands (config : Config ) {
232+ if (config.getStrings(" level-commands" ).isNotEmpty()) {
233+ plugin.logger.warning(" $id pet: The `level-commands` key is deprecated and will be removed in future versions. Switch to `level-up-effects` instead. Refer to the wiki for more info." )
234+ }
235+ for (string in config.getStrings(" level-commands" )) {
236+ val split = string.split(" :" )
237+
238+ if (split.size == 1 ) {
239+ for (level in 1 .. maxLevel) {
240+ val commands = levelCommands[level] ? : mutableListOf ()
241+ commands.add(string)
242+ levelCommands[level] = commands
243+ }
244+ } else {
245+ val level = split[0 ].toInt()
246+
247+ val command = string.removePrefix(" $level :" )
248+ val commands = levelCommands[level] ? : mutableListOf ()
249+ commands.add(command)
250+ levelCommands[level] = commands
251+ }
252+ }
253+ }
254+
255+ val levelUpEffects = Effects .compileChain(
256+ config.getSubsections(" level-up-effects" ),
257+ NormalExecutorFactory .create(),
258+ ViolationContext (plugin, " Job $id level-up-effects" )
259+ )
260+
240261 fun makePetEntity (): PetEntity {
241262 return PetEntity .create(this )
242263 }
@@ -313,14 +334,7 @@ class Pet(
313334 .map {
314335 it.replace(" %percentage_progress%" , (player.getPetProgress(this ) * 100 ).toNiceString())
315336 .replace(" %current_xp%" , player.getPetXP(this ).toNiceString())
316- .replace(" %required_xp%" , this .getExpForLevel(player.getPetLevel(this ) + 1 ).let { req ->
317- if (req == Int .MAX_VALUE ) {
318- plugin.langYml.getFormattedString(" infinity" )
319- } else {
320- req.toNiceString()
321- }
322- }
323- )
337+ .replace(" %required_xp%" , this .getFormattedExpForLevel(player.getPetLevel(this ) + 1 ))
324338 .replace(" %description%" , this .description)
325339 .replace(" %pet%" , this .name)
326340 .replace(" %level%" , (forceLevel ? : player.getPetLevel(this )).toString())
@@ -395,14 +409,36 @@ class Pet(
395409 .build()
396410 }
397411
398- fun getExpForLevel (level : Int ): Int {
399- if (level < 1 || level > maxLevel) {
400- return Int .MAX_VALUE
412+ /* *
413+ * Get the XP required to reach the next level, if currently at [level].
414+ */
415+ fun getExpForLevel (level : Int ): Double {
416+ if (xpFormula != null ) {
417+ return evaluateExpression(
418+ xpFormula,
419+ placeholderContext(
420+ injectable = LevelInjectable (level)
421+ )
422+ )
401423 }
402424
403- return levelXpRequirements[level - 1 ]
425+ if (levelXpRequirements != null ) {
426+ return levelXpRequirements.getOrNull(level) ? : Double .POSITIVE_INFINITY
427+ }
428+
429+ return Double .POSITIVE_INFINITY
404430 }
405431
432+ fun getFormattedExpForLevel (level : Int ): String {
433+ val required = getExpForLevel(level)
434+ return if (required.isInfinite()) {
435+ plugin.langYml.getFormattedString(" infinity" )
436+ } else {
437+ required.toNiceString()
438+ }
439+ }
440+
441+ @Deprecated(" Use level-up-effects instead" )
406442 fun executeLevelCommands (player : Player , level : Int ) {
407443 val commands = levelCommands[level] ? : emptyList()
408444
@@ -492,45 +528,8 @@ fun OfflinePlayer.getPetXP(pet: Pet): Double =
492528fun OfflinePlayer.setPetXP (pet : Pet , xp : Double ) =
493529 this .profile.write(pet.xpKey, xp)
494530
495- fun OfflinePlayer.getPetXPRequired (pet : Pet ): Int =
496- pet.getExpForLevel(this .getPetLevel(pet) + 1 )
497-
498- private val expMultiplierCache = Caffeine .newBuilder()
499- .expireAfterWrite(10 , TimeUnit .SECONDS )
500- .build<Player , Double > {
501- it.cacheSkillExperienceMultiplier()
502- }
503-
504- val Player .petExperienceMultiplier: Double
505- get() = expMultiplierCache.get(this )!!
506-
507- private fun Player.cacheSkillExperienceMultiplier (): Double {
508- if (this .hasPermission(" ecopets.xpmultiplier.quadruple" )) {
509- return 4.0
510- }
511-
512- if (this .hasPermission(" ecopets.xpmultiplier.triple" )) {
513- return 3.0
514- }
515-
516- if (this .hasPermission(" ecopets.xpmultiplier.double" )) {
517- return 2.0
518- }
519-
520- if (this .hasPermission(" ecopets.xpmultiplier.50percent" )) {
521- return 1.5
522- }
523-
524- val prefix = " ecopets.xpmultiplier."
525- for (permissionAttachmentInfo in this .effectivePermissions) {
526- val permission = permissionAttachmentInfo.permission
527- if (permission.startsWith(prefix)) {
528- return ((permission.substring(permission.lastIndexOf(" ." ) + 1 ).toDoubleOrNull() ? : 100.0 ) / 100 ) + 1
529- }
530- }
531-
532- return 1.0
533- }
531+ fun OfflinePlayer.getPetXPRequired (pet : Pet ) =
532+ this .profile.read(pet.xpKey)
534533
535534fun Player.givePetExperience (pet : Pet , experience : Double , noMultiply : Boolean = false) {
536535 val exp = abs(if (noMultiply) experience else experience * this .petExperienceMultiplier)
0 commit comments