aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/util/skyblock/SackUtil.kt
blob: a69309b47702f696fe6f0f4c370feedabd217a8f (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
package moe.nea.firmament.util.skyblock

import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import net.minecraft.client.gui.screens.inventory.ContainerScreen
import net.minecraft.network.chat.HoverEvent
import net.minecraft.network.chat.Component
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ChestInventoryUpdateEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.gui.config.storage.ConfigFixEvent
import moe.nea.firmament.gui.config.storage.ConfigStorageClass
import moe.nea.firmament.repo.ItemNameLookup
import moe.nea.firmament.util.SHORT_NUMBER_FORMAT
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.data.Config
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.iterableView
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch

object SackUtil {
	@Serializable
	data class SackContents(
		// TODO: store the certainty of knowledge for each item.
		val contents: MutableMap<SkyblockId, Long> = mutableMapOf(),
//		val sackTypes:
	)

	@Config
	object Store : ProfileSpecificDataHolder<SackContents>(serializer(), "sacks", ::SackContents)

	@Subscribe
	fun onConfigFix(event: ConfigFixEvent) {
		event.on(996, ConfigStorageClass.PROFILE) {
			move("Sacks", "sacks")
		}
	}

	val items get() = Store.data?.contents ?: mutableMapOf()
	val storedRegex = "^Stored: (?<stored>$SHORT_NUMBER_FORMAT)/(?<max>$SHORT_NUMBER_FORMAT)$".toPattern()

	@Subscribe
	fun storeDataFromInventory(event: ChestInventoryUpdateEvent) {
		val screen = event.inventory as? ContainerScreen ?: return
		if (!screen.title.unformattedString.endsWith(" Sack")) return
		val inv = screen.menu?.container ?: return
		if (inv.containerSize < 18) return
		val backSlot = inv.getItem(inv.containerSize - 5)
		if (backSlot.displayNameAccordingToNbt.unformattedString != "Go Back") return
		if (backSlot.loreAccordingToNbt.map { it.unformattedString } != listOf("To Sack of Sacks")) return
		for (itemStack in inv.iterableView) {
			// TODO: handle runes and gemstones
			val stored = itemStack.loreAccordingToNbt.firstNotNullOfOrNull {
				storedRegex.useMatch(it.unformattedString) {
					val stored = parseShortNumber(group("stored")).toLong()
					val max = parseShortNumber(group("max")).toLong()
					stored
				}
			} ?: continue
			val itemId = itemStack.skyBlockId ?: continue
			items[itemId] = stored
		}
		Store.markDirty()
	}

	@Subscribe
	fun updateFromChat(event: ProcessChatEvent) {
		if (!event.unformattedString.startsWith("[Sacks]")) return
		getUpdatesFromMessage(event.text)
	}

	fun getUpdatesFromMessage(text: Component): List<SackUpdate> {
		val update = ChatUpdate()
		text.siblings.forEach(update::updateFromHoverText)
		return update.updates
	}

	data class SackUpdate(
		val itemId: SkyblockId?,
		val itemName: String,
		val changeAmount: Long,
	)

	private class ChatUpdate {
		val updates = mutableListOf<SackUpdate>()
		var foundAdded = false
		var foundRemoved = false

		fun updateFromCleanText(cleanedText: String) {
			cleanedText.split("\n").forEach { line ->
				changePattern.useMatch(line) {
					val amount = parseShortNumber(group("amount")).toLong()
					val itemName = group("itemName")
					val itemId = ItemNameLookup.guessItemByName(itemName, false)
					updates.add(SackUpdate(itemId, itemName, amount))
				}
			}
		}

		fun updateFromHoverText(text: Component) {
			text.siblings.forEach(::updateFromHoverText)
			val hoverText = (text.style.hoverEvent as? HoverEvent.ShowText)?.value ?: return
			val cleanedText = hoverText.unformattedString
			if (cleanedText.startsWith("Added items:\n")) {
				if (!foundAdded) {
					updateFromCleanText(cleanedText)
					foundAdded = true
				}
			}
			if (cleanedText.startsWith("Removed items:\n")) {
				if (!foundRemoved) {
					updateFromCleanText(cleanedText)
					foundRemoved = true
				}
			}
		}

	}

	val changePattern = "  (?<amount>[+\\-]$SHORT_NUMBER_FORMAT) (?<itemName>[^(]+) \\(.*\\)".toPattern()
}