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

import kotlin.time.Duration
import net.minecraft.item.ItemStack
import net.minecraft.text.Text
import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.directLiteralStringContent
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.parseTimePattern
import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch

object AbilityUtils {
	data class ItemAbility(
		val name: String,
		val hasPowerScroll: Boolean,
		val activation: AbilityActivation,
		val manaCost: Int?,
		val descriptionLines: List<Text>,
		val cooldown: Duration?,
	)

	@JvmInline
	value class AbilityActivation(
		val label: String
	) {
		companion object {
			val RIGHT_CLICK = AbilityActivation("RIGHT CLICK")
			val SNEAK_RIGHT_CLICK = AbilityActivation("SNEAK RIGHT CLICK")
			val SNEAK = AbilityActivation("SNEAK")
			val EMPTY = AbilityActivation("")
			fun of(text: String?): AbilityActivation {
				val trimmed = text?.trim()
				if (trimmed.isNullOrBlank())
					return EMPTY
				return AbilityActivation(trimmed)
			}
		}
	}

	private val abilityNameRegex = "Ability: (?<name>.*?) *".toPattern()
	private fun findAbility(iterator: ListIterator<Text>): ItemAbility? {
		if (!iterator.hasNext()) {
			return null
		}
		val line = iterator.next()
		// The actual information about abilities is stored in the siblings
		if (line.directLiteralStringContent != "") return null
		var powerScroll: Boolean = false // This should instead determine the power scroll based on text colour
		var abilityName: String? = null
		var activation: String? = null
		var hasProcessedActivation = false
		for (sibling in line.siblings) {
			val directContent = sibling.directLiteralStringContent ?: continue
			if (directContent == "⦾ ") {
				powerScroll = true
				continue
			}
			if (!hasProcessedActivation && abilityName != null) {
				hasProcessedActivation = true
				activation = directContent
				continue
			}
			abilityNameRegex.useMatch<Nothing>(directContent) {
				abilityName = group("name")
				continue
			}
			if (abilityName != null) {
				ErrorUtil.softError("Found abilityName $abilityName without finding but encountered unprocessable element in: $line")
			}
			return null
		}
		if (abilityName == null) return null
		val descriptionLines = mutableListOf<Text>()
		var manaCost: Int? = null
		var cooldown: Duration? = null
		while (iterator.hasNext()) {
			val descriptionLine = iterator.next()
			if (descriptionLine.unformattedString == "") break
			var nextIsManaCost = false
			var isSpecialLine = false
			var nextIsDuration = false
			for (sibling in descriptionLine.siblings) {
				val directContent = sibling.directLiteralStringContent ?: continue
				if ("Mana Cost: " == directContent) { // TODO: 'Soulflow Cost: ' support (or maybe a generic 'XXX Cost: ')
					nextIsManaCost = true
					isSpecialLine = true
					continue
				}
				if ("Cooldown: " == directContent) {
					nextIsDuration = true
					isSpecialLine = true
					continue
				}
				if (nextIsDuration) {
					nextIsDuration = false
					cooldown = parseTimePattern(directContent)
					continue
				}
				if (nextIsManaCost) {
					nextIsManaCost = false
					manaCost = parseShortNumber(directContent).toInt()
					continue
				}
				if (isSpecialLine) {
					ErrorUtil.softError("Unknown special line segment: '$sibling' in '$descriptionLine'")
				}
			}
			if (!isSpecialLine) {
				descriptionLines.add(descriptionLine)
			}
		}
		return ItemAbility(
			abilityName,
			powerScroll,
			AbilityActivation.of(activation),
			manaCost,
			descriptionLines,
			cooldown
		)
	}

	fun getAbilities(lore: List<Text>): List<ItemAbility> {
		val iterator = lore.listIterator()
		val abilities = mutableListOf<ItemAbility>()
		while (iterator.hasNext()) {
			findAbility(iterator)?.let(abilities::add)
		}

		return abilities
	}

	// TODO: memoize
	fun getAbilities(itemStack: ItemStack): List<ItemAbility> {
		return getAbilities(itemStack.loreAccordingToNbt)
	}

}