summaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/features
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/main/java/at/hannibal2/skyhanni/features
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/main/java/at/hannibal2/skyhanni/features')
-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
12 files changed, 764 insertions, 36 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/features/chroma/ChromaShaderManager.kt b/src/main/java/at/hannibal2/skyhanni/features/chroma/ChromaShaderManager.kt
index 98e1daa71..60b748d33 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/chroma/ChromaShaderManager.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/chroma/ChromaShaderManager.kt
@@ -54,4 +54,4 @@ enum class ChromaType(val shaderName: String) {
* See [TexturedChromaShader]
*/
TEXTURED("textured_chroma")
-} \ No newline at end of file
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/chroma/StandardChromaShader.kt b/src/main/java/at/hannibal2/skyhanni/features/chroma/StandardChromaShader.kt
index 268b37b4c..e1f39aa84 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/chroma/StandardChromaShader.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/chroma/StandardChromaShader.kt
@@ -17,4 +17,4 @@ package at.hannibal2.skyhanni.features.chroma
object StandardChromaShader : ChromaShader("standard_chroma", "standard_chroma") {
val INSTANCE: StandardChromaShader
get() = this
-} \ No newline at end of file
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/chroma/TexturedChromaShader.kt b/src/main/java/at/hannibal2/skyhanni/features/chroma/TexturedChromaShader.kt
index 544d10184..26417d8dc 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/chroma/TexturedChromaShader.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/chroma/TexturedChromaShader.kt
@@ -17,4 +17,4 @@ package at.hannibal2.skyhanni.features.chroma
object TexturedChromaShader : ChromaShader("textured_chroma", "textured_chroma") {
val INSTANCE: TexturedChromaShader
get() = this
-} \ No newline at end of file
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt
index 733be8cd0..62eff052a 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt
@@ -182,7 +182,7 @@ object GhostCounter {
val bestiary = if (config.showMax) {
when (nextLevel) {
26 -> bestiaryFormatting.maxed.replace("%currentKill%", currentKill.addSeparators())
- in 1..25 -> {
+ in 1 .. 25 -> {
val sum = bestiaryData.filterKeys { it <= nextLevel - 1 }.values.sum()
val cKill = sum + currentKill
@@ -195,7 +195,7 @@ object GhostCounter {
} else {
when (nextLevel) {
26 -> bestiaryFormatting.maxed
- in 1..25 -> bestiaryFormatting.progress
+ in 1 .. 25 -> bestiaryFormatting.progress
else -> bestiaryFormatting.openMenu
}
}
@@ -301,7 +301,7 @@ object GhostCounter {
val res = current.formatNumber().toString()
gain = (res.toLong() - lastXp.toLong()).toDouble().roundToInt()
num = (gain.toDouble() / gained)
- if (gained in 150.0..450.0 && lastXp != "0" && num >= 0) {
+ if (gained in 150.0 .. 450.0 && lastXp != "0" && num >= 0) {
KILLS.add(num)
KILLS.add(num, true)
Option.GHOSTSINCESORROW.add(num)
diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropMilestoneDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropMilestoneDisplay.kt
index b27d3e863..0ac715598 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropMilestoneDisplay.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropMilestoneDisplay.kt
@@ -147,7 +147,7 @@ object GardenCropMilestoneDisplay {
if (crop.isMaxed()) {
list.add("§7" + crop.cropName + " §eMAXED")
} else {
- list.add("§7" + crop.cropName + " $currentTier➜$nextTier")
+ list.add("§7" + crop.cropName + " §8$currentTier➜§3$nextTier")
}
lineMap[1] = list
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/ItemDisplayOverlayFeatures.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/ItemDisplayOverlayFeatures.kt
index bc3f52ef2..35f0c6f4a 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/inventory/ItemDisplayOverlayFeatures.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/ItemDisplayOverlayFeatures.kt
@@ -2,6 +2,7 @@ package at.hannibal2.skyhanni.features.inventory
import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.api.CollectionAPI
+import at.hannibal2.skyhanni.api.SkillAPI
import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator
import at.hannibal2.skyhanni.config.features.inventory.InventoryConfig.ItemNumberEntry
import at.hannibal2.skyhanni.config.features.inventory.InventoryConfig.ItemNumberEntry.BINGO_GOAL_RANK
@@ -23,6 +24,7 @@ import at.hannibal2.skyhanni.config.features.inventory.InventoryConfig.ItemNumbe
import at.hannibal2.skyhanni.data.PetAPI
import at.hannibal2.skyhanni.events.RenderItemTipEvent
import at.hannibal2.skyhanni.features.garden.pests.PestAPI
+import at.hannibal2.skyhanni.features.skillprogress.SkillType
import at.hannibal2.skyhanni.utils.ConfigUtils
import at.hannibal2.skyhanni.utils.InventoryUtils
import at.hannibal2.skyhanni.utils.ItemCategory
@@ -158,9 +160,14 @@ object ItemDisplayOverlayFeatures {
if (CollectionAPI.isCollectionTier0(lore)) return "0"
val split = itemName.split(" ")
if (!itemName.contains("Dungeon")) {
+ val skillName = split.first()
val text = split.last()
if (split.size < 2) return "0"
- return "" + text.romanToDecimalIfNecessary()
+ val level = "" + text.romanToDecimalIfNecessary()
+ val skill = SkillType.getByNameOrNull(skillName) ?: return level
+ val skillInfo = SkillAPI.storage?.get(skill) ?: return level
+ return if (SkyHanniMod.feature.skillProgress.overflowConfig.enableInSkillMenuAsStackSize)
+ "" + skillInfo.overflowLevel else level
}
}
@@ -205,9 +212,9 @@ object ItemDisplayOverlayFeatures {
item.name?.let {
dungeonPotionPattern.matchMatcher(it.removeColor()) {
return when (val level = group("level").romanToDecimal()) {
- in 1..2 -> "§f$level"
- in 3..4 -> "§a$level"
- in 5..6 -> "§9$level"
+ in 1 .. 2 -> "§f$level"
+ in 3 .. 4 -> "§a$level"
+ in 5 .. 6 -> "§9$level"
else -> "§5$level"
}
}
@@ -279,4 +286,4 @@ object ItemDisplayOverlayFeatures {
}
fun ItemNumberEntry.isSelected() = config.itemNumberAsStackSize.contains(this)
-}
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/minion/MinionXp.kt b/src/main/java/at/hannibal2/skyhanni/features/minion/MinionXp.kt
index 4d5d54bae..22969de1c 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/minion/MinionXp.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/minion/MinionXp.kt
@@ -9,6 +9,7 @@ import at.hannibal2.skyhanni.events.MinionCloseEvent
import at.hannibal2.skyhanni.events.MinionOpenEvent
import at.hannibal2.skyhanni.events.MinionStorageOpenEvent
import at.hannibal2.skyhanni.events.RepositoryReloadEvent
+import at.hannibal2.skyhanni.features.skillprogress.SkillType
import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName
import at.hannibal2.skyhanni.utils.ItemUtils.getLore
import at.hannibal2.skyhanni.utils.LorenzUtils
@@ -38,28 +39,15 @@ class MinionXp {
private var xpInfoMap: Map<NEUInternalName, XpInfo> = hashMapOf()
- data class XpInfo(val type: XpType, val amount: Double)
-
- private data class MinionStorage(val position: LorenzVec, val xpList: EnumMap<XpType, Double>) {
+ data class XpInfo(val type: SkillType, val amount: Double)
+ private data class MinionStorage(val position: LorenzVec, val xpList: EnumMap<SkillType, Double>) {
val timestamp: SimpleTimeMark = SimpleTimeMark.now()
}
private fun toPrimitiveItemStack(itemStack: ItemStack) =
PrimitiveItemStack(itemStack.getInternalName(), itemStack.stackSize)
-
- // TODO use upper case names, created a function to get type by lowercase name
- // TODO maybe: rename to SkillType, move somewhere else
- enum class XpType {
-
- Farming,
- Mining,
- Combat,
- Foraging,
- Fishing,
- Alchemy
- }
-
+
@SubscribeEvent
fun onMinionOpen(event: MinionOpenEvent) {
if (!config.xpDisplay) return
@@ -84,7 +72,7 @@ class MinionXp {
private fun getStorageXpAndUpdateTotal(
minionPosition: LorenzVec,
- xpTotal: EnumMap<XpType, Double>,
+ xpTotal: EnumMap<SkillType, Double>,
): Boolean {
if (!getHasStorage(minionPosition)) return false
val storage = minionStorages.firstOrNull {
@@ -101,10 +89,10 @@ class MinionXp {
}
}
- private fun handleItems(inventoryItems: Map<Int, ItemStack>, isMinion: Boolean): EnumMap<XpType, Double> {
- val xpTotal = EnumMap<XpType, Double>(XpType::class.java)
+ private fun handleItems(inventoryItems: Map<Int, ItemStack>, isMinion: Boolean): EnumMap<SkillType, Double> {
+ val xpTotal = EnumMap<SkillType, Double>(SkillType::class.java)
inventoryItems.filter {
- it.value.getLore().isNotEmpty() && (!isMinion || it.key in listOf(21..26, 30..35, 39..44).flatten())
+ it.value.getLore().isNotEmpty() && (!isMinion || it.key in listOf(21 .. 26, 30 .. 35, 39 .. 44).flatten())
}.forEach { (_, itemStack) ->
val item = toPrimitiveItemStack(itemStack)
val name = item.internalName
@@ -133,8 +121,8 @@ class MinionXp {
minionStorages.add(MinionStorage(event.position, xpTotal))
}
- private fun collectMessage(type: XpType, amount: Double) =
- "§7Collect to get: §b${amount.addSeparators()} §e${type.name} XP"
+ private fun collectMessage(type: SkillType, amount: Double) =
+ "§7Collect to get: §b${amount.addSeparators()} §e${type.displayName} XP"
private fun getHasStorage(minionPosition: LorenzVec): Boolean {
val positionsToCheck = listOf(
@@ -192,7 +180,7 @@ class MinionXp {
@SubscribeEvent
fun onRepoReload(event: RepositoryReloadEvent) {
xpInfoMap = event.getConstant<MinionXPJson>("MinionXP").minion_xp.mapNotNull { xpType ->
- xpType.value.mapNotNull { it.key.asInternalName() to XpInfo(XpType.valueOf(xpType.key), it.value) }
+ xpType.value.mapNotNull { it.key.asInternalName() to XpInfo(SkillType.getByName(xpType.key), it.value) }
}.flatten().toMap()
}
}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/RoundedRectangleShader.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/RoundedRectangleShader.kt
index 6b0ce5e63..9ce2d0ce7 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/misc/RoundedRectangleShader.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/misc/RoundedRectangleShader.kt
@@ -25,4 +25,4 @@ object RoundedRectangleShader : Shader("rounded_rect", "rounded_rect") {
registerUniform(Uniform.UniformType.VEC2, "halfSize") { halfSize }
registerUniform(Uniform.UniformType.VEC2, "centerPos") { centerPos }
}
-} \ No newline at end of file
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillProgress.kt b/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillProgress.kt
new file mode 100644
index 000000000..01f60ac87
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillProgress.kt
@@ -0,0 +1,472 @@
+package at.hannibal2.skyhanni.features.skillprogress
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.api.SkillAPI
+import at.hannibal2.skyhanni.api.SkillAPI.activeSkill
+import at.hannibal2.skyhanni.api.SkillAPI.lastUpdate
+import at.hannibal2.skyhanni.api.SkillAPI.oldSkillInfoMap
+import at.hannibal2.skyhanni.api.SkillAPI.showDisplay
+import at.hannibal2.skyhanni.api.SkillAPI.skillXPInfoMap
+import at.hannibal2.skyhanni.config.features.skillprogress.SkillProgressConfig
+import at.hannibal2.skyhanni.events.ActionBarUpdateEvent
+import at.hannibal2.skyhanni.events.ConfigLoadEvent
+import at.hannibal2.skyhanni.events.GuiRenderEvent
+import at.hannibal2.skyhanni.events.LorenzTickEvent
+import at.hannibal2.skyhanni.events.ProfileJoinEvent
+import at.hannibal2.skyhanni.events.SkillOverflowLevelupEvent
+import at.hannibal2.skyhanni.features.skillprogress.SkillUtil.XP_NEEDED_FOR_60
+import at.hannibal2.skyhanni.utils.ChatUtils.chat
+import at.hannibal2.skyhanni.utils.ConditionalUtils.onToggle
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
+import at.hannibal2.skyhanni.utils.NumberUtil.interpolate
+import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision
+import at.hannibal2.skyhanni.utils.Quad
+import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables
+import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems
+import at.hannibal2.skyhanni.utils.SimpleTimeMark
+import at.hannibal2.skyhanni.utils.SoundUtils
+import at.hannibal2.skyhanni.utils.SoundUtils.playSound
+import at.hannibal2.skyhanni.utils.SpecialColour
+import at.hannibal2.skyhanni.utils.TimeUnit
+import at.hannibal2.skyhanni.utils.TimeUtils.format
+import at.hannibal2.skyhanni.utils.renderables.Renderable
+import at.hannibal2.skyhanni.utils.renderables.Renderable.Companion.horizontalContainer
+import net.minecraftforge.fml.common.eventhandler.EventPriority
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.awt.Color
+import kotlin.math.ceil
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+
+object SkillProgress {
+
+ private val config get() = SkyHanniMod.feature.skillProgress
+ private val barConfig get() = config.skillProgressBarConfig
+ private val allSkillConfig get() = config.allSkillDisplayConfig
+ val etaConfig get() = config.skillETADisplayConfig
+ val customGoalConfig get() = config.customGoalConfig
+
+ private var skillExpPercentage = 0.0
+ private var display = emptyList<Renderable>()
+ private var allDisplay = emptyList<Renderable>()
+ private var etaDisplay = emptyList<Renderable>()
+ private var lastGainUpdate = SimpleTimeMark.farPast()
+ private var maxWidth = 0
+ var hideInActionBar = listOf<String>()
+
+ @SubscribeEvent
+ fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) {
+ if (!isEnabled()) return
+ if (display.isEmpty()) return
+
+ if (showDisplay) {
+ renderDisplay()
+
+ if (barConfig.enabled.get()) {
+ renderBar()
+ }
+ }
+
+ if (etaConfig.enabled.get()) {
+ config.etaPosition.renderRenderables(etaDisplay, posLabel = "Skill ETA")
+ }
+ }
+
+ @SubscribeEvent
+ fun onGuiRender(event: GuiRenderEvent) {
+ if (!isEnabled()) return
+ if (display.isEmpty()) return
+
+ if (allSkillConfig.enabled.get()) {
+ config.allSkillPosition.renderRenderables(allDisplay, posLabel = "All Skills Display")
+ }
+ }
+
+ private fun renderDisplay() {
+ when (val textAlignment = config.textAlignmentProperty.get()) {
+ SkillProgressConfig.TextAlignment.NONE -> {
+ config.displayPosition.renderStringsAndItems(listOf(display), posLabel = "Skill Progress")
+ }
+
+ SkillProgressConfig.TextAlignment.CENTERED,
+ SkillProgressConfig.TextAlignment.LEFT,
+ SkillProgressConfig.TextAlignment.RIGHT,
+ -> {
+ config.displayPosition.renderRenderables(
+ listOf(Renderable.fixedSizeLine(horizontalContainer(display, horizontalAlign = textAlignment.alignment), maxWidth)),
+ posLabel = "Skill Progress")
+ }
+
+ else -> {}
+ }
+ }
+
+ private fun renderBar() {
+ val progress = if (barConfig.useTexturedBar.get()) {
+ val factor = (skillExpPercentage.toFloat().coerceAtMost(1f)) * 182
+ maxWidth = 182
+ Renderable.progressBar(
+ percent = factor.toDouble(),
+ startColor = Color(SpecialColour.specialToChromaRGB(barConfig.barStartColor)),
+ texture = barConfig.texturedBar.usedTexture.get(),
+ useChroma = barConfig.useChroma.get())
+
+ } else {
+ maxWidth = barConfig.regularBar.width
+ Renderable.progressBar(
+ percent = skillExpPercentage,
+ startColor = Color(SpecialColour.specialToChromaRGB(barConfig.barStartColor)),
+ endColor = Color(SpecialColour.specialToChromaRGB(barConfig.barStartColor)),
+ width = maxWidth,
+ height = barConfig.regularBar.height,
+ useChroma = barConfig.useChroma.get())
+ }
+
+ config.barPosition.renderRenderables(listOf(progress), posLabel = "Skill Progress Bar")
+ }
+
+ @SubscribeEvent
+ fun onProfileJoin(event: ProfileJoinEvent) {
+ display = emptyList()
+ allDisplay = emptyList()
+ etaDisplay = emptyList()
+ skillExpPercentage = 0.0
+ }
+
+ @SubscribeEvent
+ fun onTick(event: LorenzTickEvent) {
+ if (!isEnabled()) return
+ if (lastUpdate.passedSince() > 3.seconds) showDisplay = config.alwaysShow.get()
+
+ if (event.repeatSeconds(1)) {
+ allDisplay = formatAllDisplay(drawAllDisplay())
+ etaDisplay = drawETADisplay()
+ }
+
+ if (event.repeatSeconds(2)) {
+ update()
+ updateSkillInfo()
+ }
+ }
+
+ @SubscribeEvent
+ fun onLevelUp(event: SkillOverflowLevelupEvent) {
+ if (!isEnabled()) return
+ if (!config.overflowConfig.enableInChat) return
+ val skillName = event.skill.displayName
+ val oldLevel = event.oldLevel
+ val newLevel = event.newLevel
+ val skill = SkillAPI.storage?.get(event.skill) ?: return
+ val goalReached = newLevel == skill.customGoalLevel && customGoalConfig.enableInChat
+
+ val rewards = buildList {
+ add(" §r§7§8+§b1 Flexing Point")
+ if (newLevel % 5 == 0)
+ add(" §r§7§8+§d50 SkyHanni User Luck")
+ }
+ val messages = listOf(
+ "§3§l▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬",
+ " §r§b§lSKILL LEVEL UP §3$skillName §8$oldLevel➜§3$newLevel",
+ if (goalReached)
+ listOf(
+ "",
+ " §r§d§lGOAL REACHED!",
+ "",
+ ).joinToString("\n") else
+ "",
+ " §r§a§lREWARDS",
+ rewards.joinToString("\n"),
+ "§3§l▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬"
+ )
+
+ chat(messages.joinToString("\n"), false)
+
+ if (goalReached)
+ chat("§lYou have reached your goal level of §b§l${skill.customGoalLevel} §e§lin the §b§l$skillName §e§lskill!")
+
+ SoundUtils.createSound("random.levelup", 1f, 1f).playSound()
+ }
+
+ @SubscribeEvent
+ fun onConfigLoad(event: ConfigLoadEvent) {
+ onToggle(
+ config.enabled,
+ config.alwaysShow,
+ config.showActionLeft,
+ config.useIcon,
+ config.usePercentage,
+ config.useSkillName,
+ config.overflowConfig.enableInDisplay,
+ config.overflowConfig.enableInProgressBar,
+ config.overflowConfig.enableInEtaDisplay,
+ barConfig.enabled,
+ barConfig.useChroma,
+ barConfig.useTexturedBar,
+ allSkillConfig.enabled,
+ etaConfig.enabled
+ ) {
+ updateDisplay()
+ update()
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.LOW)
+ fun onActionBar(event: ActionBarUpdateEvent) {
+ if (!config.hideInActionBar) return
+ if (event.isCanceled) return
+ var msg = event.actionBar
+ for (line in hideInActionBar) {
+ msg = msg.replace(Regex("\\s*" + Regex.escape(line)), "")
+ }
+ msg = msg.trim()
+
+ event.changeActionBar(msg)
+ }
+
+ fun updateDisplay() {
+ display = drawDisplay()
+ }
+
+ private fun update() {
+ lastGainUpdate = SimpleTimeMark.now()
+ skillXPInfoMap.forEach {
+ it.value.xpGainLast = it.value.xpGainHour
+ }
+ }
+
+ private fun formatAllDisplay(map: Map<SkillType, Renderable>): List<Renderable> {
+ val newList = mutableListOf<Renderable>()
+ if (map.isEmpty()) return newList
+ for (skillType in allSkillConfig.skillEntryList) {
+ map[skillType]?.let {
+ newList.add(it)
+ }
+ }
+ return newList
+ }
+
+ private fun drawAllDisplay() = buildMap {
+ val skillMap = SkillAPI.storage ?: return@buildMap
+ val sortedMap = SkillType.entries.filter { it.displayName.isNotEmpty() }.sortedBy { it.displayName.take(2) }
+
+ for (skill in sortedMap) {
+ val skillInfo = skillMap[skill] ?: SkillAPI.SkillInfo(level = -1, overflowLevel = -1)
+ val lockedLevels = skillInfo.overflowCurrentXp > skillInfo.overflowCurrentXpMax
+ val useCustomGoalLevel = skillInfo.customGoalLevel != 0 && skillInfo.customGoalLevel > skillInfo.overflowLevel && customGoalConfig.enableInAllDisplay
+ val targetLevel = skillInfo.customGoalLevel
+ var xp = skillInfo.overflowTotalXp
+ if (targetLevel in 50 .. 60 && skillInfo.overflowLevel >= 50) xp += SkillUtil.xpRequiredForLevel(50.0)
+ else if (targetLevel > 60 && skillInfo.overflowLevel >= 60) xp += SkillUtil.xpRequiredForLevel(60.0)
+
+ var have = skillInfo.overflowTotalXp
+ val need = SkillUtil.xpRequiredForLevel(targetLevel.toDouble())
+ if (targetLevel in 51 .. 59) have += SkillUtil.xpRequiredForLevel(50.0)
+ else if (targetLevel > 60) have += SkillUtil.xpRequiredForLevel(60.0)
+
+ val (level, currentXp, currentXpMax, totalXp) =
+ if (useCustomGoalLevel)
+ Quad(skillInfo.overflowLevel, have, need, xp)
+ else if (config.overflowConfig.enableInAllDisplay.get() && !lockedLevels)
+ Quad(skillInfo.overflowLevel, skillInfo.overflowCurrentXp, skillInfo.overflowCurrentXpMax, skillInfo.overflowTotalXp)
+ else
+ Quad(skillInfo.level, skillInfo.currentXp, skillInfo.currentXpMax, skillInfo.totalXp)
+
+ this[skill] = if (level == -1) {
+ Renderable.clickAndHover(
+ "§cOpen your skills menu !",
+ listOf("§eClick here to execute §6/skills"),
+ onClick = { LorenzUtils.sendCommandToServer("skills") }
+ )
+ } else {
+ val tips = buildList {
+ add("§6Level: §b${level}")
+ add("§6Current XP: §b${currentXp.addSeparators()}")
+ add("§6Needed XP: §b${currentXpMax.addSeparators()}")
+ add("§6Total XP: §b${totalXp.addSeparators()}")
+ }
+ val nameColor = if (skill == activeSkill) "§2" else "§a"
+ Renderable.hoverTips(buildString {
+ append("$nameColor${skill.displayName} $level ")
+ append("§7(")
+ append("§b${currentXp.addSeparators()}")
+ if (currentXpMax != 0L) {
+ append("§6/")
+ append("§b${currentXpMax.addSeparators()}")
+ }
+ append("§7)")
+ }, tips)
+ }
+ }
+ }
+
+ private fun drawETADisplay() = buildList {
+ val activeSkill = activeSkill ?: return@buildList
+ val skillInfo = SkillAPI.storage?.get(activeSkill) ?: return@buildList
+ val xpInfo = skillXPInfoMap[activeSkill] ?: return@buildList
+ val skillInfoLast = oldSkillInfoMap[activeSkill] ?: return@buildList
+ oldSkillInfoMap[activeSkill] = skillInfo
+ val level = if (config.overflowConfig.enableInEtaDisplay.get() || config.customGoalConfig.enableInETADisplay) skillInfo.overflowLevel else skillInfo.level
+
+ val useCustomGoalLevel = skillInfo.customGoalLevel != 0 && skillInfo.customGoalLevel > skillInfo.overflowLevel && customGoalConfig.enableInETADisplay
+ var targetLevel = if (useCustomGoalLevel) skillInfo.customGoalLevel else level + 1
+ if (targetLevel <= level || targetLevel > 400) targetLevel = (level + 1)
+ val currentLevelNeededXp = SkillUtil.xpRequiredForLevel(level.toDouble()) + skillInfo.overflowCurrentXp
+ val targetNeededXp = SkillUtil.xpRequiredForLevel(targetLevel.toDouble())
+ var remaining = if (useCustomGoalLevel) targetNeededXp - currentLevelNeededXp else skillInfo.overflowCurrentXpMax - skillInfo.overflowCurrentXp
+
+ if (!useCustomGoalLevel) {
+ if (skillInfo.overflowCurrentXpMax == skillInfoLast.overflowCurrentXpMax) {
+ remaining = interpolate(remaining.toFloat(), (skillInfoLast.overflowCurrentXpMax - skillInfoLast.overflowCurrentXp).toFloat(), lastGainUpdate.toMillis()).toLong()
+ }
+ }
+
+ add(Renderable.string("§6Skill: §a${activeSkill.displayName} §8$level➜§3$targetLevel"))
+
+ if (useCustomGoalLevel)
+ add(Renderable.string("§7Needed XP: §e${remaining.addSeparators()}"))
+
+ var xpInterp = xpInfo.xpGainHour
+ if (xpInfo.xpGainHour < 1000) {
+ add(Renderable.string("§7In §cN/A"))
+ } else {
+ val duration = ((remaining) * 1000 * 60 * 60 / xpInterp.toLong()).milliseconds
+ val format = duration.format(TimeUnit.DAY)
+ add(Renderable.string("§7In §b$format " +
+ if (xpInfo.isActive) "" else "§c(PAUSED)"))
+ }
+
+ if (xpInfo.xpGainLast == xpInfo.xpGainHour && xpInfo.xpGainHour <= 0) {
+ add(Renderable.string("§7XP/h: §cN/A"))
+ } else {
+ xpInterp = interpolate(xpInfo.xpGainHour, xpInfo.xpGainLast, lastGainUpdate.toMillis())
+ add(Renderable.string("§7XP/h: §e${xpInterp.toLong().addSeparators()} " +
+ if (xpInfo.isActive) "" else "§c(PAUSED)"))
+ }
+
+
+ val session = xpInfo.timeActive.seconds.format(TimeUnit.HOUR)
+ add(Renderable.clickAndHover("§7Session: §e$session ${if (xpInfo.sessionTimerActive) "" else "§c(PAUSED)"}",
+ listOf("§eClick to reset!")) {
+ xpInfo.sessionTimerActive = false
+ xpInfo.shouldStartTimer = true
+ xpInfo.timeActive = 0L
+ chat("Timer for §b${activeSkill.displayName} §ehas been reset!")
+ })
+ }
+
+ private fun drawDisplay() = buildList {
+ val activeSkill = activeSkill ?: return@buildList
+ val skillMap = SkillAPI.storage ?: return@buildList
+ val skill = skillMap[activeSkill] ?: return@buildList
+ val useCustomGoalLevel = skill.customGoalLevel != 0 && skill.customGoalLevel > skill.overflowLevel
+ val targetLevel = skill.customGoalLevel
+ val xp = skill.totalXp
+ val currentLevel = if (xp <= XP_NEEDED_FOR_60) {
+ SkillUtil.getLevel(xp)
+ } else {
+ SkillUtil.calculateOverFlow(xp).first
+ }
+ var have = skill.overflowTotalXp
+ val need = SkillUtil.xpRequiredForLevel(targetLevel.toDouble())
+ if (targetLevel in 51 .. 59) have += SkillUtil.xpRequiredForLevel(50.0)
+ else if (targetLevel > 60) have += SkillUtil.xpRequiredForLevel(60.0)
+
+
+ val (level, currentXp, currentXpMax, _) =
+ if (useCustomGoalLevel && customGoalConfig.enableInDisplay)
+ Quad(currentLevel, have, need, xp)
+ else if (config.overflowConfig.enableInDisplay.get())
+ Quad(skill.overflowLevel, skill.overflowCurrentXp, skill.overflowCurrentXpMax, skill.overflowTotalXp)
+ else
+ Quad(skill.level, skill.currentXp, skill.currentXpMax, skill.totalXp)
+
+ if (config.showLevel.get())
+ add(Renderable.string("§9[§d$level§9] "))
+
+ if (config.useIcon.get()) {
+ add(Renderable.itemStack(activeSkill.item, 1.2))
+ }
+
+ add(Renderable.string(buildString {
+ append("§b+${skill.lastGain} ")
+
+ if (config.useSkillName.get())
+ append("${activeSkill.displayName} ")
+
+ val (barCurrent, barMax) =
+ if (useCustomGoalLevel && customGoalConfig.enableInProgressBar)
+ Pair(have, need)
+ else if (config.overflowConfig.enableInProgressBar.get())
+ Pair(skill.overflowCurrentXp, skill.overflowCurrentXpMax)
+ else
+ Pair(skill.currentXp, skill.currentXpMax)
+
+ val barPercent = if (barMax == 0L) 100F else 100F * barCurrent / barMax
+ skillExpPercentage = (barPercent.toDouble() / 100)
+
+ val percent = if (currentXpMax == 0L) 100F else 100F * currentXp / currentXpMax
+
+ if (config.usePercentage.get())
+ append("§7(§6${percent.roundToPrecision(2)}%§7)")
+ else {
+ if (currentXpMax == 0L)
+ append("§7(§6${currentXp.addSeparators()}§7)")
+ else
+ append("§7(§6${currentXp.addSeparators()}§7/§6${currentXpMax.addSeparators()}§7)")
+ }
+
+ if (config.showActionLeft.get() && percent != 100f) {
+ append(" - ")
+ val gain = skill.lastGain.replace(",", "")
+ val actionLeft = (ceil(currentXpMax.toDouble() - currentXp) / gain.toDouble()).toLong().addSeparators()
+ if (skill.lastGain != "" && !actionLeft.contains("-")) {
+ append("§6$actionLeft Left")
+ } else {
+ append("§6∞ Left")
+ }
+ }
+ }))
+ }
+
+ private fun updateSkillInfo() {
+ val activeSkill = activeSkill ?: return
+ val xpInfo = skillXPInfoMap.getOrPut(activeSkill) { SkillAPI.SkillXPInfo() }
+ val skillInfo = SkillAPI.storage?.get(activeSkill) ?: return
+ oldSkillInfoMap[activeSkill] = skillInfo
+
+ val totalXp = skillInfo.currentXp
+
+ if (xpInfo.lastTotalXp > 0) {
+ val delta = totalXp - xpInfo.lastTotalXp
+ if (delta > 0 && delta < 1000) {
+ xpInfo.xpGainQueue.add(0, delta)
+
+ calculateXPHour(xpInfo)
+ } else if (xpInfo.timer > 0) {
+ xpInfo.timer--
+ xpInfo.xpGainQueue.add(0, 0f)
+
+ calculateXPHour(xpInfo)
+ } else if (delta <= 0) {
+ xpInfo.isActive = false
+ }
+ }
+ xpInfo.lastTotalXp = totalXp.toFloat()
+ }
+
+ private fun calculateXPHour(xpInfo: SkillAPI.SkillXPInfo) {
+ while (xpInfo.xpGainQueue.size > 30) {
+ xpInfo.xpGainQueue.removeLast()
+ }
+
+ var totalGain = 0f
+ for (f in xpInfo.xpGainQueue) totalGain += f
+
+ xpInfo.xpGainHour = totalGain * (60 * 60) / xpInfo.xpGainQueue.size
+
+ xpInfo.isActive = true
+ }
+
+ private fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled.get()
+}
diff --g