summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHiZe <super@hize.be>2024-02-24 17:25:08 +0100
committerGitHub <noreply@github.com>2024-02-24 17:25:08 +0100
commit8dfbcf2a67f0a8207e49d8a97e18c4e80f8fbb96 (patch)
tree786c4cf47b06f7ac2990e2a62967492d0a5511a5 /src
parentbc239065f94548814717e2d07588696ee261c1e3 (diff)
downloadskyhanni-8dfbcf2a67f0a8207e49d8a97e18c4e80f8fbb96.tar.gz
skyhanni-8dfbcf2a67f0a8207e49d8a97e18c4e80f8fbb96.tar.bz2
skyhanni-8dfbcf2a67f0a8207e49d8a97e18c4e80f8fbb96.zip
Feature: Skill progress display (#957)
Co-authored-by: Thunderblade73 <gaidermarkus@gmail.com> Co-authored-by: superhize <superhize@gmail.com> Co-authored-by: Cal <cwolfson58@gmail.com> Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
Diffstat (limited to 'src')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/api/SkillAPI.kt449
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/Features.java5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/Storage.java5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt31
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/skillprogress/AllSkillDisplayConfig.java37
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/skillprogress/CustomGoalConfig.java39
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/skillprogress/SkillETADisplayConfig.java44
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/skillprogress/SkillOverflowConfig.java46
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/skillprogress/SkillProgressBarConfig.java107
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/skillprogress/SkillProgressConfig.java117
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/ActionBarData.kt7
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/ActionBarUpdateEvent.kt9
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/SkillOverflowLevelupEvent.kt5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/chroma/ChromaShaderManager.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/chroma/StandardChromaShader.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/chroma/TexturedChromaShader.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropMilestoneDisplay.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/ItemDisplayOverlayFeatures.kt17
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/minion/MinionXp.kt34
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/misc/RoundedRectangleShader.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillProgress.kt472
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillTooltip.kt85
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillType.kt36
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillUtil.kt140
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ColorUtils.kt10
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/GuiRenderUtils.kt47
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt15
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/Quad.kt31
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt113
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/shader/Shader.kt8
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderHelper.kt6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderManager.kt10
-rw-r--r--src/main/resources/assets/skyhanni/bars/1.pngbin0 -> 83571 bytes
-rw-r--r--src/main/resources/assets/skyhanni/bars/2.pngbin0 -> 2868 bytes
-rw-r--r--src/main/resources/assets/skyhanni/bars/3.pngbin0 -> 3077 bytes
-rw-r--r--src/main/resources/assets/skyhanni/bars/4.pngbin0 -> 9030 bytes
-rw-r--r--src/main/resources/assets/skyhanni/bars/5.pngbin0 -> 1189 bytes
-rw-r--r--src/main/resources/assets/skyhanni/shaders/textured_chroma.vsh2
-rw-r--r--src/main/resources/mixins.skyhanni.json14
43 files changed, 1871 insertions, 98 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
index de14fd4e4..ac02e25c1 100644
--- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
+++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
@@ -2,6 +2,7 @@ package at.hannibal2.skyhanni
import at.hannibal2.skyhanni.api.CollectionAPI
import at.hannibal2.skyhanni.api.DataWatcherAPI
+import at.hannibal2.skyhanni.api.SkillAPI
import at.hannibal2.skyhanni.api.GetFromSackAPI
import at.hannibal2.skyhanni.config.ConfigFileType
import at.hannibal2.skyhanni.config.ConfigManager
@@ -284,6 +285,8 @@ import at.hannibal2.skyhanni.features.misc.items.EstimatedItemValue
import at.hannibal2.skyhanni.features.misc.items.EstimatedWardrobePrice
import at.hannibal2.skyhanni.features.misc.items.GlowingDroppedItems
import at.hannibal2.skyhanni.features.misc.massconfiguration.DefaultConfigFeatures
+import at.hannibal2.skyhanni.features.skillprogress.SkillTooltip
+import at.hannibal2.skyhanni.features.skillprogress.SkillProgress
import at.hannibal2.skyhanni.features.misc.teleportpad.TeleportPadCompactName
import at.hannibal2.skyhanni.features.misc.teleportpad.TeleportPadInventoryNumber
import at.hannibal2.skyhanni.features.misc.trevor.TrevorFeatures
@@ -473,6 +476,7 @@ class SkyHanniMod {
loadModule(SackAPI)
loadModule(BingoAPI)
loadModule(FishingAPI)
+ loadModule(SkillAPI)
loadModule(IsFishingDetection)
loadModule(LorenzUtils)
loadModule(NEUItems)
@@ -747,6 +751,8 @@ class SkyHanniMod {
loadModule(SulphurSkitterBox())
loadModule(HighlightInquisitors())
loadModule(VerminTracker)
+ loadModule(SkillProgress)
+ loadModule(SkillTooltip())
loadModule(QuiverNotification)
init()
diff --git a/src/main/java/at/hannibal2/skyhanni/api/SkillAPI.kt b/src/main/java/at/hannibal2/skyhanni/api/SkillAPI.kt
new file mode 100644
index 000000000..63b9236de
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/api/SkillAPI.kt
@@ -0,0 +1,449 @@
+package at.hannibal2.skyhanni.api
+
+import at.hannibal2.skyhanni.data.ProfileStorageData
+import at.hannibal2.skyhanni.events.ActionBarUpdateEvent
+import at.hannibal2.skyhanni.events.ConfigLoadEvent
+import at.hannibal2.skyhanni.events.DebugDataCollectEvent
+import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent
+import at.hannibal2.skyhanni.events.SkillOverflowLevelupEvent
+import at.hannibal2.skyhanni.features.skillprogress.SkillProgress
+import at.hannibal2.skyhanni.features.skillprogress.SkillType
+import at.hannibal2.skyhanni.features.skillprogress.SkillUtil.SPACE_SPLITTER
+import at.hannibal2.skyhanni.features.skillprogress.SkillUtil.XP_NEEDED_FOR_60
+import at.hannibal2.skyhanni.features.skillprogress.SkillUtil.calculateLevelXp
+import at.hannibal2.skyhanni.features.skillprogress.SkillUtil.calculateOverFlow
+import at.hannibal2.skyhanni.features.skillprogress.SkillUtil.getLevel
+import at.hannibal2.skyhanni.features.skillprogress.SkillUtil.getLevelExact
+import at.hannibal2.skyhanni.features.skillprogress.SkillUtil.getSkillInfo
+import at.hannibal2.skyhanni.features.skillprogress.SkillUtil.levelArray
+import at.hannibal2.skyhanni.features.skillprogress.SkillUtil.xpRequiredForLevel
+import at.hannibal2.skyhanni.utils.ChatUtils
+import at.hannibal2.skyhanni.utils.ChatUtils.userError
+import at.hannibal2.skyhanni.utils.ItemUtils.cleanName
+import at.hannibal2.skyhanni.utils.ItemUtils.getLore
+import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
+import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber
+import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimalIfNecessary
+import at.hannibal2.skyhanni.utils.SimpleTimeMark
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import at.hannibal2.skyhanni.utils.TabListData
+import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern
+import com.google.gson.GsonBuilder
+import com.google.gson.annotations.Expose
+import com.google.gson.reflect.TypeToken
+import io.github.moulberry.notenoughupdates.util.Constants
+import io.github.moulberry.notenoughupdates.util.Utils
+import net.minecraft.command.CommandBase
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.LinkedList
+import java.util.regex.Matcher
+import kotlin.concurrent.fixedRateTimer
+import kotlin.time.Duration.Companion.seconds
+
+object SkillAPI {
+ private val patternGroup = RepoPattern.group("api.skilldisplay")
+ private val skillPercentPattern by patternGroup.pattern(
+ "skill.percent",
+ "\\+(?<gained>[\\d.,]+) (?<skillName>.+) \\((?<progress>[\\d.]+)%\\)"
+ )
+ private val skillPattern by patternGroup.pattern(
+ "skill",
+ "\\+(?<gained>[\\d.,]+) (?<skillName>\\w+) \\((?<current>[\\d.,]+)/(?<needed>[\\d.,]+)\\)"
+ )
+ private val skillMultiplierPattern by patternGroup.pattern(
+ "skill.multiplier",
+ "\\+(?<gained>[\\d.,]+) (?<skillName>.+) \\((?<current>[\\d.,]+)/(?<needed>[\\d,.]+[kmb])\\)"
+ )
+ private val skillTabPattern by patternGroup.pattern(
+ "skill.tab",
+ "^§e§lSkills: §r§a(?<type>\\w+) (?<level>\\d+): §r§3(?<progress>.+)%\$"
+ )
+ private val maxSkillTabPattern by patternGroup.pattern(
+ "skill.tab.max",
+ "^§e§lSkills: §r§a(?<type>\\w+) (?<level>\\d+): §r§c§lMAX\$"
+ )
+
+ var skillXPInfoMap = mutableMapOf<SkillType, SkillXPInfo>()
+ var oldSkillInfoMap = mutableMapOf<SkillType?, SkillInfo?>()
+ val storage get() = ProfileStorageData.profileSpecific?.skillData
+ var exactLevelingMap = mapOf<Int, Int>()
+ var levelingMap = mapOf<Int, Int>()
+ var activeSkill: SkillType? = null
+
+ // TODO Use a map maxSkillLevel and move it into the repo
+ val excludedSkills = listOf(
+ SkillType.FORAGING,
+ SkillType.FISHING,
+ SkillType.ALCHEMY,
+ SkillType.CARPENTRY
+ )
+ var showDisplay = false
+ var lastUpdate = SimpleTimeMark.farPast()
+
+ @SubscribeEvent
+ fun onActionBar(event: ActionBarUpdateEvent) {
+ val actionBar = event.actionBar.removeColor()
+ val components = SPACE_SPLITTER.splitToList(actionBar)
+ for (component in components) {
+ val matcher = listOf(skillPattern, skillPercentPattern, skillMultiplierPattern)
+ .firstOrNull { it.matcher(component).matches() }
+ ?.matcher(component)
+
+ if (matcher?.matches() == true) {
+ val skillName = matcher.group("skillName")
+ val skillType = SkillType.getByNameOrNull(skillName) ?: return
+ val skillInfo = storage?.get(skillType) ?: SkillInfo()
+ val skillXp = skillXPInfoMap[skillType] ?: SkillXPInfo()
+ activeSkill = skillType
+ when (matcher.pattern()) {
+ skillPattern -> handleSkillPattern(matcher, skillType, skillInfo)
+ skillPercentPattern -> handleSkillPatternPercent(matcher, skillType)
+ skillMultiplierPattern -> handleSkillPatternMultiplier(matcher, skillType, skillInfo)
+ }
+ showDisplay = true
+ lastUpdate = SimpleTimeMark.now()
+ skillXp.lastUpdate = SimpleTimeMark.now()
+ skillXp.sessionTimerActive = true
+
+ if (skillXp.shouldStartTimer) {
+ runTimer(skillName, skillXp)
+ skillXp.shouldStartTimer = false
+ }
+ SkillProgress.updateDisplay()
+ SkillProgress.hideInActionBar = listOf(component)
+ return
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onConfigLoad(event: ConfigLoadEvent) {
+ val gson = GsonBuilder().create()
+ val xpList: List<Int> = gson.fromJson(
+ Utils.getElement(Constants.LEVELING, "leveling_xp").asJsonArray.toString(),
+ object : TypeToken<List<Int>>() {}.type
+ )
+ levelingMap = xpList.withIndex().associate { (index, xp) -> index to xp }
+ exactLevelingMap = xpList.withIndex().associate { (index, xp) -> xp to index }
+ }
+
+ @SubscribeEvent
+ fun onInventoryOpen(event: InventoryFullyOpenedEvent) {
+ val inventoryName = event.inventoryName
+ for (stack in event.inventoryItems.values) {
+ val lore = stack.getLore()
+ if (inventoryName == "Your Skills" &&
+ lore.any { it.contains("Click to view!") || it.contains("Not unlocked!") }
+ ) {
+ val cleanName = stack.cleanName()
+ val split = cleanName.split(" ")
+ val skillName = split.first()
+ val skill = SkillType.getByNameOrNull(skillName) ?: continue
+ val skillLevel = if (split.size > 1) split.last().romanToDecimalIfNecessary() else 0
+ val skillInfo = storage?.getOrPut(skill) { SkillInfo() }
+
+ for ((lineIndex, line) in lore.withIndex()) {
+ val cleanLine = line.removeColor()
+ if (!cleanLine.startsWith(" ")) continue
+ val previousLine = stack.getLore()[lineIndex - 1]
+ val progress = cleanLine.substring(cleanLine.lastIndexOf(' ') + 1)
+ if (previousLine == "§7§8Max Skill level reached!") {
+ var totalXp = progress.formatNumber()
+ val minus = if (skillLevel == 50) 4_000_000 else if (skillLevel == 60) 7_000_000 else 0
+ totalXp -= minus
+ val (overflowLevel, overflowCurrent, overflowNeeded, overflowTotal) = getSkillInfo(skillLevel, totalXp, 0L, totalXp)
+ skillInfo?.apply {
+ this.overflowLevel = overflowLevel
+ this.overflowCurrentXp = overflowCurrent
+ this.overflowCurrentXpMax = overflowNeeded
+ this.overflowTotalXp = overflowTotal
+
+ this.totalXp = totalXp
+ this.level = skillLevel
+ this.currentXp = totalXp
+ this.currentXpMax = 0L
+ }
+ } else {
+ val splitProgress = progress.split("/")
+ val currentXp = splitProgress.first().formatNumber()
+ val neededXp = splitProgress.last().formatNumber()
+ val levelingArray = levelArray()
+ val levelXp = calculateLevelXp(levelingArray, skillLevel - 1).toLong()
+
+ skillInfo?.apply {
+ this.currentXp = currentXp
+ this.level = skillLevel
+ this.currentXpMax = neededXp
+ this.totalXp = levelXp + currentXp
+
+ this.overflowCurrentXp = currentXp
+ this.overflowLevel = skillLevel
+ this.overflowCurrentXpMax = neededXp
+ this.overflowTotalXp = levelXp + currentXp
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onDebugDataCollect(event: DebugDataCollectEvent) {
+ event.title("Skills")
+ val storage = storage
+ if (storage == null) {
+ event.addIrrelevant("SkillMap is empty")
+ return
+ }
+ event.addData {
+ for ((skillName, skillInfo) in storage) {
+ add("- Name: $skillName")
+ add("- Level: ${skillInfo.level}")
+ add("- CurrentXp: ${skillInfo.currentXp}")
+ add("- CurrentXpMax: ${skillInfo.currentXpMax}")
+ add("- TotalXp: ${skillInfo.totalXp}")
+ add("- OverflowLevel: ${skillInfo.overflowLevel}")
+ add("- OverflowCurrentXp: ${skillInfo.overflowCurrentXp}")
+ add("- OverflowCurrentXpMax: ${skillInfo.overflowCurrentXpMax}")
+ add("- OverflowTotalXp: ${skillInfo.overflowTotalXp}")
+ add("- CustomGoalLevel: ${skillInfo.customGoalLevel}\n")
+ }
+
+ }
+ }
+
+ private fun runTimer(skillName: String, info: SkillXPInfo) {
+ fixedRateTimer(name = "skyhanni-skillprogress-timer-$skillName", initialDelay = 1_000L, period = 1_000L) {
+ if (info.shouldStartTimer) cancel()
+ val time = when (activeSkill) {
+ SkillType.FARMING -> SkillProgress.etaConfig.farmingPauseTime
+ SkillType.MINING -> SkillProgress.etaConfig.miningPauseTime
+ SkillType.COMBAT -> SkillProgress.etaConfig.combatPauseTime
+ SkillType.FORAGING -> SkillProgress.etaConfig.foragingPauseTime
+ SkillType.FISHING -> SkillProgress.etaConfig.fishingPauseTime
+ else -> 0
+ }
+ if (info.lastUpdate.passedSince() > time.seconds) {
+ info.sessionTimerActive = false
+ }
+ if (info.sessionTimerActive) {
+ info.timeActive++
+ }
+ }
+ }
+
+ private fun handleSkillPattern(matcher: Matcher, skillType: SkillType, skillInfo: SkillInfo) {
+ val currentXp = matcher.group("current").formatNumber()
+ val maxXp = matcher.group("needed").formatNumber()
+ val level = getLevelExact(maxXp)
+
+ val (levelOverflow, currentOverflow, currentMaxOverflow, totalOverflow) = getSkillInfo(level, currentXp, maxXp, currentXp)
+ if (skillInfo.overflowLevel != 0 && levelOverflow == skillInfo.overflowLevel + 1)
+ SkillOverflowLevelupEvent(skillType, skillInfo.overflowLevel, levelOverflow).postAndCatch()
+
+ skillInfo.apply {
+ this.level = level
+ this.currentXp = currentXp
+ this.currentXpMax = maxXp
+ this.totalXp = currentXp
+
+ this.overflowLevel = levelOverflow
+ this.overflowCurrentXp = currentOverflow
+ this.overflowCurrentXpMax = currentMaxOverflow
+ this.overflowTotalXp = totalOverflow
+
+ this.lastGain = matcher.group("gained")
+ }
+ storage?.set(skillType, skillInfo)
+ }
+
+ private fun handleSkillPatternPercent(matcher: Matcher, skillType: SkillType) {
+ var tablistLevel = 0
+ for (line in TabListData.getTabList()) {
+ var levelMatcher = skillTabPattern.matcher(line)
+ if (levelMatcher.matches()) {
+ tablistLevel = levelMatcher.group("level").toInt()
+ if (levelMatcher.group("type").lowercase() != activeSkill?.lowercaseName) tablistLevel = 0
+ } else {
+ levelMatcher = maxSkillTabPattern.matcher(line)
+ if (levelMatcher.matches()) {
+ tablistLevel = levelMatcher.group("level").toInt()
+ if (levelMatcher.group("type").lowercase() != activeSkill?.lowercaseName) tablistLevel = 0
+ }
+ }
+ }
+ val existingLevel = getSkillInfo(skillType) ?: SkillInfo()
+ val xpPercentageS = matcher.group("progress").replace(",", "")
+ val xpPercentage = xpPercentageS.toFloatOrNull() ?: return
+ val levelingArray = levelArray()
+ val levelXp = calculateLevelXp(levelingArray, existingLevel.level - 1)
+ val nextLevelDiff = levelingArray[tablistLevel]?.asDouble ?: 7_600_000.0
+ val nextLevelProgress = nextLevelDiff * xpPercentage / 100
+ val totalXp = levelXp + nextLevelProgress
+ val (_, currentOverflow, currentMaxOverflow, totalOverflow) = getSkillInfo(tablistLevel, nextLevelProgress.toLong(), nextLevelDiff.toLong(), totalXp.toLong())
+ existingLevel.apply {
+ this.totalXp = totalXp.toLong()
+ this.currentXp = nextLevelProgress.toLong()
+ this.currentXpMax = nextLevelDiff.toLong()
+ this.level = tablistLevel
+
+ this.overflowTotalXp = totalOverflow
+ this.overflowCurrentXp = currentOverflow
+ this.overflowCurrentXpMax = currentMaxOverflow
+ this.overflowLevel = tablistLevel
+
+ this.lastGain = matcher.group("gained")
+ }
+ storage?.set(skillType, existingLevel)
+ }
+
+ private fun handleSkillPatternMultiplier(matcher: Matcher, skillType: SkillType, skillInfo: SkillInfo) {
+ val currentXp = matcher.group("current").formatNumber()
+ val maxXp = matcher.group("needed").formatNumber()
+ val level = getLevelExact(maxXp)
+ val levelingArray = levelArray()
+ val levelXp = calculateLevelXp(levelingArray, level - 1).toLong() + currentXp
+ val (currentLevel, currentOverflow, currentMaxOverflow, totalOverflow) = getSkillInfo(level, currentXp, maxXp, levelXp)
+ skillInfo.apply {
+ this.overflowCurrentXp = currentOverflow
+ this.overflowCurrentXpMax = currentMaxOverflow
+ this.overflowTotalXp = totalOverflow
+ this.overflowLevel = currentLevel
+
+ this.currentXp = currentXp
+ this.currentXpMax = maxXp
+ this.totalXp = levelXp
+ this.level = level
+
+ this.lastGain = matcher.group("gained")
+ }
+ storage?.set(skillType, skillInfo)
+ }
+
+ fun onCommand(it: Array<String>) {
+ if (it.isEmpty()) {
+ commandHelp()
+ return
+ }
+
+ val first = it.first()
+ if (it.size == 1) {
+ when (first) {
+ "skillgoal" -> {
+ ChatUtils.chat("§bSkill Custom Goal Level")
+ if (storage?.isEmpty() == true) {
+ ChatUtils.chat("§cYou haven't set any custom goal yet!")
+ }
+ storage?.filter { it.value.customGoalLevel != 0 }?.forEach { (skill, data) ->
+ ChatUtils.chat("§e${skill.displayName}: §b${data.customGoalLevel}")
+ }
+ }
+ }
+ }
+
+ if (it.size == 2) {
+ val second = it[1]
+ when (first) {
+ "levelwithxp" -> {
+ val xp = second.toLong()
+ if (xp <= XP_NEEDED_FOR_60) {
+ val level = getLevel(xp)
+ ChatUtils.chat("With §b${xp.addSeparators()} §eXP you would be level §b$level")
+ } else {
+ val (overflowLevel, current, needed, _) = calculateOverFlow(second.toLong())
+ ChatUtils.chat("With §b${xp.addSeparators()} §eXP you would be level §b$overflowLevel " +
+ "§ewith progress (§b${current.addSeparators()}§e/§b${needed.addSeparators()}§e) XP")
+ }
+ }
+
+ "xpforlevel" -> {
+ val level = second.toInt()
+ if (level <= 60) {
+ val neededXp = levelingMap.filter { it.key < level }.values.sum().toLong()
+ ChatUtils.chat("You need §b${neededXp.addSeparators()} §eXP to be level §b${level.toDouble()}")
+ } else {
+ val base = levelingMap.values.sum().toLong()
+ val neededXP = xpRequiredForLevel(level.toDouble()) + base
+ ChatUtils.chat("You need §b${neededXP.addSeparators()} §eXP to be level §b${level.toDouble()}")
+ }
+
+ }
+ }
+ }
+ if (it.size == 3) {
+ when (first) {
+ "skillgoal" -> {
+ val rawSkill = it[1].lowercase()
+ val skillType = SkillType.getByNameOrNull(rawSkill)
+ if (skillType == null) {
+ userError("Unknown Skill type: $rawSkill")
+ return
+ }
+ val rawLevel = it[2]
+ val targetLevel = rawLevel.toIntOrNull()
+ if (targetLevel == null) {
+ userError("$rawLevel is not a valid number.")
+ return
+ }
+ val skill = storage?.get(skillType) ?: return
+
+ if (targetLevel <= skill.overflowLevel && targetLevel != 0) {
+ userError("Custom goal level ($targetLevel) must be greater than your current level (${skill.overflowLevel}).")
+ return
+ }
+
+ skill.customGoalLevel = targetLevel
+ ChatUtils.chat("Custom goal level for §b${skillType.displayName} §eset to §b$targetLevel")
+ }
+ }
+ }
+ }
+
+ fun onComplete(strings: Array<String>): List<String> {
+ return when (strings.size) {
+ 1 -> listOf("levelwithxp", "xpforlevel", "skillgoal")
+ 2 -> if (strings[0].lowercase() == "skillgoal") CommandBase.getListOfStringsMatchingLastWord(
+ strings,
+ SkillType.entries.map { it.displayName })
+ else
+ listOf()
+
+ else -> listOf()
+ }
+ }
+
+ private fun commandHelp() {
+ ChatUtils.chat("", false)
+ ChatUtils.chat("§6/shskills levelwithxp <currentXP> - §bGet a level with the given current XP.", false)
+ ChatUtils.chat("§6/shskills xpforlevel <desiredLevel> - §bGet how much XP you need for a desired level.", false)
+ ChatUtils.chat("§6/shskills skillgoal - §bView your current goal", false)
+ ChatUtils.chat("§6/shskills skillgoal <skill> <level> - §bDefine your goal for <skill>", false)
+ ChatUtils.chat("", false)
+ }
+
+ data class SkillInfo(
+ @Expose var level: Int = 0,
+ @Expose var totalXp: Long = 0,
+ @Expose var currentXp: Long = 0,
+ @Expose var currentXpMax: Long = 0,
+ @Expose var overflowLevel: Int = 0,
+ @Expose var overflowCurrentXp: Long = 0,
+ @Expose var overflowTotalXp: Long = 0,
+ @Expose var overflowCurrentXpMax: Long = 0,
+ @Expose var lastGain: String = "",
+ @Expose var customGoalLevel: Int = 0,
+ )
+
+ data class SkillXPInfo(
+ var lastTotalXp: Float = 0f,
+ var xpGainQueue: LinkedList<Float> = LinkedList(),
+ var xpGainHour: Float = 0f,
+ var xpGainLast: Float = 0f,
+ var timer: Int = 3,
+ var sessionTimerActive: Boolean = false,
+ var isActive: Boolean = false,
+ var lastUpdate: SimpleTimeMark = SimpleTimeMark.farPast(),
+ var timeActive: Long = 0L,
+ var shouldStartTimer: Boolean = true,
+ )
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/config/Features.java b/src/main/java/at/hannibal2/skyhanni/config/Features.java
index ded615520..af464f366 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/Features.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/Features.java
@@ -21,6 +21,7 @@ import at.hannibal2.skyhanni.config.features.mining.MiningConfig;
import at.hannibal2.skyhanni.config.features.minion.MinionsConfig;
import at.hannibal2.skyhanni.config.features.misc.MiscConfig;
import at.hannibal2.skyhanni.config.features.rift.RiftConfig;
+import at.hannibal2.skyhanni.config.features.skillprogress.SkillProgressConfig;
import at.hannibal2.skyhanni.config.features.slayer.SlayerConfig;
import at.hannibal2.skyhanni.config.features.stranded.StrandedConfig;
import com.google.gson.annotations.Expose;
@@ -137,6 +138,10 @@ public class Features extends Config {
public MiningConfig mining = new MiningConfig();
@Expose
+ @Category(name = "Skill Progress", desc = "Skill Progress related config options.")
+ public SkillProgressConfig skillProgress = new SkillProgressConfig();
+
+