Add price tooltips

This commit is contained in:
nea
2023-08-31 19:38:38 +02:00
parent b7b01f1c6f
commit cb032e7c22
7 changed files with 237 additions and 100 deletions

View File

@@ -14,13 +14,13 @@ import io.ktor.client.plugins.compression.*
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.* import io.ktor.client.plugins.logging.*
import io.ktor.serialization.kotlinx.json.* import io.ktor.serialization.kotlinx.json.*
import java.lang.Exception
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource 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.ClientLifecycleEvents
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents 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.fabric.api.client.screen.v1.ScreenEvents
import net.fabricmc.loader.api.FabricLoader import net.fabricmc.loader.api.FabricLoader
import net.fabricmc.loader.api.Version import net.fabricmc.loader.api.Version
@@ -41,6 +41,7 @@ import net.minecraft.command.CommandRegistryAccess
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import moe.nea.firmament.commands.registerFirmamentCommand import moe.nea.firmament.commands.registerFirmamentCommand
import moe.nea.firmament.dbus.FirmamentDbusObject import moe.nea.firmament.dbus.FirmamentDbusObject
import moe.nea.firmament.events.ItemTooltipEvent
import moe.nea.firmament.events.ScreenRenderPostEvent import moe.nea.firmament.events.ScreenRenderPostEvent
import moe.nea.firmament.events.TickEvent import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.FeatureManager import moe.nea.firmament.features.FeatureManager
@@ -133,6 +134,9 @@ object Firmament {
globalJob.cancel() 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.AFTER_INIT.register(ScreenEvents.AfterInit { client, screen, scaledWidth, scaledHeight ->
ScreenEvents.afterRender(screen) ScreenEvents.afterRender(screen)
.register(ScreenEvents.AfterRender { screen, drawContext, mouseX, mouseY, tickDelta -> .register(ScreenEvents.AfterRender { screen, drawContext, mouseX, mouseY, tickDelta ->

View File

@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
*
* 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<Text>
) : FirmamentEvent() {
companion object : FirmamentEventBus<ItemTooltipEvent>()
}

View File

@@ -14,6 +14,7 @@ import moe.nea.firmament.features.debug.DebugView
import moe.nea.firmament.features.debug.DeveloperFeatures import moe.nea.firmament.features.debug.DeveloperFeatures
import moe.nea.firmament.features.fixes.Fixes import moe.nea.firmament.features.fixes.Fixes
import moe.nea.firmament.features.inventory.CraftingOverlay 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.SaveCursorPosition
import moe.nea.firmament.features.inventory.SlotLocking import moe.nea.firmament.features.inventory.SlotLocking
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay
@@ -48,6 +49,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
loadFeature(ChatLinks) loadFeature(ChatLinks)
loadFeature(SaveCursorPosition) loadFeature(SaveCursorPosition)
loadFeature(CustomSkyBlockTextures) loadFeature(CustomSkyBlockTextures)
loadFeature(PriceData)
loadFeature(Fixes) loadFeature(Fixes)
if (Firmament.DEBUG) { if (Firmament.DEBUG) {
loadFeature(DeveloperFeatures) loadFeature(DeveloperFeatures)

View File

@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
*
* 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)
)
)
}
}
}
}

View File

@@ -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.Axis
import io.github.cottonmc.cotton.gui.widget.data.Insets import io.github.cottonmc.cotton.gui.widget.data.Insets
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment 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.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject 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.Firmament
import moe.nea.firmament.gui.WTightScrollPanel import moe.nea.firmament.gui.WTightScrollPanel
import moe.nea.firmament.keybindings.SavedKeyBinding import moe.nea.firmament.keybindings.SavedKeyBinding
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.ScreenUtil.setScreenLater 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() { abstract class ManagedConfig(override val name: String) : ManagedConfigElement() {
@@ -110,15 +111,21 @@ abstract class ManagedConfig(override val name: String) : ManagedConfigElement()
protected fun keyBinding( protected fun keyBinding(
propertyName: String, propertyName: String,
default: () -> Int, default: () -> Int,
): ManagedOption<SavedKeyBinding> = keyBindingWithDefaultModifiers(propertyName) { SavedKeyBinding(default()) } ): ManagedOption<SavedKeyBinding> = keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(default()) }
protected fun keyBindingWithDefaultModifiers( protected fun keyBindingWithOutDefaultModifiers(
propertyName: String, propertyName: String,
default: () -> SavedKeyBinding, default: () -> SavedKeyBinding,
): ManagedOption<SavedKeyBinding> { ): ManagedOption<SavedKeyBinding> {
return option(propertyName, default, KeyBindingHandler("firmament.config.${name}.${propertyName}", this)) return option(propertyName, default, KeyBindingHandler("firmament.config.${name}.${propertyName}", this))
} }
protected fun keyBindingWithDefaultUnbound(
propertyName: String,
): ManagedOption<SavedKeyBinding> {
return keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN) }
}
protected fun integer( protected fun integer(
propertyName: String, propertyName: String,
min: Int, min: Int,

View File

@@ -6,8 +6,11 @@
package moe.nea.firmament.keybindings package moe.nea.firmament.keybindings
import kotlinx.serialization.Serializable
import org.lwjgl.glfw.GLFW 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 @Serializable
data class SavedKeyBinding( 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 { override fun matches(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt) return keyCode == this.keyCode && getMods(modifiers) == Triple(shift, ctrl, alt)
} }

View File

@@ -1,91 +1,97 @@
{ {
"firmament.dev.resourcerebuild.start": "Invoking gradle resource rebuild (./gradlew :processResources)", "firmament.dev.resourcerebuild.start": "Invoking gradle resource rebuild (./gradlew :processResources)",
"firmament.dev.resourcerebuild.done": "Gradle resource rebuild done in %s", "firmament.dev.resourcerebuild.done": "Gradle resource rebuild done in %s",
"firmament.config.developer": "Developer Settings", "firmament.config.developer": "Developer Settings",
"firmament.config.developer.auto-rebuild": "Automatically rebuild resources", "firmament.config.developer.auto-rebuild": "Automatically rebuild resources",
"firmament.price": "Checking price for %s", "firmament.price": "Checking price for %s",
"firmament.price.bazaar": "Bazaar stats:", "firmament.price.bazaar": "Bazaar stats:",
"firmament.price.bazaar.productid": "Stock id: %s", "firmament.price.bazaar.productid": "Stock id: %s",
"firmament.price.bazaar.buy.price": "Buy Price: %s", "firmament.price.bazaar.buy.price": "Buy Price: %s",
"firmament.price.bazaar.buy.order": "Buy orders: %d", "firmament.price.bazaar.buy.order": "Buy orders: %d",
"firmament.pv.pets": "Pets", "firmament.tooltip.bazaar.sell-order": "Bazaar Sell Order: %s",
"firmament.debug.skyblockid": "SkyBlock ID: %s", "firmament.tooltip.bazaar.buy-order": "Bazaar Buy Order: %s",
"firmament.debug.skyblockid.copy": "Click to copy SkyBlock ID", "firmament.tooltip.ah.lowestbin": "Lowest BIN: %d",
"firmament.price.bazaar.sell.price": "Sell Price: %s", "firmament.pv.pets": "Pets",
"firmament.price.bazaar.sell.order": "Sell orders: %d", "firmament.debug.skyblockid": "SkyBlock ID: %s",
"firmament.price.lowestbin": "Lowest BIN: %s", "firmament.debug.skyblockid.copy": "Click to copy SkyBlock ID",
"firmament.repo.reload.network": "Trying to redownload the repository", "firmament.price.bazaar.sell.price": "Sell Price: %s",
"firmament.repo.reload.disk": "Reloading repository from disk. This may lag a bit.", "firmament.price.bazaar.sell.order": "Sell orders: %d",
"firmament.repo.cache": "Recaching items", "firmament.price.lowestbin": "Lowest BIN: %s",
"firmament.repo.brokenitem": "Failed to render item: %s", "firmament.repo.reload.network": "Trying to redownload the repository",
"firmanent.config.edit": "Edit", "firmament.repo.reload.disk": "Reloading repository from disk. This may lag a bit.",
"firmament.config.repo": "Firmament Repo Settings", "firmament.repo.cache": "Recaching items",
"firmament.config.repo.autoUpdate": "Auto Update", "firmament.repo.brokenitem": "Failed to render item: %s",
"firmament.config.repo.username": "Repo Username", "firmanent.config.edit": "Edit",
"firmament.config.repo.username.hint": "NotEnoughUpdates", "firmament.config.repo": "Firmament Repo Settings",
"firmament.config.repo.reponame": "Repo Name", "firmament.config.repo.autoUpdate": "Auto Update",
"firmament.config.repo.reponame.hint": "NotEnoughUpdates-REPO", "firmament.config.repo.username": "Repo Username",
"firmament.config.repo.branch": "Repo Branch", "firmament.config.repo.username.hint": "NotEnoughUpdates",
"firmament.config.repo.branch.hint": "dangerous", "firmament.config.repo.reponame": "Repo Name",
"firmament.config.repo.reset": "Reset", "firmament.config.repo.reponame.hint": "NotEnoughUpdates-REPO",
"firmament.ursa.debugrequest.start": "Ursa request launched", "firmament.config.repo.branch": "Repo Branch",
"firmament.ursa.debugrequest.result": "Ursa request succeeded: %s", "firmament.config.repo.branch.hint": "dangerous",
"firmament.sbinfo.nolocraw": "No locraw data available", "firmament.config.repo.reset": "Reset",
"firmament.sbinfo.profile": "Current profile cutename: %s", "firmament.ursa.debugrequest.start": "Ursa request launched",
"firmament.sbinfo.server": "Locraw Server: %s", "firmament.ursa.debugrequest.result": "Ursa request succeeded: %s",
"firmament.sbinfo.gametype": "Locraw Gametype: %s", "firmament.sbinfo.nolocraw": "No locraw data available",
"firmament.sbinfo.mode": "Locraw Mode: %s", "firmament.sbinfo.profile": "Current profile cutename: %s",
"firmament.sbinfo.map": "Locraw Map: %s", "firmament.sbinfo.server": "Locraw Server: %s",
"firmament.config.fairy-souls": "Fairy Souls", "firmament.sbinfo.gametype": "Locraw Gametype: %s",
"firmament.config.fairy-souls.show": "Show Fairy Soul Waypoints", "firmament.sbinfo.mode": "Locraw Mode: %s",
"firmament.config.fairy-souls.reset": "Reset Collected Fairy Souls", "firmament.sbinfo.map": "Locraw Map: %s",
"firmament.config.fishing-warning": "Fishing Warning", "firmament.config.price-data": "Price data",
"firmament.config.fishing-warning.display-warning": "Display a warning when you are about to hook a fish", "firmament.config.price-data.enable-always": "Enable Item Price",
"firmament.config.fishing-warning.highlight-wake-chain": "Highlight fishing particles", "firmament.config.price-data.enable-keybind": "Enable only with Keybinding",
"firmament.key.slotlocking": "Lock Slot / Slot Binding", "firmament.config.fairy-souls": "Fairy Souls",
"firmament.key.category": "Firmament", "firmament.config.fairy-souls.show": "Show Fairy Soul Waypoints",
"firmament.protectitem": "Firmament protected your item: ", "firmament.config.fairy-souls.reset": "Reset Collected Fairy Souls",
"firmament.recipe.forge.time": "Forging Time: %s", "firmament.config.fishing-warning": "Fishing Warning",
"firmament.pv.skills": "Skills", "firmament.config.fishing-warning.display-warning": "Display a warning when you are about to hook a fish",
"firmament.pv.skills.farming": "Farming", "firmament.config.fishing-warning.highlight-wake-chain": "Highlight fishing particles",
"firmament.pv.skills.foraging": "Foraging", "firmament.key.slotlocking": "Lock Slot / Slot Binding",
"firmament.pv.skills.mining": "Mining", "firmament.key.category": "Firmament",
"firmament.pv.skills.alchemy": "Alchemy", "firmament.protectitem": "Firmament protected your item: ",
"firmament.pv.skills.taming": "Taming", "firmament.recipe.forge.time": "Forging Time: %s",
"firmament.pv.skills.fishing": "Fishing", "firmament.pv.skills": "Skills",
"firmament.pv.skills.runecrafting": "Runecrafting", "firmament.pv.skills.farming": "Farming",
"firmament.pv.skills.carpentry": "Carpentry", "firmament.pv.skills.foraging": "Foraging",
"firmament.pv.skills.combat": "Combat", "firmament.pv.skills.mining": "Mining",
"firmament.pv.skills.social": "Social", "firmament.pv.skills.alchemy": "Alchemy",
"firmament.pv.skills.rift": "Rift", "firmament.pv.skills.taming": "Taming",
"firmament.pv.skills.enchanting": "Enchanting", "firmament.pv.skills.fishing": "Fishing",
"firmament.pv.skills.total": "Total Exp: %s", "firmament.pv.skills.runecrafting": "Runecrafting",
"firmament.pv.lookingup": "Looking up %s", "firmament.pv.skills.carpentry": "Carpentry",
"firmament.pv.noprofile": "%s has no SkyBlock profiles", "firmament.pv.skills.combat": "Combat",
"firmament.pv.noplayer": "%s is not a Minecraft player", "firmament.pv.skills.social": "Social",
"firmament.config.save-cursor-position.enable": "Enable", "firmament.pv.skills.rift": "Rift",
"firmament.config.save-cursor-position.tolerance": "Tolerance", "firmament.pv.skills.enchanting": "Enchanting",
"firmament.config.save-cursor-position": "Save Cursor Position", "firmament.pv.skills.total": "Total Exp: %s",
"firmament.config.storage-overlay": "Storage Overlay", "firmament.pv.lookingup": "Looking up %s",
"firmament.config.storage-overlay.rows": "Rows", "firmament.pv.noprofile": "%s has no SkyBlock profiles",
"firmament.config.storage-overlay.padding": "Padding", "firmament.pv.noplayer": "%s is not a Minecraft player",
"firmament.config.storage-overlay.scroll-speed": "Scroll Speed", "firmament.config.save-cursor-position.enable": "Enable",
"firmament.config.storage-overlay.inverse-scroll": "Invert Scroll", "firmament.config.save-cursor-position.tolerance": "Tolerance",
"firmament.config.storage-overlay.margin": "Margin", "firmament.config.save-cursor-position": "Save Cursor Position",
"firmament.config.chat-links": "Chat Links", "firmament.config.storage-overlay": "Storage Overlay",
"firmament.config.chat-links.links-enabled": "Enable Clickable Links", "firmament.config.storage-overlay.rows": "Rows",
"firmament.config.chat-links.image-enabled": "Enable Image Preview", "firmament.config.storage-overlay.padding": "Padding",
"firmament.config.chat-links.allow-all-hosts": "Allow all Image Hosts", "firmament.config.storage-overlay.scroll-speed": "Scroll Speed",
"firmament.config.chat-links.allowed-hosts": "Allowed Image Hosts", "firmament.config.storage-overlay.inverse-scroll": "Invert Scroll",
"firmament.config.chat-links.position": "Chat Image Preview", "firmament.config.storage-overlay.margin": "Margin",
"firmament.hud.edit": "Edit %s", "firmament.config.chat-links": "Chat Links",
"firmament.keybinding.external": "External", "firmament.config.chat-links.links-enabled": "Enable Clickable Links",
"firmament.config.slot-locking": "Slot Locking", "firmament.config.chat-links.image-enabled": "Enable Image Preview",
"firmament.config.slot-locking.lock": "Lock Slot", "firmament.config.chat-links.allow-all-hosts": "Allow all Image Hosts",
"firmament.config.fixes.auto-sprint": "Auto Sprint", "firmament.config.chat-links.allowed-hosts": "Allowed Image Hosts",
"firmament.config.custom-skyblock-textures": "Custom SkyBlock Item Textures", "firmament.config.chat-links.position": "Chat Image Preview",
"firmament.config.custom-skyblock-textures.cache-duration": "Model Cache Duration", "firmament.hud.edit": "Edit %s",
"firmament.config.custom-skyblock-textures.enabled": "Enable Custom Item Textures", "firmament.keybinding.external": "External",
"firmament.config.fixes": "Fixes", "firmament.config.slot-locking": "Slot Locking",
"firmament.config.fixes.player-skins": "Fix unsigned Player Skins" "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"
} }