summaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/features/mining/eventtracker/MiningEventTracker.kt
blob: 97806b6fb0ada5aab6b71f6e5b05bed6270223c3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package at.hannibal2.skyhanni.features.mining.eventtracker

import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.config.ConfigManager
import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator
import at.hannibal2.skyhanni.data.BossbarData
import at.hannibal2.skyhanni.data.HypixelData
import at.hannibal2.skyhanni.data.IslandType
import at.hannibal2.skyhanni.events.BossbarUpdateEvent
import at.hannibal2.skyhanni.events.IslandChangeEvent
import at.hannibal2.skyhanni.events.LorenzChatEvent
import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent
import at.hannibal2.skyhanni.events.SecondPassedEvent
import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
import at.hannibal2.skyhanni.test.command.ErrorManager
import at.hannibal2.skyhanni.utils.APIUtil
import at.hannibal2.skyhanni.utils.ChatUtils
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland
import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher
import at.hannibal2.skyhanni.utils.SimpleTimeMark
import at.hannibal2.skyhanni.utils.TimeUtils
import at.hannibal2.skyhanni.utils.json.fromJson
import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern
import com.google.gson.JsonPrimitive
import kotlinx.coroutines.launch
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import java.io.IOException
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

@SkyHanniModule
object MiningEventTracker {
    private val config get() = SkyHanniMod.feature.mining.miningEvent

    private val patternGroup = RepoPattern.group("mining.eventtracker")
    private val bossbarPassivePattern by patternGroup.pattern(
        "bossbar.passive",
        "§e§lPASSIVE EVENT (?<event>.+) §e§lRUNNING FOR §a§l(?<time>\\S+)§r"
    )
    private val bossbarActivePattern by patternGroup.pattern(
        "bossbar.active",
        "§e§lEVENT (?<event>.+) §e§lACTIVE IN (?<area>.+) §e§lfor §a§l(?<time>\\S+)§r"
    )

    // TODO add test messages
    private val eventStartedPattern by patternGroup.pattern(
        "started",
        "(?:§.)*\\s+(?:§.)+§l(?<event>.+) STARTED!"
    )
    private val eventEndedPattern by patternGroup.pattern(
        "ended",
        "(?:§.)*\\s+(?:§.)+§l(?<event>.+) ENDED!"
    )

    private val defaultCooldown = 1.minutes

    private var eventEndTime = SimpleTimeMark.farPast()
    private var lastSentEvent: MiningEventType? = null

    private var canRequestAt = SimpleTimeMark.farPast()

    var apiErrorCount = 0

    val apiError get() = apiErrorCount > 0

    @SubscribeEvent
    fun onWorldChange(event: LorenzWorldChangeEvent) {
        eventEndTime = SimpleTimeMark.farPast()
        lastSentEvent = null
    }

    @SubscribeEvent
    fun onBossbarChange(event: BossbarUpdateEvent) {
        if (!isMiningIsland()) return
        if (LorenzUtils.lastWorldSwitch.passedSince() < 5.seconds) return
        if (!eventEndTime.isInPast()) {
            return
        }

        bossbarPassivePattern.matchMatcher(event.bossbar) {
            sendData(group("event"), group("time"))
        }
        bossbarActivePattern.matchMatcher(event.bossbar) {
            sendData(group("event"), group("time"))
        }
    }

    @SubscribeEvent
    fun onChat(event: LorenzChatEvent) {
        if (!isMiningIsland()) return

        eventStartedPattern.matchMatcher(event.message) {
            sendData(group("event"), null)
        }
        eventEndedPattern.matchMatcher(event.message) {
            lastSentEvent = null
        }
    }

    @SubscribeEvent
    fun onSecondPassed(event: SecondPassedEvent) {
        if (!config.enabled) return
        if (!LorenzUtils.inSkyBlock || (!config.outsideMining && !isMiningIsland())) return
        if (!canRequestAt.isInPast()) return

        fetchData()
    }

    private fun sendData(eventName: String, time: String?) {
        // we now ignore mineshaft events.
        if (IslandType.MINESHAFT.isInIsland()) return
        // TODO fix this via regex
        if (eventName == "SLAYER QUEST") return

        val eventType = MiningEventType.fromEventName(eventName) ?: run {
            if (!config.enabled) return
            ErrorManager.logErrorWithData(
                Exception("UnknownMiningEvent"), "Unknown mining event detected from string $eventName",
                "eventName" to eventName,
                "bossbar" to BossbarData.getBossbar(),
                "serverType" to LorenzUtils.skyBlockIsland,
                "fromChat" to (time == null)
            )
            return
        }

        if (!IslandType.DWARVEN_MINES.isInIsland() && eventType.dwarvenSpecific) return

        if (lastSentEvent == eventType) return
        lastSentEvent = eventType

        val timeRemaining = if (time == null) {
            eventType.defaultLength
        } else {
            TimeUtils.getDuration(time)
        }
        eventEndTime = SimpleTimeMark.now() + timeRemaining

        val serverId = HypixelData.serverId ?: return

        val miningEventData = MiningEventDataSend(
            LorenzUtils.skyBlockIsland,
            serverId,
            eventType,
            timeRemaining.inWholeMilliseconds,
            LorenzUtils.getPlayerUuid()
        )
        val miningEventJson = ConfigManager.gson.toJson(miningEventData)

        if (apiError) {
            ChatUtils.debug("blocked sending mining event data: api error")
            return
        }
        SkyHanniMod.coroutineScope.launch {
            sendData(miningEventJson)
        }
    }

    private fun sendData(json: String) {
        val response = try {
            APIUtil.postJSON("https://api.soopy.dev/skyblock/chevents/set", json)
        } catch (e: IOException) {
            if (LorenzUtils.debug) {
                ErrorManager.logErrorWithData(
                    e, "Sending mining event data was unsuccessful",
                    "sentData" to json
                )
            }
            return
        }
        if (!response.success) return

        val formattedResponse = ConfigManager.gson.fromJson<MiningEventDataReceive>(response.data)
        if (!formattedResponse.success) {
            if (!config.enabled) return
            ErrorManager.logErrorWithData(
                Exception("PostFailure"), "Sending mining event data was unsuccessful",
                "cause" to formattedResponse.cause,
                "sentData" to json
            )
        }
    }

    @SubscribeEvent
    fun onIslandChange(event: IslandChangeEvent) {
        if (apiError) {
            canRequestAt = SimpleTimeMark.now()
        }
    }

    private fun fetchData() {
        canRequestAt = SimpleTimeMark.now() + defaultCooldown
        SkyHanniMod.coroutineScope.launch {
            val data = try {
                APIUtil.getJSONResponse("https://api.soopy.dev/skyblock/chevents/get")
            } catch (e: Exception) {
                apiErrorCount++
                canRequestAt = SimpleTimeMark.now() + 20.minutes
                if (LorenzUtils.debug) {
                    ErrorManager.logErrorWithData(
                        e, "Receiving mining event data was unsuccessful",
                    )
                }
                return@launch
            }
            val miningEventData = ConfigManager.gson.fromJson(data, MiningEventDataReceive::class.java)

            if (!miningEventData.success) {
                ErrorManager.logErrorWithData(
                    Exception("PostFailure"), "Receiving mining event data was unsuccessful",
                    "cause" to miningEventData.cause,
                    "recievedData" to data
                )
                return@launch
            }
            apiErrorCount = 0

            canRequestAt = SimpleTimeMark.now() + miningEventData.data.updateIn.milliseconds

            MiningEventDisplay.updateData(miningEventData.data)
        }
    }

    @SubscribeEvent
    fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) {
        event.transform(29, "mining.miningEvent.showType") { element ->
            if (element.asString == "BOTH") JsonPrimitive("ALL") else element
        }
    }

    // ignoring mineshaft here is intentional
    fun isMiningIsland() = IslandType.DWARVEN_MINES.isInIsland() || IslandType.CRYSTAL_HOLLOWS.isInIsland()
}