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

import io.github.notenoughupdates.moulconfig.observer.ObservableList
import io.github.notenoughupdates.moulconfig.xml.Bind
import org.joml.Vector2i
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.gui.hud.MoulConfigHud
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.ItemNameLookup
import moe.nea.firmament.repo.SBItemStack
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.data.Config
import moe.nea.firmament.util.data.ManagedConfig
import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.useMatch

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

	@Config
	object TConfig : ManagedConfig(identifier, Category.EVENTS) {
		val enableShinyPigTracker by toggle("shiny-pigs") { true }
		val trackPigCooldown by position("pig-hud", 200, 300) { Vector2i(100, 200) }
	}

	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 = "(?i)\\+(?<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) {
				SBItemStack(backedBy.item, backedBy.amount)
			} else {
				SBItemStack(SkyblockId.NULL)
			}

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

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

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

	}

}