From a193bdca479ae7531da6f034c597335918b52699 Mon Sep 17 00:00:00 2001 From: Lorenz Date: Sat, 9 Jul 2022 04:18:07 +0200 Subject: added dungeon boss damage indicator --- src/main/java/at/lorenz/mod/dungeon/DungeonData.kt | 14 +- .../damageindicator/DungeonBossDamageIndicator.kt | 154 ++++++++++ .../dungeon/damageindicator/DungeonBossFinder.kt | 338 +++++++++++++++++++++ .../mod/dungeon/damageindicator/EntityData.kt | 6 + 4 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossDamageIndicator.kt create mode 100644 src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossFinder.kt create mode 100644 src/main/java/at/lorenz/mod/dungeon/damageindicator/EntityData.kt (limited to 'src/main/java/at/lorenz/mod/dungeon') diff --git a/src/main/java/at/lorenz/mod/dungeon/DungeonData.kt b/src/main/java/at/lorenz/mod/dungeon/DungeonData.kt index d14183d99..870bcaa50 100644 --- a/src/main/java/at/lorenz/mod/dungeon/DungeonData.kt +++ b/src/main/java/at/lorenz/mod/dungeon/DungeonData.kt @@ -9,7 +9,19 @@ import net.minecraftforge.fml.common.gameevent.TickEvent class DungeonData { - var dungeonFloor: String? = null + companion object { + var dungeonFloor: String? = null + + fun isOneOf(vararg floors: String): Boolean { + for (floor in floors) { + if (dungeonFloor == floor) { + return true + } + } + + return false + } + } @SubscribeEvent fun onTick(event: TickEvent.ClientTickEvent) { diff --git a/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossDamageIndicator.kt b/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossDamageIndicator.kt new file mode 100644 index 000000000..47e0d7f52 --- /dev/null +++ b/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossDamageIndicator.kt @@ -0,0 +1,154 @@ +package at.lorenz.mod.dungeon.damageindicator + +import at.lorenz.mod.LorenzMod +import at.lorenz.mod.events.DungeonEnterEvent +import at.lorenz.mod.events.LorenzChatEvent +import at.lorenz.mod.utils.LorenzColor +import at.lorenz.mod.utils.LorenzUtils +import at.lorenz.mod.utils.LorenzUtils.baseMaxHealth +import at.lorenz.mod.utils.NumberUtil +import at.lorenz.mod.utils.RenderUtils +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.entity.EntityLivingBase +import net.minecraft.util.Vec3 +import net.minecraftforge.client.event.RenderLivingEvent +import net.minecraftforge.client.event.RenderWorldLastEvent +import net.minecraftforge.event.entity.EntityJoinWorldEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.text.DecimalFormat +import java.util.* +import kotlin.math.max + +class DungeonBossDamageIndicator { + + var data = mutableMapOf() + private var bossFinder: DungeonBossFinder? = null + private val decimalFormat = DecimalFormat("0.0") + + @SubscribeEvent + fun onDungeonStart(event: DungeonEnterEvent) { + bossFinder = DungeonBossFinder() + } + + @SubscribeEvent(receiveCanceled = true) + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.inDungeons) return + if (!LorenzMod.feature.dungeon.bossDamageIndicator) return + + bossFinder?.handleChat(event.message) + } + + @SubscribeEvent + fun onWorldRender(event: RenderWorldLastEvent) { + GlStateManager.disableDepth() + GlStateManager.disableCull() + + val player = Minecraft.getMinecraft().thePlayer + + for (data in data.values) { + if (System.currentTimeMillis() > data.time + 100) continue//TODO use removeIf + if (!data.ignoreBlocks) { + if (!player.canEntityBeSeen(data.entity)) continue + } + + val entity = data.entity + + var color = data.color + var text = data.text + val delayedStart = data.delayedStart + if (delayedStart != -1L) { + if (delayedStart > System.currentTimeMillis()) { + val delay = delayedStart - System.currentTimeMillis() + color = colorForTime(delay) + var d = delay * 1.0 + d /= 1000 + text = decimalFormat.format(d) + } + } + + val partialTicks = event.partialTicks + RenderUtils.drawLabel( + Vec3( + RenderUtils.interpolate(entity.posX, entity.lastTickPosX, partialTicks), + RenderUtils.interpolate(entity.posY, entity.lastTickPosY, partialTicks) + 0.5f, + RenderUtils.interpolate(entity.posZ, entity.lastTickPosZ, partialTicks) + ), + text, + color.toColor(), + partialTicks, + true, + 6f + ) + } + GlStateManager.enableDepth() + GlStateManager.enableCull() + } + + private fun colorForTime(delayedStart: Long): LorenzColor = when { + delayedStart < 1_000 -> LorenzColor.DARK_PURPLE + delayedStart < 3_000 -> LorenzColor.LIGHT_PURPLE + + else -> LorenzColor.WHITE + } + + @SubscribeEvent + fun onRenderLivingPost(event: RenderLivingEvent.Post<*>) { + if (!LorenzUtils.inDungeons) return + if (!LorenzMod.feature.dungeon.bossDamageIndicator) return + + try { + val entity = event.entity + + var ignoreBlocks = false + var delayedStart = -1L + val show = bossFinder?.shouldShow(entity, { ignoreBlocks = it }, { delayedStart = it }) ?: false + if (!show) return + + val currentMaxHealth = event.entity.baseMaxHealth + + val debugMaxHealth = getMaxHealthFor(event.entity) + val biggestHealth = max(currentMaxHealth, debugMaxHealth) + if (biggestHealth > debugMaxHealth) { + setMaxHealth(event.entity, biggestHealth) + } + + val percentage = event.entity.health / max(debugMaxHealth, currentMaxHealth) + val color = when { + percentage > 0.9 -> LorenzColor.DARK_GREEN + percentage > 0.75 -> LorenzColor.GREEN + percentage > 0.5 -> LorenzColor.YELLOW + percentage > 0.25 -> LorenzColor.GOLD + else -> LorenzColor.RED + } + + data[entity] = EntityData( + entity, + NumberUtil.format(event.entity.health), + color, + System.currentTimeMillis(), + ignoreBlocks, + delayedStart + ) + + } catch (e: Throwable) { + e.printStackTrace() + } + } + + //TODO test why this does not work + val maxHealth = mutableMapOf() + + private fun setMaxHealth(entity: EntityLivingBase, currentMaxHealth: Double) { + maxHealth[entity.uniqueID!!] = currentMaxHealth + } + + private fun getMaxHealthFor(entity: EntityLivingBase): Double { + return maxHealth.getOrDefault(entity.uniqueID!!, 0.0) + } + + @SubscribeEvent + fun onWorldRender(event: EntityJoinWorldEvent) { + bossFinder?.handleNewEntity(event.entity) + } +} \ No newline at end of file diff --git a/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossFinder.kt b/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossFinder.kt new file mode 100644 index 000000000..edc38edeb --- /dev/null +++ b/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossFinder.kt @@ -0,0 +1,338 @@ +package at.lorenz.mod.dungeon.damageindicator + +import at.lorenz.mod.dungeon.DungeonData +import at.lorenz.mod.utils.LorenzUtils +import at.lorenz.mod.utils.LorenzUtils.baseMaxHealth +import at.lorenz.mod.utils.LorenzUtils.matchRegex +import at.lorenz.mod.utils.getLorenzVec +import net.minecraft.client.Minecraft +import net.minecraft.client.entity.EntityOtherPlayerMP +import net.minecraft.entity.Entity +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.monster.EntityGiantZombie +import net.minecraft.entity.monster.EntityGuardian + +class DungeonBossFinder { + + //F1 + private var floor1bonzo1 = false + private var floor1bonzo1SpawnTime = 0L + private var floor1bonzo2 = false + private var floor1bonzo2SpawnTime = 0L + + //F2 + private var floor2summons1 = false + private var floor2summons1SpawnTime = 0L + private var floor2summonsDiedOnce = mutableListOf() + private var floor2secondPhase = false + private var floor2secondPhaseSpawnTime = 0L + + //F3 + private var floor3GuardianShield = false + private var floor3GuardianShieldSpawnTime = 0L + private var guardians = mutableListOf() + private var floor3Professor = false + private var floor3ProfessorSpawnTime = 0L + private var floor3ProfessorGuardianPrepare = false + private var floor3ProfessorGuardianPrepareSpawnTime = 0L + private var floor3ProfessorGuardian = false + private var floor3ProfessorGuardianEntity: EntityGuardian? = null + + //F5 + private var floor5lividEntity: EntityOtherPlayerMP? = null + + //F6 + private var floor6Giants = false + private var floor6GiantsSpawnTime = 0L //TODO different giants cooldown delayed from each other + private var floor6Sadan = false + private var floor6SadanSpawnTime = 0L + + internal fun shouldShow( + entity: EntityLivingBase, + ignoreBlocks: (Boolean) -> Unit, + delayedStart: (Long) -> Unit + ): Boolean { + if (LorenzUtils.inDungeons) { + if (DungeonData.isOneOf("F1", "M1")) { + if (floor1bonzo1) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Bonzo ") { + delayedStart(floor1bonzo1SpawnTime) + return true + } + } + } + if (floor1bonzo2) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Bonzo ") { + delayedStart(floor1bonzo2SpawnTime) + return true + } + } + } + } + + if (DungeonData.isOneOf("F2", "M2")) { + if (entity.name == "Summon ") { + if (entity is EntityOtherPlayerMP) { + if (floor2summons1) { + if (!floor2summonsDiedOnce.contains(entity)) { + if (entity.health.toInt() != 0) { + delayedStart(floor2summons1SpawnTime) + return true + } else { + floor2summonsDiedOnce.add(entity) + } + } + } + if (floor2secondPhase) { + delayedStart(floor2secondPhaseSpawnTime) + return true + } + } + } + + if (floor2secondPhase) { + if (entity is EntityOtherPlayerMP) { + //TODO only show scarf after (all/at least x) summons are dead? + val result = entity.name == "Scarf " + if (result) { + delayedStart(floor2secondPhaseSpawnTime) + return true + } + } + } + } + + if (DungeonData.isOneOf("F3", "M3")) { + if (entity is EntityGuardian) { + if (floor3GuardianShield) { + if (guardians.size == 4) { + var totalHealth = 0 + for (guardian in guardians) { + totalHealth += guardian.health.toInt() + } + if (totalHealth == 0) { + floor3GuardianShield = false + guardians.clear() + } + } else { + findGuardians() + } + if (guardians.contains(entity)) { + ignoreBlocks(true) + delayedStart(floor3GuardianShieldSpawnTime) + return true + } + } + } + + if (floor3Professor) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "The Professor") { + delayedStart(floor3ProfessorSpawnTime) + ignoreBlocks(floor3ProfessorSpawnTime + 1_000 > System.currentTimeMillis()) + return true + } + } + } + if (floor3ProfessorGuardianPrepare) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "The Professor") { + delayedStart(floor3ProfessorGuardianPrepareSpawnTime) + ignoreBlocks(true) + return true + } + } + } + + if (entity is EntityGuardian) { + if (floor3ProfessorGuardian) { + if (entity == floor3ProfessorGuardianEntity) { + return true + } + } + } + } + + if (DungeonData.isOneOf("F5", "M5")) { + if (entity is EntityOtherPlayerMP) { + val floor5lividEntity1 = floor5lividEntity + if (entity == floor5lividEntity1) { + ignoreBlocks(entity.getLorenzVec().distance(5.5, 69.0, -2.5) < 5) + return true + } + } + } + + if (DungeonData.isOneOf("F6", "M6")) { + if (entity is EntityGiantZombie && !entity.isInvisible) { + if (floor6Giants && entity.posY > 68) { + ignoreBlocks(floor6GiantsSpawnTime + 1_000 > System.currentTimeMillis()) + delayedStart(floor6GiantsSpawnTime) + return true + } + + if (floor6Sadan) { + delayedStart(floor6SadanSpawnTime) + return true + } + } + } + } + + return false + } + + fun handleChat(message: String) { + when (message) { + //F1 + "§c[BOSS] Bonzo§r§f: Gratz for making it this far, but I’m basically unbeatable." -> { + floor1bonzo1 = true + floor1bonzo1SpawnTime = System.currentTimeMillis() + 11_250 + } + + "§c[BOSS] Bonzo§r§f: Oh noes, you got me.. what ever will I do?!" -> { + floor1bonzo1 = false + } + + "§c[BOSS] Bonzo§r§f: Oh I'm dead!" -> { + floor1bonzo2 = true + floor1bonzo2SpawnTime = System.currentTimeMillis() + 4_200 + } + + "§c[BOSS] Bonzo§r§f: Alright, maybe I'm just weak after all.." -> { + floor1bonzo2 = false + } + + //F2 + "§c[BOSS] Scarf§r§f: ARISE, MY CREATIONS!" -> { + floor2summons1 = true + floor2summons1SpawnTime = System.currentTimeMillis() + 3_500 + } + + "§c[BOSS] Scarf§r§f: Those toys are not strong enough I see." -> { + floor2summons1 = false + } + + "§c[BOSS] Scarf§r§f: Don't get too excited though." -> { + floor2secondPhase = true + floor2secondPhaseSpawnTime = System.currentTimeMillis() + 6_300 + } + + "§c[BOSS] Scarf§r§f: Whatever..." -> { + floor2secondPhase = false + } + + //F3 + "§c[BOSS] The Professor§r§f: I was burdened with terrible news recently..." -> { + floor3GuardianShield = true + floor3GuardianShieldSpawnTime = System.currentTimeMillis() + 16_400 + } + + "§c[BOSS] The Professor§r§f: Even if you took my barrier down, I can still fight." -> { + floor3GuardianShield = false + } + + "§c[BOSS] The Professor§r§f: Oh? You found my Guardians one weakness?" -> { + floor3Professor = true + floor3ProfessorSpawnTime = System.currentTimeMillis() + 10_300 + } + + "§c[BOSS] The Professor§r§f: I see. You have forced me to use my ultimate technique." -> { + floor3Professor = false + + floor3ProfessorGuardianPrepare = true + floor3ProfessorGuardianPrepareSpawnTime = System.currentTimeMillis() + 10_500 + } + + "§c[BOSS] The Professor§r§f: The process is irreversible, but I'll be stronger than a Wither now!" -> { + floor3ProfessorGuardian = true + } + + "§c[BOSS] The Professor§r§f: What?! My Guardian power is unbeatable!" -> { + floor3ProfessorGuardian = false + } + + + //F5 + "§c[BOSS] Livid§r§f: I respect you for making it to here, but I'll be your undoing." -> { + floor5lividEntity = findLivid() + } + + //F6 + "§c[BOSS] Sadan§r§f: ENOUGH!" -> { + floor6Giants = true + floor6GiantsSpawnTime = System.currentTimeMillis() + 7_400 + } + + "§c[BOSS] Sadan§r§f: You did it. I understand now, you have earned my respect." -> { + floor6Giants = false + floor6Sadan = true + floor6SadanSpawnTime = System.currentTimeMillis() + 32_500 + } + + "§c[BOSS] Sadan§r§f: NOOOOOOOOO!!! THIS IS IMPOSSIBLE!!" -> { + floor6Sadan = false + } + } + + if (message.matchRegex("§c\\[BOSS] (.*) Livid§r§f: Impossible! How did you figure out which one I was\\?!")) { + floor5lividEntity = null + } + } + + fun handleNewEntity(entity: Entity) { + if (floor3ProfessorGuardian) { + if (entity is EntityGuardian) { + if (floor3ProfessorGuardianEntity == null) { + floor3ProfessorGuardianEntity = entity + floor3ProfessorGuardianPrepare = false + } + } + } + } + + private fun findGuardians() { + guardians.clear() + + for (entity in Minecraft.getMinecraft().theWorld.loadedEntityList) { + if (entity is EntityGuardian) { + + val maxHealth = entity.baseMaxHealth.toInt() + + //F3 + if (maxHealth == 1_000_000 || maxHealth == 1_200_000) { + guardians.add(entity) + } + + //F3 Derpy + if (maxHealth == 2_000_000 || maxHealth == 2_400_000) { + guardians.add(entity) + } + + //M3 + if (maxHealth == 240_000_000 || maxHealth == 280_000_000) { + guardians.add(entity) + } + + //M3 Derpy + if (maxHealth == 120_000_000 || maxHealth == 140_000_000) { + guardians.add(entity) + } + } + } + } + + private fun findLivid(): EntityOtherPlayerMP? { + for (entity in Minecraft.getMinecraft().theWorld.loadedEntityList) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Livid ") { + return entity + } + } + } + + return null + } +} \ No newline at end of file diff --git a/src/main/java/at/lorenz/mod/dungeon/damageindicator/EntityData.kt b/src/main/java/at/lorenz/mod/dungeon/damageindicator/EntityData.kt new file mode 100644 index 000000000..4d60626fc --- /dev/null +++ b/src/main/java/at/lorenz/mod/dungeon/damageindicator/EntityData.kt @@ -0,0 +1,6 @@ +package at.lorenz.mod.dungeon.damageindicator + +import at.lorenz.mod.utils.LorenzColor +import net.minecraft.entity.EntityLivingBase + +class EntityData(val entity: EntityLivingBase, val text: String, val color: LorenzColor, val time: Long, val ignoreBlocks: Boolean, val delayedStart: Long) \ No newline at end of file -- cgit