aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/moe/nea/firmament/features/events/anniversity/AnniversaryFeatures.kt
blob: 8926a9595e61dcb39700ac255c40eb3c724df999 (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
package moe.nea.firmament.features.events.anniversity

import io.github.notenoughupdates.moulconfig.observer.ObservableList
import io.github.notenoughupdates.moulconfig.xml.Bind
import moe.nea.jarvis.api.Point
import kotlin.time.Duration.Companion.seconds
import net.minecraft.entity.passive.PigEntity
import net.minecraft.util.math.BlockPos
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.EntityInteractionEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.gui.hud.MoulConfigHud
import moe.nea.firmament.rei.SBItemEntryDefinition
import moe.nea.firmament.repo.ItemNameLookup
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SHORT_NUMBER_FORMAT
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.useMatch

object AnniversaryFeatures : FirmamentFeature {
    override val identifier: String
        get() = "anniversary"

    object TConfig : ManagedConfig(identifier) {
        val enableShinyPigTracker by toggle("shiny-pigs") {true}
        val trackPigCooldown by position("pig-hud", 200, 300) { Point(0.1, 0.2) }
    }

    override val config: ManagedConfig?
        get() = TConfig

    data class ClickedPig(
        val clickedAt: TimeMark,
        val startLocation: BlockPos,
        val pigEntity: PigEntity
    ) {
        @Bind("timeLeft")
        fun getTimeLeft(): Double = 1 - clickedAt.passedTime() / pigDuration
    }

    val clickedPigs = ObservableList<ClickedPig>(mutableListOf())
    var lastClickedPig: PigEntity? = null

    val pigDuration = 90.seconds

    @Subscribe
    fun onTick(event: TickEvent) {
        clickedPigs.removeIf { it.clickedAt.passedTime() > pigDuration }
    }

    val pattern = "SHINY! You extracted (?<reward>.*) from the piglet's orb!".toPattern()

    @Subscribe
    fun onChat(event: ProcessChatEvent) {
        if(!TConfig.enableShinyPigTracker)return
        if (event.unformattedString == "Oink! Bring the pig back to the Shiny Orb!") {
            val pig = lastClickedPig ?: return
            // TODO: store proper location based on the orb location, maybe
            val startLocation = pig.blockPos ?: return
            clickedPigs.add(ClickedPig(TimeMark.now(), startLocation, pig))
            lastClickedPig = null
        }
        if (event.unformattedString == "SHINY! The orb is charged! Click on it for loot!") {
            val player = MC.player ?: return
            val lowest =
                clickedPigs.minByOrNull { it.startLocation.getSquaredDistance(player.pos) } ?: return
            clickedPigs.remove(lowest)
        }
        pattern.useMatch(event.unformattedString) {
            val reward = group("reward")
            val parsedReward = parseReward(reward)
            addReward(parsedReward)
            PigCooldown.rewards.atOnce {
                PigCooldown.rewards.clear()
                rewards.mapTo(PigCooldown.rewards) { PigCooldown.DisplayReward(it) }
            }
        }
    }

    fun addReward(reward: Reward) {
        val it = rewards.listIterator()
        while (it.hasNext()) {
            val merged = reward.mergeWith(it.next()) ?: continue
            it.set(merged)
            return
        }
        rewards.add(reward)
    }

    val rewards = mutableListOf<Reward>()

    fun <T> ObservableList<T>.atOnce(block: () -> Unit) {
        val oldObserver = observer
        observer = null
        block()
        observer = oldObserver
        update()
    }

    sealed interface Reward {
        fun mergeWith(other: Reward): Reward?
        data class EXP(val amount: Double, val skill: String) : Reward {
            override fun mergeWith(other: Reward): Reward? {
                if (other is EXP && other.skill == skill)
                    return EXP(amount + other.amount, skill)
                return null
            }
        }

        data class Coins(val amount: Double) : Reward {
            override fun mergeWith(other: Reward): Reward? {
                if (other is Coins)
                    return Coins(other.amount + amount)
                return null
            }
        }

        data class Items(val amount: Int, val item: SkyblockId) : Reward {
            override fun mergeWith(other: Reward): Reward? {
                if (other is Items && other.item == item)
                    return Items(amount + other.amount, item)
                return null
            }
        }

        data class Unknown(val text: String) : Reward {
            override fun mergeWith(other: Reward): Reward? {
                return null
            }
        }
    }

    val expReward = "\\+(?<exp>$SHORT_NUMBER_FORMAT) (?<kind>[^ ]+) XP".toPattern()
    val coinReward = "\\+(?<amount>$SHORT_NUMBER_FORMAT) coins".toPattern()
    val itemReward = "(?:(?<amount>[0-9]+)x )?(?<name>.*)".toPattern()
    fun parseReward(string: String): Reward {
        expReward.useMatch<Unit>(string) {
            val exp = parseShortNumber(group("exp"))
            val kind = group("kind")
            return Reward.EXP(exp, kind)
        }
        coinReward.useMatch<Unit>(string) {
            val coins = parseShortNumber(group("amount"))
            return Reward.Coins(coins)
        }
        itemReward.useMatch(string) {
            val amount = group("amount")?.toIntOrNull() ?: 1
            val name = group("name")
            val item = ItemNameLookup.guessItemByName(name, false) ?: return@useMatch
            return Reward.Items(amount, item)
        }
        return Reward.Unknown(string)
    }

    @Subscribe
    fun onWorldClear(event: WorldReadyEvent) {
        lastClickedPig = null
        clickedPigs.clear()
    }

    @Subscribe
    fun onEntityClick(event: EntityInteractionEvent) {
        if (event.entity is PigEntity) {
            lastClickedPig = event.entity
        }
    }

    @Subscribe
    fun init(event: WorldReadyEvent) {
        PigCooldown.forceInit()
    }

    object PigCooldown : MoulConfigHud("anniversary_pig", TConfig.trackPigCooldown) {
        override fun shouldRender(): Boolean {
            return clickedPigs.isNotEmpty() && TConfig.enableShinyPigTracker
        }

        @Bind("pigs")
        fun getPigs() = clickedPigs

        class DisplayReward(val backedBy: Reward) {
            @Bind
            fun count(): String {
                return when (backedBy) {
                    is Reward.Coins -> backedBy.amount
                    is Reward.EXP -> backedBy.amount
                    is Reward.Items -> backedBy.amount
                    is Reward.Unknown -> 0
                }.toString()
            }

            val itemStack = if (backedBy is Reward.Items) {
                SBItemEntryDefinition.getEntry(backedBy.item, backedBy.amount)
            } else {
                SBItemEntryDefinition.getEntry(SkyblockId.NULL)
            }

            @Bind
            fun name(): String {
                return when (backedBy) {
                    is Reward.Coins -> "Coins"
                    is Reward.EXP -> backedBy.skill
                    is Reward.Items -> itemStack.value.asItemStack().name.string
                    is Reward.Unknown -> backedBy.text
                }
            }

            @Bind
            fun isKnown() = backedBy !is Reward.Unknown
        }

        @get:Bind("rewards")
        val rewards = ObservableList<DisplayReward>(mutableListOf())

    }

}