Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Totem Of Corruption Overlay & Effective Area #1139

Merged
merged 23 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,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
Expand Down Expand Up @@ -780,6 +781,7 @@ class SkyHanniMod {
loadModule(PresentWaypoints())
loadModule(MiningEventTracker())
loadModule(JyrreTimer())
loadModule(TotemOfCorruption())
loadModule(NewYearCakeReminder())
loadModule(SulphurSkitterBox())
loadModule(HighlightInquisitors())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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." +
j10a1n15 marked this conversation as resolved.
Show resolved Hide resolved
"\nShows the totem, in which effective area you are in, with the longest time left.")
@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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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.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
import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings
import at.hannibal2.skyhanni.utils.SoundUtils.playPlingSound
import at.hannibal2.skyhanni.utils.SpecialColour
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 java.awt.Color
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
import kotlin.time.toDuration

private val config get() = SkyHanniMod.feature.fishing.totemOfCorruption

private var display = emptyList<String>()
private var totems: List<Totem> = emptyList()
j10a1n15 marked this conversation as resolved.
Show resolved Hide resolved

class TotemOfCorruption {

private val group = RepoPattern.group("features.fishing.totemofcorruption")
j10a1n15 marked this conversation as resolved.
Show resolved Hide resolved
private val totemNamePattern by group.pattern(
"totemname",
"§5§lTotem of Corruption"
)
private val timeRemainingPattern by group.pattern(
"timeremaining",
"§7Remaining: §e(?:(?<min>\\d+)m )?(?<sec>\\d+)s"
)
private val ownerPattern by group.pattern(
"owner",
"§7Owner: §e(?<owner>.+)"
)

@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()
.filterNotNull()
.mapNotNull { totem ->
val timeRemaining = getTimeRemaining(totem)
val owner = getOwner(totem)
if (timeRemaining != null && owner != null) {
j10a1n15 marked this conversation as resolved.
Show resolved Hide resolved
if (
timeRemaining == config.warnWhenAboutToExpire.toDuration(DurationUnit.SECONDS)
j10a1n15 marked this conversation as resolved.
Show resolved Hide resolved
&& config.warnWhenAboutToExpire.toDuration(DurationUnit.SECONDS) > 0.seconds
) {
playPlingSound()
sendTitle("§c§lTotem of Corruption §eabout to expire!", 5.seconds)
}
Totem(totem.getLorenzVec(), timeRemaining, owner)
} else {
null
}
}
j10a1n15 marked this conversation as resolved.
Show resolved Hide resolved

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 = Color(SpecialColour.specialToChromaRGB(config.color), true)
j10a1n15 marked this conversation as resolved.
Show resolved Hide resolved
for (totem in totems) {
// The center of the totem is the upper part
RenderUtils.drawSphereInWorld(color, totem.location.add(y = 1), 16f, event.partialTicks)
}
}

private fun getTimeRemaining(totem: EntityArmorStand): Duration? =
EntityUtils.getEntitiesNearby<EntityArmorStand>(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<EntityArmorStand>(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<EntityArmorStand?> {
return EntityUtils.getEntitiesNextToPlayer<EntityArmorStand>(100.0)
.filter { totemNamePattern.matches(it.name) }.toList()
}

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()
)
85 changes: 85 additions & 0 deletions src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,91 @@ object RenderUtils {
GlStateManager.popMatrix()
}

fun drawSphereInWorld(
color: Color,
location: LorenzVec,
radius: Float,
partialTicks: Float,
) {
drawSphereInWorld(color, location.x, location.y, location.z, radius, partialTicks)
}

fun drawSphereInWorld(
color: Color,
x: Double,
y: Double,
z: Double,
radius: Float,
partialTicks: 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)

Expand Down
Loading