From 6e4a3b7284d9f5b7f40d604a6713f3259641ed9e Mon Sep 17 00:00:00 2001 From: HiZe Date: Fri, 24 May 2024 19:34:54 +0200 Subject: Feature: Flare Display (#1803) Co-authored-by: CalMWolfs <94038482+CalMWolfs@users.noreply.github.com> Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt | 2 + .../config/features/combat/CombatConfig.java | 5 + .../config/features/combat/FlareConfig.java | 119 ++++++++++++ .../skyhanni/features/combat/FlareDisplay.kt | 199 +++++++++++++++++++++ 4 files changed, 325 insertions(+) create mode 100644 src/main/java/at/hannibal2/skyhanni/config/features/combat/FlareConfig.java create mode 100644 src/main/java/at/hannibal2/skyhanni/features/combat/FlareDisplay.kt (limited to 'src/main/java/at/hannibal2') diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 54a69f87b..3af43afb0 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -90,6 +90,7 @@ import at.hannibal2.skyhanni.features.chat.playerchat.PlayerChatModifier import at.hannibal2.skyhanni.features.chroma.ChromaManager import at.hannibal2.skyhanni.features.combat.BestiaryData import at.hannibal2.skyhanni.features.combat.FerocityDisplay +import at.hannibal2.skyhanni.features.combat.FlareDisplay import at.hannibal2.skyhanni.features.combat.HideDamageSplash import at.hannibal2.skyhanni.features.combat.damageindicator.DamageIndicatorManager import at.hannibal2.skyhanni.features.combat.endernodetracker.EnderNodeTracker @@ -943,6 +944,7 @@ class SkyHanniMod { loadModule(ColdOverlay()) loadModule(QuiverDisplay()) loadModule(QuiverWarning()) + loadModule(FlareDisplay) init() diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/combat/CombatConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/combat/CombatConfig.java index bf8e795d7..1c1ae14ae 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/combat/CombatConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/combat/CombatConfig.java @@ -49,6 +49,11 @@ public class CombatConfig { @Accordion public FerocityDisplayConfig ferocityDisplay = new FerocityDisplayConfig(); + @Expose + @ConfigOption(name = "Flare", desc = "") + @Accordion + public FlareConfig flare = new FlareConfig(); + @Expose @ConfigOption(name = "Hide Damage Splash", desc = "Hide all damage splashes anywhere in SkyBlock.") @ConfigEditorBoolean diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/combat/FlareConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/combat/FlareConfig.java new file mode 100644 index 000000000..b685f2942 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/combat/FlareConfig.java @@ -0,0 +1,119 @@ +package at.hannibal2.skyhanni.config.features.combat; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import at.hannibal2.skyhanni.config.core.config.Position; +import com.google.gson.annotations.Expose; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorColour; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDropdown; +import io.github.notenoughupdates.moulconfig.annotations.ConfigLink; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; + +public class FlareConfig { + + @Expose + @ConfigOption(name = "Enable", desc = "Show current active flares.") + @ConfigEditorBoolean + @FeatureToggle + public boolean enabled = false; + + @Expose + @ConfigOption(name = "Alert Type", desc = "What type of alert should be sent when a flare is about to expire.") + @ConfigEditorDropdown + public AlertType alertType = AlertType.CHAT; + + public enum AlertType { + NONE("No alert"), + CHAT("Chat"), + TITLE("Title"), + CHAT_TITLE("Chat & Title"), + ; + + private final String str; + + AlertType(String str) { + this.str = str; + } + + @Override + public String toString() { + return str; + } + } + + @Expose + @ConfigOption(name = "Display Type", desc = "Where to show the timer, as GUI element or in the world") + @ConfigEditorDropdown + public DisplayType displayType = DisplayType.GUI; + + public enum DisplayType { + GUI("GUI Element"), + WORLD("In World"), + BOTH("Both"), + ; + + private final String str; + + DisplayType(String str) { + this.str = str; + } + + @Override + public String toString() { + return str; + } + } + + @Expose + @ConfigOption(name = "Show Effective Area", desc = "Show the effective area of the Flare.") + @ConfigEditorDropdown + public OutlineType outlineType = OutlineType.NONE; + + public enum OutlineType { + NONE("No Outline"), + FILLED("Filled"), + WIREFRAME("Wireframe"), + CIRCLE("Circle") + ; + + private final String str; + + OutlineType(String str) { + this.str = str; + } + + @Override + public String toString() { + return str; + } + } + + @Expose + @ConfigOption(name = "Warning Flare Color", desc = "Color for Warning Flare.") + @ConfigEditorColour + public String warningColor = "0:153:29:255:136"; + + @Expose + @ConfigOption(name = "Alert Flare Color", desc = "Color for Alert Flare.") + @ConfigEditorColour + public String alertColor = "0:153:0:159:137"; + + @Expose + @ConfigOption(name = "SOS Flare Color", desc = "Color for SOS Flare.") + @ConfigEditorColour + public String sosColor = "0:153:159:0:5"; + + @Expose + @ConfigLink(owner = FlareConfig.class, field = "enabled") + public Position position = new Position(150, 200, false, true); + + @Expose + @ConfigOption(name = "Show Buff", desc = "Show the mana regen buff next to the flare name.") + @ConfigEditorBoolean + public boolean showManaBuff = false; + + @Expose + @ConfigOption(name = "Hide particles", desc = "Hide flame particles spawning around the flare.") + @ConfigEditorBoolean + public boolean hideParticles = false; +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/FlareDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/FlareDisplay.kt new file mode 100644 index 000000000..4dcadb3a5 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/FlareDisplay.kt @@ -0,0 +1,199 @@ +package at.hannibal2.skyhanni.features.combat + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.features.combat.FlareConfig +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.events.ReceiveParticleEvent +import at.hannibal2.skyhanni.events.SecondPassedEvent +import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.ColorUtils.toChromaColor +import at.hannibal2.skyhanni.utils.EntityUtils +import at.hannibal2.skyhanni.utils.EntityUtils.canBeSeen +import at.hannibal2.skyhanni.utils.EntityUtils.hasSkullTexture +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.RenderUtils +import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText +import at.hannibal2.skyhanni.utils.RenderUtils.drawSphereInWorld +import at.hannibal2.skyhanni.utils.RenderUtils.drawSphereWireframeInWorld +import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables +import at.hannibal2.skyhanni.utils.TimeUtils.format +import at.hannibal2.skyhanni.utils.TimeUtils.ticks +import at.hannibal2.skyhanni.utils.getLorenzVec +import at.hannibal2.skyhanni.utils.renderables.Renderable +import net.minecraft.entity.item.EntityArmorStand +import net.minecraft.util.EnumParticleTypes +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds + +object FlareDisplay { + + private val config get() = SkyHanniMod.feature.combat.flare + private var display = emptyList() + private var flares = mutableListOf() + + class Flare(val type: FlareType, val entity: EntityArmorStand, val location: LorenzVec = entity.getLorenzVec()) + + private val MAX_FLARE_TIME = 3.minutes + + private val flareSkins = mapOf( + "ewogICJ0aW1lc3RhbXAiIDogMTY0NjY4NzMwNjIyMywKICAicHJvZmlsZUlkIiA6ICI0MWQzYWJjMmQ3NDk0MDBjOTA5MGQ1NDM0ZDAzODMxYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZWdha2xvb24iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjJlMmJmNmMxZWMzMzAyNDc5MjdiYTYzNDc5ZTU4NzJhYzY2YjA2OTAzYzg2YzgyYjUyZGFjOWYxYzk3MTQ1OCIKICAgIH0KICB9Cn0=" + to FlareType.WARNING, + "ewogICJ0aW1lc3RhbXAiIDogMTY0NjY4NzMyNjQzMiwKICAicHJvZmlsZUlkIiA6ICI0MWQzYWJjMmQ3NDk0MDBjOTA5MGQ1NDM0ZDAzODMxYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZWdha2xvb24iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWQyYmY5ODY0NzIwZDg3ZmQwNmI4NGVmYTgwYjc5NWM0OGVkNTM5YjE2NTIzYzNiMWYxOTkwYjQwYzAwM2Y2YiIKICAgIH0KICB9Cn0=" + to FlareType.ALERT, + "ewogICJ0aW1lc3RhbXAiIDogMTY0NjY4NzM0NzQ4OSwKICAicHJvZmlsZUlkIiA6ICI0MWQzYWJjMmQ3NDk0MDBjOTA5MGQ1NDM0ZDAzODMxYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZWdha2xvb24iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzAwNjJjYzk4ZWJkYTcyYTZhNGI4OTc4M2FkY2VmMjgxNWI0ODNhMDFkNzNlYTg3YjNkZjc2MDcyYTg5ZDEzYiIKICAgIH0KICB9Cn0=" + to FlareType.SOS + ) + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!isEnabled()) return + if (config.displayType == FlareConfig.DisplayType.WORLD) return + config.position.renderRenderables(display, posLabel = "Flare Timer") + } + + @SubscribeEvent + fun onRenderWorld(event: LorenzRenderWorldEvent) { + if (!isEnabled()) return + if (config.displayType == FlareConfig.DisplayType.GUI) return + + for (flare in flares) { + val location = flare.location.add(-0.5, 0.0, -0.5) + val name = flare.type.displayName + val time = "§b${getRemainingTime(flare).format()}" + event.drawDynamicText(location, name, 1.5, ignoreBlocks = false) + event.drawDynamicText(location, time, 1.5, yOff = 10f, ignoreBlocks = false) + } + } + + @SubscribeEvent + fun onSecondsPassed(event: SecondPassedEvent) { + if (!isEnabled()) return + flares.removeIf { !it.entity.isEntityAlive } + for (entity in EntityUtils.getAllEntities().filterIsInstance()) { + if (!entity.canBeSeen()) continue + if (entity.ticksExisted.ticks > MAX_FLARE_TIME) continue + if (isAlreadyKnownFlare(entity)) continue + getFlareTypeForTexuture(entity)?.let { + flares.add(Flare(it, entity)) + } + } + var newDisplay: List? = null + for (type in FlareType.entries) { + val flare = getFlareForType(type) ?: continue + val remainingTime = getRemainingTime(flare) + + val name = type.displayName + if (newDisplay == null) { + newDisplay = buildList { + add(Renderable.string("$name: §b${remainingTime.format()}")) + if (config.showManaBuff) { + type.manaBuff?.let { + add(Renderable.string(" §b$it §7mana regen")) + } + } + } + } + if (remainingTime > 5.seconds) continue + val message = "$name §eexpires in: §b${remainingTime.inWholeSeconds}s" + when (config.alertType) { + FlareConfig.AlertType.CHAT -> { + ChatUtils.chat(message) + } + + FlareConfig.AlertType.TITLE -> { + LorenzUtils.sendTitle(message, 1.seconds) + } + + FlareConfig.AlertType.CHAT_TITLE -> { + ChatUtils.chat(message) + LorenzUtils.sendTitle(message, 1.seconds) + } + + else -> {} + } + } + display = newDisplay ?: emptyList() + } + + private fun getRemainingTime(flare: Flare): Duration { + val entity = flare.entity + val aliveTime = entity.ticksExisted.ticks + val remainingTime = (MAX_FLARE_TIME - aliveTime) + return remainingTime + } + + private fun getFlareForType(type: FlareType): Flare? = flares.firstOrNull { it.type == type } + + private fun getFlareTypeForTexuture(entity: EntityArmorStand): FlareType? = + flareSkins.entries.firstOrNull { entity.hasSkullTexture(it.key) }?.value + + private fun isAlreadyKnownFlare(entity: EntityArmorStand): Boolean = + flares.any { it.entity.entityId == entity.entityId } + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + flares.clear() + display = emptyList() + } + + @SubscribeEvent + fun onRender(event: LorenzRenderWorldEvent) { + if (!isEnabled()) return + if (config.outlineType == FlareConfig.OutlineType.NONE) return + + for (flare in flares) { + val entity = flare.entity + val location = flare.location + + val color = when (flare.type) { + FlareType.WARNING -> config.warningColor + FlareType.ALERT -> config.alertColor + FlareType.SOS -> config.sosColor + }.toChromaColor() + + when (config.outlineType) { + FlareConfig.OutlineType.FILLED -> { + event.drawSphereInWorld(color, location, 40f) + } + + FlareConfig.OutlineType.WIREFRAME -> { + event.drawSphereWireframeInWorld(color, location, 40f) + } + + FlareConfig.OutlineType.CIRCLE -> { + RenderUtils.drawCircle(entity, event.partialTicks, 40.0, color) + } + + else -> {} + } + } + } + + @SubscribeEvent + fun onReceiveParticle(event: ReceiveParticleEvent) { + if (!isEnabled()) return + if (!config.hideParticles) return + + val location = event.location + val distance = flares.minOfOrNull { it.location.distance(location) } ?: return + if (distance < 2.5) { + if (event.type == EnumParticleTypes.FLAME) { + event.cancel() + } + } + } + + enum class FlareType(val displayName: String, val manaBuff: String?) { + SOS("§5SOS Flare", "+125%"), + ALERT("§9Alert Flare", "+50%"), + WARNING("§aWarning Flare", null), + ; + } + + private fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled +} -- cgit