From c9c25b61560e6fb4e1ba2dfb1d5cc61b0ec9ddab Mon Sep 17 00:00:00 2001
From: Linnea Gräf <nea@nea.moe>
Date: Wed, 30 Oct 2024 16:35:19 +0100
Subject: Add warning for missing REI

---
 .../features/inventory/REIDependencyWarner.kt      |  83 ++++
 src/main/kotlin/repo/RepoManager.kt                |   2 +-
 src/main/kotlin/util/render/DrawContextExt.kt      |   1 +
 src/main/kotlin/util/textutil.kt                   |  26 +-
 .../resources/assets/firmament/lang/en_us.json     | 457 +++++++++++----------
 src/main/resources/fabric.mod.json                 |   5 +-
 .../resources/testdata/items/rune-in-sack.snbt     |  31 ++
 7 files changed, 373 insertions(+), 232 deletions(-)
 create mode 100644 src/main/kotlin/features/inventory/REIDependencyWarner.kt
 create mode 100644 src/test/resources/testdata/items/rune-in-sack.snbt

(limited to 'src')

diff --git a/src/main/kotlin/features/inventory/REIDependencyWarner.kt b/src/main/kotlin/features/inventory/REIDependencyWarner.kt
new file mode 100644
index 0000000..1e9b1b8
--- /dev/null
+++ b/src/main/kotlin/features/inventory/REIDependencyWarner.kt
@@ -0,0 +1,83 @@
+package moe.nea.firmament.features.inventory
+
+import net.fabricmc.loader.api.FabricLoader
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.SharedConstants
+import net.minecraft.text.ClickEvent
+import net.minecraft.text.Text
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.commands.thenExecute
+import moe.nea.firmament.events.CommandEvent
+import moe.nea.firmament.events.SkyblockServerUpdateEvent
+import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.aqua
+import moe.nea.firmament.util.bold
+import moe.nea.firmament.util.clickCommand
+import moe.nea.firmament.util.grey
+import moe.nea.firmament.util.lime
+import moe.nea.firmament.util.red
+import moe.nea.firmament.util.white
+import moe.nea.firmament.util.yellow
+
+object REIDependencyWarner {
+	val reiModId = "roughlyenoughitems"
+	val hasREI = FabricLoader.getInstance().isModLoaded(reiModId)
+	var sentWarning = false
+
+	fun modrinthLink(slug: String) =
+		"https://modrinth.com/mod/$slug/versions?g=${SharedConstants.getGameVersion().name}&l=fabric"
+
+	fun downloadButton(modName: String, modId: String, slug: String): Text {
+		val alreadyDownloaded = FabricLoader.getInstance().isModLoaded(modId)
+		return Text.literal(" - ")
+			.white()
+			.append(Text.literal("[").aqua())
+			.append(Text.translatable("firmament.download", modName)
+				        .styled { it.withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, modrinthLink(slug))) }
+				        .yellow()
+				        .also {
+					        if (alreadyDownloaded)
+						        it.append(Text.translatable("firmament.download.already", modName)
+							                  .lime())
+				        })
+			.append(Text.literal("]").aqua())
+	}
+
+	@Subscribe
+	fun checkREIDependency(event: SkyblockServerUpdateEvent) {
+		if (!SBData.isOnSkyblock) return
+		if (hasREI) return
+		if (sentWarning) return
+		sentWarning = true
+		Firmament.coroutineScope.launch {
+			delay(2.seconds)
+			// TODO: should we offer an automatic install that actually downloads the JARs and places them into the mod folder?
+			MC.sendChat(
+				Text.translatable("firmament.reiwarning").red().bold().append("\n")
+					.append(downloadButton("RoughlyEnoughItems", reiModId, "rei")).append("\n")
+					.append(downloadButton("Architectury API", "architectury", "architectury-api")).append("\n")
+					.append(downloadButton("Cloth Config API", "cloth-config", "cloth-config")).append("\n")
+					.append(Text.translatable("firmament.reiwarning.disable")
+						        .clickCommand("/firm disablereiwarning")
+						        .grey())
+			)
+		}
+	}
+
+	@Subscribe
+	fun onSubcommand(event: CommandEvent.SubCommand) {
+		if (hasREI) return
+		event.subcommand("disablereiwarning") {
+			thenExecute {
+				RepoManager.Config.warnForMissingItemListMod = false
+				RepoManager.Config.save()
+				MC.sendChat(Text.translatable("firmament.reiwarning.disabled").yellow())
+			}
+		}
+	}
+}
diff --git a/src/main/kotlin/repo/RepoManager.kt b/src/main/kotlin/repo/RepoManager.kt
index b586548..9f8f422 100644
--- a/src/main/kotlin/repo/RepoManager.kt
+++ b/src/main/kotlin/repo/RepoManager.kt
@@ -40,7 +40,7 @@ object RepoManager {
 			RepoManager.launchAsyncUpdate(true)
 		}
 		val alwaysSuperCraft by toggle("enable-super-craft") { true }
-
+		var warnForMissingItemListMod by toggle("warn-for-missing-item-list-mod") { true }
 	}
 
 	val currentDownloadedSha by RepoDownloadManager::latestSavedVersionHash
diff --git a/src/main/kotlin/util/render/DrawContextExt.kt b/src/main/kotlin/util/render/DrawContextExt.kt
index 3e086b8..fc38aa6 100644
--- a/src/main/kotlin/util/render/DrawContextExt.kt
+++ b/src/main/kotlin/util/render/DrawContextExt.kt
@@ -12,6 +12,7 @@ fun DrawContext.isUntranslatedGuiDrawContext(): Boolean {
 
 fun DrawContext.drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int, color: Color) {
 	// TODO: push scissors
+	// TODO: use matrix translations and a different render layer
 	if (toY < fromY) {
 		drawLine(toX, toY, fromX, fromY, color)
 		return
diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt
index 252f708..a6a7e87 100644
--- a/src/main/kotlin/util/textutil.kt
+++ b/src/main/kotlin/util/textutil.kt
@@ -1,5 +1,6 @@
 package moe.nea.firmament.util
 
+import net.minecraft.text.ClickEvent
 import net.minecraft.text.MutableText
 import net.minecraft.text.PlainTextContent
 import net.minecraft.text.Text
@@ -119,7 +120,30 @@ fun Text.iterator(): Sequence<Text> {
 
 fun Text.allSiblings(): List<Text> = listOf(this) + siblings.flatMap { it.allSiblings() }
 
-fun MutableText.withColor(formatting: Formatting) = this.styled { it.withColor(formatting).withItalic(false) }
+fun MutableText.withColor(formatting: Formatting): MutableText = this.styled {
+	it.withColor(formatting)
+		.withItalic(false)
+		.withBold(false)
+}
+
+fun MutableText.blue() = withColor(Formatting.BLUE)
+fun MutableText.aqua() = withColor(Formatting.AQUA)
+fun MutableText.lime() = withColor(Formatting.GREEN)
+fun MutableText.darkGreen() = withColor(Formatting.DARK_GREEN)
+fun MutableText.yellow() = withColor(Formatting.YELLOW)
+fun MutableText.grey() = withColor(Formatting.GRAY)
+fun MutableText.red() = withColor(Formatting.RED)
+fun MutableText.white() = withColor(Formatting.WHITE)
+fun MutableText.bold(): MutableText = styled { it.withBold(true) }
+
+
+fun MutableText.clickCommand(command: String): MutableText {
+	require(command.startsWith("/"))
+	return this.styled {
+		it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND,
+		                             "/firm disablereiwarning"))
+	}
+}
 
 fun Text.transformEachRecursively(function: (Text) -> Text): Text {
 	val c = this.content
diff --git a/src/main/resources/assets/firmament/lang/en_us.json b/src/main/resources/assets/firmament/lang/en_us.json
index fef8996..125b49f 100644
--- a/src/main/resources/assets/firmament/lang/en_us.json
+++ b/src/main/resources/assets/firmament/lang/en_us.json
@@ -1,240 +1,245 @@
 {
-    "firmament.dev.resourcerebuild.start": "Invoking gradle resource rebuild (./gradlew :processResources)",
-    "firmament.dev.resourcerebuild.done": "Gradle resource rebuild done in %s",
-    "firmament.command.toggle.no-config-found": "Could not find config %s",
-    "firmament.command.toggle.no-property-found": "Could not find property %s",
-    "firmament.command.toggle.not-a-toggle": "Property %s is not a toggle",
-    "firmament.command.toggle.toggled": "Toggled %s / %s %s",
-    "firmament.command.waypoint.import": "Imported %s waypoints from clipboard.",
-    "firmament.command.waypoint.import.error": "Could not import waypoints from clipboard. Make sure they are on ColeWeight format:\n[{\"x\": 69, \"y\":420, \"z\": 36}]",
-    "firmament.command.waypoint.clear": "Cleared waypoints.",
-    "firmament.command.waypoint.added": "Added waypoint %s %s %s.",
-    "firmament.command.waypoint.remove": "Removed waypoint %s. Other waypoints may have different indexes now.",
-    "firmament.command.waypoint.remove.error": "Could not find waypoint with that index to delete.",
-    "firmament.command.waypoint.skip.error": "Could not skip a waypoint. Are you in ordered waypoint mode with waypoints loaded?",
-    "firmament.command.waypoint.skip": "Skipped 1 waypoint",
-    "firmament.poweruser.entity.fail": "No entity found under cursor",
-    "firmament.poweruser.entity.type": "Entity Type: %s",
-    "firmament.poweruser.entity.name": "Entity Name: %s",
-    "firmament.poweruser.entity.position": "Position: %s",
-    "firmament.poweruser.entity.armor": "Entity Armor:",
-    "firmament.poweruser.entity.armor.item": " - %s",
-    "firmament.poweruser.entity.passengers": "%s Passengers",
-    "firmament.mixins.start": "Applied firmament mixins:",
-    "firmament.event.start": "Event Bus Readout:",
-    "firmament.event.bustype": " - %s:",
-    "firmament.event.handler": "   * %s",
-    "firmament.config.all-configs": "- All Configs -",
-    "firmament.config.anniversary": "Anniversary Features",
-    "firmament.config.anniversary.shiny-pigs": "Shiny Pigs Tracker",
-    "firmament.config.anniversary.pig-hud": "Pig Tracker Hud",
-    "firmament.pristine-profit.collection": "Collection: %s/h",
-    "firmament.pristine-profit.money": "Money: %s/h",
-    "firmament.toggle.true": "On",
-    "firmament.toggle.false": "Off",
-    "firmament.config.developer": "Developer Settings",
-    "firmament.config.developer.auto-rebuild": "Automatically rebuild resources",
-    "firmament.price": "Checking price for %s",
-    "firmament.price.bazaar": "Bazaar stats:",
-    "firmament.price.bazaar.productid": "Stock id: %s",
-    "firmament.price.bazaar.buy.price": "Buy Price: %s",
-    "firmament.price.bazaar.buy.order": "Buy orders: %d",
-    "firmament.tooltip.bazaar.sell-order": "Bazaar Sell Order: %s",
-    "firmament.tooltip.bazaar.buy-order": "Bazaar Buy Order: %s",
-    "firmament.tooltip.ah.lowestbin": "Lowest BIN: %d",
-    "firmament.pv.pets": "Pets",
-    "firmament.config.diana": "Diana",
-    "firmament.config.diana.ancestral-teleport": "Warp near guess",
-    "firmament.config.diana.ancestral-spade": "Ancestral Spade Solver",
-    "firmament.config.diana.nearby-waypoints": "Nearby Waypoints Highlighter",
-    "firmament.config.pristine-profit": "Pristine Profit Tracker",
-    "firmament.config.pristine-profit.timeout": "Timeout (0 = disabled)",
-    "firmament.config.pristine-profit.position": "Position",
-    "firmament.debug.skyblockid": "SkyBlock ID: %s",
-    "firmament.debug.skyblockid.copy": "Click to copy SkyBlock ID",
-    "firmament.price.bazaar.sell.price": "Sell Price: %s",
-    "firmament.price.bazaar.sell.order": "Sell orders: %d",
-    "firmament.price.lowestbin": "Lowest BIN: %s",
-    "firmament.repo.reload.network": "Trying to redownload the repository",
-    "firmament.repo.reload.disk": "Reloading repository from disk. This may lag a bit.",
-    "firmament.repo.cache": "Recaching items",
-    "firmament.repo.brokenitem": "Failed to render item: %s",
-    "firmament.config.auto-completions": "Hypixel Command Improvements",
-    "firmament.config.auto-completions.warp-complete": "Auto Complete /warp",
-    "firmament.config.auto-completions.warp-is": "Redirect /warp is to /warp island",
-    "firmanent.config.edit": "Edit",
-    "firmament.config.carnival": "Carnival Features",
-    "firmament.config.carnival.bombs-solver": "Bombs Solver",
-    "firmament.config.carnival.tutorials": "Tutorial Reminder",
-    "firmament.carnival.tutorial.minesweeper": "§eClick here to check out Firmaments Tutorial for this minigame!",
-    "firmament.config.repo": "Firmament Repo Settings",
-    "firmament.config.repo.autoUpdate": "Auto Update",
-    "firmament.config.repo.username": "Repo Username",
-    "firmament.config.repo.username.hint": "NotEnoughUpdates",
-    "firmament.config.repo.reponame": "Repo Name",
-    "firmament.config.repo.reponame.hint": "NotEnoughUpdates-REPO",
-    "firmament.config.configconfig.enable-yacl": "Use YACL Config",
-    "firmament.config.repo.branch": "Repo Branch",
+	"firmament.dev.resourcerebuild.start": "Invoking gradle resource rebuild (./gradlew :processResources)",
+	"firmament.dev.resourcerebuild.done": "Gradle resource rebuild done in %s",
+	"firmament.command.toggle.no-config-found": "Could not find config %s",
+	"firmament.command.toggle.no-property-found": "Could not find property %s",
+	"firmament.command.toggle.not-a-toggle": "Property %s is not a toggle",
+	"firmament.command.toggle.toggled": "Toggled %s / %s %s",
+	"firmament.command.waypoint.import": "Imported %s waypoints from clipboard.",
+	"firmament.command.waypoint.import.error": "Could not import waypoints from clipboard. Make sure they are on ColeWeight format:\n[{\"x\": 69, \"y\":420, \"z\": 36}]",
+	"firmament.command.waypoint.clear": "Cleared waypoints.",
+	"firmament.command.waypoint.added": "Added waypoint %s %s %s.",
+	"firmament.command.waypoint.remove": "Removed waypoint %s. Other waypoints may have different indexes now.",
+	"firmament.command.waypoint.remove.error": "Could not find waypoint with that index to delete.",
+	"firmament.command.waypoint.skip.error": "Could not skip a waypoint. Are you in ordered waypoint mode with waypoints loaded?",
+	"firmament.command.waypoint.skip": "Skipped 1 waypoint",
+	"firmament.poweruser.entity.fail": "No entity found under cursor",
+	"firmament.poweruser.entity.type": "Entity Type: %s",
+	"firmament.poweruser.entity.name": "Entity Name: %s",
+	"firmament.poweruser.entity.position": "Position: %s",
+	"firmament.poweruser.entity.armor": "Entity Armor:",
+	"firmament.poweruser.entity.armor.item": " - %s",
+	"firmament.poweruser.entity.passengers": "%s Passengers",
+	"firmament.mixins.start": "Applied firmament mixins:",
+	"firmament.event.start": "Event Bus Readout:",
+	"firmament.event.bustype": " - %s:",
+	"firmament.event.handler": "   * %s",
+	"firmament.config.all-configs": "- All Configs -",
+	"firmament.config.anniversary": "Anniversary Features",
+	"firmament.config.anniversary.shiny-pigs": "Shiny Pigs Tracker",
+	"firmament.config.anniversary.pig-hud": "Pig Tracker Hud",
+	"firmament.pristine-profit.collection": "Collection: %s/h",
+	"firmament.pristine-profit.money": "Money: %s/h",
+	"firmament.toggle.true": "On",
+	"firmament.toggle.false": "Off",
+	"firmament.config.developer": "Developer Settings",
+	"firmament.config.developer.auto-rebuild": "Automatically rebuild resources",
+	"firmament.price": "Checking price for %s",
+	"firmament.price.bazaar": "Bazaar stats:",
+	"firmament.price.bazaar.productid": "Stock id: %s",
+	"firmament.price.bazaar.buy.price": "Buy Price: %s",
+	"firmament.price.bazaar.buy.order": "Buy orders: %d",
+	"firmament.tooltip.bazaar.sell-order": "Bazaar Sell Order: %s",
+	"firmament.tooltip.bazaar.buy-order": "Bazaar Buy Order: %s",
+	"firmament.tooltip.ah.lowestbin": "Lowest BIN: %d",
+	"firmament.pv.pets": "Pets",
+	"firmament.reiwarning.disable": "Click here to disable this warning",
+	"firmament.reiwarning.disabled": "Disabled the RoughlyEnoughItems warning. Keep in mind that you will not have an item list without REI.",
+	"firmament.download": "Click here to download %s",
+	"firmament.download.already": " (Already downloaded)",
+	"firmament.reiwarning": "Firmament needs RoughlyEnoughItems to display its item list!",
+	"firmament.config.diana": "Diana",
+	"firmament.config.diana.ancestral-teleport": "Warp near guess",
+	"firmament.config.diana.ancestral-spade": "Ancestral Spade Solver",
+	"firmament.config.diana.nearby-waypoints": "Nearby Waypoints Highlighter",
+	"firmament.config.pristine-profit": "Pristine Profit Tracker",
+	"firmament.config.pristine-profit.timeout": "Timeout (0 = disabled)",
+	"firmament.config.pristine-profit.position": "Position",
+	"firmament.debug.skyblockid": "SkyBlock ID: %s",
+	"firmament.debug.skyblockid.copy": "Click to copy SkyBlock ID",
+	"firmament.price.bazaar.sell.price": "Sell Price: %s",
+	"firmament.price.bazaar.sell.order": "Sell orders: %d",
+	"firmament.price.lowestbin": "Lowest BIN: %s",
+	"firmament.repo.reload.network": "Trying to redownload the repository",
+	"firmament.repo.reload.disk": "Reloading repository from disk. This may lag a bit.",
+	"firmament.repo.cache": "Recaching items",
+	"firmament.repo.brokenitem": "Failed to render item: %s",
+	"firmament.config.auto-completions": "Hypixel Command Improvements",
+	"firmament.config.auto-completions.warp-complete": "Auto Complete /warp",
+	"firmament.config.auto-completions.warp-is": "Redirect /warp is to /warp island",
+	"firmanent.config.edit": "Edit",
+	"firmament.config.carnival": "Carnival Features",
+	"firmament.config.carnival.bombs-solver": "Bombs Solver",
+	"firmament.config.carnival.tutorials": "Tutorial Reminder",
+	"firmament.carnival.tutorial.minesweeper": "§eClick here to check out Firmaments Tutorial for this minigame!",
+	"firmament.config.repo": "Firmament Repo Settings",
+	"firmament.config.repo.autoUpdate": "Auto Update",
+	"firmament.config.repo.username": "Repo Username",
+	"firmament.config.repo.username.hint": "NotEnoughUpdates",
+	"firmament.config.repo.reponame": "Repo Name",
+	"firmament.config.repo.reponame.hint": "NotEnoughUpdates-REPO",
+	"firmament.config.configconfig.enable-yacl": "Use YACL Config",
+	"firmament.config.repo.branch": "Repo Branch",
 	"firmament.config.configconfig": "Firmaments Config",
 	"firmament.config.commissions": "Commissions",
 	"firmament.config.commissions.highlight-completed": "Highlight Completed",
 	"firmament.config.repo.branch.hint": "dangerous",
-    "firmament.config.repo.reset": "Reset",
-    "firmament.config.repo.disable-item-groups": "Disable Item Groups",
-    "firmament.config.repo.enable-super-craft": "Always use Super Craft",
-    "firmament.config.repo.reload": "Reload Item List",
-    "firmament.config.repo.redownload": "Redownload Item List",
-    "firmament.config.pets": "Pets",
-    "firmament.config.pets.highlight-pet": "Highlight active pet",
-    "firmament.config.category.misc": "Miscellaneous",
-    "firmament.config.category.mining": "Mining",
-    "firmament.config.category.events": "Events",
-    "firmament.config.category.chat": "Chat",
-    "firmament.config.category.inventory": "Inventory",
-    "firmament.config.category.integrations": "Integrations & Textures",
-    "firmament.config.category.meta": "Meta & Firmament",
-    "firmament.config.category.dev": "Developer & Debug",
-    "firmament.ursa.debugrequest.start": "Ursa request launched",
+	"firmament.config.repo.reset": "Reset",
+	"firmament.config.repo.disable-item-groups": "Disable Item Groups",
+	"firmament.config.repo.enable-super-craft": "Always use Super Craft",
+	"firmament.config.repo.reload": "Reload Item List",
+	"firmament.config.repo.redownload": "Redownload Item List",
+	"firmament.config.pets": "Pets",
+	"firmament.config.pets.highlight-pet": "Highlight active pet",
+	"firmament.config.category.misc": "Miscellaneous",
+	"firmament.config.category.mining": "Mining",
+	"firmament.config.category.events": "Events",
+	"firmament.config.category.chat": "Chat",
+	"firmament.config.category.inventory": "Inventory",
+	"firmament.config.category.integrations": "Integrations & Textures",
+	"firmament.config.category.meta": "Meta & Firmament",
+	"firmament.config.category.dev": "Developer & Debug",
+	"firmament.ursa.debugrequest.start": "Ursa request launched",
 	"firmament.hotmpreset.openinghotm": "Opening /hotm menu for export.",
 	"firmament.hotmpreset.scrollprompt": "We need to scroll! Please click anywhere to continue.",
 	"firmament.hotmpreset.scrolled": "Just scrolled. Waiting on server to update items.",
 	"firmament.hotmpreset.copied": "Collected all HOTM perks to clipboard. Use /firm importhotm to import.",
 	"firmament.hotmpreset.okayimport": "Imported a HOTM perk preset.",
 	"firmament.hotmpreset.failedimport": "Could not find a HOTM perk preset in your clipboard. You can export your current HOTM perks with /firm exporthotm",
-    "firmament.ursa.debugrequest.result": "Ursa request succeeded: %s",
-    "firmament.sbinfo.nolocraw": "No locraw data available",
-    "firmament.sbinfo.profile": "Current profile cutename: %s",
-    "firmament.sbinfo.server": "Locraw Server: %s",
-    "firmament.sbinfo.gametype": "Locraw Gametype: %s",
-    "firmament.sbinfo.mode": "Locraw Mode: %s",
-    "firmament.sbinfo.map": "Locraw Map: %s",
-    "firmament.config.price-data": "Price data",
-    "firmament.config.price-data.enable-always": "Enable Item Price",
-    "firmament.config.price-data.enable-keybind": "Enable only with Keybinding",
-    "firmament.config.fairy-souls": "Fairy Souls",
-    "firmament.config.fairy-souls.show": "Show Fairy Soul Waypoints",
-    "firmament.config.fairy-souls.reset": "Reset Collected Fairy Souls",
-    "firmament.config.fishing-warning": "Fishing Warning",
-    "firmament.config.fishing-warning.display-warning": "Display a warning when you are about to hook a fish",
-    "firmament.config.fishing-warning.highlight-wake-chain": "Highlight fishing particles",
-    "firmament.protectitem": "Firmament protected your item: ",
-    "firmament.inventory-buttons.save-preset": "Save Preset",
-    "firmament.inventory-buttons.load-preset": "Load Preset",
-    "firmament.inventory-buttons.import-failed": "One of your buttons could only be imported partially",
-    "firmament.config.inventory-buttons": "Inventory buttons",
-    "firmament.config.inventory-buttons.open-editor": "Open Editor",
-    "firmament.warp-util.mark-excluded": "Firmament: Tried to warp to %s, but it was not unlocked. I will avoid warping there again.",
-    "firmament.waypoint.temporary": "Temporary Waypoint: %s",
-    "firmament.config.waypoints": "Waypoints",
-    "firmament.config.waypoints.temp-waypoint-duration": "Temporary Waypoint Duration",
-    "firmament.config.waypoints.show-index": "Show ordered waypoint indexes",
-    "firmament.config.waypoints.skip-to-nearest": "Allow skipping waypoints",
-    "firmament.recipe.forge.time": "Forging Time: %s",
-    "firmament.recipe.mobs.drops": "§e§lDrop Chance: %s",
-    "firmament.recipe.mobs.name": "§8[§7Lv %d§8] §c%s",
-    "firmament.recipe.mobs.name.nolevel": "§c%s",
-    "firmament.recipe.mobs.coins": "§eCoins: %s",
-    "firmament.recipe.mobs.combat": "§bCombat Experience: %s",
-    "firmament.recipe.mobs.exp": "§6Experience: %s",
-    "firmament.pv.skills": "Skills",
-    "firmament.pv.skills.farming": "Farming",
-    "firmament.pv.skills.foraging": "Foraging",
-    "firmament.pv.skills.mining": "Mining",
-    "firmament.pv.skills.alchemy": "Alchemy",
-    "firmament.pv.skills.taming": "Taming",
-    "firmament.pv.skills.fishing": "Fishing",
-    "firmament.pv.skills.runecrafting": "Runecrafting",
-    "firmament.pv.skills.carpentry": "Carpentry",
-    "firmament.pv.skills.combat": "Combat",
-    "firmament.pv.skills.social": "Social",
-    "firmament.pv.skills.rift": "Rift",
-    "firmament.pv.skills.enchanting": "Enchanting",
-    "firmament.pv.skills.total": "Total Exp: %s",
-    "firmament.pv.lookingup": "Looking up %s",
-    "firmament.pv.noprofile": "%s has no SkyBlock profiles",
-    "firmament.pv.noplayer": "%s is not a Minecraft player",
-    "firmament.config.save-cursor-position.enable": "Enable",
-    "firmament.config.save-cursor-position.tolerance": "Tolerance",
-    "firmament.config.save-cursor-position": "Save Cursor Position",
-    "firmament.config.storage-overlay": "Storage Overlay",
-    "firmament.config.storage-overlay.rows": "Columns",
-    "firmament.config.storage-overlay.always-replace": "Always Open Overlay",
-    "firmament.config.storage-overlay.padding": "Padding",
-    "firmament.config.storage-overlay.scroll-speed": "Scroll Speed",
-    "firmament.config.storage-overlay.inverse-scroll": "Invert Scroll",
-    "firmament.config.storage-overlay.margin": "Margin",
-    "firmament.config.chat-links": "Chat Links",
-    "firmament.config.chat-links.links-enabled": "Enable Clickable Links",
-    "firmament.config.chat-links.image-enabled": "Enable Image Preview",
-    "firmament.config.chat-links.allow-all-hosts": "Allow all Image Hosts",
-    "firmament.config.chat-links.allowed-hosts": "Allowed Image Hosts",
-    "firmament.config.chat-links.position": "Chat Image Preview",
-    "firmament.config.item-rarity-cosmetics": "Item Rarity Cosmetics",
-    "firmament.config.item-rarity-cosmetics.background": "Slot Background Rarity",
-    "firmament.config.item-rarity-cosmetics.background-hotbar": "Hotbar Background Rarity",
-    "firmament.hud.edit": "Edit %s",
-    "firmament.key.category": "Firmament",
-    "firmament.keybinding.external": "%s",
-    "firmament.config.slot-locking": "Slot Locking",
-    "firmament.config.slot-locking.lock": "Lock Slot",
-    "firmament.config.slot-locking.lock-uuid": "Lock UUID (Lock Item)",
-    "firmament.config.slot-locking.bind": "Bind Slot",
-    "firmament.config.slot-locking.require-quick-move": "Require Shift-Click for Bound Slots",
-    "firmament.config.fixes.auto-sprint": "Auto Sprint",
-    "firmament.config.fixes.auto-sprint-keybinding": "Auto Sprint KeyBinding",
-    "firmament.config.fixes.auto-sprint-hud": "Sprint State Hud",
-    "firmament.config.fixes.peek-chat": "Peek Chat",
-    "firmament.config.fixes.clientside-lefthand": "Fix Left Handed",
-    "firmament.fixes.auto-sprint.on": "Sprint toggled",
-    "firmament.fixes.auto-sprint.sprinting": "Sprinting",
-    "firmament.fixes.auto-sprint.not-sprinting": "Not Sprinting",
+	"firmament.ursa.debugrequest.result": "Ursa request succeeded: %s",
+	"firmament.sbinfo.nolocraw": "No locraw data available",
+	"firmament.sbinfo.profile": "Current profile cutename: %s",
+	"firmament.sbinfo.server": "Locraw Server: %s",
+	"firmament.sbinfo.gametype": "Locraw Gametype: %s",
+	"firmament.sbinfo.mode": "Locraw Mode: %s",
+	"firmament.sbinfo.map": "Locraw Map: %s",
+	"firmament.config.price-data": "Price data",
+	"firmament.config.price-data.enable-always": "Enable Item Price",
+	"firmament.config.price-data.enable-keybind": "Enable only with Keybinding",
+	"firmament.config.fairy-souls": "Fairy Souls",
+	"firmament.config.fairy-souls.show": "Show Fairy Soul Waypoints",
+	"firmament.config.fairy-souls.reset": "Reset Collected Fairy Souls",
+	"firmament.config.fishing-warning": "Fishing Warning",
+	"firmament.config.fishing-warning.display-warning": "Display a warning when you are about to hook a fish",
+	"firmament.config.fishing-warning.highlight-wake-chain": "Highlight fishing particles",
+	"firmament.protectitem": "Firmament protected your item: ",
+	"firmament.inventory-buttons.save-preset": "Save Preset",
+	"firmament.inventory-buttons.load-preset": "Load Preset",
+	"firmament.inventory-buttons.import-failed": "One of your buttons could only be imported partially",
+	"firmament.config.inventory-buttons": "Inventory buttons",
+	"firmament.config.inventory-buttons.open-editor": "Open Editor",
+	"firmament.warp-util.mark-excluded": "Firmament: Tried to warp to %s, but it was not unlocked. I will avoid warping there again.",
+	"firmament.waypoint.temporary": "Temporary Waypoint: %s",
+	"firmament.config.waypoints": "Waypoints",
+	"firmament.config.waypoints.temp-waypoint-duration": "Temporary Waypoint Duration",
+	"firmament.config.waypoints.show-index": "Show ordered waypoint indexes",
+	"firmament.config.waypoints.skip-to-nearest": "Allow skipping waypoints",
+	"firmament.recipe.forge.time": "Forging Time: %s",
+	"firmament.recipe.mobs.drops": "§e§lDrop Chance: %s",
+	"firmament.recipe.mobs.name": "§8[§7Lv %d§8] §c%s",
+	"firmament.recipe.mobs.name.nolevel": "§c%s",
+	"firmament.recipe.mobs.coins": "§eCoins: %s",
+	"firmament.recipe.mobs.combat": "§bCombat Experience: %s",
+	"firmament.recipe.mobs.exp": "§6Experience: %s",
+	"firmament.pv.skills": "Skills",
+	"firmament.pv.skills.farming": "Farming",
+	"firmament.pv.skills.foraging": "Foraging",
+	"firmament.pv.skills.mining": "Mining",
+	"firmament.pv.skills.alchemy": "Alchemy",
+	"firmament.pv.skills.taming": "Taming",
+	"firmament.pv.skills.fishing": "Fishing",
+	"firmament.pv.skills.runecrafting": "Runecrafting",
+	"firmament.pv.skills.carpentry": "Carpentry",
+	"firmament.pv.skills.combat": "Combat",
+	"firmament.pv.skills.social": "Social",
+	"firmament.pv.skills.rift": "Rift",
+	"firmament.pv.skills.enchanting": "Enchanting",
+	"firmament.pv.skills.total": "Total Exp: %s",
+	"firmament.pv.lookingup": "Looking up %s",
+	"firmament.pv.noprofile": "%s has no SkyBlock profiles",
+	"firmament.pv.noplayer": "%s is not a Minecraft player",
+	"firmament.config.save-cursor-position.enable": "Enable",
+	"firmament.config.save-cursor-position.tolerance": "Tolerance",
+	"firmament.config.save-cursor-position": "Save Cursor Position",
+	"firmament.config.storage-overlay": "Storage Overlay",
+	"firmament.config.storage-overlay.rows": "Columns",
+	"firmament.config.storage-overlay.always-replace": "Always Open Overlay",
+	"firmament.config.storage-overlay.padding": "Padding",
+	"firmament.config.storage-overlay.scroll-speed": "Scroll Speed",
+	"firmament.config.storage-overlay.inverse-scroll": "Invert Scroll",
+	"firmament.config.storage-overlay.margin": "Margin",
+	"firmament.config.chat-links": "Chat Links",
+	"firmament.config.chat-links.links-enabled": "Enable Clickable Links",
+	"firmament.config.chat-links.image-enabled": "Enable Image Preview",
+	"firmament.config.chat-links.allow-all-hosts": "Allow all Image Hosts",
+	"firmament.config.chat-links.allowed-hosts": "Allowed Image Hosts",
+	"firmament.config.chat-links.position": "Chat Image Preview",
+	"firmament.config.item-rarity-cosmetics": "Item Rarity Cosmetics",
+	"firmament.config.item-rarity-cosmetics.background": "Slot Background Rarity",
+	"firmament.config.item-rarity-cosmetics.background-hotbar": "Hotbar Background Rarity",
+	"firmament.hud.edit": "Edit %s",
+	"firmament.key.category": "Firmament",
+	"firmament.keybinding.external": "%s",
+	"firmament.config.slot-locking": "Slot Locking",
+	"firmament.config.slot-locking.lock": "Lock Slot",
+	"firmament.config.slot-locking.lock-uuid": "Lock UUID (Lock Item)",
+	"firmament.config.slot-locking.bind": "Bind Slot",
+	"firmament.config.slot-locking.require-quick-move": "Require Shift-Click for Bound Slots",
+	"firmament.config.fixes.auto-sprint": "Auto Sprint",
+	"firmament.config.fixes.auto-sprint-keybinding": "Auto Sprint KeyBinding",
+	"firmament.config.fixes.auto-sprint-hud": "Sprint State Hud",
+	"firmament.config.fixes.peek-chat": "Peek Chat",
+	"firmament.config.fixes.clientside-lefthand": "Fix Left Handed",
+	"firmament.fixes.auto-sprint.on": "Sprint toggled",
+	"firmament.fixes.auto-sprint.sprinting": "Sprinting",
+	"firmament.fixes.auto-sprint.not-sprinting": "Not Sprinting",
 	"firmament.recipe.novanilla": "Hypixel cannot super craft vanilla recipes",
-    "firmament.config.custom-skyblock-textures": "Custom SkyBlock Item Textures",
-    "firmament.config.custom-skyblock-textures.cache-duration": "Model Cache Duration",
-    "firmament.config.custom-skyblock-textures.model-overrides": "Enable model overrides/predicates",
-    "firmament.config.custom-skyblock-textures.legacy-cit": "Enable legacy CIT Resewn compat",
-    "firmament.config.custom-skyblock-textures.recolor-text": "Allow packs to recolor text",
-    "firmament.config.custom-skyblock-textures.armor-overrides": "Enable Armor re-texturing",
-    "firmament.config.custom-skyblock-textures.block-overrides": "Enable Block re-modelling",
-    "firmament.config.custom-skyblock-textures.enabled": "Enable Custom Item Textures",
-    "firmament.config.custom-skyblock-textures.skulls-enabled": "Enable Custom Placed Skull Textures",
-    "firmament.config.custom-skyblock-textures.cache-forever": "Disable cache clearing",
-    "firmament.config.fixes": "Fixes",
-    "firmament.config.fixes.player-skins": "Fix unsigned Player Skins",
-    "firmament.config.power-user.show-item-id": "Show SkyBlock Ids",
-    "firmament.config.power-user.copy-item-id": "Copy SkyBlock Id",
-    "firmament.modapi.event": "Received mod API event: %s",
-    "firmament.config.power-user.copy-texture-pack-id": "Copy Texture Pack Id",
-    "firmament.config.power-user.copy-skull-texture": "Copy Placed Skull Id",
-    "firmament.config.power-user.entity-data": "Show Entity Data",
-    "firmament.config.power-user.copy-nbt-data": "Copy ExtraAttributes data",
-    "firmament.config.power-user.copy-lore": "Copy Name + Lore",
-    "firmament.config.power-user.copy-item-stack": "Copy ItemStack",
-    "firmament.config.power-user": "Power Users",
-    "firmament.tooltip.skyblockid": "SkyBlock Id: %s",
-    "firmament.tooltip.copied.skyblockid.fail": "Failed to copy SkyBlock Id",
-    "firmament.tooltip.copied.skyblockid": "Copied SkyBlock Id: %s",
-    "firmament.tooltip.copied.modelid.fail": "Failed to copy Texture Id",
-    "firmament.tooltip.copied.modelid": "Copied Texture Id: %s",
-    "firmament.tooltip.copied.skull": "Copied Skull Id: %s",
-    "firmament.tooltip.copied.skull.fail": "Failed to copy skull id.",
-    "firmament.tooltip.copied.nbt": "Copied NBT data",
-    "firmament.tooltip.copied.lore": "Copied Name and Lore",
+	"firmament.config.custom-skyblock-textures": "Custom SkyBlock Item Textures",
+	"firmament.config.custom-skyblock-textures.cache-duration": "Model Cache Duration",
+	"firmament.config.custom-skyblock-textures.model-overrides": "Enable model overrides/predicates",
+	"firmament.config.custom-skyblock-textures.legacy-cit": "Enable legacy CIT Resewn compat",
+	"firmament.config.custom-skyblock-textures.recolor-text": "Allow packs to recolor text",
+	"firmament.config.custom-skyblock-textures.armor-overrides": "Enable Armor re-texturing",
+	"firmament.config.custom-skyblock-textures.block-overrides": "Enable Block re-modelling",
+	"firmament.config.custom-skyblock-textures.enabled": "Enable Custom Item Textures",
+	"firmament.config.custom-skyblock-textures.skulls-enabled": "Enable Custom Placed Skull Textures",
+	"firmament.config.custom-skyblock-textures.cache-forever": "Disable cache clearing",
+	"firmament.config.fixes": "Fixes",
+	"firmament.config.fixes.player-skins": "Fix unsigned Player Skins",
+	"firmament.config.power-user.show-item-id": "Show SkyBlock Ids",
+	"firmament.config.power-user.copy-item-id": "Copy SkyBlock Id",
+	"firmament.modapi.event": "Received mod API event: %s",
+	"firmament.config.power-user.copy-texture-pack-id": "Copy Texture Pack Id",
+	"firmament.config.power-user.copy-skull-texture": "Copy Placed Skull Id",
+	"firmament.config.power-user.entity-data": "Show Entity Data",
+	"firmament.config.power-user.copy-nbt-data": "Copy ExtraAttributes data",
+	"firmament.config.power-user.copy-lore": "Copy Name + Lore",
+	"firmament.config.power-user.copy-item-stack": "Copy ItemStack",
+	"firmament.config.power-user": "Power Users",
+	"firmament.tooltip.skyblockid": "SkyBlock Id: %s",
+	"firmament.tooltip.copied.skyblockid.fail": "Failed to copy SkyBlock Id",
+	"firmament.tooltip.copied.skyblockid": "Copied SkyBlock Id: %s",
+	"firmament.tooltip.copied.modelid.fail": "Failed to copy Texture Id",
+	"firmament.tooltip.copied.modelid": "Copied Texture Id: %s",
+	"firmament.tooltip.copied.skull": "Copied Skull Id: %s",
+	"firmament.tooltip.copied.skull.fail": "Failed to copy skull id.",
+	"firmament.tooltip.copied.nbt": "Copied NBT data",
+	"firmament.tooltip.copied.lore": "Copied Name and Lore",
 	"firmament.tooltip.copied.stack": "Copied ItemStack",
-    "firmament.config.compatibility": "Intermod Features",
-    "firmament.config.compatibility.explosion-enabled": "Redirect Enhanced Explosions",
-    "firmament.config.compatibility.explosion-power": "Enhanced Explosion Power",
-    "firmament.quick-commands.join.unknown": "Could not find instance for %s",
-    "firmament.quick-commands.join.success": "Joining: %s",
-    "firmament.quick-commands.join.explain": "Join a dungeon or kuudra floor by using commands like /join f1, /join m7, /join fe or /join khot",
-    "firmament.quick-commands.join.unknown-kuudra": "Unknown kuudra floor %s",
-    "firmament.quick-commands.join.unknown-catacombs": "Unknown catacombs floor %s",
-    "firmament.config.pickaxe-info": "Pickaxes",
-    "firmament.config.pickaxe-info.ability-cooldown": "Pickaxe Ability Cooldown",
-    "firmament.config.pickaxe-info.ability-scale": "Ability Cooldown Scale",
-    "firmament.config.pickaxe-info.fuel-bar": "Drill Fuel Durability Bar",
-    "firmament.warp-util.no-warp-found": "Could not find an unlocked warp in %s",
-    "firmament.warp-util.already-close": "Already closer to destination than /warp %s",
-    "firmament.warp-util.attempting-to-warp": "Trying to warp to /warp %s",
-    "firmament.warp-util.clear-excluded": "Marked all /warp commands as potentially available."
+	"firmament.config.compatibility": "Intermod Features",
+	"firmament.config.compatibility.explosion-enabled": "Redirect Enhanced Explosions",
+	"firmament.config.compatibility.explosion-power": "Enhanced Explosion Power",
+	"firmament.quick-commands.join.unknown": "Could not find instance for %s",
+	"firmament.quick-commands.join.success": "Joining: %s",
+	"firmament.quick-commands.join.explain": "Join a dungeon or kuudra floor by using commands like /join f1, /join m7, /join fe or /join khot",
+	"firmament.quick-commands.join.unknown-kuudra": "Unknown kuudra floor %s",
+	"firmament.quick-commands.join.unknown-catacombs": "Unknown catacombs floor %s",
+	"firmament.config.pickaxe-info": "Pickaxes",
+	"firmament.config.pickaxe-info.ability-cooldown": "Pickaxe Ability Cooldown",
+	"firmament.config.pickaxe-info.ability-scale": "Ability Cooldown Scale",
+	"firmament.config.pickaxe-info.fuel-bar": "Drill Fuel Durability Bar",
+	"firmament.warp-util.no-warp-found": "Could not find an unlocked warp in %s",
+	"firmament.warp-util.already-close": "Already closer to destination than /warp %s",
+	"firmament.warp-util.attempting-to-warp": "Trying to warp to /warp %s",
+	"firmament.warp-util.clear-excluded": "Marked all /warp commands as potentially available."
 }
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index b40514e..609304a 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -46,10 +46,7 @@
     "depends": {
         "fabric": "*",
         "fabric-language-kotlin": ">=${fabric_kotlin_version}",
-        "minecraft": ">=${minecraft_version}",
-        "roughlyenoughitems": ">=${rei_version}",
-        "cloth-config": "*",
-        "architectury": "*"
+        "minecraft": ">=${minecraft_version}"
     },
     "custom": {
         "configured": {
diff --git a/src/test/resources/testdata/items/rune-in-sack.snbt b/src/test/resources/testdata/items/rune-in-sack.snbt
new file mode 100644
index 0000000..b15488a
--- /dev/null
+++ b/src/test/resources/testdata/items/rune-in-sack.snbt
@@ -0,0 +1,31 @@
+{
+	components: {
+		"minecraft:custom_data": {
+		},
+		"minecraft:custom_name": '{"extra":[{"color":"dark_blue","text":"◆ "},{"bold":true,"color":"dark_blue","text":"Tidal Rune"}],"italic":false,"text":""}',
+		"minecraft:lore": [
+			'{"extra":[{"color":"dark_gray","text":"Rune Sack"}],"italic":false,"text":""}',
+			'{"italic":false,"text":""}',
+			'{"extra":[{"color":"yellow","text":"I"},{"color":"gray","text":": "},{"color":"yellow","text":"1"},{"color":"gray","text":"/64"}],"italic":false,"text":""}',
+			'{"extra":[{"color":"yellow","text":"II"},{"color":"gray","text":": "},{"color":"dark_gray","text":"0"},{"color":"gray","text":"/64"}],"italic":false,"text":""}',
+			'{"extra":[{"color":"yellow","text":"III"},{"color":"gray","text":": "},{"color":"dark_gray","text":"0"},{"color":"gray","text":"/64"}],"italic":false,"text":""}',
+			'{"italic":false,"text":""}',
+			'{"extra":[{"color":"gray","text":"Tier: "},{"color":"gold","text":"Legendary"}],"italic":false,"text":""}',
+			'{"italic":false,"text":""}',
+			'{"extra":[{"color":"yellow","text":"Click to pickup!"}],"italic":false,"text":""}'
+		],
+		"minecraft:profile": {
+			id: "30ea28d50755386a90924ae91af1b7e5",
+			name: "30ea28d50755386a",
+			properties: [
+				{
+					name: "textures",
+					signature: "",
+					value: "ewogICJ0aW1lc3RhbXAiIDogMTcxOTUwMzQ3NzI1MSwKICAicHJvZmlsZUlkIiA6ICIxOWY1YzkwMWEzMjQ0YzVmYTM4NThjZGVhNDk5ZWMwYSIsCiAgInByb2ZpbGVOYW1lIiA6ICJzb2RpdW16aXAiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjk2NzEwNDdjNmFkZThhOWM0ZDZhNTgxYmMyNmQyODRhNTRhZTMyZTg1YzM0Y2U2OWQ4MWY5Mjc5OWJmM2ZiYiIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9"
+				}
+			]
+		}
+	},
+	count: 1,
+	id: "minecraft:player_head"
+}
-- 
cgit