From b2d1c22d4baebc6eaf9eef823b11fa9d1801ced1 Mon Sep 17 00:00:00 2001 From: ILike2WatchMemes Date: Thu, 12 Sep 2024 02:31:14 +0200 Subject: Feature: Zombie Shootout QOL (#2497) --- .../config/features/event/EventConfig.java | 6 + .../features/event/carnival/CarnivalConfig.java | 13 ++ .../event/carnival/ZombieShootoutConfig.java | 41 ++++++ .../event/carnival/CarnivalZombieShootout.kt | 164 +++++++++++++++++++++ .../at/hannibal2/skyhanni/utils/RenderUtils.kt | 21 +++ 5 files changed, 245 insertions(+) create mode 100644 src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java create mode 100644 src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/ZombieShootoutConfig.java create mode 100644 src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalZombieShootout.kt diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java index 57d3e0699..1c916bbe1 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java @@ -1,6 +1,7 @@ package at.hannibal2.skyhanni.config.features.event; import at.hannibal2.skyhanni.config.features.event.bingo.BingoConfig; +import at.hannibal2.skyhanni.config.features.event.carnival.CarnivalConfig; import at.hannibal2.skyhanni.config.features.event.diana.DianaConfig; import at.hannibal2.skyhanni.config.features.event.hoppity.HoppityEggsConfig; import at.hannibal2.skyhanni.config.features.event.waypoints.LobbyWaypointsConfig; @@ -43,6 +44,11 @@ public class EventConfig { @Expose public GreatSpookConfig spook = new GreatSpookConfig(); + @ConfigOption(name = "Carnival", desc = "") + @Accordion + @Expose + public CarnivalConfig carnival = new CarnivalConfig(); + // comment in if the event is needed again // @ConfigOption(name = "300þ Anniversary Celebration", desc = "Features for the 300þ year of SkyBlock") @Accordion diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java new file mode 100644 index 000000000..b874f7558 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java @@ -0,0 +1,13 @@ +package at.hannibal2.skyhanni.config.features.event.carnival; + +import com.google.gson.annotations.Expose; +import io.github.notenoughupdates.moulconfig.annotations.Accordion; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; + +public class CarnivalConfig { + + @Expose + @ConfigOption(name = "Zombie Shootout", desc = "") + @Accordion + public ZombieShootoutConfig zombieShootout = new ZombieShootoutConfig(); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/ZombieShootoutConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/ZombieShootoutConfig.java new file mode 100644 index 000000000..5709e7e58 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/ZombieShootoutConfig.java @@ -0,0 +1,41 @@ +package at.hannibal2.skyhanni.config.features.event.carnival; + +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.ConfigLink; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; + +public class ZombieShootoutConfig { + + @Expose + @ConfigOption(name = "Enabled", desc = "QOL Features for Zombie Shootout.") + @FeatureToggle + @ConfigEditorBoolean + public boolean enabled = false; + + @Expose + @ConfigOption(name = "Colored Hitboxes", desc = "Display colored hitboxes for zombies and lamps.") + @ConfigEditorBoolean + public boolean coloredHitboxes = true; + + @Expose + @ConfigOption(name = "Colored Lines", desc = "Display a colored line to lamps.") + @ConfigEditorBoolean + public boolean coloredLines = true; + + @Expose + @ConfigOption(name = "Highest Only", desc = "Only draw colored hitboxes/lines for the highest scoring zombies.") + @ConfigEditorBoolean + public boolean highestOnly = false; + + @Expose + @ConfigOption(name = "Lamp Timer", desc = "Show time until current lamp disappears.") + @ConfigEditorBoolean + public boolean lampTimer = true; + + @Expose + @ConfigLink(owner = ZombieShootoutConfig.class, field = "lampTimer") + public Position lampPosition = new Position(20, 20, false, true); +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalZombieShootout.kt b/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalZombieShootout.kt new file mode 100644 index 000000000..cae5cbda1 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/event/carnival/CarnivalZombieShootout.kt @@ -0,0 +1,164 @@ +package at.hannibal2.skyhanni.features.event.carnival + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent +import at.hannibal2.skyhanni.events.ServerBlockChangeEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.EntityUtils +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.RegexUtils.matches +import at.hannibal2.skyhanni.utils.RenderUtils +import at.hannibal2.skyhanni.utils.RenderUtils.draw3DLine +import at.hannibal2.skyhanni.utils.RenderUtils.drawHitbox +import at.hannibal2.skyhanni.utils.RenderUtils.drawWaypointFilled +import at.hannibal2.skyhanni.utils.RenderUtils.exactPlayerEyeLocation +import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderable +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.renderables.Renderable +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraft.entity.monster.EntityZombie +import net.minecraft.init.Blocks +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.awt.Color +import kotlin.time.Duration.Companion.seconds + +@SkyHanniModule +object CarnivalZombieShootout { + + private val config get() = SkyHanniMod.feature.event.carnival.zombieShootout + + private data class Lamp(var pos: LorenzVec, var time: SimpleTimeMark) + private data class Updates(var zombie: SimpleTimeMark, var content: SimpleTimeMark) + + private var lastUpdate = Updates(SimpleTimeMark.farPast(), SimpleTimeMark.farPast()) + + private var content = Renderable.horizontalContainer(listOf()) + private var drawZombies = mapOf() + private var lamp: Lamp? = null + private var started = false + + private val patternGroup = RepoPattern.group("event.carnival") + + /** + * REGEX-TEST: [NPC] Carnival Cowboy: Good luck, pal! + */ + private val startPattern by patternGroup.pattern( + "shootout.start", + "\\[NPC] Carnival Cowboy: Good luck, pal!", + ) + + /** + * REGEX-TEST: Zombie Shootout + */ + private val endPattern by patternGroup.pattern( + "shootout.end", + " {29}Zombie Shootout", + ) + + enum class ZombieType(val points: Int, val helmet: String, val color: Color) { + LEATHER(30, "Leather Cap", Color(165, 42, 42)), //Brown + IRON(50, "Iron Helmet", Color(192, 192, 192)), //Silver + GOLD(80, "Golden Helmet", Color(255, 215, 0)), //Gold + DIAMOND(120, "Diamond Helmet", Color(185, 242, 255)) //Diamond + } + + @SubscribeEvent + fun onRenderWorld(event: LorenzRenderWorldEvent) { + if (!isEnabled() || !started || (!config.coloredHitboxes && !config.coloredLines)) return + + lamp?.let { + if (config.coloredLines) event.draw3DLine(event.exactPlayerEyeLocation(), it.pos.add(0.0, 0.5, 0.0), Color.RED, 3, false) + if (config.coloredHitboxes) event.drawWaypointFilled(it.pos, Color.RED, minimumAlpha = 1.0f) + } + + if (!config.coloredHitboxes) return + + if (lastUpdate.zombie.passedSince() >= 0.25.seconds) { + val nearbyZombies = EntityUtils.getEntitiesNextToPlayer(50.0).mapNotNull { zombie -> + if (zombie.health <= 0) return@mapNotNull null + val armor = zombie.getCurrentArmor(3) ?: return@mapNotNull null + val type = toType(armor) ?: return@mapNotNull null + zombie to type + }.toMap() + + drawZombies = nearbyZombies.filterValues { it == nearbyZombies.values.maxByOrNull { it.points } } + lastUpdate.zombie = SimpleTimeMark.now() + } + + for ((zombie, type) in drawZombies) { + val entity = EntityUtils.getEntityByID(zombie.entityId) ?: continue + + event.drawHitbox( + entity.entityBoundingBox.expand(0.1, 0.05, 0.0).offset(0.0, 0.05, 0.0), + lineWidth = 3, + type.color, + depth = false, + ) + } + } + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!isEnabled() || !started || !config.lampTimer) return + + val time = lamp?.time ?: return + + val lamp = ItemStack(Blocks.redstone_lamp) + val timer = 6.seconds - (SimpleTimeMark.now() - time) + val prefix = when (timer) { + in 4.seconds..6.seconds -> "§a" + in 2.seconds..4.seconds -> "§e" + else -> "§c" + } + + if (lastUpdate.content.passedSince() >= 0.1.seconds) { + content = Renderable.horizontalContainer( + listOf( + Renderable.itemStack(lamp), + Renderable.string("§6Disappears in $prefix${timer}"), + ), + spacing = 1, + verticalAlign = RenderUtils.VerticalAlignment.CENTER, + ) + lastUpdate.content = SimpleTimeMark.now() + } + + config.lampPosition.renderRenderable(content, posLabel = "Lantern Timer") + } + + @SubscribeEvent + fun onBlockChange(event: ServerBlockChangeEvent) { + if (!isEnabled() || !started) return + + val old = event.old + val new = event.new + + lamp = when { + old == "redstone_lamp" && new == "lit_redstone_lamp" -> Lamp(event.location, SimpleTimeMark.now()) + old == "lit_redstone_lamp" && new == "redstone_lamp" -> null + else -> lamp + } + } + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!isEnabled()) return + + val message = event.message.removeColor() + + if (startPattern.matches(message)) { + started = true + } else if (endPattern.matches(message)) { + started = false + } + } + + private fun toType(item: ItemStack) = ZombieType.entries.find { it.helmet == item.displayName } + + private fun isEnabled() = config.enabled && LorenzUtils.skyBlockArea == "Carnival" +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt index 0d6417688..3442032e6 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt @@ -1611,6 +1611,27 @@ object RenderUtils { GlStateManager.enableCull() } + fun LorenzRenderWorldEvent.drawHitbox( + boundingBox: AxisAlignedBB, + lineWidth: Int, + color: Color, + depth: Boolean, + ) { + val cornersTop = boundingBox.getCorners(boundingBox.maxY) + val cornersBottom = boundingBox.getCorners(boundingBox.minY) + + // Draw lines for the top and bottom faces + for (i in 0..3) { + this.draw3DLine(cornersTop[i], cornersTop[(i + 1) % 4], color, lineWidth, depth) + this.draw3DLine(cornersBottom[i], cornersBottom[(i + 1) % 4], color, lineWidth, depth) + } + + // Draw lines connecting the top and bottom faces + for (i in 0..3) { + this.draw3DLine(cornersBottom[i], cornersTop[i], color, lineWidth, depth) + } + } + fun chromaColor( timeTillRepeat: Duration, offset: Float = 0f, -- cgit