From cb032e7c221e40736ab26b193eea4e32d06113d9 Mon Sep 17 00:00:00 2001 From: nea Date: Thu, 31 Aug 2023 19:38:38 +0200 Subject: Add price tooltips --- src/main/kotlin/moe/nea/firmament/Firmament.kt | 6 +- .../moe/nea/firmament/events/ItemTooltipEvent.kt | 17 ++ .../moe/nea/firmament/features/FeatureManager.kt | 2 + .../nea/firmament/features/inventory/PriceData.kt | 61 +++++++ .../moe/nea/firmament/gui/config/ManagedConfig.kt | 25 ++- .../nea/firmament/keybindings/SavedKeyBinding.kt | 42 ++++- .../resources/assets/firmament/lang/en_us.json | 184 +++++++++++---------- 7 files changed, 237 insertions(+), 100 deletions(-) create mode 100644 src/main/kotlin/moe/nea/firmament/events/ItemTooltipEvent.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt diff --git a/src/main/kotlin/moe/nea/firmament/Firmament.kt b/src/main/kotlin/moe/nea/firmament/Firmament.kt index 1a2739e..e4b874a 100644 --- a/src/main/kotlin/moe/nea/firmament/Firmament.kt +++ b/src/main/kotlin/moe/nea/firmament/Firmament.kt @@ -14,13 +14,13 @@ import io.ktor.client.plugins.compression.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.logging.* import io.ktor.serialization.kotlinx.json.* -import java.lang.Exception import java.nio.file.Files import java.nio.file.Path import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents import net.fabricmc.loader.api.FabricLoader import net.fabricmc.loader.api.Version @@ -41,6 +41,7 @@ import net.minecraft.command.CommandRegistryAccess import net.minecraft.util.Identifier import moe.nea.firmament.commands.registerFirmamentCommand import moe.nea.firmament.dbus.FirmamentDbusObject +import moe.nea.firmament.events.ItemTooltipEvent import moe.nea.firmament.events.ScreenRenderPostEvent import moe.nea.firmament.events.TickEvent import moe.nea.firmament.features.FeatureManager @@ -133,6 +134,9 @@ object Firmament { globalJob.cancel() } }) + ItemTooltipCallback.EVENT.register { a, b, c -> + ItemTooltipEvent.publish(ItemTooltipEvent(a, b, c)) + } ScreenEvents.AFTER_INIT.register(ScreenEvents.AfterInit { client, screen, scaledWidth, scaledHeight -> ScreenEvents.afterRender(screen) .register(ScreenEvents.AfterRender { screen, drawContext, mouseX, mouseY, tickDelta -> diff --git a/src/main/kotlin/moe/nea/firmament/events/ItemTooltipEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ItemTooltipEvent.kt new file mode 100644 index 0000000..ba597cd --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/ItemTooltipEvent.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events + +import net.minecraft.client.item.TooltipContext +import net.minecraft.item.ItemStack +import net.minecraft.text.Text + +data class ItemTooltipEvent( + val stack: ItemStack, val context: TooltipContext, val lines: MutableList +) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt index facb821..91f5e03 100644 --- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt +++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt @@ -14,6 +14,7 @@ import moe.nea.firmament.features.debug.DebugView import moe.nea.firmament.features.debug.DeveloperFeatures import moe.nea.firmament.features.fixes.Fixes import moe.nea.firmament.features.inventory.CraftingOverlay +import moe.nea.firmament.features.inventory.PriceData import moe.nea.firmament.features.inventory.SaveCursorPosition import moe.nea.firmament.features.inventory.SlotLocking import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay @@ -48,6 +49,7 @@ object FeatureManager : DataHolder(serializer(), "feature loadFeature(ChatLinks) loadFeature(SaveCursorPosition) loadFeature(CustomSkyBlockTextures) + loadFeature(PriceData) loadFeature(Fixes) if (Firmament.DEBUG) { loadFeature(DeveloperFeatures) diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt new file mode 100644 index 0000000..3ae7b00 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/PriceData.kt @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.inventory + +import net.minecraft.text.Text +import moe.nea.firmament.events.ItemTooltipEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.repo.HypixelStaticData +import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.skyBlockId + +object PriceData : FirmamentFeature { + override val identifier: String + get() = "price-data" + + object TConfig : ManagedConfig(identifier) { + val tooltipEnabled by toggle("enable-always") { true } + val enableKeybinding by keyBindingWithDefaultUnbound("enable-keybind") + } + + override val config get() = TConfig + + override fun onLoad() { + ItemTooltipEvent.subscribe { + if (!TConfig.tooltipEnabled && !TConfig.enableKeybinding.isPressed()) { + return@subscribe + } + val sbId = it.stack.skyBlockId + val bazaarData = HypixelStaticData.bazaarData[sbId] + val lowestBin = HypixelStaticData.lowestBin[sbId] + if (bazaarData != null) { + it.lines.add(Text.literal("")) + it.lines.add( + Text.translatable( + "firmament.tooltip.bazaar.sell-order", + FirmFormatters.toString(bazaarData.quickStatus.sellPrice, 1) + ) + ) + it.lines.add( + Text.translatable( + "firmament.tooltip.bazaar.buy-order", + FirmFormatters.toString(bazaarData.quickStatus.buyPrice, 1) + ) + ) + } else if (lowestBin != null) { + it.lines.add(Text.literal("")) + it.lines.add( + Text.translatable( + "firmament.tooltip.ah.lowestbin", + FirmFormatters.toString(lowestBin, 1) + ) + ) + } + } + } +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt b/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt index 8743293..8e05632 100644 --- a/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt +++ b/src/main/kotlin/moe/nea/firmament/gui/config/ManagedConfig.kt @@ -14,22 +14,23 @@ import io.github.cottonmc.cotton.gui.widget.WLabel import io.github.cottonmc.cotton.gui.widget.data.Axis import io.github.cottonmc.cotton.gui.widget.data.Insets import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment +import moe.nea.jarvis.api.Point +import org.lwjgl.glfw.GLFW import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject +import kotlin.io.path.createDirectories +import kotlin.io.path.readText +import kotlin.io.path.writeText +import kotlin.time.Duration +import net.minecraft.client.gui.screen.Screen +import net.minecraft.text.Text import moe.nea.firmament.Firmament import moe.nea.firmament.gui.WTightScrollPanel import moe.nea.firmament.keybindings.SavedKeyBinding import moe.nea.firmament.util.MC import moe.nea.firmament.util.ScreenUtil.setScreenLater -import moe.nea.jarvis.api.Point -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.Text -import kotlin.io.path.createDirectories -import kotlin.io.path.readText -import kotlin.io.path.writeText -import kotlin.time.Duration abstract class ManagedConfig(override val name: String) : ManagedConfigElement() { @@ -110,15 +111,21 @@ abstract class ManagedConfig(override val name: String) : ManagedConfigElement() protected fun keyBinding( propertyName: String, default: () -> Int, - ): ManagedOption = keyBindingWithDefaultModifiers(propertyName) { SavedKeyBinding(default()) } + ): ManagedOption = keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(default()) } - protected fun keyBindingWithDefaultModifiers( + protected fun keyBindingWithOutDefaultModifiers( propertyName: String, default: () -> SavedKeyBinding, ): ManagedOption { return option(propertyName, default, KeyBindingHandler("firmament.config.${name}.${propertyName}", this)) } + protected fun keyBindingWithDefaultUnbound( + propertyName: String, + ): ManagedOption { + return keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN) } + } + protected fun integer( propertyName: String, min: Int, diff --git a/src/main/kotlin/moe/nea/firmament/keybindings/SavedKeyBinding.kt b/src/main/kotlin/moe/nea/firmament/keybindings/SavedKeyBinding.kt index e0f0c50..606485b 100644 --- a/src/main/kotlin/moe/nea/firmament/keybindings/SavedKeyBinding.kt +++ b/src/main/kotlin/moe/nea/firmament/keybindings/SavedKeyBinding.kt @@ -6,8 +6,11 @@ package moe.nea.firmament.keybindings -import kotlinx.serialization.Serializable import org.lwjgl.glfw.GLFW +import kotlinx.serialization.Serializable +import net.minecraft.client.MinecraftClient +import net.minecraft.client.util.InputUtil +import moe.nea.firmament.util.MC @Serializable data class SavedKeyBinding( @@ -35,6 +38,43 @@ data class SavedKeyBinding( } } + fun hasShiftDown(): Boolean { + return InputUtil.isKeyPressed( + MinecraftClient.getInstance().window.handle, + GLFW.GLFW_KEY_LEFT_SHIFT + ) || InputUtil.isKeyPressed( + MinecraftClient.getInstance().window.handle, GLFW.GLFW_KEY_RIGHT_SHIFT + ) + } + + fun hasAltDown(): Boolean { + return InputUtil.isKeyPressed( + MinecraftClient.getInstance().window.handle, + GLFW.GLFW_KEY_LEFT_ALT + ) || InputUtil.isKeyPressed( + MinecraftClient.getInstance().window.handle, GLFW.GLFW_KEY_RIGHT_ALT + ) + } + + fun isPressed(): Boolean { + val h = MC.window.handle + if (!InputUtil.isKeyPressed(h, keyCode)) return false + + val ctrl = if (MinecraftClient.IS_SYSTEM_MAC) { + InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SUPER) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SUPER) + } else InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_CONTROL) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_CONTROL) + val shift = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_SHIFT) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_SHIFT) + val alt = InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_LEFT_ALT) + || InputUtil.isKeyPressed(h, GLFW.GLFW_KEY_RIGHT_ALT) + + return (ctrl == this.ctrl) && + (alt == this.alt) && + (shift == this.shift) + } + override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt) } diff --git a/src/main/resources/assets/firmament/lang/en_us.json b/src/main/resources/assets/firmament/lang/en_us.json index 8315cef..2058c4b 100644 --- a/src/main/resources/assets/firmament/lang/en_us.json +++ b/src/main/resources/assets/firmament/lang/en_us.json @@ -1,91 +1,97 @@ { - "firmament.dev.resourcerebuild.start": "Invoking gradle resource rebuild (./gradlew :processResources)", - "firmament.dev.resourcerebuild.done": "Gradle resource rebuild done in %s", - "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.pv.pets": "Pets", - "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", - "firmanent.config.edit": "Edit", - "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.repo.branch": "Repo Branch", - "firmament.config.repo.branch.hint": "dangerous", - "firmament.config.repo.reset": "Reset", - "firmament.ursa.debugrequest.start": "Ursa request launched", - "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.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.key.slotlocking": "Lock Slot / Slot Binding", - "firmament.key.category": "Firmament", - "firmament.protectitem": "Firmament protected your item: ", - "firmament.recipe.forge.time": "Forging Time: %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": "Rows", - "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.hud.edit": "Edit %s", - "firmament.keybinding.external": "External", - "firmament.config.slot-locking": "Slot Locking", - "firmament.config.slot-locking.lock": "Lock Slot", - "firmament.config.fixes.auto-sprint": "Auto Sprint", - "firmament.config.custom-skyblock-textures": "Custom SkyBlock Item Textures", - "firmament.config.custom-skyblock-textures.cache-duration": "Model Cache Duration", - "firmament.config.custom-skyblock-textures.enabled": "Enable Custom Item Textures", - "firmament.config.fixes": "Fixes", - "firmament.config.fixes.player-skins": "Fix unsigned Player Skins" + "firmament.dev.resourcerebuild.start": "Invoking gradle resource rebuild (./gradlew :processResources)", + "firmament.dev.resourcerebuild.done": "Gradle resource rebuild done in %s", + "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.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", + "firmanent.config.edit": "Edit", + "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.repo.branch": "Repo Branch", + "firmament.config.repo.branch.hint": "dangerous", + "firmament.config.repo.reset": "Reset", + "firmament.ursa.debugrequest.start": "Ursa request launched", + "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.key.slotlocking": "Lock Slot / Slot Binding", + "firmament.key.category": "Firmament", + "firmament.protectitem": "Firmament protected your item: ", + "firmament.recipe.forge.time": "Forging Time: %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": "Rows", + "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.hud.edit": "Edit %s", + "firmament.keybinding.external": "External", + "firmament.config.slot-locking": "Slot Locking", + "firmament.config.slot-locking.lock": "Lock Slot", + "firmament.config.fixes.auto-sprint": "Auto Sprint", + "firmament.config.custom-skyblock-textures": "Custom SkyBlock Item Textures", + "firmament.config.custom-skyblock-textures.cache-duration": "Model Cache Duration", + "firmament.config.custom-skyblock-textures.enabled": "Enable Custom Item Textures", + "firmament.config.fixes": "Fixes", + "firmament.config.fixes.player-skins": "Fix unsigned Player Skins" } -- cgit