summaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityCallWarning.kt
blob: 57bc0975fac839796577d9c88fd20384dbb5fede (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
package at.hannibal2.skyhanni.features.event.hoppity

import at.hannibal2.skyhanni.events.ConfigLoadEvent
import at.hannibal2.skyhanni.events.GuiRenderEvent
import at.hannibal2.skyhanni.events.LorenzChatEvent
import at.hannibal2.skyhanni.events.LorenzKeyPressEvent
import at.hannibal2.skyhanni.events.SecondPassedEvent
import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryAPI
import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
import at.hannibal2.skyhanni.utils.ColorUtils.toChromaColorInt
import at.hannibal2.skyhanni.utils.ConditionalUtils
import at.hannibal2.skyhanni.utils.DelayedRun
import at.hannibal2.skyhanni.utils.HypixelCommands
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.RegexUtils.matches
import at.hannibal2.skyhanni.utils.SoundUtils
import at.hannibal2.skyhanni.utils.StringUtils.isValidUuid
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.Gui
import net.minecraft.client.renderer.GlStateManager
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import org.lwjgl.input.Keyboard
import java.time.Instant
import kotlin.math.sin
import kotlin.time.Duration.Companion.seconds

@SkyHanniModule
object HoppityCallWarning {

    /**
     * Test messages (and the real ones from Hypixel) have a space at the end of
     * them that the IDE kills. So it's "§r§e ✆ "
     *
     * REGEX-TEST: §e✆ §r§bHoppity§r§e ✆
     * REGEX-TEST: §e✆ §r§aHoppity§r§e ✆
     */
    private val initHoppityCallPattern by ChocolateFactoryAPI.patternGroup.pattern(
        "hoppity.call.init",
        "§e✆ §r(?:§a|§b)Hoppity§r§e ✆.*",
    )

    /**
     * REGEX-TEST: §a✆ RING... §r §r§2§l[PICK UP]
     * REGEX-TEST: §a✆ RING... RING... §r §r§2§l[PICK UP]
     * REGEX-TEST: §a✆ RING... RING... RING... §r §r§2§l[PICK UP]
     * REGEX-TEST: §a✆ RING... RING... RING...
     */
    private val callRingPattern by ChocolateFactoryAPI.patternGroup.pattern(
        "hoppity.call.ring",
        "§a✆ (?:RING\\.{3} ?){1,3}(?:§r §r§2§l\\[PICK UP])?",
    )

    /**
     * REGEX-TEST: §e[NPC] §aHoppity§f: §b✆ §f§rWhat's up, §boBlazin§f?
     */
    private val pickupHoppityCallPattern by ChocolateFactoryAPI.patternGroup.pattern(
        "hoppity.call.pickup",
        "§e\\[NPC] §aHoppity§f: §b✆ §f§rWhat's up, .*§f\\?",
    )

    private val config get() = HoppityEggsManager.config.hoppityCallWarning
    private var warningSound = SoundUtils.createSound("note.pling", 1f)
    private var activeWarning = false
    private var nextWarningTime: Instant? = null
    private var finalWarningTime: Instant? = null
    private val callLength = 7.seconds
    private var acceptUUID: String? = null

    @SubscribeEvent
    fun onKeyPress(event: LorenzKeyPressEvent) {
        if (config.acceptHotkey == Keyboard.KEY_NONE || config.acceptHotkey != event.keyCode) return
        acceptUUID?.let {
            HypixelCommands.callback(acceptUUID!!)
            acceptUUID = null
        }
    }

    @SubscribeEvent
    fun onConfigLoad(event: ConfigLoadEvent) {
        val soundProperty = config.hoppityCallSound
        ConditionalUtils.onToggle(soundProperty) {
            warningSound = SoundUtils.createSound(soundProperty.get(), 1f)
        }
        nextWarningTime = null
        finalWarningTime = null
    }

    @SubscribeEvent(priority = EventPriority.HIGHEST)
    fun onChat(event: LorenzChatEvent) {
        if (callRingPattern.matches(event.message) && acceptUUID == null) readPickupUuid(event)
        if (!isEnabled()) return
        if (initHoppityCallPattern.matches(event.message)) startWarningUser()
        if (pickupHoppityCallPattern.matches(event.message)) stopWarningUser()
    }

    @SubscribeEvent
    fun onTick(event: SecondPassedEvent) {
        if (!isEnabled()) return
        if (!activeWarning) return
        if (nextWarningTime == null || finalWarningTime == null) return
        val currentTime = Instant.now()
        if (currentTime.isAfter(nextWarningTime)) {
            SoundUtils.repeatSound(100, 10, warningSound)
            nextWarningTime = currentTime.plusMillis(100)
        }
        if (currentTime >= finalWarningTime) stopWarningUser()
    }

    @SubscribeEvent
    fun onRender(event: GuiRenderEvent.GuiOverlayRenderEvent) {
        if (!isEnabled() || !activeWarning) return
        val minecraft = Minecraft.getMinecraft()
        // Calculate a fluctuating alpha value based on the sine of time, for a smooth oscillation
        val randomizationAlphaDouble = ((2 + sin(Instant.now().toEpochMilli().toDouble() / 1000)) * 255 / 4)
        // Ensure the alpha value is an integer and within the valid range (0-255)
        val randomizationAlphaInt = randomizationAlphaDouble.toInt().coerceIn(0..255)
        // Shift the alpha value 24 bits to the left to position it in the color's alpha channel.
        val shiftedRandomAlpha = randomizationAlphaInt shl 24
        Gui.drawRect(
            0,
            0,
            minecraft.displayWidth,
            minecraft.displayHeight,
            // Apply the shifted alpha and combine it with the RGB components of flashColor.
            shiftedRandomAlpha or (config.flashColor.toChromaColorInt() and 0xFFFFFF),
        )
        GlStateManager.color(1F, 1F, 1F, 1F)
    }

    private fun readPickupUuid(event: LorenzChatEvent) {
        val siblings = event.chatComponent.siblings.takeIf { it.size >= 3 } ?: return
        val clickEvent = siblings[2]?.chatStyle?.chatClickEvent ?: return
        if (clickEvent.action.name.lowercase() != "run_command" || !clickEvent.value.lowercase().startsWith("/cb")) return
        acceptUUID = clickEvent.value.lowercase().replace("/cb ", "").takeIf { it.isValidUuid() }
        if (acceptUUID != null) DelayedRun.runDelayed(12.seconds) { acceptUUID = null }
    }

    private fun startWarningUser() {
        if (activeWarning) return
        activeWarning = true
        SoundUtils.repeatSound(100, 10, warningSound)
        val currentTime = Instant.now()
        nextWarningTime = currentTime.plusMillis(100)
        finalWarningTime = finalWarningTime ?: currentTime.plusMillis(callLength.inWholeMilliseconds)
    }

    private fun stopWarningUser() {
        activeWarning = false
        finalWarningTime = null
        nextWarningTime = null
    }

    private fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled
}