From 5e77ccdf9c55ba3791427b67dc9456d5ae874380 Mon Sep 17 00:00:00 2001 From: Empa <42304516+ItsEmpa@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:00:51 +0200 Subject: Feature: Miniboss timer (#2042) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> Co-authored-by: ItsEmpa --- .../features/crimsonisle/CrimsonIsleConfig.java | 10 + .../features/nether/CrimsonMinibossRespawnTimer.kt | 273 +++++++++++++++++++++ .../at/hannibal2/skyhanni/utils/EntityUtils.kt | 18 +- 3 files changed, 294 insertions(+), 7 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/features/nether/CrimsonMinibossRespawnTimer.kt (limited to 'src/main') diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/crimsonisle/CrimsonIsleConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/crimsonisle/CrimsonIsleConfig.java index f2398e207..b0b554224 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/crimsonisle/CrimsonIsleConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/crimsonisle/CrimsonIsleConfig.java @@ -26,6 +26,16 @@ public class CrimsonIsleConfig { @Accordion public MatriarchHelperConfig matriarchHelper = new MatriarchHelperConfig(); + @Expose + @ConfigOption(name = "Miniboss Respawn Timer", desc = "Shows a timer for when minibosses will respawn.") + @ConfigEditorBoolean + @FeatureToggle + public boolean minibossRespawnTimer = false; + + @Expose + @ConfigLink(owner = CrimsonIsleConfig.class, field = "minibossRespawnTimer") + public Position minibossTimerPosition = new Position(20, 50); + @Expose @ConfigOption(name = "Pablo NPC Helper", desc = "Show a clickable message that grabs the flower needed from your sacks.") @ConfigEditorBoolean diff --git a/src/main/java/at/hannibal2/skyhanni/features/nether/CrimsonMinibossRespawnTimer.kt b/src/main/java/at/hannibal2/skyhanni/features/nether/CrimsonMinibossRespawnTimer.kt new file mode 100644 index 000000000..3c5325697 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/nether/CrimsonMinibossRespawnTimer.kt @@ -0,0 +1,273 @@ +package at.hannibal2.skyhanni.features.nether + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.data.mob.MobData +import at.hannibal2.skyhanni.events.DebugDataCollectEvent +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.events.SecondPassedEvent +import at.hannibal2.skyhanni.features.nether.CrimsonMinibossRespawnTimer.MiniBoss.Companion.isSpawned +import at.hannibal2.skyhanni.features.nether.CrimsonMinibossRespawnTimer.MiniBoss.Companion.isSpawningSoon +import at.hannibal2.skyhanni.features.nether.CrimsonMinibossRespawnTimer.MiniBoss.Companion.isTimerKnown +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.EntityUtils +import at.hannibal2.skyhanni.utils.LocationUtils.isInside +import at.hannibal2.skyhanni.utils.LocationUtils.isPlayerInside +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher +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.TimeUtils.format +import at.hannibal2.skyhanni.utils.renderables.Renderable +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import at.hannibal2.skyhanni.utils.toLorenzVec +import net.minecraft.tileentity.TileEntityBeacon +import net.minecraft.util.AxisAlignedBB +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds + +@SkyHanniModule +object CrimsonMinibossRespawnTimer { + + private val config get() = SkyHanniMod.feature.crimsonIsle + + private val patternGroup = RepoPattern.group("crimson.miniboss") + + /** + * REGEX-TEST: §c§lBEWARE - Bladesoul Is Spawning. + */ + private val spawnPattern by patternGroup.pattern( + "spawn", + "§c§lBEWARE - (?.+) Is Spawning\\.", + ) + + /** + * REGEX-TEST: §f §r§6§lBLADESOUL DOWN! + */ + private val downPattern by patternGroup.pattern( + "down", + "§f\\s*§r§6§l(?.+) DOWN!", + ) + + private var currentAreaBoss: MiniBoss? = null + + private var display: Renderable? = null + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!isEnabled()) return + val message = event.message + downPattern.matchMatcher(message) { + val miniBoss = MiniBoss.fromName(group("name")) ?: return + miniBoss.nextSpawnTime = SimpleTimeMark.now() + 2.minutes + miniBoss.spawned = false + miniBoss.possibleSpawnTime = null + miniBoss.foundBeacon = null + update() + return + } + spawnPattern.matchMatcher(message) { + val miniBoss = MiniBoss.fromName(group("name")) ?: return + miniBoss.spawned = true + miniBoss.possibleSpawnTime = null + miniBoss.foundBeacon = null + update() + return + } + } + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!isEnabled()) return + val renderable = display ?: drawDisplay() + config.minibossTimerPosition.renderRenderable(renderable, posLabel = "Miniboss Timer") + } + + @SubscribeEvent + fun onSecondPassed(event: SecondPassedEvent) { + if (!isEnabled()) return + updateArea() + update() + } + + private fun updateArea() { + MiniBoss.entries.forEach { + if (it.lastSeenArea.passedSince() > 2.minutes) { + it.nextSpawnTime = null + it.possibleSpawnTime = null + it.foundBeacon = null + it.spawned = null + } + } + currentAreaBoss = MiniBoss.entries.firstOrNull { + it.area.isPlayerInside() + } + val now = SimpleTimeMark.now() + currentAreaBoss?.lastSeenArea = now + val boss = currentAreaBoss ?: return + if (boss.isTimerKnown()) return + + val isBossInArea = MobData.skyblockMobs.filter { + it.name == boss.displayName + }.any { boss.area.isInside(it.baseEntity.position.toLorenzVec()) } + if (isBossInArea) { + boss.spawned = true + boss.foundBeacon = null + boss.possibleSpawnTime = null + return + } + boss.spawned = false + + val isThereBeacon = EntityUtils.getAllTileEntities().filter { it is TileEntityBeacon }.any { + boss.area.isInside(it.pos.toLorenzVec()) + } + if (boss.foundBeacon == true && !isThereBeacon) { + boss.foundBeacon = false + boss.possibleSpawnTime = null + boss.nextSpawnTime = now + 1.minutes + return + } + if (boss.possibleSpawnTime != null) return + if (isThereBeacon && boss.foundBeacon == null) { + boss.foundBeacon = true + boss.possibleSpawnTime = now + 1.minutes to now + 2.minutes + return + } + if (!isThereBeacon && boss.foundBeacon == null) { + boss.foundBeacon = false + boss.possibleSpawnTime = now to now + 1.minutes + } + } + + private fun update() { + display = drawDisplay() + } + + private fun drawDisplay(): Renderable { + val lines = MiniBoss.entries.map { + val timer = it.nextSpawnTime + val possibleTimer = it.possibleSpawnTime + Renderable.string( + buildString { + append("§b${it.displayName}: ") + if (it.isSpawned()) append("§aSPAWNED!") + else when { + it.isSpawningSoon() -> append("§6Soon!") + it.isTimerKnown() -> append("§e${timer?.timeUntil()?.format()}") + possibleTimer != null -> { + val (start, end) = possibleTimer + if (start.timeUntil().isNegative()) append("§e~Now - ") + else append("§e~${start.timeUntil().format()} - ") + if (end.timeUntil().isNegative()) append("§eNow") + else append("§e${end.timeUntil().format()}") + } + else -> append("§cUnknown") + } + }, + ) + } + return Renderable.verticalContainer(lines) + } + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + MiniBoss.entries.forEach { + it.nextSpawnTime = null + it.possibleSpawnTime = null + it.foundBeacon = null + it.spawned = null + it.lastSeenArea = SimpleTimeMark.farPast() + } + currentAreaBoss = null + } + + @SubscribeEvent + fun onDebug(event: DebugDataCollectEvent) { + event.title("Crimson Isle Miniboss") + event.addIrrelevant { + if (!isEnabled()) { + add("Feature is Disabled") + return@addIrrelevant + } + add("Current Area Boss: ${currentAreaBoss?.displayName}") + MiniBoss.entries.forEach { + add("") + add(it.displayName) + add(" Timer ${it.nextSpawnTime?.timeUntil()?.format()}") + add( + " Possible Timer ${it.possibleSpawnTime?.first?.timeUntil()?.format()} - " + "${ + it.possibleSpawnTime?.second?.timeUntil()?.format() + }", + ) + add(" Found Beacon ${it.foundBeacon}") + add(" Spawned ${it.spawned}") + add(" Last Seen Area ${it.lastSeenArea.passedSince().format()}") + } + } + } + + enum class MiniBoss( + val displayName: String, + val area: AxisAlignedBB, + var nextSpawnTime: SimpleTimeMark? = null, + var possibleSpawnTime: Pair? = null, + var foundBeacon: Boolean? = null, + var spawned: Boolean? = null, + var lastSeenArea: SimpleTimeMark = SimpleTimeMark.farPast(), + ) { + BLADESOUL( + "Bladesoul", + LorenzVec(-330, 80, -486).axisAlignedTo(LorenzVec(-257, 107, -545)), + ), + MAGE_OUTLAW( + "Mage Outlaw", + LorenzVec(-200, 98, -843).axisAlignedTo(LorenzVec(-162, 116, -878)), + ), + BARBARIAN_DUKE_X( + "Barbarian Duke X", + LorenzVec(-550, 101, -890).axisAlignedTo(LorenzVec(-522, 131, -918)), + ), + ASHFANG( + "Ashfang", + LorenzVec(-462, 155, -1035).axisAlignedTo(LorenzVec(-507, 131, -955)), + ), + MAGMA_BOSS( + "Magma Boss", + LorenzVec(-318, 59, -751).axisAlignedTo(LorenzVec(-442, 90, -851)), + ), + ; + + override fun toString() = displayName + + companion object { + fun fromName(spawnName: String) = entries.firstOrNull { + it.displayName.removeColor().lowercase() == spawnName.lowercase() + } + + fun MiniBoss.isTimerKnown(): Boolean { + val timer = nextSpawnTime ?: return false + return timer.passedSince() < 2.minutes + 5.seconds + } + + fun MiniBoss.isSpawningSoon(): Boolean { + if (spawned == true) return false + val timer = nextSpawnTime ?: return false + return timer.passedSince() in 0.seconds..10.seconds + } + + fun MiniBoss.isSpawned(): Boolean { + if (spawned == true) return true + val timer = nextSpawnTime ?: return false + return (timer.passedSince() - 2.minutes) in 0.seconds..20.seconds + } + } + } + + private fun isEnabled() = IslandType.CRIMSON_ISLE.isInIsland() && config.minibossRespawnTimer + +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt index 5e85bb64f..5f52508f2 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/EntityUtils.kt @@ -27,12 +27,15 @@ import net.minecraft.entity.monster.EntityEnderman import net.minecraft.entity.player.EntityPlayer import net.minecraft.item.ItemStack import net.minecraft.potion.Potion +import net.minecraft.scoreboard.ScorePlayerTeam +import net.minecraft.tileentity.TileEntity import net.minecraft.util.AxisAlignedBB import net.minecraftforge.client.event.RenderLivingEvent //#if FORGE import net.minecraftforge.fml.common.eventhandler.Event import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + //#endif @SkyHanniModule @@ -131,8 +134,7 @@ object EntityUtils { return gameProfile.properties.entries() .filter { it.key == "textures" } .map { it.value } - .firstOrNull { it.name == "textures" } - ?.value + .firstOrNull { it.name == "textures" }?.value } inline fun getEntitiesNextToPlayer(radius: Double): Sequence = @@ -182,6 +184,10 @@ object EntityUtils { ) it else it.toMutableList() // TODO: while i am here, i want to point out that copying the entity list does not constitute proper synchronization, but *does* make crashes because of it rarer. }?.asSequence()?.filterNotNull() ?: emptySequence() + fun getAllTileEntities(): Sequence = Minecraft.getMinecraft()?.theWorld?.loadedTileEntityList?.let { + if (Minecraft.getMinecraft().isCallingFromMinecraftThread) it else it.toMutableList() + }?.asSequence()?.filterNotNull() ?: emptySequence() + fun Entity.canBeSeen(radius: Double = 150.0) = getLorenzVec().add(y = 0.5).canBeSeen(radius) fun getEntityByID(entityId: Int) = Minecraft.getMinecraft()?.thePlayer?.entityWorld?.getEntityByID(entityId) @@ -217,11 +223,10 @@ object EntityUtils { SkyHanniRenderEntityEvent.Post(event.entity, event.renderer, event.x, event.y, event.z).postAndCatch() } -//#if MC < 11400 + //#if MC < 11400 @SubscribeEvent fun onEntityRenderSpecialsPre( - event: - RenderLivingEvent.Specials.Pre<*>, + event: RenderLivingEvent.Specials.Pre<*>, ) { val shEvent = SkyHanniRenderEntityEvent.Specials.Pre(event.entity, event.renderer, event.x, event.y, event.z) if (shEvent.postAndCatch()) { @@ -231,8 +236,7 @@ object EntityUtils { @SubscribeEvent fun onEntityRenderSpecialsPost( - event: - RenderLivingEvent.Specials.Post<*>, + event: RenderLivingEvent.Specials.Post<*>, ) { SkyHanniRenderEntityEvent.Specials.Post(event.entity, event.renderer, event.x, event.y, event.z).postAndCatch() } -- cgit