summaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/features/combat
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/features/combat')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt489
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/HideDamageSplash.kt28
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/BossType.kt111
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageCounter.kt12
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt862
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/EntityData.kt30
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/EntityResult.kt8
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/MobFinder.kt513
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/OldDamage.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/endernodetracker/EnderNode.kt33
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/endernodetracker/EnderNodeTracker.kt239
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt494
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostData.kt90
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostFormatting.kt182
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostUtil.kt144
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/mobs/AreaMiniBossFeatures.kt121
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/mobs/AshfangMinisNametagHider.kt28
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/mobs/MobHighlight.kt99
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/mobs/SpawnTimers.kt98
19 files changed, 3584 insertions, 0 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt
new file mode 100644
index 000000000..5f0a4125e
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt
@@ -0,0 +1,489 @@
+package at.hannibal2.skyhanni.features.combat
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator
+import at.hannibal2.skyhanni.events.GuiContainerEvent
+import at.hannibal2.skyhanni.events.GuiRenderEvent
+import at.hannibal2.skyhanni.events.InventoryCloseEvent
+import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent
+import at.hannibal2.skyhanni.utils.InventoryUtils
+import at.hannibal2.skyhanni.utils.ItemUtils.getLore
+import at.hannibal2.skyhanni.utils.LorenzColor
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList
+import at.hannibal2.skyhanni.utils.LorenzUtils.addButton
+import at.hannibal2.skyhanni.utils.NumberUtil
+import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
+import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber
+import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimalIfNeeded
+import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision
+import at.hannibal2.skyhanni.utils.NumberUtil.toRoman
+import at.hannibal2.skyhanni.utils.RenderUtils.highlight
+import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems
+import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import at.hannibal2.skyhanni.utils.renderables.Renderable
+import net.minecraft.item.ItemStack
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+
+object BestiaryData {
+
+ private val config get() = SkyHanniMod.feature.combat.bestiary
+ private var display = emptyList<List<Any>>()
+ private val mobList = mutableListOf<BestiaryMob>()
+ private val stackList = mutableMapOf<Int, ItemStack>()
+ private val catList = mutableListOf<Category>()
+ private val progressPattern = "(?<current>[0-9kKmMbB,.]+)/(?<needed>[0-9kKmMbB,.]+$)".toPattern()
+ private val titlePattern = "^(?:\\(\\d+/\\d+\\) )?(Bestiary|.+) ➜ (.+)$".toPattern()
+ private var inInventory = false
+ private var isCategory = false
+ private var indexes = listOf(
+ 10, 11, 12, 13, 14, 15, 16,
+ 19, 20, 21, 22, 23, 24, 25,
+ 28, 29, 30, 31, 32, 33, 34,
+ 37, 38, 39, 40, 41, 42, 43
+ )
+
+ @SubscribeEvent
+ fun onBackgroundDraw(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) {
+ if (!isEnabled()) return
+ if (inInventory) {
+ config.position.renderStringsAndItems(
+ display,
+ extraSpace = -1,
+ itemScale = 1.3,
+ posLabel = "Bestiary Data"
+ )
+ }
+ }
+
+ @SubscribeEvent
+ fun onRender(event: GuiContainerEvent.BackgroundDrawnEvent) {
+ if (!isEnabled()) return
+ if (inInventory) {
+ for (slot in InventoryUtils.getItemsInOpenChest()) {
+ val stack = slot.stack
+ val lore = stack.getLore()
+ if (lore.any { it == "§7Overall Progress: §b100% §7(§c§lMAX!§7)" || it == "§7Families Completed: §a100%" }) {
+ slot highlight LorenzColor.GREEN
+ }
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onInventoryOpen(event: InventoryFullyOpenedEvent) {
+ if (!isEnabled()) return
+ val inventoryName = event.inventoryName
+ val stack = event.inventoryItems[4] ?: return
+ if ((inventoryName == "Bestiary ➜ Fishing" || inventoryName == "Bestiary") || isBestiaryGui(
+ stack,
+ inventoryName
+ )
+ ) {
+ isCategory = inventoryName == "Bestiary ➜ Fishing" || inventoryName == "Bestiary"
+ stackList.putAll(event.inventoryItems)
+ inInventory = true
+ update()
+ }
+ }
+
+ @SubscribeEvent
+ fun onInventoryClose(event: InventoryCloseEvent) {
+ mobList.clear()
+ stackList.clear()
+ inInventory = false
+ }
+
+ @SubscribeEvent
+ fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) {
+ event.move(2, "misc.bestiaryData", "combat.bestiary")
+ }
+
+ private fun update() {
+ display = drawDisplay()
+ }
+
+ private fun init() {
+ mobList.clear()
+ catList.clear()
+ if (isCategory) {
+ inCategory()
+ } else {
+ notInCategory()
+ }
+ }
+
+ private fun inCategory() {
+ for ((index, stack) in stackList) {
+ if (stack.displayName == " ") continue
+ if (!indexes.contains(index)) continue
+ inInventory = true
+ val name = stack.displayName
+ var familiesFound: Long = 0
+ var totalFamilies: Long = 0
+ var familiesCompleted: Long = 0
+ for ((lineIndex, loreLine) in stack.getLore().withIndex()) {
+ val line = loreLine.removeColor()
+ if (!line.startsWith(" ")) continue
+ val previousLine = stack.getLore()[lineIndex - 1]
+ val progress = line.substring(line.lastIndexOf(' ') + 1)
+ if (previousLine.contains("Families Found")) {
+ progressPattern.matchMatcher(progress) {
+ familiesFound = group("current").formatNumber()
+ totalFamilies = group("needed").formatNumber()
+ }
+ } else if (previousLine.contains("Families Completed")) {
+ progressPattern.matchMatcher(progress) {
+ familiesCompleted = group("current").formatNumber()
+ }
+ }
+ }
+ catList.add(Category(name, familiesFound, totalFamilies, familiesCompleted))
+ }
+ }
+
+ private fun notInCategory() {
+ for ((index, stack) in stackList) {
+ if (stack.displayName == " ") continue
+ if (!indexes.contains(index)) continue
+ inInventory = true
+ val name = " [IVX0-9]+$".toPattern().matcher(stack.displayName).replaceFirst("")
+ val level = " ([IVX0-9]+$)".toRegex().find(stack.displayName)?.groupValues?.get(1) ?: "0"
+ var totalKillToMax: Long = 0
+ var currentTotalKill: Long = 0
+ var totalKillToTier: Long = 0
+ var currentKillToTier: Long = 0
+ var actualRealTotalKill: Long = 0
+ for ((lineIndex, line) in stack.getLore().withIndex()) {
+ val loreLine = line.removeColor()
+ if (loreLine.startsWith("Kills: ")) {
+ actualRealTotalKill =
+ "([0-9,.]+)".toRegex().find(loreLine)?.groupValues?.get(1)?.formatNumber()
+ ?: 0
+ }
+ if (!loreLine.startsWith(" ")) continue
+ val previousLine = stack.getLore()[lineIndex - 1]
+ val progress = loreLine.substring(loreLine.lastIndexOf(' ') + 1)
+ if (previousLine.contains("Progress to Tier")) {
+ progressPattern.matchMatcher(progress) {
+ totalKillToTier = group("needed").formatNumber()
+ currentKillToTier = group("current").formatNumber()
+ }
+ } else if (previousLine.contains("Overall Progress")) {
+ progressPattern.matchMatcher(progress) {
+ totalKillToMax = group("needed").formatNumber()
+ currentTotalKill = group("current").formatNumber()
+ }
+ }
+ }
+ mobList.add(
+ BestiaryMob(
+ name,
+ level,
+ totalKillToMax,
+ currentTotalKill,
+ totalKillToTier,
+ currentKillToTier,
+ actualRealTotalKill
+ )
+ )
+ }
+ }
+
+ private fun drawDisplay(): List<List<Any>> {
+ val newDisplay = mutableListOf<List<Any>>()
+ init()
+
+ addCategories(newDisplay)
+
+ if (mobList.isEmpty()) return newDisplay
+
+ addList(newDisplay)
+
+ addButtons(newDisplay)
+
+ return newDisplay
+ }
+
+ private fun sortMobList(): MutableList<BestiaryMob> {
+ val sortedMobList = when (config.displayType) {
+ 0 -> mobList.sortedBy { it.percentToMax() }
+ 1 -> mobList.sortedBy { it.percentToTier() }
+ 2 -> mobList.sortedBy { it.totalKills }
+ 3 -> mobList.sortedByDescending { it.totalKills }
+ 4 -> mobList.sortedBy { it.killNeededToMax() }
+ 5 -> mobList.sortedByDescending { it.killNeededToMax() }
+ 6 -> mobList.sortedBy { it.killNeededToNextLevel() }
+ 7 -> mobList.sortedByDescending { it.killNeededToNextLevel() }
+ else -> mobList.sortedBy { it.totalKills }
+ }.toMutableList()
+ return sortedMobList
+ }
+
+ private fun addList(newDisplay: MutableList<List<Any>>) {
+ val sortedMobList = sortMobList()
+
+ newDisplay.addAsSingletonList("§7Bestiary Data")
+ for (mob in sortedMobList) {
+ val isUnlocked = mob.totalKills != 0.toLong()
+ val isMaxed = mob.percentToMax() >= 1
+ if (!isUnlocked) {
+ newDisplay.add(buildList {
+ add(" §7- ")
+ add("${mob.name}: §cNot unlocked!")
+ })
+ continue
+ }
+ if (isMaxed && config.hideMaxed) continue
+ val text = getMobLine(mob, isMaxed)
+ val tips = getMobHover(mob)
+ newDisplay.addAsSingletonList(Renderable.hoverTips(text, tips) { true })
+ }
+ }
+
+ private fun getMobHover(mob: BestiaryMob) = listOf(
+ "§6Name: §b${mob.name}",
+ "§6Level: §b${mob.level} ${if (!config.replaceRoman) "§7(${mob.level.romanToDecimalIfNeeded()})" else ""}",
+ "§6Total Kills: §b${mob.actualRealTotalKill.formatNumber()}",
+ "§6Kills needed to max: §b${mob.killNeededToMax().formatNumber()}",
+ "§6Kills needed to next lvl: §b${mob.killNeededToNextLevel().formatNumber()}",
+ "§6Current kill to next level: §b${mob.currentKillToNextLevel.formatNumber()}",
+ "§6Kill needed for next level: §b${mob.killNeededForNextLevel.formatNumber()}",
+ "§6Current kill to max: §b${mob.killToMax.formatNumber()}",
+ "§6Percent to max: §b${mob.percentToMaxFormatted()}",
+ "§6Percent to tier: §b${mob.percentToTierFormatted()}",
+ "",
+ "§7More info thing"
+ )
+
+ private fun getMobLine(
+ mob: BestiaryMob,
+ isMaxed: Boolean
+ ): String {
+ val displayType = config.displayType
+ var text = ""
+ text += " §7- "
+ text += "${mob.name} ${mob.level.romanOrInt()} "
+ text += if (isMaxed) {
+ "§c§lMAXED! §7(§b${mob.actualRealTotalKill.formatNumber()}§7 kills)"
+ } else {
+ when (displayType) {
+ 0, 1 -> {
+ val currentKill = when (displayType) {
+ 0 -> mob.totalKills
+ 1 -> mob.currentKillToNextLevel
+ else -> 0
+ }
+ val killNeeded = when (displayType) {
+ 0 -> mob.killToMax
+ 1 -> mob.killNeededForNextLevel
+ else -> 0
+ }
+ "§7(§b${currentKill.formatNumber()}§7/§b${killNeeded.formatNumber()}§7) §a${
+ ((currentKill.toDouble() / killNeeded) * 100).roundToPrecision(
+ 2
+ )
+ }§6% ${if (displayType == 1) "§ato level ${mob.getNextLevel()}" else ""}"
+ }
+
+ 2, 3 -> {
+
+ "§6${mob.totalKills.formatNumber()} §7total kills"
+ }
+
+ 4, 5 -> {
+ "§6${mob.killNeededToMax().formatNumber()} §7kills needed"
+ }
+
+ 6, 7 -> {
+ "§6${mob.killNeededToNextLevel().formatNumber()} §7kills needed"
+ }
+
+ else -> "§cYou are not supposed to see this, please report it to @HiZe on discord!"
+ }
+ }
+ return text
+ }
+
+ private fun addButtons(newDisplay: MutableList<List<Any>>) {
+ newDisplay.addButton(
+ prefix = "§7Number Format: ",
+ getName = FormatType.entries[config.numberFormat].type,
+ onChange = {
+ config.numberFormat = (config.numberFormat + 1) % 2
+ update()
+ })
+
+ newDisplay.addButton(
+ prefix = "§7Display Type: ",
+ getName = DisplayType.entries[config.displayType].type,
+ onChange = {
+ config.displayType = (config.displayType + 1) % 8
+ update()
+ })
+
+ newDisplay.addButton(
+ prefix = "§7Number Type: ",
+ getName = NumberType.entries[if (config.replaceRoman) 0 else 1].type,
+ onChange = {
+ config.replaceRoman = !config.replaceRoman
+ update()
+ }
+ )
+ newDisplay.addButton(
+ prefix = "§7Hide Maxed: ",
+ getName = HideMaxed.entries[if (config.hideMaxed) 1 else 0].type,
+ onChange = {
+ config.hideMaxed = !config.hideMaxed
+ update()
+ }
+ )
+ }
+
+ private fun addCategories(newDisplay: MutableList<List<Any>>) {
+ if (catList.isNotEmpty()) {
+ newDisplay.addAsSingletonList("§7Category")
+ for (cat in catList) {
+ newDisplay.add(buildList {
+ add(" §7- ${cat.name}§7: ")
+ val element = when {
+ cat.familiesCompleted == cat.totalFamilies -> "§c§lCompleted!"
+ cat.familiesFound == cat.totalFamilies -> "§b${cat.familiesCompleted}§7/§b${cat.totalFamilies} §7completed"
+ cat.familiesFound < cat.totalFamilies ->
+ "§b${cat.familiesFound}§7/§b${cat.totalFamilies} §7found, §b${cat.familiesCompleted}§7/§b${cat.totalFamilies} §7completed"
+
+ else -> continue
+ }
+ add(element)
+ })
+ }
+ }
+ }
+
+ private fun isBestiaryGui(stack: ItemStack?, name: String): Boolean {
+ if (stack == null) return false
+ val bestiaryGuiTitleMatcher = titlePattern.matcher(name)
+ if (bestiaryGuiTitleMatcher.matches()) {
+ if ("Bestiary" != bestiaryGuiTitleMatcher.group(1)) {
+ var loreContainsFamiliesFound = false
+ for (line in stack.getLore()) {
+ if (line.removeColor().startsWith("Families Found")) {
+ loreContainsFamiliesFound = true
+ break
+ }
+ }
+ if (!loreContainsFamiliesFound) {
+ return false
+ }
+ }
+ return true
+ } else if (name == "Search Results") {
+ val loreList = stack.getLore()
+ if (loreList.size >= 2 && loreList[0].startsWith("§7Query: §a")
+ && loreList[1].startsWith("§7Results: §a")
+ ) {
+ return true
+ }
+ }
+ return false
+ }
+
+ enum class FormatType(val type: String) {
+ SHORT("Short"),
+ LONG("Long")
+ }
+
+ enum class NumberType(val type: String) {
+ INT("Normal (1, 2, 3)"),
+ ROMAN("Roman (I, II, III")
+ }
+
+ enum class DisplayType(val type: String) {
+ GLOBAL_MAX("Global display (to max)"),
+ GLOBAL_TIER("Global display (to next tier)"),
+ LOWEST_TOTAL("Lowest total kills"),
+ HIGHEST_TOTAL("Highest total kills"),
+ LOWEST_NEEDED_MAX("Lowest kills needed to max"),
+ HIGHEST_NEEDED_MAX("Highest kills needed to max"),
+ LOWEST_NEEDED_TIER("Lowest kills needed to next tier"),
+ HIGHEST_NEEDED_TIER("Highest kills needed to next tier"),
+ }
+
+ enum class HideMaxed(val type: String) {
+ NO("Show"),
+ YES("Hide")
+ }
+
+ private fun Long.formatNumber(): String = when (config.numberFormat) {
+ 0 -> NumberUtil.format(this)
+ 1 -> this.addSeparators()
+ else -> "0"
+ }
+
+ data class Category(
+ val name: String,
+ val familiesFound: Long,
+ val totalFamilies: Long,
+ val familiesCompleted: Long
+ )
+
+ data class BestiaryMob(
+ var name: String,
+ var level: String,
+ var killToMax: Long,
+ var totalKills: Long,
+ var killNeededForNextLevel: Long,
+ var currentKillToNextLevel: Long,
+ var actualRealTotalKill: Long
+ ) {
+
+ fun killNeededToMax(): Long {
+ return 0L.coerceAtLeast(killToMax - totalKills)
+ }
+
+ fun killNeededToNextLevel(): Long {
+ return 0L.coerceAtLeast(killNeededForNextLevel - currentKillToNextLevel)
+ }
+
+ fun percentToMax() = totalKills.toDouble() / killToMax
+
+ fun percentToMaxFormatted() = LorenzUtils.formatPercentage(percentToMax())
+
+ fun percentToTier() = currentKillToNextLevel.toDouble() / killNeededForNextLevel
+
+ fun percentToTierFormatted() = LorenzUtils.formatPercentage(percentToTier())
+
+ fun getNextLevel() = level.getNextLevel()
+ }
+
+
+ private fun String.romanOrInt() = romanToDecimalIfNeeded().let {
+ if (config.replaceRoman || it == 0) it.toString() else it.toRoman()
+ }
+
+ fun Any.getNextLevel(): String {
+ return when (this) {
+ is Int -> {
+ (this + 1).toString().romanOrInt()
+ }
+
+ is String -> {
+ if (this == "0") {
+ "I".romanOrInt()
+ } else {
+ val intValue = romanToDecimalIfNeeded()
+ (intValue + 1).toRoman().romanOrInt()
+ }
+ }
+
+ else -> {
+ "Unsupported type: ${this::class.simpleName}"
+ }
+ }
+ }
+
+ private fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled
+
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/HideDamageSplash.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/HideDamageSplash.kt
new file mode 100644
index 000000000..7b2684c40
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/combat/HideDamageSplash.kt
@@ -0,0 +1,28 @@
+package at.hannibal2.skyhanni.features.combat
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator
+import at.hannibal2.skyhanni.features.combat.damageindicator.DamageIndicatorManager
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import net.minecraft.entity.EntityLivingBase
+import net.minecraftforge.client.event.RenderLivingEvent
+import net.minecraftforge.fml.common.eventhandler.EventPriority
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+class HideDamageSplash {
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ fun onRenderDamage(event: RenderLivingEvent.Specials.Pre<EntityLivingBase>) {
+ if (!LorenzUtils.inSkyBlock) return
+ if (!SkyHanniMod.feature.combat.hideDamageSplash) return
+
+ if (DamageIndicatorManager.isDamageSplash(event.entity)) {
+ event.isCanceled = true
+ }
+ }
+
+ @SubscribeEvent
+ fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) {
+ event.move(2, "misc.hideDamageSplash", "combat.hideDamageSplash")
+ }
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/BossType.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/BossType.kt
new file mode 100644
index 000000000..664aa63d4
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/BossType.kt
@@ -0,0 +1,111 @@
+package at.hannibal2.skyhanni.features.combat.damageindicator
+
+enum class BossType(
+ val fullName: String,
+ val bossTypeToggle: Int,
+ val shortName: String = fullName,
+ val showDeathTime: Boolean = false
+) {
+ GENERIC_DUNGEON_BOSS("Generic Dungeon boss", 0),//TODO split into different bosses
+
+ //Nether Mini Bosses
+ NETHER_BLADESOUL("§8Bladesoul", 1),
+ NETHER_MAGMA_BOSS("§4Magma Boss", 1),
+ NETHER_ASHFANG("§cAshfang", 1),
+ NETHER_BARBARIAN_DUKE("§eBarbarian Duke", 1),
+ NETHER_MAGE_OUTLAW("§5Mage Outlaw", 1),
+
+ NETHER_VANQUISHER("§5Vanquisher", 2),
+
+ END_ENDSTONE_PROTECTOR("§cEndstone Protector", 3),
+ END_ENDER_DRAGON("Ender Dragon", 4),//TODO fix totally
+
+ SLAYER_ZOMBIE_1("§aRevenant Horror 1", 5, "§aRev 1", showDeathTime = true),
+ SLAYER_ZOMBIE_2("§eRevenant Horror 2", 5, "§eRev 2", showDeathTime = true),
+ SLAYER_ZOMBIE_3("§cRevenant Horror 3", 5, "§cRev 3", showDeathTime = true),
+ SLAYER_ZOMBIE_4("§4Revenant Horror 4", 5, "§4Rev 4", showDeathTime = true),
+ SLAYER_ZOMBIE_5("§5Revenant Horror 5", 5, "§5Rev 5", showDeathTime = true),
+
+ SLAYER_SPIDER_1("§aTarantula Broodfather 1", 6, "§aTara 1", showDeathTime = true),
+ SLAYER_SPIDER_2("§eTarantula Broodfather 2", 6, "§eTara 2", showDeathTime = true),
+ SLAYER_SPIDER_3("§cTarantula Broodfather 3", 6, "§cTara 3", showDeathTime = true),
+ SLAYER_SPIDER_4("§4Tarantula Broodfather 4", 6, "§4Tara 4", showDeathTime = true),
+
+ SLAYER_WOLF_1("§aSven Packmaster 1", 7, "§aSven 1", showDeathTime = true),
+ SLAYER_WOLF_2("§eSven Packmaster 2", 7, "§eSven 2", showDeathTime = true),
+ SLAYER_WOLF_3("§cSven Packmaster 3", 7, "§cSven 3", showDeathTime = true),
+ SLAYER_WOLF_4("§4Sven Packmaster 4", 7, "§4Sven 4", showDeathTime = true),
+
+ SLAYER_ENDERMAN_1("§aVoidgloom Seraph 1", 8, "§aVoid 1", showDeathTime = true),
+ SLAYER_ENDERMAN_2("§eVoidgloom Seraph 2", 8, "§eVoid 2", showDeathTime = true),
+ SLAYER_ENDERMAN_3("§cVoidgloom Seraph 3", 8, "§cVoid 3", showDeathTime = true),
+ SLAYER_ENDERMAN_4("§4Voidgloom Seraph 4", 8, "§4Void 4", showDeathTime = true),
+
+ SLAYER_BLAZE_1("§aInferno Demonlord 1", 9, "§aInferno 1", showDeathTime = true),
+ SLAYER_BLAZE_2("§aInferno Demonlord 2", 9, "§aInferno 2", showDeathTime = true),
+ SLAYER_BLAZE_3("§aInferno Demonlord 3", 9, "§aInferno 3", showDeathTime = true),
+ SLAYER_BLAZE_4("§aInferno Demonlord 4", 9, "§aInferno 4", showDeathTime = true),
+
+ SLAYER_BLAZE_TYPHOEUS_1("§aInferno Typhoeus 1", 9, "§aTyphoeus 1"),
+ SLAYER_BLAZE_TYPHOEUS_2("§eInferno Typhoeus 2", 9, "§eTyphoeus 2"),
+ SLAYER_BLAZE_TYPHOEUS_3("§cInferno Typhoeus 3", 9, "§cTyphoeus 3"),
+ SLAYER_BLAZE_TYPHOEUS_4("§cInferno Typhoeus 4", 9, "§cTyphoeus 4"),
+
+ SLAYER_BLAZE_QUAZII_1("§aInferno Quazii 1", 9, "§aQuazii 1"),
+ SLAYER_BLAZE_QUAZII_2("§eInferno Quazii 2", 9, "§eQuazii 2"),
+ SLAYER_BLAZE_QUAZII_3("§cInferno Quazii 3", 9, "§cQuazii 3"),
+ SLAYER_BLAZE_QUAZII_4("§cInferno Quazii 4", 9, "§cQuazii 4"),
+
+ SLAYER_BLOODFIEND_1("§aRiftstalker Bloodfiend 1", 23, "§aBlood 1", showDeathTime = true),
+ SLAYER_BLOODFIEND_2("§6Riftstalker Bloodfiend 2", 23, "§6Blood 2", showDeathTime = true),
+ SLAYER_BLOODFIEND_3("§cRiftstalker Bloodfiend 3", 23, "§cBlood 3", showDeathTime = true),
+ SLAYER_BLOODFIEND_4("§4Riftstalker Bloodfiend 4", 23, "§4Blood 4", showDeathTime = true),
+ SLAYER_BLOODFIEND_5("§5Riftstalker Bloodfiend 5", 23, "§5Blood 5", showDeathTime = true),
+
+ HUB_HEADLESS_HORSEMAN("§6Headless Horseman", 10),
+
+ DUNGEON_F1("", 11),
+ DUNGEON_F2("", 12),
+ DUNGEON_F3("", 13),
+ DUNGEON_F4_THORN("§cThorn", 14),
+ DUNGEON_F5("§dLivid", 15),
+ DUNGEON_F("", 16),
+ DUNGEON_75("", 17),
+
+ MINOS_INQUISITOR("§5Minos Inquisitor", 18),
+ MINOS_CHAMPION("§2Minos Champion", 18),
+ GAIA_CONSTURUCT("§2Gaia Construct", 18),
+ MINOTAUR("§2Minotaur", 18),
+
+ THUNDER("§cThunder", 19),
+ LORD_JAWBUS("§cLord Jawbus", 19),
+
+ DUMMY("Dummy", 20),
+ ARACHNE_SMALL("§cSmall Arachne", 21),
+ ARACHNE_BIG("§4Big Arachne", 21),
+
+ // The Rift
+ LEECH_SUPREME("§cLeech Supreme", 22),
+ BACTE("§aBacte", 22),
+
+ WINTER_REINDRAKE("Reindrake", 24),//TODO fix totally
+
+ //TODO arachne
+
+ //TODO corelone
+ //TODO bal
+
+
+ /**
+ * TODO dungeon mini bosses
+ * shadow assassin
+ * lost adventurer
+ * frozen adventurer
+ * king midas
+ * in blood room: bonzo, scarf, ??
+ * f7 blood room giants
+ *
+ */
+
+ //TODO diana mythological creatures
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageCounter.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageCounter.kt
new file mode 100644
index 000000000..1595b728a
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageCounter.kt
@@ -0,0 +1,12 @@
+package at.hannibal2.skyhanni.features.combat.damageindicator
+
+import java.util.LinkedList
+
+class DamageCounter {
+
+ var currentDamage = 0L
+ var currentHealing = 0L
+ var oldDamages = LinkedList<OldDamage>()
+ var firstTick = 0L
+
+} \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt
new file mode 100644
index 000000000..7735c5835
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt
@@ -0,0 +1,862 @@
+package at.hannibal2.skyhanni.features.combat.damageindicator
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator
+import at.hannibal2.skyhanni.data.ScoreboardData
+import at.hannibal2.skyhanni.events.BossHealthChangeEvent
+import at.hannibal2.skyhanni.events.DamageIndicatorDetectedEvent
+import at.hannibal2.skyhanni.events.DamageIndicatorFinalBossEvent
+import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent
+import at.hannibal2.skyhanni.events.LorenzTickEvent
+import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent
+import at.hannibal2.skyhanni.features.dungeon.DungeonAPI
+import at.hannibal2.skyhanni.features.slayer.blaze.HellionShield
+import at.hannibal2.skyhanni.features.slayer.blaze.setHellionShield
+import at.hannibal2.skyhanni.utils.EntityUtils
+import at.hannibal2.skyhanni.utils.EntityUtils.getNameTagWith
+import at.hannibal2.skyhanni.utils.EntityUtils.hasNameTagWith
+import at.hannibal2.skyhanni.utils.LocationUtils
+import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer
+import at.hannibal2.skyhanni.utils.LorenzColor
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth
+import at.hannibal2.skyhanni.utils.LorenzUtils.between
+import at.hannibal2.skyhanni.utils.LorenzUtils.editCopy
+import at.hannibal2.skyhanni.utils.LorenzUtils.put
+import at.hannibal2.skyhanni.utils.LorenzUtils.round
+import at.hannibal2.skyhanni.utils.LorenzVec
+import at.hannibal2.skyhanni.utils.NumberUtil
+import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
+import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import at.hannibal2.skyhanni.utils.TimeUtils
+import at.hannibal2.skyhanni.utils.getLorenzVec
+import net.minecraft.client.Minecraft
+import net.minecraft.client.entity.EntityOtherPlayerMP
+import net.minecraft.client.renderer.GlStateManager
+import net.minecraft.entity.EntityLiving
+import net.minecraft.entity.EntityLivingBase
+import net.minecraft.entity.item.EntityArmorStand
+import net.minecraft.entity.monster.EntityEnderman
+import net.minecraft.entity.monster.EntityMagmaCube
+import net.minecraft.entity.monster.EntityZombie
+import net.minecraft.entity.passive.EntityWolf
+import net.minecraftforge.client.event.RenderLivingEvent
+import net.minecraftforge.event.entity.EntityJoinWorldEvent
+import net.minecraftforge.fml.common.eventhandler.EventPriority
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.UUID
+import kotlin.math.max
+
+class DamageIndicatorManager {
+
+ private var mobFinder: MobFinder? = null
+ private val maxHealth = mutableMapOf<UUID, Long>()
+ private val config get() = SkyHanniMod.feature.combat.damageIndicator
+
+ companion object {
+ private var data = mapOf<UUID, EntityData>()
+ private val damagePattern = "[✧✯]?(\\d+[⚔+✧❤♞☄✷ﬗ✯]*)".toPattern()
+
+ fun isBoss(entity: EntityLivingBase) = data.values.any { it.entity == entity }
+
+ fun isDamageSplash(entity: EntityLivingBase): Boolean {
+ if (entity.ticksExisted > 300 || entity !is EntityArmorStand) return false
+ if (!entity.hasCustomName()) return false
+ if (entity.isDead) return false
+ val name = entity.customNameTag.removeColor().replace(",", "")
+
+ return damagePattern.matcher(name).matches()
+ }
+
+ fun isBossSpawned(type: BossType) = data.entries.find { it.value.bossType == type } != null
+
+ fun isBossSpawned(vararg types: BossType) = types.any { isBossSpawned(it) }
+
+ fun getDistanceTo(vararg types: BossType): Double {
+ val playerLocation = LocationUtils.playerLocation()
+ return data.values.filter { it.bossType in types }
+ .map { it.entity.getLorenzVec().distance(playerLocation) }
+ .let { list ->
+ if (list.isEmpty()) Double.MAX_VALUE else list.minOf { it }
+ }
+ }
+
+ fun getNearestDistanceTo(location: LorenzVec): Double {
+ return data.values
+ .map { it.entity.getLorenzVec() }
+ .minOfOrNull { it.distance(location) } ?: Double.MAX_VALUE
+ }
+ }
+
+ @SubscribeEvent
+ fun onWorldChange(event: LorenzWorldChangeEvent) {
+ mobFinder = MobFinder()
+ data = emptyMap()
+ }
+
+ @SubscribeEvent(receiveCanceled = true)
+ fun onChatMessage(event: LorenzChatEvent) {
+ mobFinder?.handleChat(event.message)
+ }
+
+ @SubscribeEvent
+ fun onWorldRender(event: LorenzRenderWorldEvent) {
+ if (!isEnabled()) return
+
+ GlStateManager.disableDepth()