From 44014aa68a8c672c779f38202a215ab1e8dcd8c5 Mon Sep 17 00:00:00 2001 From: J10a1n15 <45315647+j10a1n15@users.noreply.github.com> Date: Sat, 23 Mar 2024 19:14:25 +0100 Subject: [PATCH] Feature: Totem Of Corruption Overlay & Effective Area (#1139) Co-authored-by: hannibal2 <24389977+hannibal002@users.noreply.github.com> --- .../java/at/hannibal2/skyhanni/SkyHanniMod.kt | 2 + .../features/fishing/FishingConfig.java | 5 + .../fishing/TotemOfCorruptionConfig.java | 51 +++++++ .../features/fishing/TotemOfCorruption.kt | 143 ++++++++++++++++++ .../hannibal2/skyhanni/utils/RenderUtils.kt | 83 ++++++++++ 5 files changed, 284 insertions(+) create mode 100644 src/main/java/at/hannibal2/skyhanni/config/features/fishing/TotemOfCorruptionConfig.java create mode 100644 src/main/java/at/hannibal2/skyhanni/features/fishing/TotemOfCorruption.kt diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 125f3f0a74c5..d1fdbd9ea29f 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -294,6 +294,7 @@ import at.hannibal2.skyhanni.features.misc.ServerRestartTitle import at.hannibal2.skyhanni.features.misc.SkyBlockKickDuration import at.hannibal2.skyhanni.features.misc.SuperpairsClicksAlert import at.hannibal2.skyhanni.features.misc.TimeFeatures +import at.hannibal2.skyhanni.features.fishing.TotemOfCorruption import at.hannibal2.skyhanni.features.misc.TpsCounter import at.hannibal2.skyhanni.features.misc.compacttablist.AdvancedPlayerList import at.hannibal2.skyhanni.features.misc.compacttablist.TabListReader @@ -785,6 +786,7 @@ class SkyHanniMod { loadModule(PresentWaypoints()) loadModule(MiningEventTracker()) loadModule(JyrreTimer()) + loadModule(TotemOfCorruption()) loadModule(NewYearCakeReminder()) loadModule(SulphurSkitterBox()) loadModule(HighlightInquisitors()) diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/fishing/FishingConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/fishing/FishingConfig.java index d0bf5d1f38fb..0814f3a40bf2 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/fishing/FishingConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/fishing/FishingConfig.java @@ -55,6 +55,11 @@ public class FishingConfig { @Accordion public FishingProfitTrackerConfig fishingProfitTracker = new FishingProfitTrackerConfig(); + @Expose + @ConfigOption(name = "Totem of Corruption", desc = "") + @Accordion + public TotemOfCorruptionConfig totemOfCorruption = new TotemOfCorruptionConfig(); + @Expose @ConfigOption(name = "Sea Creature Tracker", desc = "") @Accordion diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/fishing/TotemOfCorruptionConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/fishing/TotemOfCorruptionConfig.java new file mode 100644 index 000000000000..67dbf364f7a9 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/fishing/TotemOfCorruptionConfig.java @@ -0,0 +1,51 @@ +package at.hannibal2.skyhanni.config.features.fishing; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import at.hannibal2.skyhanni.config.core.config.Position; +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigEditorColour; +import io.github.moulberry.moulconfig.annotations.ConfigEditorSlider; +import io.github.moulberry.moulconfig.annotations.ConfigOption; + +public class TotemOfCorruptionConfig { + + @Expose + @ConfigOption(name = "Show Overlay", desc = "Show the Totem of Corruption overlay." + + "\nShows the totem, in which effective area you are in, with the longest time left." + + "\n§cThis needs to be enabled for the other options to work.") + @ConfigEditorBoolean + @FeatureToggle + public boolean showOverlay = true; + + @Expose + @ConfigOption(name = "Distance Threshold", desc = "The minimum distance to the Totem of Corruption for the overlay." + + "\nThe effective distance of the totem is 16." + + "\n§cLimited by how far you can see the nametags.") + @ConfigEditorSlider(minValue = 0, maxValue = 100, minStep = 1) + public int distanceThreshold = 16; + + @Expose + @ConfigOption(name = "Hide Particles", desc = "Hide the particles of the Totem of Corruption.") + @ConfigEditorBoolean + public boolean hideParticles = true; + + @Expose + @ConfigOption(name = "Show Effective Area", desc = "Show the effective area (16 blocks) of the Totem of Corruption.") + @ConfigEditorBoolean + public boolean showEffectiveArea = true; + + @Expose + @ConfigOption(name = "Color of the area", desc = "The color of the area of the Totem of Corruption.") + @ConfigEditorColour + public String color = "0:153:18:159:85"; + + @Expose + @ConfigOption(name = "Warn when about to expire", desc = "Select the time in seconds when the totem is about to expire to warn you." + + "\nSelect 0 to disable.") + @ConfigEditorSlider(minValue = 0, maxValue = 60, minStep = 1) + public int warnWhenAboutToExpire = 5; + + @Expose + public Position position = new Position(50, 20, false, true); +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/fishing/TotemOfCorruption.kt b/src/main/java/at/hannibal2/skyhanni/features/fishing/TotemOfCorruption.kt new file mode 100644 index 000000000000..2deb676af114 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/fishing/TotemOfCorruption.kt @@ -0,0 +1,143 @@ +package at.hannibal2.skyhanni.features.fishing + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.events.ReceiveParticleEvent +import at.hannibal2.skyhanni.utils.ColorUtils.toChromaColor +import at.hannibal2.skyhanni.utils.EntityUtils +import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.sendTitle +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.RenderUtils.drawSphereInWorld +import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings +import at.hannibal2.skyhanni.utils.SoundUtils.playPlingSound +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.matches +import at.hannibal2.skyhanni.utils.TimeUnit +import at.hannibal2.skyhanni.utils.TimeUtils.format +import at.hannibal2.skyhanni.utils.getLorenzVec +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +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.seconds + +class TotemOfCorruption { + + private val config get() = SkyHanniMod.feature.fishing.totemOfCorruption + + private var display = emptyList() + private var totems: List = emptyList() + + private val group = RepoPattern.group("fishing.totemofcorruption") + private val totemNamePattern by group.pattern( + "totemname", + "§5§lTotem of Corruption" + ) + private val timeRemainingPattern by group.pattern( + "timeremaining", + "§7Remaining: §e(?:(?\\d+)m )?(?\\d+)s" + ) + private val ownerPattern by group.pattern( + "owner", + "§7Owner: §e(?.+)" + ) + + @SubscribeEvent + fun onRender(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!isOverlayEnabled() || display.isEmpty()) return + config.position.renderStrings(display, posLabel = "Totem of Corruption") + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!event.repeatSeconds(2)) return + if (!isOverlayEnabled()) return + + totems = getTotems() + display = createDisplay() + } + + @SubscribeEvent + fun onChatPacket(event: ReceiveParticleEvent) { + if (!isHideParticlesEnabled()) return + + for (totem in totems) { + if (event.type == EnumParticleTypes.SPELL_WITCH && event.speed == 0.0f) { + if (totem.location.distance(event.location) < 4.0) { + event.isCanceled = true + } + } + } + } + + @SubscribeEvent + fun onRenderWorld(event: LorenzRenderWorldEvent) { + if (!isEffectiveAreaEnabled()) return + + val color = config.color.toChromaColor() + for (totem in totems) { + // The center of the totem is the upper part + event.drawSphereInWorld(color, totem.location.add(y = 1), 16f) + } + } + + private fun getTimeRemaining(totem: EntityArmorStand): Duration? = + EntityUtils.getEntitiesNearby(totem.getLorenzVec(), 2.0) + .firstNotNullOfOrNull { entity -> + timeRemainingPattern.matchMatcher(entity.name) { + val minutes = group("min")?.toIntOrNull() ?: 0 + val seconds = group("sec")?.toInt() ?: 0 + (minutes * 60 + seconds).seconds + } + } + + private fun getOwner(totem: EntityArmorStand): String? = + EntityUtils.getEntitiesNearby(totem.getLorenzVec(), 2.0) + .firstNotNullOfOrNull { entity -> + ownerPattern.matchMatcher(entity.name) { + group("owner") + } + } + + + private fun createDisplay() = buildList { + val totem = getTotemToShow() ?: return@buildList + add("§5§lTotem of Corruption") + add("§7Remaining: §e${totem.timeRemaining.format(TimeUnit.MINUTE)}") + add("§7Owner: §e${totem.ownerName}") + } + + private fun getTotemToShow(): Totem? = totems + .filter { it.distance < config.distanceThreshold } + .maxByOrNull { it.timeRemaining } + + private fun getTotems(): List = EntityUtils.getEntitiesNextToPlayer(100.0) + .filter { totemNamePattern.matches(it.name) }.toList() + .mapNotNull { totem -> + val timeRemaining = getTimeRemaining(totem) ?: return@mapNotNull null + val owner = getOwner(totem) ?: return@mapNotNull null + + val timeToWarn = config.warnWhenAboutToExpire.seconds + if (timeToWarn > 0.seconds && timeRemaining == timeToWarn) { + playPlingSound() + sendTitle("§c§lTotem of Corruption §eabout to expire!", 5.seconds) + } + Totem(totem.getLorenzVec(), timeRemaining, owner) + } + + private fun isOverlayEnabled() = LorenzUtils.inSkyBlock && config.showOverlay + private fun isHideParticlesEnabled() = LorenzUtils.inSkyBlock && config.hideParticles + private fun isEffectiveAreaEnabled() = LorenzUtils.inSkyBlock && config.showEffectiveArea +} + +class Totem( + val location: LorenzVec, + val timeRemaining: Duration, + val ownerName: String, + val distance: Double = location.distanceToPlayer() +) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt index 53a228625cd1..c253c91ee871 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt @@ -702,6 +702,89 @@ object RenderUtils { GlStateManager.popMatrix() } + fun LorenzRenderWorldEvent.drawSphereInWorld( + color: Color, + location: LorenzVec, + radius: Float, + ) { + drawSphereInWorld(color, location.x, location.y, location.z, radius) + } + + fun LorenzRenderWorldEvent.drawSphereInWorld( + color: Color, + x: Double, + y: Double, + z: Double, + radius: Float, + ) { + GlStateManager.pushMatrix() + GL11.glNormal3f(0.0f, 1.0f, 0.0f) + + GlStateManager.enableDepth() + GlStateManager.enableBlend() + GlStateManager.depthFunc(GL11.GL_LEQUAL) + GlStateManager.disableCull() + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) + GlStateManager.enableAlpha() + GlStateManager.disableTexture2D() + color.bindColor() + + var x1 = x + var y1 = y + var z1 = z + val renderViewEntity = Minecraft.getMinecraft().renderViewEntity + val viewX = + renderViewEntity.prevPosX + (renderViewEntity.posX - renderViewEntity.prevPosX) * partialTicks.toDouble() + val viewY = + renderViewEntity.prevPosY + (renderViewEntity.posY - renderViewEntity.prevPosY) * partialTicks.toDouble() + val viewZ = + renderViewEntity.prevPosZ + (renderViewEntity.posZ - renderViewEntity.prevPosZ) * partialTicks.toDouble() + x1 -= viewX + y1 -= viewY + z1 -= viewZ + + val tessellator = Tessellator.getInstance() + val worldrenderer = tessellator.worldRenderer + worldrenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION) + + val segments = 32 + + for (phi in 0 until segments) { + for (theta in 0 until segments * 2) { + val x2 = x1 + radius * sin(Math.PI * phi / segments) * cos(2.0 * Math.PI * theta / (segments * 2)) + val y2 = y1 + radius * cos(Math.PI * phi / segments) + val z2 = z1 + radius * sin(Math.PI * phi / segments) * sin(2.0 * Math.PI * theta / (segments * 2)) + + val x3 = x1 + radius * sin(Math.PI * (phi + 1) / segments) * cos(2.0 * Math.PI * theta / (segments * 2)) + val y3 = y1 + radius * cos(Math.PI * (phi + 1) / segments) + val z3 = z1 + radius * sin(Math.PI * (phi + 1) / segments) * sin(2.0 * Math.PI * theta / (segments * 2)) + + worldrenderer.pos(x2, y2, z2).endVertex() + worldrenderer.pos(x3, y3, z3).endVertex() + + val x4 = x1 + radius * sin(Math.PI * (phi + 1) / segments) * cos(2.0 * Math.PI * (theta + 1) / (segments * 2)) + val y4 = y1 + radius * cos(Math.PI * (phi + 1) / segments) + val z4 = z1 + radius * sin(Math.PI * (phi + 1) / segments) * sin(2.0 * Math.PI * (theta + 1) / (segments * 2)) + + val x5 = x1 + radius * sin(Math.PI * phi / segments) * cos(2.0 * Math.PI * (theta + 1) / (segments * 2)) + val y5 = y1 + radius * cos(Math.PI * phi / segments) + val z5 = z1 + radius * sin(Math.PI * phi / segments) * sin(2.0 * Math.PI * (theta + 1) / (segments * 2)) + + worldrenderer.pos(x4, y4, z4).endVertex() + worldrenderer.pos(x5, y5, z5).endVertex() + } + } + + tessellator.draw() + + GlStateManager.enableCull() + GlStateManager.enableTexture2D() + GlStateManager.enableDepth() + GlStateManager.disableBlend() + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f) + GlStateManager.popMatrix() + } + private fun Color.bindColor() = GlStateManager.color(this.red / 255f, this.green / 255f, this.blue / 255f, this.alpha / 255f)