nageyqma·git
HTTPS https://git.nageyqma.icu/nageyqma/odin_next.git SSH git@git.nageyqma.icu:nageyqma/odin_next.git

HTTPS 链接可直接 git clone;推送代码请使用 SSH。

10.4 KB · 220 行raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
package com.odtheking.odin.utils.skyblock

import com.odtheking.odin.events.LevelEvent
import com.odtheking.odin.events.MessageEvent
import com.odtheking.odin.events.TickEvent
import com.odtheking.odin.events.core.on
import com.odtheking.odin.features.impl.skyblock.Splits
import com.odtheking.odin.utils.PersonalBest
import com.odtheking.odin.utils.formatTime
import com.odtheking.odin.utils.handlers.schedule
import com.odtheking.odin.utils.modMessage
import com.odtheking.odin.utils.skyblock.dungeon.DungeonListener

data class Split(val regex: Regex, val name: String, var time: Long = 0L, var ticks: Long = 0L)
data class SplitsGroup(val splits: List<Split>, val personalBest: PersonalBest?)
data class SplitRow(val name: String, val time: Long, val tickTime: Long, val isCurrent: Boolean)

object SplitsManager {

    private var currentSplits: SplitsGroup = SplitsGroup(emptyList(), null)
    private var tickCounter: Long = 0L

    init {
        on<MessageEvent.Chat> {
            if (message == "Starting in 1 second.") startRun() else onSplitMessage(message)
        }

        on<TickEvent.Server> {
            tickCounter++
        }

        on<LevelEvent.Load> {
            currentSplits = SplitsGroup(emptyList(), null)
            tickCounter = 0L
        }
    }

    private fun startRun() {
        tickCounter = 0L
        currentSplits = when (LocationUtils.currentArea) {
            Island.Dungeon -> buildDungeonSplits() ?: return
            Island.Kuudra -> buildKuudraSplits() ?: return
            else -> SplitsGroup(emptyList(), null)
        }
    }

    private fun buildDungeonSplits(): SplitsGroup? {
        if (Splits.splitLocation == Splits.SplitLocation.KUUDRA_ONLY) return null
        val floor = DungeonListener.floor ?: return null

        val splits = dungeonSplits[floor.floorNumber].toMutableList().apply {
            addAll(0, listOf(
                Split(MORT_REGEX, "§2Blood Open"),
                Split(BLOOD_OPEN_REGEX, "§bBlood Clear"),
                Split(PORTAL_ENTRY_REGEX, "§dPortal Entry"),
            ))
            add(Split(DUNGEON_CLEARED_REGEX, "§1Total"))
        }
        return SplitsGroup(splits.map { it.copy() }, Splits.dungeonPBsList[floor.ordinal])
    }

    private fun buildKuudraSplits(): SplitsGroup? {
        if (Splits.splitLocation == Splits.SplitLocation.DUNGEONS_ONLY) return null
        return when (KuudraUtils.kuudraTier) {
            5 -> SplitsGroup(kuudraT5SplitsGroup.map { it.copy() }, Splits.kuudraT5PBs)
            4 -> SplitsGroup(kuudraSplitsGroup.map { it.copy() }, Splits.kuudraT4PBs)
            3 -> SplitsGroup(kuudraSplitsGroup.map { it.copy() }, Splits.kuudraT3PBs)
            2 -> SplitsGroup(kuudraSplitsGroup.map { it.copy() }, Splits.kuudraT2PBs)
            1 -> SplitsGroup(kuudraSplitsGroup.map { it.copy() }, Splits.kuudraT1PBs)
            else -> SplitsGroup(emptyList(), null)
        }
    }

    private fun onSplitMessage(message: String) {
        val splits = currentSplits.splits
        val split = splits.find { it.regex.matches(message) } ?: return
        if (split.time != 0L) return

        split.time = System.currentTimeMillis()
        split.ticks = tickCounter

        val index = splits.indexOf(split)
        if (index == 0) return

        val previous = splits[index - 1]
        val segmentTime = (split.time - previous.time) / 1000f

        if (index == splits.lastIndex) finishRun(index, segmentTime)
        else currentSplits.personalBest?.time(previous.name, segmentTime, "s§7!", "§6${previous.name} §7took §6", Splits.enabled)
    }

    private fun finishRun(totalIndex: Int, lastSegmentTime: Float) {
        val rows = currentRows()
        val splits = currentSplits.splits
        val personalBest = currentSplits.personalBest

        schedule(10) {
            if (rows.isEmpty()) return@schedule
            personalBest?.time(splits[totalIndex - 1].name, lastSegmentTime, "s§7!", "§6${splits[totalIndex - 1].name} §7took §6", Splits.enabled)
            personalBest?.time(splits[totalIndex].name, rows.last().time / 1000f, "s§7!", "§6Total time §7took §6", Splits.enabled)
            rows.forEachIndexed { i, row ->
                if (Splits.enabled) modMessage("§6${if (i == rows.lastIndex) "Total" else row.name} §7took §6${formatTime(row.time)}§7.")
            }
        }
    }

    fun currentRows(): List<SplitRow> {
        val splits = currentSplits.splits
        if (splits.isEmpty()) return emptyList()
        if (splits[0].time == 0L) return splits.map { SplitRow(it.name, 0L, 0L, isCurrent = false) }

        val last = splits.last()
        val latestTime = if (last.time != 0L) last.time else System.currentTimeMillis()
        val latestTicks = if (last.ticks != 0L) last.ticks else tickCounter

        val times = LongArray(splits.size).apply { this[splits.lastIndex] = latestTime - splits.first().time }
        val ticks = LongArray(splits.size).apply { this[splits.lastIndex] = latestTicks - splits.first().ticks }

        var currentIndex = -1
        for (i in 0 until splits.lastIndex) {
            val next = splits[i + 1]
            if (next.time != 0L) {
                times[i] = next.time - splits[i].time
                ticks[i] = next.ticks - splits[i].ticks
            } else {
                times[i] = latestTime - splits[i].time
                ticks[i] = latestTicks - splits[i].ticks
                currentIndex = i
                break
            }
        }
        return splits.indices.map { i -> SplitRow(splits[i].name, times[i], ticks[i], isCurrent = i == currentIndex) }
    }
}

val kuudraT5SplitsGroup = mutableListOf(
    Split(Regex("^\\[NPC] Elle: Okay adventurers, I will go and fish up Kuudra!$"), "§2Supplies"),
    Split(Regex("^\\[NPC] Elle: OMG! Great work collecting my supplies!$"), "§bBuild"),
    Split(Regex("^\\[NPC] Elle: Phew! The Ballista is finally ready! It should be strong enough to tank Kuudra's blows now!$"), "§dEaten"),
    Split(Regex("^(?!Elle has been eaten by Kuudra!$)(.{1,16}) has been eaten by Kuudra!$"), "§cStun"),
    Split(Regex("^(.{1,16}) destroyed one of Kuudra's pods!$"), "§4DPS"),
    Split(Regex("^\\[NPC] Elle: POW! SURELY THAT'S IT! I don't think he has any more in him!$"), "§4Cleared"),
    Split(Regex("^\\[NPC] Elle: Good job everyone. A hard fought battle come to an end. Let's get out of here before we run into any more trouble!$"), "Total"))

val kuudraSplitsGroup = mutableListOf(
    Split(Regex("^\\[NPC] Elle: Okay adventurers, I will go and fish up Kuudra!$"), "§2Supplies"),
    Split(Regex("^\\[NPC] Elle: OMG! Great work collecting my supplies!$"), "§bBuild"),
    Split(Regex("^\\[NPC] Elle: Phew! The Ballista is finally ready! It should be strong enough to tank Kuudra's blows now!$"), "§cStun"),
    Split(Regex("^\\[NPC] Elle: POW! SURELY THAT'S IT! I don't think he has any more in him!$"), "§4Cleared"),
    Split(Regex("^\\[NPC] Elle: Good job everyone. A hard fought battle come to an end. Let's get out of here before we run into any more trouble!$"), "Total"))

private val entryRegexes = listOf(
    Regex("^\\[BOSS] Bonzo: Gratz for making it this far, but I'm basically unbeatable\\.$"),
    Regex("^\\[BOSS] Scarf: This is where the journey ends for you, Adventurers\\.$"),
    Regex("^\\[BOSS] The Professor: I was burdened with terrible news recently\\.\\.\\.$"),
    Regex("^\\[BOSS] Thorn: Welcome Adventurers! I am Thorn, the Spirit! And host of the Vegan Trials!$"),
    Regex("^\\[BOSS] Livid: Welcome, you've arrived right on time\\. I am Livid, the Master of Shadows\\.$"),
    Regex("^\\[BOSS] Sadan: So you made it all the way here\\.\\.\\. Now you wish to defy me\\? Sadan\\?!$"),
    Regex("^\\[BOSS] Maxor: WELL! WELL! WELL! LOOK WHO'S HERE!$")
)

private val entranceSplitGroup = mutableListOf<Split>()

private val floor1SplitGroup = mutableListOf(
    Split(entryRegexes[0], "§cBonzo's Sike"),
    Split(Regex("\\[BOSS] Bonzo: Oh I'm dead!"), "§4Cleared"),
)

private val floor2SplitGroup = mutableListOf(
    Split(entryRegexes[1], "§cScarf's minions"),
    Split(Regex("^\\[BOSS] Scarf: Did you forget\\? I was taught by the best! Let's dance\\.$"), "§4Cleared"),
)

private val floor3SplitGroup = mutableListOf(
    Split(entryRegexes[2], "§cThe Guardians"),
    Split(Regex("^\\[BOSS] The Professor: Oh\\? You found my Guardians' one weakness\\?$"), "§aThe Professor"),
    Split(Regex("^\\[BOSS] The Professor: What\\?! My Guardian power is unbeatable!$"), "§4Cleared"),
)

private val floor4SplitGroup = mutableListOf(
    Split(entryRegexes[3], "§4Cleared"),
)

private val floor5SplitGroup = mutableListOf(
    Split(entryRegexes[4], "§4Cleared"),
)

private val floor6SplitGroup = mutableListOf(
    Split(entryRegexes[5], "§cTerracottas"),
    Split(Regex("^\\[BOSS] Sadan: ENOUGH!$"), "§aGiants"),
    Split(Regex("^\\[BOSS] Sadan: You did it\\. I understand now, you have earned my respect\\.$"), "§4Cleared"),
)

val floor7SplitGroup = mutableListOf(
    Split(entryRegexes[6], "§5Maxor"),
    Split(Regex("\\[BOSS] Storm: Pathetic Maxor, just like expected\\."), "§3Storm"),
    Split(Regex("\\[BOSS] Goldor: Who dares trespass into my domain\\?"), "§6Terminals"),
    Split(Regex("The Core entrance is opening!"), "§7Goldor"),
    Split(Regex("\\[BOSS] Necron: You went further than any human before, congratulations\\."), "§cNecron"),
    Split(Regex("\\[BOSS] Necron: All this, for nothing\\.\\.\\."), "§4Cleared"),
)

private val dungeonSplits = listOf(
    entranceSplitGroup,
    floor1SplitGroup,
    floor2SplitGroup,
    floor3SplitGroup,
    floor4SplitGroup,
    floor5SplitGroup,
    floor6SplitGroup,
    floor7SplitGroup,
)

private val PORTAL_ENTRY_REGEX = Regex("\\[BOSS] The Watcher: You have proven yourself\\. You may pass\\.")
private val DUNGEON_CLEARED_REGEX = Regex("^\\s*☠ Defeated (.+) in 0?([\\dhms ]+?)\\s*(\\(NEW RECORD!\\))?$")

// https://regex101.com/r/BXKhOI/1
private val BLOOD_OPEN_REGEX = Regex("^\\[BOSS] The Watcher: (Congratulations, you made it through the Entrance\\.|Ah, you've finally arrived\\.|Ah, we meet again\\.\\.\\.|So you made it this far\\.\\.\\. interesting\\.|You've managed to scratch and claw your way here, eh\\?|I'm starting to get tired of seeing you around here\\.\\.\\.|Oh\\.\\. hello\\?|Things feel a little more roomy now, eh\\?)$|^The BLOOD DOOR has been opened!$")
val MORT_REGEX = Regex("\\[NPC] Mort: Here, I found this map when I first entered the dungeon\\.|\\[NPC] Mort: Right-click the Orb for spells, and Left-click \\(or Drop\\) to use your Ultimate!")