Skip to content

Commit

Permalink
Improvement + Backend: Roman Allow List (#3113)
Browse files Browse the repository at this point in the history
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
  • Loading branch information
hannibal002 and hannibal002 authored Dec 25, 2024
1 parent ea9d839 commit d4510fa
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ public class MiscConfig {
@ConfigOption(name = "Replace Roman Numerals", desc = "Replace Roman Numerals with Arabic Numerals on any item.")
@ConfigEditorBoolean
@FeatureToggle
public boolean replaceRomanNumerals = false;
public Property<Boolean> replaceRomanNumerals = Property.of(false);

@Expose
@ConfigOption(name = "Thunder Bottle", desc = "Show a notification when your Thunder Bottle is fully charged.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package at.hannibal2.skyhanni.features.gui.customscoreboard.elements

import at.hannibal2.skyhanni.data.SlayerAPI
import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboard.informationFilteringConfig
import at.hannibal2.skyhanni.features.misc.ReplaceRomanNumerals

// internal
// scoreboard update event
object ScoreboardElementSlayer : ScoreboardElement() {
override fun getDisplay() = buildList {
if (!SlayerAPI.hasActiveSlayerQuest()) return@buildList
add("Slayer Quest")
add(SlayerAPI.latestSlayerCategory)
add(ReplaceRomanNumerals.replaceLine(SlayerAPI.latestSlayerCategory))
add(SlayerAPI.latestSlayerProgress)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,65 @@ import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.api.event.HandleEvent
import at.hannibal2.skyhanni.data.hypixel.chat.event.SystemMessageEvent
import at.hannibal2.skyhanni.events.ChatHoverEvent
import at.hannibal2.skyhanni.events.item.ItemHoverEvent
import at.hannibal2.skyhanni.events.DebugDataCollectEvent
import at.hannibal2.skyhanni.events.LorenzToolTipEvent
import at.hannibal2.skyhanni.events.RepositoryReloadEvent
import at.hannibal2.skyhanni.mixins.hooks.GuiChatHook
import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimal
import at.hannibal2.skyhanni.utils.RegexUtils.findMatcher
import at.hannibal2.skyhanni.utils.RecalculatingValue
import at.hannibal2.skyhanni.utils.RegexUtils.matches
import at.hannibal2.skyhanni.utils.StringUtils.applyIfPossible
import at.hannibal2.skyhanni.utils.StringUtils.isRoman
import at.hannibal2.skyhanni.utils.StringUtils.removeColor
import at.hannibal2.skyhanni.utils.TimeLimitedCache
import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern
import net.minecraft.event.HoverEvent
import net.minecraft.util.ChatComponentText
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import kotlin.time.Duration.Companion.seconds

@SkyHanniModule
object ReplaceRomanNumerals {
private val patternGroup = RepoPattern.group("replace.roman")

/**
* REGEX-TEST: §9Dedication IV
* REGEX-FAIL: §cD§6y§ee§as
*/
private val findRomanNumeralPattern by patternGroup.pattern(
"findroman",
"[ ➜](?=[MDCLXVI])(?<roman>M*(?:C[MD]|D?C{0,3})(?:X[CL]|L?X{0,3})(?:I[XV]|V?I{0,3}))(?<extra>.?)"
)

/**
* REGEX-TEST: K
*/
private val isWordPattern by patternGroup.pattern(
"findword",
"^[\\w-']"
// Using toRegex here since toPattern doesn't seem to provide the necessary functionality
private val splitRegex = "((§\\w)|(\\s+)|(\\W))+|(\\w*)".toRegex()
private val cachedStrings = TimeLimitedCache<String, String>(5.seconds)

private val patternGroup = RepoPattern.group("replace.roman.numerals")

@Suppress("MaxLineLength")
private val allowedPatterns by patternGroup.list(
"allowed.patterns",
"§o§a(?:Combat|Farming|Fishing|Mining|Foraging|Enchanting|Alchemy|Carpentry|Runecrafting|Taming|Social|)( Level)? (?<roman>[IVXLCDM]+)§r",
"(?:§5§o)?§7Progress to (?:Collection|Level|Tier|Floor|Milestone|Chocolate Factory) (?<roman>[IVXLCDM]+): §.(?:.*)%",
"§5§o §e(?:\\w+) (?<roman>[IVXLCDM]+)",
"(?:§.)*Abiphone (?<roman>[IVXLCDM]+) .*",
"§o§a§a(?:§c§lMM§c )?The Catacombs §8- §eFloor (?<roman>[IVXLCDM]+)§r",
".*Extra Farming Fortune (?<roman>[IVXLCDM]+)",
".*(?:Collection|Level|Tier|Floor|Milestone) (?<roman>[IVXLCDM]+)(?: ?§(?:7|r).*)?",
"(?:§5§o§a ✔|§5§o§c ✖) §.* (?<roman>[IVXLCDM]+)",
"§o§a✔ §.* (?<roman>[IVXLCDM]+)§r",
"§5§o§7Purchase §a.* (?<roman>[IVXLCDM]+) §7.*",
"§5§o(?:§7)§.(?<roman>[IVXLCDM]+).*",
".*Heart of the Mountain (?<roman>[IVXLCDM]+) ?.*"
)

/**
* REGEX-TEST:
* REGEX-TEST: §eSelect an option: §r§a[§aOk, then what?§a]
*/
private val allowedCharactersAfter by patternGroup.pattern(
"allowedcharactersafter",
"[➜):]?"
private val isSelectOptionPattern by patternGroup.pattern(
"string.isselectoption",
"§eSelect an option: .*",
)

@HandleEvent(priority = HandleEvent.LOWEST)
fun onTooltip(event: ItemHoverEvent) {
// TODO: Remove after pr 1717 is ready and switch to ItemHoverEvent
@SubscribeEvent(priority = EventPriority.LOWEST)
fun onTooltip(event: LorenzToolTipEvent) {
if (!isEnabled()) return

event.toolTip.replaceAll { it.transformLine() }
event.toolTip.replaceAll { it.tryReplace() }
}

@HandleEvent(priority = HandleEvent.LOWEST)
Expand All @@ -60,7 +71,7 @@ object ReplaceRomanNumerals {
if (!isEnabled()) return

val lore = event.getHoverEvent().value.formattedText.split("\n").toMutableList()
lore.replaceAll { it.transformLine() }
lore.replaceAll { it.tryReplace() }

val chatComponentText = ChatComponentText(lore.joinToString("\n"))
val hoverEvent = HoverEvent(event.component.chatStyle.chatHoverEvent?.action, chatComponentText)
Expand All @@ -70,38 +81,58 @@ object ReplaceRomanNumerals {

@HandleEvent
fun onSystemMessage(event: SystemMessageEvent) {
if (!isEnabled()) return
event.applyIfPossible { it.transformLine() }
if (!isEnabled() || event.message.isSelectOption()) return
event.applyIfPossible { it.tryReplace() }
}

/**
* Transforms a line with a roman numeral to a line with a decimal numeral.
* Override block one is to be used for tablist or other places where there is no need to check for normal text containing
* the word "I".
*
* Currently not replaced:
* - "§7Bonzo I Reward:" in the collection rewards when hovering on the collection
*/
private fun String.transformLine(overrideBlockOne: Boolean = false): String {
val (romanNumeral, rest) = findRomanNumeralPattern.findMatcher(this.removeFormatting()) {
group("roman") to group("extra")
} ?: return this
@SubscribeEvent(priority = EventPriority.LOW)
fun onRepoReload(event: RepositoryReloadEvent) {
cachedStrings.clear()
}

if (romanNumeral.isNullOrEmpty() || !romanNumeral.isRoman() || isWordPattern.matches(rest)) {
return recursiveSplit(romanNumeral)
}
private fun String.isSelectOption(): Boolean = isSelectOptionPattern.matches(this)

val parsedRomanNumeral = romanNumeral.romanToDecimal()
private fun String.tryReplace(): String = cachedStrings.getOrPut(this) {
if (allowedPatterns.matches(this)) replace() else this
}

fun replaceLine(line: String): String {
if (!isEnabled()) return line

return takeIf { parsedRomanNumeral != 1 || overrideBlockOne || rest.isEmpty() || allowedCharactersAfter.matches(rest) }
?.replaceFirst(romanNumeral, parsedRomanNumeral.toString())?.transformLine()
?: recursiveSplit(romanNumeral)
return cachedStrings.getOrPut(line) {
line.replace()
}
}

private fun String.recursiveSplit(romanNumeral: String) =
this.split(romanNumeral, limit = 2).let { it[0] + romanNumeral + it[1].transformLine() }
private fun String.replace() = splitRegex.findAll(this).map { it.value }.joinToString("") {
it.takeIf { it.isValidRomanNumeral() && it.removeFormatting().romanToDecimal() != 2000 }?.coloredRomanToDecimal() ?: it
}

private fun String.removeFormatting() = removeColor().replace(",", "")

private fun isEnabled() = LorenzUtils.inSkyBlock && SkyHanniMod.feature.misc.replaceRomanNumerals
private fun String.isValidRomanNumeral() = removeFormatting().let { it.isRoman() && it.isNotEmpty() }

private fun String.coloredRomanToDecimal() = removeFormatting().let { replace(it, it.romanToDecimal().toString()) }

private fun isEnabled() = LorenzUtils.inSkyBlock && SkyHanniMod.feature.misc.replaceRomanNumerals.get()

init {
RecalculatingValue
}

@HandleEvent
fun onDebug(event: DebugDataCollectEvent) {
event.title("Replace Roman Numerals")
event.addIrrelevant {
val map = cachedStrings.toMap()
add("cachedStrings: (${map.size})")
for ((original, changed) in map) {
if (original == changed) {
add("unchanged: '$original'")
} else {
add("'$original' -> '$changed'")
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import at.hannibal2.skyhanni.utils.ItemUtils.getAttributeFromShard
import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName
import at.hannibal2.skyhanni.utils.ItemUtils.getInternalNameOrNull
import at.hannibal2.skyhanni.utils.ItemUtils.getItemRarityOrNull
import at.hannibal2.skyhanni.utils.ItemUtils.getLore
import at.hannibal2.skyhanni.utils.ItemUtils.getReadableNBTDump
import at.hannibal2.skyhanni.utils.ItemUtils.isRune
import at.hannibal2.skyhanni.utils.ItemUtils.itemName
Expand Down Expand Up @@ -777,10 +776,9 @@ object EstimatedItemValueCalculator {
if (rawName in tieredEnchants) level = 1

val enchantmentName = "$rawName;$level".uppercase().toInternalName()
val itemStack = enchantmentName.getItemStackOrNull() ?: continue
val singlePrice = enchantmentName.getPriceOrNull(config.priceSource.get()) ?: continue

var name = itemStack.getLore()[0]
var name = enchantmentName.itemName
// TODO find a way to use this here "".toInternalName().getPriceName(multiplier)
if (multiplier > 1) {
name = "§8${multiplier}x $name"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import at.hannibal2.skyhanni.utils.ItemCategory
import at.hannibal2.skyhanni.utils.ItemUtils.getItemCategoryOrNull
import at.hannibal2.skyhanni.utils.ItemUtils.isEnchanted
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimal
import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimalIfNecessary
import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getEnchantments
import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getExtraAttributes
import at.hannibal2.skyhanni.utils.StringUtils.removeColor
Expand Down Expand Up @@ -53,6 +53,7 @@ object EnchantParser {
"exclusive",
"^(?:(?:§.)+[A-Za-z][A-Za-z '-]+ (?:[IVXLCDM]+|[0-9]+)(?:(?:§r)?§9, |\$| §8\\d{1,3}(?:[,.]\\d{1,3})*)[kKmMbB]?)+\$",
)

// Above regex tests apply to this pattern also
@Suppress("MaxLineLength")
val enchantmentPattern by patternGroup.pattern(
Expand Down Expand Up @@ -213,7 +214,7 @@ object EnchantParser {
"Item has enchants in nbt but none were found?",
"item" to currentItem,
"loreList" to loreList,
"nbt" to currentItem?.getExtraAttributes()
"nbt" to currentItem?.getExtraAttributes(),
)
return
} catch (e: ConcurrentModificationException) {
Expand All @@ -222,7 +223,7 @@ object EnchantParser {
"ConcurrentModificationException whilst formatting enchants",
"loreList" to loreList,
"format" to config.format.get(),
"orderedEnchants" to orderedEnchants.toString()
"orderedEnchants" to orderedEnchants.toString(),
)
}

Expand Down Expand Up @@ -271,22 +272,18 @@ object EnchantParser {
private fun orderEnchants(loreList: MutableList<String>) {
var lastEnchant: FormattedEnchant? = null

val isRoman = !SkyHanniMod.feature.misc.replaceRomanNumerals.get()
val regex = "[\\d,.kKmMbB]+\$".toRegex()
for (i in startEnchant..endEnchant) {
val matcher = enchantmentPattern.matcher(loreList[i])
var containsEnchant = false
var enchantsOnThisLine = 0
var isRoman = true

while (matcher.find()) {
// Pull enchant, enchant level and stacking amount if applicable
val enchant = this.enchants.getFromLore(matcher.group("enchant"))
val level = try {
// If one enchant is not a roman numeral we assume all are not roman numerals (idk a situation where this wouldn't be the case)
matcher.group("levelNumeral").toInt().also { isRoman = false }
} catch (e: NumberFormatException) {
matcher.group("levelNumeral").romanToDecimal()
}
val stacking = if (matcher.group("stacking").trimStart().removeColor().matches("[\\d,.kKmMbB]+\$".toRegex())) {
val level = matcher.group("levelNumeral").romanToDecimalIfNecessary()
val stacking = if (matcher.group("stacking").trimStart().removeColor().matches(regex)) {
shouldBeSingleColumn = true
matcher.group("stacking")
} else "empty"
Expand All @@ -295,9 +292,9 @@ object EnchantParser {
lastEnchant = FormattedEnchant(enchant, level, stacking, isRoman)

if (!orderedEnchants.add(lastEnchant)) {
for (e: FormattedEnchant in orderedEnchants) {
if (lastEnchant?.let { e.compareTo(it) } == 0) {
lastEnchant = e
for (formattedEnchant: FormattedEnchant in orderedEnchants) {
if (lastEnchant?.let { formattedEnchant.compareTo(it) } == 0) {
lastEnchant = formattedEnchant
break
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import at.hannibal2.skyhanni.events.PurseChangeEvent
import at.hannibal2.skyhanni.events.RepositoryReloadEvent
import at.hannibal2.skyhanni.events.SlayerChangeEvent
import at.hannibal2.skyhanni.events.SlayerQuestCompleteEvent
import at.hannibal2.skyhanni.features.misc.ReplaceRomanNumerals
import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
import at.hannibal2.skyhanni.utils.ChatUtils
import at.hannibal2.skyhanni.utils.CollectionUtils.addSearchString
Expand All @@ -41,7 +42,8 @@ object SlayerProfitTracker {

private val config get() = SkyHanniMod.feature.slayer.itemProfitTracker

private var itemLogCategory = ""
private var category = ""
private val categoryName get() = ReplaceRomanNumerals.replaceLine(category)
private var baseSlayerType = ""
private val trackers = mutableMapOf<String, SkyHanniItemTracker<Data>>()

Expand Down Expand Up @@ -126,21 +128,21 @@ object SlayerProfitTracker {
@HandleEvent
fun onSlayerChange(event: SlayerChangeEvent) {
val newSlayer = event.newSlayer
itemLogCategory = newSlayer.removeColor()
baseSlayerType = itemLogCategory.substringBeforeLast(" ")
category = newSlayer.removeColor()
baseSlayerType = category.substringBeforeLast(" ")
getTracker()?.update()
}

private fun getTracker(): SkyHanniItemTracker<Data>? {
if (itemLogCategory == "") return null
if (category == "") return null

return trackers.getOrPut(itemLogCategory) {
return trackers.getOrPut(category) {
val getStorage: (ProfileSpecificStorage) -> Data = {
it.slayerProfitData.getOrPut(
itemLogCategory,
category,
) { Data() }
}
SkyHanniItemTracker("$itemLogCategory Profit Tracker", { Data() }, getStorage) { drawDisplay(it) }
SkyHanniItemTracker("$categoryName Profit Tracker", { Data() }, getStorage) { drawDisplay(it) }
}
}

Expand All @@ -162,7 +164,7 @@ object SlayerProfitTracker {

private fun tryAddItem(internalName: NEUInternalName, amount: Int, command: Boolean) {
if (!isAllowedItem(internalName) && internalName != NEUInternalName.SKYBLOCK_COIN) {
ChatUtils.debug("Ignored non-slayer item pickup: '$internalName' '$itemLogCategory'")
ChatUtils.debug("Ignored non-slayer item pickup: '$internalName' '$category'")
return
}

Expand All @@ -176,7 +178,7 @@ object SlayerProfitTracker {

private fun drawDisplay(data: Data) = buildList<Searchable> {
val tracker = getTracker() ?: return@buildList
addSearchString("§e§l$itemLogCategory Profit Tracker")
addSearchString("§e§l$categoryName Profit Tracker")

var profit = tracker.drawItems(data, { true }, this)
val slayerSpawnCost = data.slayerSpawnCost
Expand All @@ -191,11 +193,14 @@ object SlayerProfitTracker {
profit += slayerSpawnCost
}

val slayerCompletedCount = data.slayerCompletedCount
val slayerCompletedCount = data.slayerCompletedCount.addSeparators()
add(
Renderable.hoverTips(
"§7Bosses killed: §e${slayerCompletedCount.addSeparators()}",
listOf("§7You killed the $itemLogCategory boss", "§e${slayerCompletedCount.addSeparators()} §7times."),
"§7Bosses killed: §e$slayerCompletedCount",
listOf(
"§7You killed the $categoryName boss",
"§e$slayerCompletedCount §7times.",
),
).toSearchable(),
)

Expand Down Expand Up @@ -248,7 +253,7 @@ object SlayerProfitTracker {
fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled

fun resetCommand() {
if (itemLogCategory == "") {
if (category == "") {
ChatUtils.userError(
"No current slayer data found! " +
"§eGo to a slayer area and start the specific slayer type you want to reset the data of.",
Expand Down
Loading

0 comments on commit d4510fa

Please sign in to comment.