aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/features/inventory
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/features/inventory')
-rw-r--r--src/main/kotlin/features/inventory/CraftingOverlay.kt33
-rw-r--r--src/main/kotlin/features/inventory/ItemHotkeys.kt10
-rw-r--r--src/main/kotlin/features/inventory/ItemRarityCosmetics.kt37
-rw-r--r--src/main/kotlin/features/inventory/JunkHighlighter.kt30
-rw-r--r--src/main/kotlin/features/inventory/PetFeatures.kt551
-rw-r--r--src/main/kotlin/features/inventory/PriceData.kt149
-rw-r--r--src/main/kotlin/features/inventory/REIDependencyWarner.kt32
-rw-r--r--src/main/kotlin/features/inventory/SaveCursorPosition.kt113
-rw-r--r--src/main/kotlin/features/inventory/SlotLocking.kt372
-rw-r--r--src/main/kotlin/features/inventory/TimerInLore.kt48
-rw-r--r--src/main/kotlin/features/inventory/WardrobeKeybinds.kt80
-rw-r--r--src/main/kotlin/features/inventory/buttons/InventoryButton.kt162
-rw-r--r--src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt231
-rw-r--r--src/main/kotlin/features/inventory/buttons/InventoryButtonTemplates.kt5
-rw-r--r--src/main/kotlin/features/inventory/buttons/InventoryButtons.kt158
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt24
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt105
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt84
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt370
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/StorageOverviewScreen.kt82
-rw-r--r--src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt68
21 files changed, 1974 insertions, 770 deletions
diff --git a/src/main/kotlin/features/inventory/CraftingOverlay.kt b/src/main/kotlin/features/inventory/CraftingOverlay.kt
index d2c79fd..5241f54 100644
--- a/src/main/kotlin/features/inventory/CraftingOverlay.kt
+++ b/src/main/kotlin/features/inventory/CraftingOverlay.kt
@@ -1,20 +1,20 @@
package moe.nea.firmament.features.inventory
import io.github.moulberry.repo.data.NEUCraftingRecipe
-import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
-import net.minecraft.item.ItemStack
-import net.minecraft.util.Formatting
+import net.minecraft.client.gui.screens.inventory.ContainerScreen
+import net.minecraft.world.item.ItemStack
+import net.minecraft.ChatFormatting
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.events.SlotRenderEvents
-import moe.nea.firmament.features.FirmamentFeature
+import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.skyblockId
-object CraftingOverlay : FirmamentFeature {
+object CraftingOverlay {
- private var screen: GenericContainerScreen? = null
+ private var screen: ContainerScreen? = null
private var recipe: NEUCraftingRecipe? = null
private var useNextScreen = false
private val craftingOverlayIndices = listOf(
@@ -24,7 +24,7 @@ object CraftingOverlay : FirmamentFeature {
)
val CRAFTING_SCREEN_NAME = "Craft Item"
- fun setOverlay(screen: GenericContainerScreen?, recipe: NEUCraftingRecipe) {
+ fun setOverlay(screen: ContainerScreen?, recipe: NEUCraftingRecipe) {
this.screen = screen
if (screen == null) {
useNextScreen = true
@@ -34,7 +34,7 @@ object CraftingOverlay : FirmamentFeature {
@Subscribe
fun onScreenChange(event: ScreenChangeEvent) {
- if (useNextScreen && event.new is GenericContainerScreen
+ if (useNextScreen && event.new is ContainerScreen
&& event.new.title?.string == "Craft Item"
) {
useNextScreen = false
@@ -42,18 +42,19 @@ object CraftingOverlay : FirmamentFeature {
}
}
- override val identifier: String
+ val identifier: String
get() = "crafting-overlay"
+ @OptIn(ExpensiveItemCacheApi::class)
@Subscribe
fun onSlotRender(event: SlotRenderEvents.After) {
val slot = event.slot
val recipe = this.recipe ?: return
- if (slot.inventory != screen?.screenHandler?.inventory) return
- val recipeIndex = craftingOverlayIndices.indexOf(slot.index)
+ if (slot.container != screen?.menu?.container) return
+ val recipeIndex = craftingOverlayIndices.indexOf(slot.containerSlot)
if (recipeIndex < 0) return
val expectedItem = recipe.inputs[recipeIndex]
- val actualStack = slot.stack ?: ItemStack.EMPTY!!
+ val actualStack = slot.item ?: ItemStack.EMPTY!!
val actualEntry = SBItemStack(actualStack)
if ((actualEntry.skyblockId != expectedItem.skyblockId || actualEntry.getStackSize() < expectedItem.amount)
&& expectedItem.amount.toInt() != 0
@@ -66,15 +67,15 @@ object CraftingOverlay : FirmamentFeature {
0x80FF0000.toInt()
)
}
- if (!slot.hasStack()) {
+ if (!slot.hasItem()) {
val itemStack = SBItemStack(expectedItem)?.asImmutableItemStack() ?: return
- event.context.drawItem(itemStack, event.slot.x, event.slot.y)
- event.context.drawStackOverlay(
+ event.context.renderItem(itemStack, event.slot.x, event.slot.y)
+ event.context.renderItemDecorations(
MC.font,
itemStack,
event.slot.x,
event.slot.y,
- "${Formatting.RED}${expectedItem.amount.toInt()}"
+ "${ChatFormatting.RED}${expectedItem.amount.toInt()}"
)
}
}
diff --git a/src/main/kotlin/features/inventory/ItemHotkeys.kt b/src/main/kotlin/features/inventory/ItemHotkeys.kt
index 4aa8202..e9d0631 100644
--- a/src/main/kotlin/features/inventory/ItemHotkeys.kt
+++ b/src/main/kotlin/features/inventory/ItemHotkeys.kt
@@ -2,22 +2,26 @@ package moe.nea.firmament.features.inventory
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
-import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.HypixelStaticData
-import moe.nea.firmament.repo.ItemCache
import moe.nea.firmament.repo.ItemCache.asItemStack
import moe.nea.firmament.repo.ItemCache.isBroken
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.asBazaarStock
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
import moe.nea.firmament.util.focusedItemStack
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.skyblock.SBItemUtil.getSearchName
object ItemHotkeys {
+ @Config
object TConfig : ManagedConfig("item-hotkeys", Category.INVENTORY) {
val openGlobalTradeInterface by keyBindingWithDefaultUnbound("global-trade-interface")
}
+ @OptIn(ExpensiveItemCacheApi::class)
@Subscribe
fun onHandledInventoryPress(event: HandledScreenKeyPressedEvent) {
if (!event.matches(TConfig.openGlobalTradeInterface)) {
@@ -26,7 +30,7 @@ object ItemHotkeys {
var item = event.screen.focusedItemStack ?: return
val skyblockId = item.skyBlockId ?: return
item = RepoManager.getNEUItem(skyblockId)?.asItemStack()?.takeIf { !it.isBroken } ?: item
- if (HypixelStaticData.hasBazaarStock(skyblockId)) {
+ if (HypixelStaticData.hasBazaarStock(skyblockId.asBazaarStock)) {
MC.sendCommand("bz ${item.getSearchName()}")
} else if (HypixelStaticData.hasAuctionHouseOffers(skyblockId)) {
MC.sendCommand("ahs ${item.getSearchName()}")
diff --git a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt
index fdc378a..9712067 100644
--- a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt
+++ b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt
@@ -1,45 +1,38 @@
package moe.nea.firmament.features.inventory
import java.awt.Color
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.client.render.RenderLayer
-import net.minecraft.item.ItemStack
-import net.minecraft.util.Formatting
-import net.minecraft.util.Identifier
+import net.minecraft.client.renderer.RenderPipelines
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.world.item.ItemStack
+import net.minecraft.resources.ResourceLocation
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HotbarItemRenderEvent
import moe.nea.firmament.events.SlotRenderEvents
-import moe.nea.firmament.features.FirmamentFeature
-import moe.nea.firmament.gui.config.ManagedConfig
-import moe.nea.firmament.util.collections.lastNotNullOfOrNull
-import moe.nea.firmament.util.collections.memoizeIdentity
-import moe.nea.firmament.util.mc.loreAccordingToNbt
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
import moe.nea.firmament.util.skyblock.Rarity
-import moe.nea.firmament.util.unformattedString
-object ItemRarityCosmetics : FirmamentFeature {
- override val identifier: String
+object ItemRarityCosmetics {
+ val identifier: String
get() = "item-rarity-cosmetics"
+ @Config
object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
val showItemRarityBackground by toggle("background") { false }
val showItemRarityInHotbar by toggle("background-hotbar") { false }
}
- override val config: ManagedConfig
- get() = TConfig
-
private val rarityToColor = Rarity.colourMap.mapValues {
- val c = Color(it.value.colorValue!!)
+ val c = Color(it.value.color!!)
c.rgb
}
- fun drawItemStackRarity(drawContext: DrawContext, x: Int, y: Int, item: ItemStack) {
+ fun drawItemStackRarity(drawContext: GuiGraphics, x: Int, y: Int, item: ItemStack) {
val rarity = Rarity.fromItem(item) ?: return
val rgb = rarityToColor[rarity] ?: 0xFF00FF80.toInt()
- drawContext.drawGuiTexture(
- RenderLayer::getGuiTextured,
- Identifier.of("firmament:item_rarity_background"),
+ drawContext.blitSprite(
+ RenderPipelines.GUI_TEXTURED,
+ ResourceLocation.parse("firmament:item_rarity_background"),
x, y,
16, 16,
rgb
@@ -50,7 +43,7 @@ object ItemRarityCosmetics : FirmamentFeature {
@Subscribe
fun onRenderSlot(it: SlotRenderEvents.Before) {
if (!TConfig.showItemRarityBackground) return
- val stack = it.slot.stack ?: return
+ val stack = it.slot.item ?: return
drawItemStackRarity(it.context, it.slot.x, it.slot.y, stack)
}
diff --git a/src/main/kotlin/features/inventory/JunkHighlighter.kt b/src/main/kotlin/features/inventory/JunkHighlighter.kt
new file mode 100644
index 0000000..15bdcfa
--- /dev/null
+++ b/src/main/kotlin/features/inventory/JunkHighlighter.kt
@@ -0,0 +1,30 @@
+package moe.nea.firmament.features.inventory
+
+import org.lwjgl.glfw.GLFW
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.SlotRenderEvents
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
+import moe.nea.firmament.util.skyblock.SBItemUtil.getSearchName
+import moe.nea.firmament.util.useMatch
+
+object JunkHighlighter {
+ val identifier: String
+ get() = "junk-highlighter"
+
+ @Config
+ object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
+ val junkRegex by string("regex") { "" }
+ val highlightBind by keyBinding("highlight") { GLFW.GLFW_KEY_LEFT_CONTROL }
+ }
+
+ @Subscribe
+ fun onDrawSlot(event: SlotRenderEvents.After) {
+ if (!TConfig.highlightBind.isPressed() || TConfig.junkRegex.isEmpty()) return
+ val junkRegex = TConfig.junkRegex.toPattern()
+ val slot = event.slot
+ junkRegex.useMatch(slot.item.getSearchName()) {
+ event.context.fill(slot.x, slot.y, slot.x + 16, slot.y + 16, 0xffff0000.toInt())
+ }
+ }
+}
diff --git a/src/main/kotlin/features/inventory/PetFeatures.kt b/src/main/kotlin/features/inventory/PetFeatures.kt
index 5ca10f7..e0bb4b1 100644
--- a/src/main/kotlin/features/inventory/PetFeatures.kt
+++ b/src/main/kotlin/features/inventory/PetFeatures.kt
@@ -1,40 +1,561 @@
package moe.nea.firmament.features.inventory
-import net.minecraft.util.Identifier
+import java.util.regex.Matcher
+import org.joml.Vector2i
+import net.minecraft.world.entity.player.Inventory
+import net.minecraft.world.item.ItemStack
+import net.minecraft.network.chat.Component
+import net.minecraft.ChatFormatting
+import net.minecraft.util.StringRepresentable
+import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.HudRenderEvent
+import moe.nea.firmament.events.ProcessChatEvent
+import moe.nea.firmament.events.ProfileSwitchEvent
+import moe.nea.firmament.events.SlotClickEvent
import moe.nea.firmament.events.SlotRenderEvents
-import moe.nea.firmament.features.FirmamentFeature
-import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.jarvis.JarvisIntegration
+import moe.nea.firmament.repo.ExpLadders
+import moe.nea.firmament.repo.ExpensiveItemCacheApi
+import moe.nea.firmament.repo.ItemCache.asItemStack
+import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.util.FirmFormatters.formatPercent
+import moe.nea.firmament.util.FirmFormatters.shortFormat
import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SBData
+import moe.nea.firmament.util.SkyBlockIsland
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
+import moe.nea.firmament.util.formattedString
+import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.petData
import moe.nea.firmament.util.render.drawGuiTexture
+import moe.nea.firmament.util.skyblock.Rarity
+import moe.nea.firmament.util.skyblock.TabListAPI
+import moe.nea.firmament.util.skyblockUUID
+import moe.nea.firmament.util.titleCase
+import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch
+import moe.nea.firmament.util.withColor
-object PetFeatures : FirmamentFeature {
- override val identifier: String
+object PetFeatures {
+ val identifier: String
get() = "pets"
- override val config: ManagedConfig?
- get() = TConfig
-
+ @Config
object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
val highlightEquippedPet by toggle("highlight-pet") { true }
+ val petOverlay by toggle("pet-overlay") { false }
+ val petOverlayHud by position("pet-overlay-hud", 80, 10) {
+ Vector2i()
+ }
+ val petOverlayHudStyle by choice("pet-overlay-hud-style") { PetOverlayHudStyles.PLAIN_NO_BACKGROUND }
}
- val petMenuTitle = "Pets(?: \\([0-9]+/[0-9]+\\))?".toPattern()
+ enum class PetOverlayHudStyles : StringRepresentable {
+ PLAIN_NO_BACKGROUND,
+ COLOUR_NO_BACKGROUND,
+ PLAIN_BACKGROUND,
+ COLOUR_BACKGROUND,
+ ICON_ONLY;
+
+ override fun getSerializedName() : String {
+ return name
+ }
+ }
+
+ private val petMenuTitle = "Pets(?: \\([0-9]+/[0-9]+\\))?".toPattern()
+ private val autopetPattern =
+ "§cAutopet §eequipped your §7\\[Lvl (\\d{1,3})\\] §([fa956d])([\\w\\s]+)§e! §aVIEW RULE".toPattern()
+ private val petItemPattern = "§aYour pet is now holding (§[fa956d][\\w\\s]+)§a.".toPattern()
+ private val petLevelUpPattern = "§aYour §([fa956d])([\\w\\s]+) §aleveled up to level §9(\\d+)§a!".toPattern()
+ private val petMap = HashMap<String, ParsedPet>()
+ private var currentPetUUID: String = ""
+ private var tempTabPet: ParsedPet? = null
+ private var tempChatPet: ParsedPet? = null
+
+ @Subscribe
+ fun onProfileSwitch(event: ProfileSwitchEvent) {
+ petMap.clear()
+ currentPetUUID = ""
+ tempTabPet = null
+ tempChatPet = null
+ }
@Subscribe
fun onSlotRender(event: SlotRenderEvents.Before) {
- if (!TConfig.highlightEquippedPet) return
- val stack = event.slot.stack
- if (stack.petData?.active == true)
- petMenuTitle.useMatch(MC.screenName ?: return) {
+ // Cache pets
+ petMenuTitle.useMatch(MC.screenName ?: return) {
+ val stack = event.slot.item
+ if (!stack.isEmpty) cachePet(stack)
+ if (stack.petData?.active == true) {
+ if (currentPetUUID == "") currentPetUUID = stack.skyblockUUID.toString()
+ // Highlight active pet feature
+ if (!TConfig.highlightEquippedPet) return
event.context.drawGuiTexture(
- event.slot.x, event.slot.y, 0, 16, 16,
- Identifier.of("firmament:selected_pet_background")
+ Firmament.identifier("selected_pet_background"),
+ event.slot.x, event.slot.y, 16, 16,
)
}
+ }
+ }
+
+ private fun cachePet(stack: ItemStack) {
+ // Cache information about a pet
+ if (stack.skyblockUUID == null) return
+ if (petMap.containsKey(stack.skyblockUUID.toString()) &&
+ petMap[stack.skyblockUUID.toString()]?.isComplete == true) return
+
+ val pet = PetParser.parsePetMenuSlot(stack) ?: return
+ petMap[stack.skyblockUUID.toString()] = pet
+ }
+
+ @Subscribe
+ fun onSlotClick(event: SlotClickEvent) {
+ // Check for switching/removing pet manually
+ petMenuTitle.useMatch(MC.screenName ?: return) {
+ if (event.slot.container is Inventory) return
+ if (event.button != 0 && event.button != 1) return
+ val petData = event.stack.petData ?: return
+ if (petData.active == true) {
+ currentPetUUID = "None"
+ return
+ }
+ if (event.button != 0) return
+ if (!petMap.containsKey(event.stack.skyblockUUID.toString())) cachePet(event.stack)
+ currentPetUUID = event.stack.skyblockUUID.toString()
+ }
}
+ @Subscribe
+ fun onChatEvent(event: ProcessChatEvent) {
+ // Handle AutoPet
+ var matcher = autopetPattern.matcher(event.text.formattedString())
+ if (matcher.matches()) {
+ val tempMap = petMap.filter { (uuid, pet) ->
+ pet.name == matcher.group(3) &&
+ pet.rarity == PetParser.reversePetColourMap[matcher.group(2)] &&
+ pet.level <= matcher.group(1).toInt()
+ }
+ if (tempMap.isNotEmpty()) {
+ currentPetUUID = tempMap.keys.first()
+ } else {
+ tempChatPet = PetParser.parsePetChatMessage(matcher.group(3), matcher.group(2), matcher.group(1).toInt())
+ currentPetUUID = ""
+ }
+ tempTabPet = null
+ return
+ }
+ // Handle changing pet item
+ // This is needed for when pet item can't be found in tab list
+ matcher = petItemPattern.matcher(event.text.formattedString())
+ if (matcher.matches()) {
+ petMap[currentPetUUID]?.petItem = matcher.group(1)
+ tempTabPet?.petItem = matcher.group(1)
+ tempChatPet?.petItem = matcher.group(1)
+ // TODO: Handle tier boost pet items if required
+ // I'm not rich enough to be able to test tier boosts
+ }
+ // Handle pet levelling up
+ // This is needed for when pet level can't be found in tab list
+ matcher = petLevelUpPattern.matcher(event.text.formattedString())
+ if (matcher.matches()) {
+ val tempPet =
+ PetParser.parsePetChatMessage(matcher.group(2), matcher.group(1), matcher.group(3).toInt()) ?: return
+ val tempMap = petMap.filter { (uuid, pet) ->
+ pet.name == tempPet.name &&
+ pet.rarity == tempPet.rarity &&
+ pet.level <= tempPet.level
+ }
+ if (tempMap.isNotEmpty()) petMap[tempMap.keys.first()]?.update(tempPet)
+ if (tempTabPet?.name == tempPet.name && tempTabPet?.rarity == tempPet.rarity) {
+ tempTabPet?.update(tempPet)
+ }
+ if (tempChatPet?.name == tempPet.name && tempChatPet?.rarity == tempPet.rarity) tempChatPet?.update(tempPet)
+ }
+ }
+ private fun renderLinesAndBackground(it: HudRenderEvent, lines: List<Component>) {
+ // Render background for the hud
+ if (TConfig.petOverlayHudStyle == PetOverlayHudStyles.PLAIN_BACKGROUND ||
+ TConfig.petOverlayHudStyle == PetOverlayHudStyles.COLOUR_BACKGROUND) {
+ var maxWidth = 0
+ lines.forEach { if (MC.font.width(it) > maxWidth) maxWidth = MC.font.width(it.unformattedString) }
+ val height = if (MC.font.lineHeight * lines.size > 32) MC.font.lineHeight * lines.size else 32
+ it.context.fill(0, -3, 40 + maxWidth, height + 2, 0x80000000.toInt())
+ }
+
+ // Render text for the hud
+ lines.forEachIndexed { index, line ->
+ it.context.drawString(
+ MC.font,
+ line.copy().withColor(ChatFormatting.GRAY),
+ 36,
+ MC.font.lineHeight * index,
+ -1,
+ true
+ )
+ }
+ }
+
+ @Subscribe
+ fun onRenderHud(it: HudRenderEvent) {
+ if (!TConfig.petOverlay || !SBData.isOnSkyblock) return
+
+ // Possibly handle Montezuma as a future feature? Could track how many pieces have been found etc
+ // Would likely need to be a separate config toggle though since that has
+ // very different usefulness/purpose to the pet hud outside of rift
+ if (SBData.skyblockLocation == SkyBlockIsland.RIFT) return
+
+ // Initial data
+ var pet: ParsedPet? = null
+ // Do not render the HUD if there is no pet active
+ if (currentPetUUID == "None") return
+ // Get active pet from cache
+ if (currentPetUUID != "") pet = petMap[currentPetUUID]
+ // Parse tab widget for pet data
+ val tabPet = PetParser.parseTabWidget(TabListAPI.getWidgetLines(TabListAPI.WidgetName.PET))
+ if (pet == null && tabPet == null && tempTabPet == null && tempChatPet == null) {
+ // No data on current pet
+ it.context.pose().pushMatrix()
+ TConfig.petOverlayHud.applyTransformations(JarvisIntegration.jarvis, it.context.pose())
+ val lines = mutableListOf<Component>()
+ lines.add(Component.literal("" + ChatFormatting.WHITE + "Unknown Pet"))
+ lines.add(Component.literal("Open Pets Menu To Fix"))
+ renderLinesAndBackground(it, lines)
+ it.context.pose().popMatrix()
+ return
+ }
+ if (pet == null) {
+ // Pet is only known through tab widget or chat message, potentially saved from tab widget elsewhere
+ // (e.g. another server or before removing the widget from the tab list)
+ pet = tabPet ?: tempTabPet ?: tempChatPet ?: return
+ if (tempTabPet == null) tempTabPet = tabPet
+ }
+
+ // Update pet based on tab widget if needed
+ if (tabPet != null && pet.name == tabPet.name && pet.rarity == tabPet.rarity) {
+ if (tabPet.level > pet.level) {
+ // Level has increased since caching
+ pet.level = tabPet.level
+ pet.currentExp = tabPet.currentExp
+ pet.expForNextLevel = tabPet.expForNextLevel
+ pet.totalExp = tabPet.totalExp
+ } else if (tabPet.currentExp > pet.currentExp) {
+ // Exp has increased since caching, level has not
+ pet.currentExp = tabPet.currentExp
+ pet.totalExp = tabPet.totalExp
+ }
+ if (tabPet.petItem != pet.petItem && tabPet.petItem != "Unknown") {
+ // Pet item has changed since caching
+ pet.petItem = tabPet.petItem
+ pet.petItemStack = tabPet.petItemStack
+ }
+ }
+
+ // Set the text for the HUD
+
+ val lines = mutableListOf<Component>()
+
+ if (TConfig.petOverlayHudStyle == PetOverlayHudStyles.COLOUR_NO_BACKGROUND ||
+ TConfig.petOverlayHudStyle == PetOverlayHudStyles.COLOUR_BACKGROUND) {
+ // Colour Style
+ lines.add(Component.literal("[Lvl ${pet.level}] ").append(Component.literal(pet.name)
+ .withColor((Rarity.colourMap[pet.rarity]) ?: ChatFormatting.WHITE)))
+
+ lines.add(Component.literal(pet.petItem))
+ if (pet.level != pet.maxLevel) {
+ // Exp data
+ lines.add(
+ Component.literal(
+ "" + ChatFormatting.YELLOW + "Required L${pet.level + 1}: ${shortFormat(pet.currentExp)}" +
+ ChatFormatting.GOLD + "/" + ChatFormatting.YELLOW +
+ "${shortFormat(pet.expForNextLevel)} " + ChatFormatting.GOLD +
+ "(${formatPercent(pet.currentExp / pet.expForNextLevel)})"
+ )
+ )
+ lines.add(
+ Component.literal(
+ "" + ChatFormatting.YELLOW + "Required L100: ${shortFormat(pet.totalExp)}" +
+ ChatFormatting.GOLD + "/" + ChatFormatting.YELLOW +
+ "${shortFormat(pet.expForMax)} " + ChatFormatting.GOLD +
+ "(${formatPercent(pet.totalExp / pet.expForMax)})"
+ )
+ )
+ } else {
+ // Overflow Exp data
+ lines.add(Component.literal(
+ "" + ChatFormatting.AQUA + ChatFormatting.BOLD + "MAX LEVEL"
+ ))
+ lines.add(Component.literal(
+ "" + ChatFormatting.GOLD + "+" + ChatFormatting.YELLOW + "${shortFormat(pet.overflowExp)} XP"
+ ))
+ }
+ } else if (TConfig.petOverlayHudStyle == PetOverlayHudStyles.PLAIN_NO_BACKGROUND ||
+ TConfig.petOverlayHudStyle == PetOverlayHudStyles.PLAIN_BACKGROUND) {
+ // Plain Style
+ lines.add(Component.literal("[Lvl ${pet.level}] ").append(Component.literal(pet.name)
+ .withColor((Rarity.colourMap[pet.rarity]) ?: ChatFormatting.WHITE)))
+
+ lines.add(Component.literal(if (pet.petItem != "None" && pet.petItem != "Unknown")
+ pet.petItem.substring(2) else pet.petItem))
+ if (pet.level != pet.maxLevel) {
+ // Exp data
+ lines.add(
+ Component.literal(
+ "Required L${pet.level + 1}: ${shortFormat(pet.currentExp)}/" +
+ "${shortFormat(pet.expForNextLevel)} " +
+ "(${formatPercent(pet.currentExp / pet.expForNextLevel)})"
+ )
+ )
+ lines.add(
+ Component.literal(
+ "Required L100: ${shortFormat(pet.totalExp)}/${shortFormat(pet.expForMax)} " +
+ "(${formatPercent(pet.totalExp / pet.expForMax)})"
+ )
+ )
+ } else {
+ // Overflow Exp data
+ lines.add(Component.literal(
+ "MAX LEVEL"
+ ))
+ lines.add(Component.literal(
+ "+${shortFormat(pet.overflowExp)} XP"
+ ))
+ }
+ }
+
+ // Render HUD
+
+ it.context.pose().pushMatrix()
+ TConfig.petOverlayHud.applyTransformations(JarvisIntegration.jarvis, it.context.pose())
+
+ renderLinesAndBackground(it, lines)
+
+ // Draw the ItemStack
+ it.context.pose().pushMatrix()
+ it.context.pose().translate(-0.5F, -0.5F)
+ it.context.pose().scale(2f, 2f)
+ it.context.renderItem(pet.petItemStack.value, 0, 0)
+ it.context.pose().popMatrix()
+
+ it.context.pose().popMatrix()
+ }
+}
+
+object PetParser {
+ private val petNamePattern = " §7\\[Lvl (\\d{1,3})] §([fa956d])([\\w\\s]+)".toPattern()
+ private val petItemPattern = " (§[fa956dbc4][\\s\\w]+)".toPattern()
+ private val petExpPattern = " §e((?:\\d{1,3}[,.]?)+\\d*[kM]?)§6\\/§e((?:\\d{1,3}[,.]?)+\\d*[kM]?) XP §6\\(\\d+(?:.\\d+)?%\\)".toPattern()
+ private val petOverflowExpPattern = " §6\\+§e((?:\\d{1,3}[,.])+\\d*[kM]?) XP".toPattern()
+ private val katPattern = " Kat:.*".toPattern()
+
+ val reversePetColourMap = mapOf(
+ "f" to Rarity.COMMON,
+ "a" to Rarity.UNCOMMON,
+ "9" to Rarity.RARE,
+ "5" to Rarity.EPIC,
+ "6" to Rarity.LEGENDARY,
+ "d" to Rarity.MYTHIC
+ )
+
+ val found = HashMap<String, Matcher>()
+
+ @OptIn(ExpensiveItemCacheApi::class)
+ fun parsePetChatMessage(name: String, rarityCode: String, level: Int) : ParsedPet? {
+ val petId = name.uppercase().replace(" ", "_")
+ val petRarity = reversePetColourMap[rarityCode] ?: Rarity.COMMON
+
+ val neuRarity = petRarity.neuRepoRarity ?: return null
+ val expLadder = ExpLadders.getExpLadder(petId, neuRarity)
+
+ var currentExp = 0.0
+ val expForNextLevel: Double
+ if (found.containsKey("exp")) {
+ currentExp = parseShortNumber(found.getValue("exp").group(1))
+ expForNextLevel = parseShortNumber(found.getValue("exp").group(2))
+ } else {
+ expForNextLevel = expLadder.getPetExpForLevel(level + 1).toDouble() -
+ expLadder.getPetExpForLevel(level).toDouble()
+ }
+
+ val totalExpBeforeLevel = expLadder.getPetExpForLevel(level).toDouble()
+ val totalExp = totalExpBeforeLevel + currentExp
+ val maxLevel = RepoManager.neuRepo.constants.petLevelingData.petLevelingBehaviourOverrides[petId]?.maxLevel ?: 100
+ val expForMax = expLadder.getPetExpForLevel(maxLevel).toDouble()
+ val petItemStack = lazy { RepoManager.neuRepo.items.items[petId + ";" + petRarity.ordinal].asItemStack() }
+
+ return ParsedPet(
+ name,
+ petRarity,
+ level,
+ -1,
+ expLadder,
+ currentExp,
+ expForNextLevel,
+ totalExp,
+ totalExpBeforeLevel,
+ expForMax,
+ 0.0,
+ "Unknown",
+ petItemStack,
+ false
+ )
+ }
+
+ @OptIn(ExpensiveItemCacheApi::class)
+ fun parseTabWidget(lines: List<Component>): ParsedPet? {
+ found.clear()
+ for (line in lines.reversed()) {
+ if (!found.containsKey("kat")) {
+ val matcher = katPattern.matcher(line.formattedString())
+ if (matcher.matches()) {
+ found["kat"] = matcher
+ continue
+ }
+ }
+ if (!found.containsKey("exp")) {
+ val matcher = petExpPattern.matcher(line.formattedString())
+ if (matcher.matches()) {
+ found["exp"] = matcher
+ continue
+ }
+ }
+ if (!found.containsKey("exp")) {
+ val matcher = petOverflowExpPattern.matcher(line.formattedString())
+ if (matcher.matches()) {
+ found["overflow"] = matcher
+ continue
+ }
+ }
+ if (!found.containsKey("item")) {
+ val matcher = petItemPattern.matcher(line.formattedString())
+ if (matcher.matches()) {
+ found["item"] = matcher
+ continue
+ }
+ }
+ if (!found.containsKey("name")) {
+ val matcher = petNamePattern.matcher(line.formattedString())
+ if (matcher.matches()) {
+ found["name"] = matcher
+ continue
+ }
+ }
+ }
+ if (!found.containsKey("name")) return null
+
+ val petName = titleCase(found.getValue("name").group(3))
+ val petRarity = reversePetColourMap.getValue(found.getValue("name").group(2))
+ val petId = petName.uppercase().replace(" ", "_")
+
+ val petLevel = found.getValue("name").group(1).toInt()
+
+ val neuRarity = petRarity.neuRepoRarity ?: return null
+ val expLadder = ExpLadders.getExpLadder(petId, neuRarity)
+
+ var currentExp = 0.0
+ val expForNextLevel: Double
+ if (found.containsKey("exp")) {
+ currentExp = parseShortNumber(found.getValue("exp").group(1))
+ expForNextLevel = parseShortNumber(found.getValue("exp").group(2))
+ } else {
+ expForNextLevel = expLadder.getPetExpForLevel(petLevel + 1).toDouble() -
+ expLadder.getPetExpForLevel(petLevel).toDouble()
+ }
+
+ val overflowExp: Double = if (found.containsKey("overflow"))
+ parseShortNumber(found.getValue("overflow").group(1)) else 0.0
+
+ val totalExpBeforeLevel = expLadder.getPetExpForLevel(petLevel).toDouble()
+ val totalExp = totalExpBeforeLevel + currentExp
+ val maxLevel = RepoManager.neuRepo.constants.petLevelingData.petLevelingBehaviourOverrides[petId]?.maxLevel ?: 100
+ val expForMax = expLadder.getPetExpForLevel(maxLevel).toDouble()
+ val petItemStack = lazy { RepoManager.neuRepo.items.items[petId + ";" + petRarity.ordinal].asItemStack() }
+
+
+ var petItem = "Unknown"
+ if (found.containsKey("item")) {
+ petItem = found.getValue("item").group(1)
+ }
+
+ return ParsedPet(
+ petName,
+ petRarity,
+ petLevel,
+ maxLevel,
+ expLadder,
+ currentExp,
+ expForNextLevel,
+ totalExp,
+ totalExpBeforeLevel,
+ expForMax,
+ overflowExp,
+ petItem,
+ petItemStack,
+ false
+ )
+ }
+
+ fun parsePetMenuSlot(stack: ItemStack) : ParsedPet? {
+ val petData = stack.petData ?: return null
+ val expData = petData.level
+ val overflow = if (expData.expTotal - expData.expRequiredForMaxLevel > 0)
+ (expData.expTotal - expData.expRequiredForMaxLevel).toDouble() else 0.0
+ val petItem = if (stack.petData?.heldItem != null)
+ RepoManager.neuRepo.items.items.getValue(stack.petData?.heldItem).displayName else "None"
+ return ParsedPet(
+ titleCase(petData.type),
+ Rarity.fromNeuRepo(petData.tier) ?: Rarity.COMMON,
+ expData.currentLevel,
+ expData.maxLevel,
+ ExpLadders.getExpLadder(petData.skyblockId.toString(), petData.tier),
+ expData.expInCurrentLevel.toDouble(),
+ expData.expRequiredForNextLevel.toDouble(),
+ expData.expTotal.toDouble(),
+ expData.expTotal.toDouble() - expData.expInCurrentLevel.toDouble(),
+ expData.expRequiredForMaxLevel.toDouble(),
+ overflow,
+ petItem,
+ lazy { stack },
+ true
+ )
+ }
+}
+
+data class ParsedPet(
+ val name: String,
+ val rarity: Rarity,
+ var level: Int,
+ val maxLevel: Int,
+ val expLadder: ExpLadders.ExpLadder?,
+ var currentExp: Double,
+ var expForNextLevel: Double,
+ var totalExp: Double,
+ var totalExpBeforeLevel: Double,
+ val expForMax: Double,
+ var overflowExp: Double,
+ var petItem: String,
+ var petItemStack: Lazy<ItemStack>,
+ var isComplete: Boolean
+) {
+ fun update(other: ParsedPet) {
+ // Update the pet data to reflect another instance (of itself)
+ if (other.level > level) {
+ level = other.level
+ currentExp = other.currentExp
+ expForNextLevel = other.expForNextLevel
+ totalExp = other.totalExp
+ totalExpBeforeLevel = other.totalExpBeforeLevel
+ overflowExp = other.overflowExp
+ } else {
+ if (other.currentExp > currentExp) currentExp = other.currentExp
+ expForNextLevel = other.expForNextLevel
+ if (other.totalExp > totalExp) totalExp = other.totalExp
+ if (other.totalExpBeforeLevel > totalExpBeforeLevel) totalExpBeforeLevel = other.totalExpBeforeLevel
+ if (other.overflowExp > overflowExp) overflowExp = other.overflowExp
+ }
+ if (other.petItem != "Unknown") petItem = other.petItem
+ isComplete = false
+ }
}
diff --git a/src/main/kotlin/features/inventory/PriceData.kt b/src/main/kotlin/features/inventory/PriceData.kt
index 4477203..54802db 100644
--- a/src/main/kotlin/features/inventory/PriceData.kt
+++ b/src/main/kotlin/features/inventory/PriceData.kt
@@ -1,51 +1,120 @@
-
-
package moe.nea.firmament.features.inventory
-import net.minecraft.text.Text
+import org.lwjgl.glfw.GLFW
+import net.minecraft.network.chat.Component
+import net.minecraft.util.StringRepresentable
import moe.nea.firmament.annotations.Subscribe
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.FirmFormatters.formatCommas
+import moe.nea.firmament.util.asBazaarStock
+import moe.nea.firmament.util.bold
+import moe.nea.firmament.util.darkGrey
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
+import moe.nea.firmament.util.getLogicalStackSize
+import moe.nea.firmament.util.gold
import moe.nea.firmament.util.skyBlockId
+import moe.nea.firmament.util.tr
+import moe.nea.firmament.util.yellow
+
+object PriceData {
+ val identifier: String
+ get() = "price-data"
+
+ @Config
+ object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
+ val tooltipEnabled by toggle("enable-always") { true }
+ val enableKeybinding by keyBindingWithDefaultUnbound("enable-keybind")
+ val stackSizeKey by keyBinding("stack-size-keybind") { GLFW.GLFW_KEY_LEFT_SHIFT }
+ val avgLowestBin by choice(
+ "avg-lowest-bin-days",
+ ) {
+ AvgLowestBin.THREEDAYAVGLOWESTBIN
+ }
+ }
-object PriceData : FirmamentFeature {
- override val identifier: String
- get() = "price-data"
+ enum class AvgLowestBin : StringRepresentable {
+ OFF,
+ ONEDAYAVGLOWESTBIN,
+ THREEDAYAVGLOWESTBIN,
+ SEVENDAYAVGLOWESTBIN;
- object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
- val tooltipEnabled by toggle("enable-always") { true }
- val enableKeybinding by keyBindingWithDefaultUnbound("enable-keybind")
- }
+ override fun getSerializedName(): String {
+ return name
+ }
+ }
- override val config get() = TConfig
+ fun formatPrice(label: Component, price: Double): Component {
+ return Component.literal("")
+ .yellow()
+ .bold()
+ .append(label)
+ .append(": ")
+ .append(
+ Component.literal(formatCommas(price, fractionalDigits = 1))
+ .append(if (price != 1.0) " coins" else " coin")
+ .gold()
+ .bold()
+ )
+ }
- @Subscribe
- fun onItemTooltip(it: ItemTooltipEvent) {
- if (!TConfig.tooltipEnabled && !TConfig.enableKeybinding.isPressed()) {
- return
- }
- 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.stringifiedTranslatable("firmament.tooltip.bazaar.sell-order",
- FirmFormatters.formatCommas(bazaarData.quickStatus.sellPrice, 1))
- )
- it.lines.add(
- Text.stringifiedTranslatable("firmament.tooltip.bazaar.buy-order",
- FirmFormatters.formatCommas(bazaarData.quickStatus.buyPrice, 1))
- )
- } else if (lowestBin != null) {
- it.lines.add(Text.literal(""))
- it.lines.add(
- Text.stringifiedTranslatable("firmament.tooltip.ah.lowestbin",
- FirmFormatters.formatCommas(lowestBin, 1))
- )
- }
- }
+ @Subscribe
+ fun onItemTooltip(it: ItemTooltipEvent) {
+ if (!TConfig.tooltipEnabled) return
+ if (TConfig.enableKeybinding.isBound && !TConfig.enableKeybinding.isPressed()) return
+ val sbId = it.stack.skyBlockId
+ val stackSize = it.stack.getLogicalStackSize()
+ val isShowingStack = TConfig.stackSizeKey.isPressed()
+ val multiplier = if (isShowingStack) stackSize else 1
+ val multiplierText =
+ if (isShowingStack)
+ tr("firmament.tooltip.multiply", "Showing prices for x${stackSize}").darkGrey()
+ else
+ tr(
+ "firmament.tooltip.multiply.hint",
+ "[${TConfig.stackSizeKey.format()}] to show x${stackSize}"
+ ).darkGrey()
+ val bazaarData = HypixelStaticData.bazaarData[sbId?.asBazaarStock]
+ val lowestBin = HypixelStaticData.lowestBin[sbId]
+ val avgBinValue: Double? = when (TConfig.avgLowestBin) {
+ AvgLowestBin.ONEDAYAVGLOWESTBIN -> HypixelStaticData.avg1dlowestBin[sbId]
+ AvgLowestBin.THREEDAYAVGLOWESTBIN -> HypixelStaticData.avg3dlowestBin[sbId]
+ AvgLowestBin.SEVENDAYAVGLOWESTBIN -> HypixelStaticData.avg7dlowestBin[sbId]
+ AvgLowestBin.OFF -> null
+ }
+ if (bazaarData != null) {
+ it.lines.add(Component.literal(""))
+ it.lines.add(multiplierText)
+ it.lines.add(
+ formatPrice(
+ tr("firmament.tooltip.bazaar.buy-order", "Bazaar Buy Order"),
+ bazaarData.quickStatus.sellPrice * multiplier
+ )
+ )
+ it.lines.add(
+ formatPrice(
+ tr("firmament.tooltip.bazaar.sell-order", "Bazaar Sell Order"),
+ bazaarData.quickStatus.buyPrice * multiplier
+ )
+ )
+ } else if (lowestBin != null) {
+ it.lines.add(Component.literal(""))
+ it.lines.add(multiplierText)
+ it.lines.add(
+ formatPrice(
+ tr("firmament.tooltip.ah.lowestbin", "Lowest BIN"),
+ lowestBin * multiplier
+ )
+ )
+ if (avgBinValue != null) {
+ it.lines.add(
+ formatPrice(
+ tr("firmament.tooltip.ah.avg-lowestbin", "AVG Lowest BIN"),
+ avgBinValue * multiplier
+ )
+ )
+ }
+ }
+ }
}
diff --git a/src/main/kotlin/features/inventory/REIDependencyWarner.kt b/src/main/kotlin/features/inventory/REIDependencyWarner.kt
index 1e9b1b8..e508016 100644
--- a/src/main/kotlin/features/inventory/REIDependencyWarner.kt
+++ b/src/main/kotlin/features/inventory/REIDependencyWarner.kt
@@ -1,12 +1,13 @@
package moe.nea.firmament.features.inventory
+import java.net.URI
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 net.minecraft.network.chat.ClickEvent
+import net.minecraft.network.chat.Component
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.thenExecute
@@ -30,27 +31,28 @@ object REIDependencyWarner {
var sentWarning = false
fun modrinthLink(slug: String) =
- "https://modrinth.com/mod/$slug/versions?g=${SharedConstants.getGameVersion().name}&l=fabric"
+ "https://modrinth.com/mod/$slug/versions?g=${SharedConstants.getCurrentVersion().name()}&l=fabric"
- fun downloadButton(modName: String, modId: String, slug: String): Text {
+ fun downloadButton(modName: String, modId: String, slug: String): Component {
val alreadyDownloaded = FabricLoader.getInstance().isModLoaded(modId)
- return Text.literal(" - ")
+ return Component.literal(" - ")
.white()
- .append(Text.literal("[").aqua())
- .append(Text.translatable("firmament.download", modName)
- .styled { it.withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, modrinthLink(slug))) }
+ .append(Component.literal("[").aqua())
+ .append(Component.translatable("firmament.download", modName)
+ .withStyle { it.withClickEvent(ClickEvent.OpenUrl(URI (modrinthLink(slug)))) }
.yellow()
.also {
if (alreadyDownloaded)
- it.append(Text.translatable("firmament.download.already", modName)
+ it.append(Component.translatable("firmament.download.already", modName)
.lime())
})
- .append(Text.literal("]").aqua())
+ .append(Component.literal("]").aqua())
}
@Subscribe
fun checkREIDependency(event: SkyblockServerUpdateEvent) {
if (!SBData.isOnSkyblock) return
+ if (!RepoManager.TConfig.warnForMissingItemListMod) return
if (hasREI) return
if (sentWarning) return
sentWarning = true
@@ -58,11 +60,11 @@ object REIDependencyWarner {
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")
+ Component.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")
+ .append(Component.translatable("firmament.reiwarning.disable")
.clickCommand("/firm disablereiwarning")
.grey())
)
@@ -74,9 +76,9 @@ object REIDependencyWarner {
if (hasREI) return
event.subcommand("disablereiwarning") {
thenExecute {
- RepoManager.Config.warnForMissingItemListMod = false
- RepoManager.Config.save()
- MC.sendChat(Text.translatable("firmament.reiwarning.disabled").yellow())
+ RepoManager.TConfig.warnForMissingItemListMod = false
+ RepoManager.TConfig.markDirty()
+ MC.sendChat(Component.translatable("firmament.reiwarning.disabled").yellow())
}
}
}
diff --git a/src/main/kotlin/features/inventory/SaveCursorPosition.kt b/src/main/kotlin/features/inventory/SaveCursorPosition.kt
index c47867b..c492a75 100644
--- a/src/main/kotlin/features/inventory/SaveCursorPosition.kt
+++ b/src/main/kotlin/features/inventory/SaveCursorPosition.kt
@@ -1,66 +1,63 @@
-
-
package moe.nea.firmament.features.inventory
+import org.lwjgl.glfw.GLFW
import kotlin.math.absoluteValue
import kotlin.time.Duration.Companion.milliseconds
-import net.minecraft.client.util.InputUtil
-import moe.nea.firmament.features.FirmamentFeature
-import moe.nea.firmament.gui.config.ManagedConfig
+import com.mojang.blaze3d.platform.InputConstants
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.assertNotNullOr
-
-object SaveCursorPosition : FirmamentFeature {
- override val identifier: String
- get() = "save-cursor-position"
-
- object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
- val enable by toggle("enable") { true }
- val tolerance by duration("tolerance", 10.milliseconds, 5000.milliseconds) { 500.milliseconds }
- }
-
- override val config: TConfig
- get() = TConfig
-
- var savedPositionedP1: Pair<Double, Double>? = null
- var savedPosition: SavedPosition? = null
-
- data class SavedPosition(
- val middle: Pair<Double, Double>,
- val cursor: Pair<Double, Double>,
- val savedAt: TimeMark = TimeMark.now()
- )
-
- @JvmStatic
- fun saveCursorOriginal(positionedX: Double, positionedY: Double) {
- savedPositionedP1 = Pair(positionedX, positionedY)
- }
-
- @JvmStatic
- fun loadCursor(middleX: Double, middleY: Double): Pair<Double, Double>? {
- if (!TConfig.enable) return null
- val lastPosition = savedPosition?.takeIf { it.savedAt.passedTime() < TConfig.tolerance }
- savedPosition = null
- if (lastPosition != null &&
- (lastPosition.middle.first - middleX).absoluteValue < 1 &&
- (lastPosition.middle.second - middleY).absoluteValue < 1
- ) {
- InputUtil.setCursorParameters(
- MC.window.handle,
- InputUtil.GLFW_CURSOR_NORMAL,
- lastPosition.cursor.first,
- lastPosition.cursor.second
- )
- return lastPosition.cursor
- }
- return null
- }
-
- @JvmStatic
- fun saveCursorMiddle(middleX: Double, middleY: Double) {
- if (!TConfig.enable) return
- val cursorPos = assertNotNullOr(savedPositionedP1) { return }
- savedPosition = SavedPosition(Pair(middleX, middleY), cursorPos)
- }
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
+
+object SaveCursorPosition {
+ val identifier: String
+ get() = "save-cursor-position"
+
+ @Config
+ object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
+ val enable by toggle("enable") { true }
+ val tolerance by duration("tolerance", 10.milliseconds, 5000.milliseconds) { 500.milliseconds }
+ }
+
+ var savedPositionedP1: Pair<Double, Double>? = null
+ var savedPosition: SavedPosition? = null
+
+ data class SavedPosition(
+ val middle: Pair<Double, Double>,
+ val cursor: Pair<Double, Double>,
+ val savedAt: TimeMark = TimeMark.now()
+ )
+
+ @JvmStatic
+ fun saveCursorOriginal(positionedX: Double, positionedY: Double) {
+ savedPositionedP1 = Pair(positionedX, positionedY)
+ }
+
+ @JvmStatic
+ fun loadCursor(middleX: Double, middleY: Double): Pair<Double, Double>? {
+ if (!TConfig.enable) return null
+ val lastPosition = savedPosition?.takeIf { it.savedAt.passedTime() < TConfig.tolerance }
+ savedPosition = null
+ if (lastPosition != null &&
+ (lastPosition.middle.first - middleX).absoluteValue < 1 &&
+ (lastPosition.middle.second - middleY).absoluteValue < 1
+ ) {
+ InputConstants.grabOrReleaseMouse(
+ MC.window,
+ InputConstants.CURSOR_NORMAL,
+ lastPosition.cursor.first,
+ lastPosition.cursor.second
+ )
+ return lastPosition.cursor
+ }
+ return null
+ }
+
+ @JvmStatic
+ fun saveCursorMiddle(middleX: Double, middleY: Double) {
+ if (!TConfig.enable) return
+ val cursorPos = assertNotNullOr(savedPositionedP1) { return }
+ savedPosition = SavedPosition(Pair(middleX, middleY), cursorPos)
+ }
}
diff --git a/src/main/kotlin/features/inventory/SlotLocking.kt b/src/main/kotlin/features/inventory/SlotLocking.kt
index 99130d5..fca40c8 100644
--- a/src/main/kotlin/features/inventory/SlotLocking.kt
+++ b/src/main/kotlin/features/inventory/SlotLocking.kt
@@ -4,107 +4,197 @@ package moe.nea.firmament.features.inventory
import java.util.UUID
import org.lwjgl.glfw.GLFW
+import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.JsonDecoder
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.int
import kotlinx.serialization.serializer
-import net.minecraft.client.gui.screen.ingame.HandledScreen
-import net.minecraft.entity.player.PlayerInventory
-import net.minecraft.screen.GenericContainerScreenHandler
-import net.minecraft.screen.slot.Slot
-import net.minecraft.screen.slot.SlotActionType
-import net.minecraft.util.Identifier
-import net.minecraft.util.StringIdentifiable
+import net.minecraft.client.renderer.RenderPipelines
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import net.minecraft.client.gui.screens.inventory.InventoryScreen
+import net.minecraft.world.entity.player.Inventory
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.inventory.ChestMenu
+import net.minecraft.world.inventory.InventoryMenu
+import net.minecraft.world.inventory.Slot
+import net.minecraft.world.inventory.ClickType
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.util.StringRepresentable
import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.ClientInitEvent
import moe.nea.firmament.events.HandledScreenForegroundEvent
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
import moe.nea.firmament.events.HandledScreenKeyReleasedEvent
import moe.nea.firmament.events.IsSlotProtectedEvent
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.events.SlotRenderEvents
-import moe.nea.firmament.features.FirmamentFeature
-import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.keybindings.InputModifiers
import moe.nea.firmament.keybindings.SavedKeyBinding
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
import moe.nea.firmament.util.CommonSoundEffects
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.SkyBlockIsland
+import moe.nea.firmament.util.accessors.castAccessor
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
+import moe.nea.firmament.util.extraAttributes
import moe.nea.firmament.util.json.DashlessUUIDSerializer
+import moe.nea.firmament.util.lime
import moe.nea.firmament.util.mc.ScreenUtil.getSlotByIndex
import moe.nea.firmament.util.mc.SlotUtils.swapWithHotBar
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
-import moe.nea.firmament.util.render.GuiRenderLayers
+import moe.nea.firmament.util.red
import moe.nea.firmament.util.render.drawLine
+import moe.nea.firmament.util.skyBlockId
+import moe.nea.firmament.util.skyblock.DungeonUtil
+import moe.nea.firmament.util.skyblock.SkyBlockItems
import moe.nea.firmament.util.skyblockUUID
+import moe.nea.firmament.util.tr
import moe.nea.firmament.util.unformattedString
-object SlotLocking : FirmamentFeature {
- override val identifier: String
+object SlotLocking {
+ val identifier: String
get() = "slot-locking"
@Serializable
- data class Data(
+ data class DimensionData(
val lockedSlots: MutableSet<Int> = mutableSetOf(),
- val lockedSlotsRift: MutableSet<Int> = mutableSetOf(),
+ val boundSlots: BoundSlots = BoundSlots(),
+ )
+
+ @Serializable
+ data class Data(
val lockedUUIDs: MutableSet<UUID> = mutableSetOf(),
- val boundSlots: MutableMap<Int, Int> = mutableMapOf()
+ val rift: DimensionData = DimensionData(),
+ val overworld: DimensionData = DimensionData(),
+ )
+
+
+ val currentWorldData
+ get() = if (SBData.skyblockLocation == SkyBlockIsland.RIFT)
+ DConfig.data.rift
+ else
+ DConfig.data.overworld
+
+ @Serializable
+ data class BoundSlot(
+ val hotbar: Int,
+ val inventory: Int,
)
+ @Serializable(with = BoundSlots.Serializer::class)
+ data class BoundSlots(
+ val pairs: MutableSet<BoundSlot> = mutableSetOf()
+ ) {
+ fun findMatchingSlots(index: Int): List<BoundSlot> {
+ return pairs.filter { it.hotbar == index || it.inventory == index }
+ }
+
+ fun removeDuplicateForInventory(index: Int) {
+ pairs.removeIf { it.inventory == index }
+ }
+
+ fun removeAllInvolving(index: Int): Boolean {
+ return pairs.removeIf { it.inventory == index || it.hotbar == index }
+ }
+
+ fun insert(hotbar: Int, inventory: Int) {
+ if (!TConfig.allowMultiBinding) {
+ removeAllInvolving(hotbar)
+ removeAllInvolving(inventory)
+ }
+ pairs.add(BoundSlot(hotbar, inventory))
+ }
+
+ object Serializer : KSerializer<BoundSlots> {
+ override val descriptor: SerialDescriptor
+ get() = serializer<JsonElement>().descriptor
+
+ override fun serialize(
+ encoder: Encoder,
+ value: BoundSlots
+ ) {
+ serializer<MutableSet<BoundSlot>>()
+ .serialize(encoder, value.pairs)
+ }
+
+ override fun deserialize(decoder: Decoder): BoundSlots {
+ decoder as JsonDecoder
+ val json = decoder.decodeJsonElement()
+ if (json is JsonObject) {
+ return BoundSlots(json.entries.map {
+ BoundSlot(it.key.toInt(), (it.value as JsonPrimitive).int)
+ }.toMutableSet())
+ }
+ return BoundSlots(decoder.json.decodeFromJsonElement(serializer<MutableSet<BoundSlot>>(), json))
+
+ }
+ }
+ }
+
+
+ @Config
object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
val lockSlot by keyBinding("lock") { GLFW.GLFW_KEY_L }
val lockUUID by keyBindingWithOutDefaultModifiers("lock-uuid") {
- SavedKeyBinding(GLFW.GLFW_KEY_L, shift = true)
+ SavedKeyBinding.keyWithMods(GLFW.GLFW_KEY_L, InputModifiers.of(shift = true))
}
val slotBind by keyBinding("bind") { GLFW.GLFW_KEY_L }
val slotBindRequireShift by toggle("require-quick-move") { true }
val slotRenderLines by choice("bind-render") { SlotRenderLinesMode.ONLY_BOXES }
+ val slotBindOnlyInInv by toggle("bind-only-in-inv") { false }
+ val allowMultiBinding by toggle("multi-bind") { true } // TODO: filter based on this option
+ val protectAllHuntingBoxes by toggle("hunting-box") { false }
+ val allowDroppingInDungeons by toggle("drop-in-dungeons") { true }
}
- enum class SlotRenderLinesMode : StringIdentifiable {
+ enum class SlotRenderLinesMode : StringRepresentable {
EVERYTHING,
ONLY_BOXES,
NOTHING;
- override fun asString(): String {
+ override fun getSerializedName(): String {
return name
}
}
- override val config: TConfig
- get() = TConfig
-
+ @Config
object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "locked-slots", ::Data)
val lockedUUIDs get() = DConfig.data?.lockedUUIDs
val lockedSlots
- get() = when (SBData.skyblockLocation) {
- SkyBlockIsland.RIFT -> DConfig.data?.lockedSlotsRift
- null -> null
- else -> DConfig.data?.lockedSlots
- }
+ get() = currentWorldData?.lockedSlots
- fun isSalvageScreen(screen: HandledScreen<*>?): Boolean {
+ fun isSalvageScreen(screen: AbstractContainerScreen<*>?): Boolean {
if (screen == null) return false
return screen.title.unformattedString.contains("Salvage Item")
}
- fun isTradeScreen(screen: HandledScreen<*>?): Boolean {
+ fun isTradeScreen(screen: AbstractContainerScreen<*>?): Boolean {
if (screen == null) return false
- val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return false
- if (handler.inventory.size() < 9) return false
- val middlePane = handler.inventory.getStack(handler.inventory.size() - 5)
+ val handler = screen.menu as? ChestMenu ?: return false
+ if (handler.container.containerSize < 9) return false
+ val middlePane = handler.container.getItem(handler.container.containerSize - 5)
if (middlePane == null) return false
return middlePane.displayNameAccordingToNbt?.unformattedString == "⇦ Your stuff"
}
- fun isNpcShop(screen: HandledScreen<*>?): Boolean {
+ fun isNpcShop(screen: AbstractContainerScreen<*>?): Boolean {
if (screen == null) return false
- val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return false
- if (handler.inventory.size() < 9) return false
- val sellItem = handler.inventory.getStack(handler.inventory.size() - 5)
+ val handler = screen.menu as? ChestMenu ?: return false
+ if (handler.container.containerSize < 9) return false
+ val sellItem = handler.container.getItem(handler.container.containerSize - 5)
if (sellItem == null) return false
if (sellItem.displayNameAccordingToNbt.unformattedString == "Sell Item") return true
val lore = sellItem.loreAccordingToNbt
@@ -114,13 +204,19 @@ object SlotLocking : FirmamentFeature {
@Subscribe
fun onSalvageProtect(event: IsSlotProtectedEvent) {
if (event.slot == null) return
- if (!event.slot.hasStack()) return
- if (event.slot.stack.displayNameAccordingToNbt.unformattedString != "Salvage Items") return
- val inv = event.slot.inventory
+ if (!event.slot.hasItem()) return
+ if (event.slot.item.displayNameAccordingToNbt.unformattedString != "Salvage Items") return
+ val inv = event.slot.container
var anyBlocked = false
- for (i in 0 until event.slot.index) {
- val stack = inv.getStack(i)
- if (IsSlotProtectedEvent.shouldBlockInteraction(null, SlotActionType.THROW, stack))
+ for (i in 0 until event.slot.containerSlot) {
+ val stack = inv.getItem(i)
+ if (IsSlotProtectedEvent.shouldBlockInteraction(
+ null,
+ ClickType.THROW,
+ IsSlotProtectedEvent.MoveOrigin.SALVAGE,
+ stack
+ )
+ )
anyBlocked = true
}
if (anyBlocked) {
@@ -130,46 +226,69 @@ object SlotLocking : FirmamentFeature {
@Subscribe
fun onProtectUuidItems(event: IsSlotProtectedEvent) {
- val doesNotDeleteItem = event.actionType == SlotActionType.SWAP
- || event.actionType == SlotActionType.PICKUP
- || event.actionType == SlotActionType.QUICK_MOVE
- || event.actionType == SlotActionType.QUICK_CRAFT
- || event.actionType == SlotActionType.CLONE
- || event.actionType == SlotActionType.PICKUP_ALL
+ val doesNotDeleteItem = event.actionType == ClickType.SWAP
+ || event.actionType == ClickType.PICKUP
+ || event.actionType == ClickType.QUICK_MOVE
+ || event.actionType == ClickType.QUICK_CRAFT
+ || event.actionType == ClickType.CLONE
+ || event.actionType == ClickType.PICKUP_ALL
val isSellOrTradeScreen =
isNpcShop(MC.handledScreen) || isTradeScreen(MC.handledScreen) || isSalvageScreen(MC.handledScreen)
- if ((!isSellOrTradeScreen || event.slot?.inventory !is PlayerInventory)
+ if ((!isSellOrTradeScreen || event.slot?.container !is Inventory)
&& doesNotDeleteItem
) return
val stack = event.itemStack ?: return
+ if (TConfig.protectAllHuntingBoxes && (stack.isHuntingBox())) {
+ event.protect()
+ return
+ }
val uuid = stack.skyblockUUID ?: return
if (uuid in (lockedUUIDs ?: return)) {
event.protect()
}
}
+ fun ItemStack.isHuntingBox(): Boolean {
+ return skyBlockId == SkyBlockItems.HUNTING_TOOLKIT || extraAttributes.get("tool_kit") != null
+ }
+
@Subscribe
fun onProtectSlot(it: IsSlotProtectedEvent) {
- if (it.slot != null && it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf())) {
+ if (it.slot != null && it.slot.container is Inventory && it.slot.containerSlot in (lockedSlots ?: setOf())) {
it.protect()
}
}
@Subscribe
+ fun onEvent(event: ClientInitEvent) {
+ IsSlotProtectedEvent.subscribe(receivesCancelled = true, "SlotLocking:unlockInDungeons") {
+ if (it.isProtected
+ && it.origin == IsSlotProtectedEvent.MoveOrigin.DROP_FROM_HOTBAR
+ && DungeonUtil.isInActiveDungeon
+ && TConfig.allowDroppingInDungeons
+ ) {
+ it.isProtected = false
+ }
+ }
+ }
+
+ @Subscribe
fun onQuickMoveBoundSlot(it: IsSlotProtectedEvent) {
- val boundSlots = DConfig.data?.boundSlots ?: mapOf()
+ val boundSlots = currentWorldData?.boundSlots ?: BoundSlots()
val isValidAction =
- it.actionType == SlotActionType.QUICK_MOVE || (it.actionType == SlotActionType.PICKUP && !TConfig.slotBindRequireShift)
+ it.actionType == ClickType.QUICK_MOVE || (it.actionType == ClickType.PICKUP && !TConfig.slotBindRequireShift)
if (!isValidAction) return
- val handler = MC.handledScreen?.screenHandler ?: return
+ val handler = MC.handledScreen?.menu ?: return
+ if (TConfig.slotBindOnlyInInv && handler !is InventoryMenu)
+ return
val slot = it.slot
- if (slot != null && it.slot.inventory is PlayerInventory) {
- val boundSlot = boundSlots.entries.find {
- it.value == slot.index || it.key == slot.index
- } ?: return
+ if (slot != null && it.slot.container is Inventory) {
+ val matchingSlots = boundSlots.findMatchingSlots(slot.containerSlot)
+ if (matchingSlots.isEmpty()) return
it.protectSilent()
- val inventorySlot = MC.handledScreen?.getSlotByIndex(boundSlot.value, true)
- inventorySlot?.swapWithHotBar(handler, boundSlot.key)
+ val boundSlot = matchingSlots.singleOrNull() ?: return
+ val inventorySlot = MC.handledScreen?.getSlotByIndex(boundSlot.inventory, true)
+ inventorySlot?.swapWithHotBar(handler, boundSlot.hotbar)
}
}
@@ -177,10 +296,25 @@ object SlotLocking : FirmamentFeature {
fun onLockUUID(it: HandledScreenKeyPressedEvent) {
if (!it.matches(TConfig.lockUUID)) return
val inventory = MC.handledScreen ?: return
- inventory as AccessorHandledScreen
+ inventory.castAccessor()
val slot = inventory.focusedSlot_Firmament ?: return
- val stack = slot.stack ?: return
+ val stack = slot.item ?: return
+ if (stack.isHuntingBox()) {
+ MC.sendChat(
+ tr(
+ "firmament.slot-locking.hunting-box-unbindable-hint",
+ "The hunting box cannot be UUID bound reliably. It changes its own UUID frequently when switching tools. "
+ ).red().append(
+ tr(
+ "firmament.slot-locking.hunting-box-unbindable-hint.solution",
+ "Use the Firmament config option for locking all hunting boxes instead."
+ ).lime()
+ )
+ )
+ CommonSoundEffects.playFailure()
+ return
+ }
val uuid = stack.skyblockUUID ?: return
val lockedUUIDs = lockedUUIDs ?: return
if (uuid in lockedUUIDs) {
@@ -197,7 +331,7 @@ object SlotLocking : FirmamentFeature {
@Subscribe
fun onLockSlotKeyRelease(it: HandledScreenKeyReleasedEvent) {
val inventory = MC.handledScreen ?: return
- inventory as AccessorHandledScreen
+ inventory.castAccessor()
val slot = inventory.focusedSlot_Firmament
val storedSlot = storedLockingSlot ?: return
@@ -205,13 +339,11 @@ object SlotLocking : FirmamentFeature {
storedLockingSlot = null
val hotBarSlot = if (slot.isHotbar()) slot else storedSlot
val invSlot = if (slot.isHotbar()) storedSlot else slot
- val boundSlots = DConfig.data?.boundSlots ?: return
- lockedSlots?.remove(hotBarSlot.index)
- lockedSlots?.remove(invSlot.index)
- boundSlots.entries.removeIf {
- it.value == invSlot.index
- }
- boundSlots[hotBarSlot.index] = invSlot.index
+ val boundSlots = currentWorldData?.boundSlots ?: return
+ lockedSlots?.remove(hotBarSlot.containerSlot)
+ lockedSlots?.remove(invSlot.containerSlot)
+ boundSlots.removeDuplicateForInventory(invSlot.containerSlot)
+ boundSlots.insert(hotBarSlot.containerSlot, invSlot.containerSlot)
DConfig.markDirty()
CommonSoundEffects.playSuccess()
return
@@ -223,61 +355,75 @@ object SlotLocking : FirmamentFeature {
}
if (it.matches(TConfig.slotBind)) {
storedLockingSlot = null
- val boundSlots = DConfig.data?.boundSlots ?: return
+ val boundSlots = currentWorldData?.boundSlots ?: return
if (slot != null)
- boundSlots.entries.removeIf {
- it.value == slot.index || it.key == slot.index
- }
+ boundSlots.removeAllInvolving(slot.containerSlot)
}
}
@Subscribe
fun onRenderAllBoundSlots(event: HandledScreenForegroundEvent) {
- val boundSlots = DConfig.data?.boundSlots ?: return
+ val boundSlots = currentWorldData?.boundSlots ?: return
fun findByIndex(index: Int) = event.screen.getSlotByIndex(index, true)
- val accScreen = event.screen as AccessorHandledScreen
+ val accScreen = event.screen.castAccessor()
val sx = accScreen.x_Firmament
val sy = accScreen.y_Firmament
- for (it in boundSlots.entries) {
- val hotbarSlot = findByIndex(it.key) ?: continue
- val inventorySlot = findByIndex(it.value) ?: continue
+ val highlitSlots = mutableSetOf<Slot>()
+ for (it in boundSlots.pairs) {
+ val hotbarSlot = findByIndex(it.hotbar) ?: continue
+ val inventorySlot = findByIndex(it.inventory) ?: continue
val (hotX, hotY) = hotbarSlot.lineCenter()
val (invX, invY) = inventorySlot.lineCenter()
val anyHovered = accScreen.focusedSlot_Firmament === hotbarSlot
- || accScreen.focusedSlot_Firmament === inventorySlot
+ || accScreen.focusedSlot_Firmament === inventorySlot
if (!anyHovered && TConfig.slotRenderLines == SlotRenderLinesMode.NOTHING)
continue
- val color = if (anyHovered)
- me.shedaniel.math.Color.ofOpaque(0x00FF00)
- else
- me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt())
+ if (anyHovered) {
+ highlitSlots.add(hotbarSlot)
+ highlitSlots.add(inventorySlot)
+ }
+ fun color(highlit: Boolean) =
+ if (highlit)
+ me.shedaniel.math.Color.ofOpaque(0x00FF00)
+ else
+ me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt())
if (TConfig.slotRenderLines == SlotRenderLinesMode.EVERYTHING || anyHovered)
event.context.drawLine(
invX + sx, invY + sy,
hotX + sx, hotY + sy,
- color
+ color(anyHovered)
)
- event.context.drawBorder(hotbarSlot.x + sx,
- hotbarSlot.y + sy,
- 16, 16, color.color)
- event.context.drawBorder(inventorySlot.x + sx,
- inventorySlot.y + sy,
- 16, 16, color.color)
+ event.context.submitOutline(
+ hotbarSlot.x + sx,
+ hotbarSlot.y + sy,
+ 16, 16, color(hotbarSlot in highlitSlots).color
+ )
+ event.context.submitOutline( // TODO: 1.21.10
+ inventorySlot.x + sx,
+ inventorySlot.y + sy,
+ 16, 16, color(inventorySlot in highlitSlots).color
+ )
}
}
@Subscribe
fun onRenderCurrentDraggingSlot(event: HandledScreenForegroundEvent) {
val draggingSlot = storedLockingSlot ?: return
- val accScreen = event.screen as AccessorHandledScreen
+ val accScreen = event.screen.castAccessor()
val hoveredSlot = accScreen.focusedSlot_Firmament
- ?.takeIf { it.inventory is PlayerInventory }
+ ?.takeIf { it.container is Inventory }
?.takeIf { it == draggingSlot || it.isHotbar() != draggingSlot.isHotbar() }
val sx = accScreen.x_Firmament
val sy = accScreen.y_Firmament
val (borderX, borderY) = draggingSlot.lineCenter()
- event.context.drawBorder(draggingSlot.x + sx, draggingSlot.y + sy, 16, 16, 0xFF00FF00u.toInt())
+ event.context.submitOutline(
+ draggingSlot.x + sx,
+ draggingSlot.y + sy,
+ 16,
+ 16,
+ 0xFF00FF00u.toInt()
+ ) // TODO: 1.21.10
if (hoveredSlot == null) {
event.context.drawLine(
borderX + sx, borderY + sy,
@@ -291,9 +437,11 @@ object SlotLocking : FirmamentFeature {
hovX + sx, hovY + sy,
me.shedaniel.math.Color.ofOpaque(0x00FF00)
)
- event.context.drawBorder(hoveredSlot.x + sx,
- hoveredSlot.y + sy,
- 16, 16, 0xFF00FF00u.toInt())
+ event.context.submitOutline(
+ hoveredSlot.x + sx,
+ hoveredSlot.y + sy,
+ 16, 16, 0xFF00FF00u.toInt()
+ )
}
}
@@ -301,13 +449,13 @@ object SlotLocking : FirmamentFeature {
return if (isHotbar()) {
x + 9 to y
} else {
- x + 9 to y + 17
+ x + 9 to y + 16
}
}
fun Slot.isHotbar(): Boolean {
- return index < 9
+ return containerSlot < 9
}
@Subscribe
@@ -319,16 +467,14 @@ object SlotLocking : FirmamentFeature {
fun toggleSlotLock(slot: Slot) {
val lockedSlots = lockedSlots ?: return
- val boundSlots = DConfig.data?.boundSlots ?: mutableMapOf()
- if (slot.inventory is PlayerInventory) {
- if (boundSlots.entries.removeIf {
- it.value == slot.index || it.key == slot.index
- }) {
+ val boundSlots = currentWorldData?.boundSlots ?: BoundSlots()
+ if (slot.container is Inventory) {
+ if (boundSlots.removeAllInvolving(slot.containerSlot)) {
// intentionally do nothing
- } else if (slot.index in lockedSlots) {
- lockedSlots.remove(slot.index)
+ } else if (slot.containerSlot in lockedSlots) {
+ lockedSlots.remove(slot.containerSlot)
} else {
- lockedSlots.add(slot.index)
+ lockedSlots.add(slot.containerSlot)
}
DConfig.markDirty()
CommonSoundEffects.playSuccess()
@@ -338,10 +484,10 @@ object SlotLocking : FirmamentFeature {
@Subscribe
fun onLockSlot(it: HandledScreenKeyPressedEvent) {
val inventory = MC.handledScreen ?: return
- inventory as AccessorHandledScreen
+ inventory.castAccessor()
val slot = inventory.focusedSlot_Firmament ?: return
- if (slot.inventory !is PlayerInventory) return
+ if (slot.container !is Inventory) return
if (it.matches(TConfig.slotBind)) {
storedLockingSlot = storedLockingSlot ?: slot
return
@@ -354,17 +500,17 @@ object SlotLocking : FirmamentFeature {
@Subscribe
fun onRenderSlotOverlay(it: SlotRenderEvents.After) {
- val isSlotLocked = it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf())
- val isUUIDLocked = (it.slot.stack?.skyblockUUID) in (lockedUUIDs ?: setOf())
+ val isSlotLocked = it.slot.container is Inventory && it.slot.containerSlot in (lockedSlots ?: setOf())
+ val isUUIDLocked = (it.slot.item?.skyblockUUID) in (lockedUUIDs ?: setOf())
if (isSlotLocked || isUUIDLocked) {
- it.context.drawGuiTexture(
- GuiRenderLayers.GUI_TEXTURED_NO_DEPTH,
+ it.context.blitSprite(
+ RenderPipelines.GUI_TEXTURED,
when {
isSlotLocked ->
- (Identifier.of("firmament:slot_locked"))
+ (ResourceLocation.parse("firmament:slot_locked"))
isUUIDLocked ->
- (Identifier.of("firmament:uuid_locked"))
+ (ResourceLocation.parse("firmament:uuid_locked"))
else ->
error("unreachable")
diff --git a/src/main/kotlin/features/inventory/TimerInLore.kt b/src/main/kotlin/features/inventory/TimerInLore.kt
index f1b77c6..9bb78c9 100644
--- a/src/main/kotlin/features/inventory/TimerInLore.kt
+++ b/src/main/kotlin/features/inventory/TimerInLore.kt
@@ -7,25 +7,29 @@ import java.time.format.DateTimeFormatterBuilder
import java.time.format.FormatStyle
import java.time.format.TextStyle
import java.time.temporal.ChronoField
-import net.minecraft.text.Text
-import net.minecraft.util.StringIdentifiable
+import net.minecraft.network.chat.Component
+import net.minecraft.util.StringRepresentable
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ItemTooltipEvent
-import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.aqua
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
import moe.nea.firmament.util.grey
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.timestamp
import moe.nea.firmament.util.tr
import moe.nea.firmament.util.unformattedString
object TimerInLore {
+ @Config
object TConfig : ManagedConfig("lore-timers", Category.INVENTORY) {
val showTimers by toggle("show") { true }
+ val showCreationTimestamp by toggle("show-creation") { true }
val timerFormat by choice("format") { TimerFormat.SOCIALIST }
}
- enum class TimerFormat(val formatter: DateTimeFormatter) : StringIdentifiable {
+ enum class TimerFormat(val formatter: DateTimeFormatter) : StringRepresentable {
RFC(DateTimeFormatter.RFC_1123_DATE_TIME),
LOCAL(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
SOCIALIST(
@@ -45,6 +49,7 @@ object TimerInLore {
appendValue(ChronoField.SECOND_OF_MINUTE, 2)
}),
AMERICAN("EEEE, MMM d h:mm a yyyy"),
+ RFCPrecise(DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss.SSS Z")),
;
constructor(block: DateTimeFormatterBuilder.() -> Unit)
@@ -52,7 +57,7 @@ object TimerInLore {
constructor(format: String) : this(DateTimeFormatter.ofPattern(format))
- override fun asString(): String {
+ override fun getSerializedName(): String {
return name
}
}
@@ -80,13 +85,25 @@ object TimerInLore {
COMMUNITYPROJECTS("Contribute again", "Come back at"),
CHOCOLATEFACTORY("Next Charge", "Available at"),
STONKSAUCTION("Auction ends in", "Ends at"),
- LIZSTONKREDEMPTION("Resets in:", "Resets at");
+ LIZSTONKREDEMPTION("Resets in:", "Resets at"),
+ TIMEREMAININGS("Time Remaining:", "Ends at"),
+ COOLDOWN("Cooldown:", "Come back at"),
+ ONCOOLDOWN("On cooldown:", "Available at"),
+ EVENTENDING("Event ends in:", "Ends at");
}
val regex =
"(?i)(?:(?<years>[0-9]+) ?(y|years?) )?(?:(?<days>[0-9]+) ?(d|days?))? ?(?:(?<hours>[0-9]+) ?(h|hours?))? ?(?:(?<minutes>[0-9]+) ?(m|minutes?))? ?(?:(?<seconds>[0-9]+) ?(s|seconds?))?\\b".toRegex()
@Subscribe
+ fun creationInLore(event: ItemTooltipEvent) {
+ if (!TConfig.showCreationTimestamp) return
+ val timestamp = event.stack.timestamp ?: return
+ val formattedTimestamp = TConfig.timerFormat.formatter.format(ZonedDateTime.ofInstant(timestamp, ZoneId.systemDefault()))
+ event.lines.add(tr("firmament.lore.creationtimestamp", "Created at: $formattedTimestamp").grey())
+ }
+
+ @Subscribe
fun modifyLore(event: ItemTooltipEvent) {
if (!TConfig.showTimers) return
var lastTimer: ZonedDateTime? = null
@@ -107,9 +124,13 @@ object TimerInLore {
var baseLine = ZonedDateTime.now(SBData.hypixelTimeZone)
if (countdownType.isRelative) {
if (lastTimer == null) {
- event.lines.add(i + 1,
- tr("firmament.loretimer.missingrelative",
- "Found a relative countdown with no baseline (Firmament)").grey())
+ event.lines.add(
+ i + 1,
+ tr(
+ "firmament.loretimer.missingrelative",
+ "Found a relative countdown with no baseline (Firmament)"
+ ).grey()
+ )
continue
}
baseLine = lastTimer
@@ -119,10 +140,11 @@ object TimerInLore {
lastTimer = timer
val localTimer = timer.withZoneSameInstant(ZoneId.systemDefault())
// TODO: install approximate time stabilization algorithm
- event.lines.add(i + 1,
- Text.literal("${countdownType.label}: ")
- .grey()
- .append(Text.literal(TConfig.timerFormat.formatter.format(localTimer)).aqua())
+ event.lines.add(
+ i + 1,
+ Component.literal("${countdownType.label}: ")
+ .grey()
+ .append(Component.literal(TConfig.timerFormat.formatter.format(localTimer)).aqua())
)
}
}
diff --git a/src/main/kotlin/features/inventory/WardrobeKeybinds.kt b/src/main/kotlin/features/inventory/WardrobeKeybinds.kt
new file mode 100644
index 0000000..8d4760b
--- /dev/null
+++ b/src/main/kotlin/features/inventory/WardrobeKeybinds.kt
@@ -0,0 +1,80 @@
+package moe.nea.firmament.features.inventory
+
+import org.lwjgl.glfw.GLFW
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import net.minecraft.world.item.Items
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.HandledScreenKeyPressedEvent
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
+import moe.nea.firmament.util.mc.SlotUtils.clickLeftMouseButton
+
+object WardrobeKeybinds {
+ @Config
+ object TConfig : ManagedConfig("wardrobe-keybinds", Category.INVENTORY) {
+ val wardrobeKeybinds by toggle("wardrobe-keybinds") { false }
+ val changePageKeybind by keyBinding("change-page") { GLFW.GLFW_KEY_ENTER }
+ val nextPage by keyBinding("next-page") { GLFW.GLFW_KEY_D }
+ val previousPage by keyBinding("previous-page") { GLFW.GLFW_KEY_A }
+ val slotKeybinds = (1..9).map {
+ keyBinding("slot-$it") { GLFW.GLFW_KEY_0 + it }
+ }
+ val allowUnequipping by toggle("allow-unequipping") { true }
+ }
+
+ val slotKeybindsWithSlot = TConfig.slotKeybinds.withIndex().map { (index, keybinding) ->
+ index + 36 to keybinding
+ }
+
+ @Subscribe
+ fun switchSlot(event: HandledScreenKeyPressedEvent) {
+ if (MC.player == null || MC.world == null || MC.interactionManager == null) return
+ if (event.screen !is AbstractContainerScreen<*>) return
+
+ val regex = Regex("Wardrobe \\([12]/2\\)")
+ if (!regex.matches(event.screen.title.string)) return
+ if (!TConfig.wardrobeKeybinds) return
+
+ if (
+ event.matches(TConfig.changePageKeybind) ||
+ event.matches(TConfig.previousPage) ||
+ event.matches(TConfig.nextPage)
+ ) {
+ event.cancel()
+
+ val handler = event.screen.menu
+ val previousSlot = handler.getSlot(45)
+ val nextSlot = handler.getSlot(53)
+
+ val backPressed = event.matches(TConfig.changePageKeybind) || event.matches(TConfig.previousPage)
+ val nextPressed = event.matches(TConfig.changePageKeybind) || event.matches(TConfig.nextPage)
+
+ if (backPressed && previousSlot.item.item == Items.ARROW) {
+ previousSlot.clickLeftMouseButton(handler)
+ } else if (nextPressed && nextSlot.item.item == Items.ARROW) {
+ nextSlot.clickLeftMouseButton(handler)
+ }
+ }
+
+
+ val slot =
+ slotKeybindsWithSlot
+ .find { event.matches(it.second.get()) }
+ ?.first ?: return
+
+ event.cancel()
+
+ val handler = event.screen.menu
+ val invSlot = handler.getSlot(slot)
+
+ val itemStack = invSlot.item
+ val isSelected = itemStack.item == Items.LIME_DYE
+ val isSelectable = itemStack.item == Items.PINK_DYE
+ if (!isSelectable && !isSelected) return
+ if (!TConfig.allowUnequipping && isSelected) return
+
+ invSlot.clickLeftMouseButton(handler)
+ }
+
+}
diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButton.kt b/src/main/kotlin/features/inventory/buttons/InventoryButton.kt
index a46bd76..0cb51ca 100644
--- a/src/main/kotlin/features/inventory/buttons/InventoryButton.kt
+++ b/src/main/kotlin/features/inventory/buttons/InventoryButton.kt
@@ -1,5 +1,3 @@
-
-
package moe.nea.firmament.features.inventory.buttons
import com.mojang.brigadier.StringReader
@@ -7,80 +5,120 @@ import me.shedaniel.math.Dimension
import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
import kotlinx.serialization.Serializable
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.command.CommandRegistryAccess
-import net.minecraft.command.argument.ItemStackArgumentType
-import net.minecraft.item.ItemStack
-import net.minecraft.resource.featuretoggle.FeatureFlags
-import net.minecraft.util.Identifier
+import net.minecraft.client.renderer.RenderPipelines
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.commands.CommandBuildContext
+import net.minecraft.commands.arguments.item.ItemArgument
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.Items
+import net.minecraft.world.flag.FeatureFlags
+import net.minecraft.resources.ResourceLocation
+import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.ItemCache.asItemStack
+import moe.nea.firmament.repo.ItemCache.isBroken
import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.collections.memoize
+import moe.nea.firmament.util.mc.arbitraryUUID
+import moe.nea.firmament.util.mc.createSkullItem
import moe.nea.firmament.util.render.drawGuiTexture
@Serializable
data class InventoryButton(
- var x: Int,
- var y: Int,
- var anchorRight: Boolean,
- var anchorBottom: Boolean,
- var icon: String? = "",
- var command: String? = "",
+ var x: Int,
+ var y: Int,
+ var anchorRight: Boolean,
+ var anchorBottom: Boolean,
+ var icon: String? = "",
+ var command: String? = "",
+ var isGigantic: Boolean = false,
) {
- companion object {
- val itemStackParser by lazy {
- ItemStackArgumentType.itemStack(CommandRegistryAccess.of(MC.defaultRegistries,
- FeatureFlags.VANILLA_FEATURES))
- }
- val dimensions = Dimension(18, 18)
- val getItemForName = ::getItemForName0.memoize(1024)
- fun getItemForName0(icon: String): ItemStack {
- val repoItem = RepoManager.getNEUItem(SkyblockId(icon))
- var itemStack = repoItem.asItemStack(idHint = SkyblockId(icon))
- if (repoItem == null) {
- val giveSyntaxItem = if (icon.startsWith("/give") || icon.startsWith("give"))
- icon.split(" ", limit = 3).getOrNull(2) ?: icon
- else icon
- val componentItem =
- runCatching {
- itemStackParser.parse(StringReader(giveSyntaxItem)).createStack(1, false)
- }.getOrNull()
- if (componentItem != null)
- itemStack = componentItem
- }
- return itemStack
- }
- }
- fun render(context: DrawContext) {
- context.drawGuiTexture(
- 0,
- 0,
- 0,
- dimensions.width,
- dimensions.height,
- Identifier.of("firmament:inventory_button_background")
- )
- context.drawItem(getItem(), 1, 1)
- }
+ val myDimension get() = if (isGigantic) bigDimension else dimensions
+
+ companion object {
+ val itemStackParser by lazy {
+ ItemArgument.item(
+ CommandBuildContext.simple(
+ MC.defaultRegistries,
+ FeatureFlags.VANILLA_SET
+ )
+ )
+ }
+ val dimensions = Dimension(18, 18)
+ val gap = 2
+ val bigDimension = Dimension(dimensions.width * 2 + gap, dimensions.height * 2 + gap)
+ val getItemForName = ::getItemForName0.memoize(1024)
+
+ @OptIn(ExpensiveItemCacheApi::class)
+ fun getItemForName0(icon: String): ItemStack {
+ val repoItem = RepoManager.getNEUItem(SkyblockId(icon))
+ var itemStack = repoItem.asItemStack(idHint = SkyblockId(icon))
+ if (repoItem == null) {
+ when {
+ icon.startsWith("skull:") -> {
+ itemStack = createSkullItem(
+ arbitraryUUID,
+ "https://textures.minecraft.net/texture/${icon.substring("skull:".length)}"
+ )
+ }
+
+ else -> {
+ val giveSyntaxItem = if (icon.startsWith("/give") || icon.startsWith("give"))
+ icon.split(" ", limit = 3).getOrNull(2) ?: icon
+ else icon
+ val componentItem =
+ runCatching {
+ itemStackParser.parse(StringReader(giveSyntaxItem)).createItemStack(1, false)
+ }.getOrNull()
+ if (componentItem != null)
+ itemStack = componentItem
+ }
+ }
+ }
+ if (itemStack.item == Items.PAINTING)
+ ErrorUtil.logError("created broken itemstack for inventory button $icon: $itemStack")
+ return itemStack
+ }
+ }
+
+ fun render(context: GuiGraphics) {
+ context.blitSprite(
+ RenderPipelines.GUI_TEXTURED,
+ ResourceLocation.parse("firmament:inventory_button_background"),
+ 0,
+ 0,
+ myDimension.width,
+ myDimension.height,
+ )
+ if (isGigantic) {
+ context.pose().pushMatrix()
+ context.pose().translate(myDimension.width / 2F, myDimension.height / 2F)
+ context.pose().scale(2F)
+ context.renderItem(getItem(), -8, -8)
+ context.pose().popMatrix()
+ } else {
+ context.renderItem(getItem(), 1, 1)
+ }
+ }
- fun isValid() = !icon.isNullOrBlank() && !command.isNullOrBlank()
+ fun isValid() = !icon.isNullOrBlank() && !command.isNullOrBlank()
- fun getPosition(guiRect: Rectangle): Point {
- return Point(
- (if (anchorRight) guiRect.maxX else guiRect.minX) + x,
- (if (anchorBottom) guiRect.maxY else guiRect.minY) + y,
- )
- }
+ fun getPosition(guiRect: Rectangle): Point {
+ return Point(
+ (if (anchorRight) guiRect.maxX else guiRect.minX) + x,
+ (if (anchorBottom) guiRect.maxY else guiRect.minY) + y,
+ )
+ }
- fun getBounds(guiRect: Rectangle): Rectangle {
- return Rectangle(getPosition(guiRect), dimensions)
- }
+ fun getBounds(guiRect: Rectangle): Rectangle {
+ return Rectangle(getPosition(guiRect), myDimension)
+ }
- fun getItem(): ItemStack {
- return getItemForName(icon ?: "")
- }
+ fun getItem(): ItemStack {
+ return getItemForName(icon ?: "")
+ }
}
diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt b/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt
index ee3ae8b..6b6a2d6 100644
--- a/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt
+++ b/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt
@@ -1,17 +1,24 @@
package moe.nea.firmament.features.inventory.buttons
import io.github.notenoughupdates.moulconfig.common.IItemStack
-import io.github.notenoughupdates.moulconfig.platform.ModernItemStack
+import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent
+import io.github.notenoughupdates.moulconfig.platform.MoulConfigPlatform
+import io.github.notenoughupdates.moulconfig.platform.MoulConfigRenderContext
import io.github.notenoughupdates.moulconfig.xml.Bind
import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
import org.lwjgl.glfw.GLFW
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.client.gui.widget.ButtonWidget
-import net.minecraft.client.util.InputUtil
-import net.minecraft.text.Text
-import net.minecraft.util.math.MathHelper
-import net.minecraft.util.math.Vec2f
+import net.minecraft.client.Minecraft
+import net.minecraft.client.input.MouseButtonEvent
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.gui.components.Button
+import net.minecraft.client.gui.components.MultiLineTextWidget
+import net.minecraft.client.gui.components.StringWidget
+import net.minecraft.client.input.KeyEvent
+import com.mojang.blaze3d.platform.InputConstants
+import net.minecraft.network.chat.Component
+import net.minecraft.util.Mth
+import net.minecraft.world.phys.Vec2
import moe.nea.firmament.util.ClipboardUtils
import moe.nea.firmament.util.FragmentGuiScreen
import moe.nea.firmament.util.MC
@@ -28,10 +35,13 @@ class InventoryButtonEditor(
@field:Bind
var icon: String = originalButton.icon ?: ""
+ @field:Bind
+ var isGigantic: Boolean = originalButton.isGigantic
+
@Bind
fun getItemIcon(): IItemStack {
save()
- return ModernItemStack.of(InventoryButton.getItemForName(icon))
+ return MoulConfigPlatform.wrap(InventoryButton.getItemForName(icon))
}
@Bind
@@ -42,6 +52,7 @@ class InventoryButtonEditor(
fun save() {
originalButton.icon = icon
+ originalButton.isGigantic = isGigantic
originalButton.command = command
}
}
@@ -49,30 +60,92 @@ class InventoryButtonEditor(
var buttons: MutableList<InventoryButton> =
InventoryButtons.DConfig.data.buttons.map { it.copy() }.toMutableList()
- override fun close() {
+ override fun onClose() {
InventoryButtons.DConfig.data.buttons = buttons
InventoryButtons.DConfig.markDirty()
- super.close()
+ super.onClose()
+ }
+
+ override fun resize(client: Minecraft, width: Int, height: Int) {
+ lastGuiRect.move(
+ MC.window.guiScaledWidth / 2 - lastGuiRect.width / 2,
+ MC.window.guiScaledHeight / 2 - lastGuiRect.height / 2
+ )
+ super.resize(client, width, height)
}
override fun init() {
super.init()
- addDrawableChild(
- ButtonWidget.builder(Text.translatable("firmament.inventory-buttons.load-preset")) {
+ addRenderableWidget(
+ MultiLineTextWidget(
+ lastGuiRect.minX,
+ 25,
+ Component.translatable("firmament.inventory-buttons.delete"),
+ MC.font
+ ).setCentered(true).setMaxWidth(lastGuiRect.width)
+ )
+ addRenderableWidget(
+ MultiLineTextWidget(
+ lastGuiRect.minX,
+ 40,
+ Component.translatable("firmament.inventory-buttons.info"),
+ MC.font
+ ).setCentered(true).setMaxWidth(lastGuiRect.width)
+ )
+ addRenderableWidget(
+ Button.builder(Component.translatable("firmament.inventory-buttons.reset")) {
+ val newButtons = InventoryButtonTemplates.loadTemplate("TkVVQlVUVE9OUy9bXQ==")
+ if (newButtons != null)
+ buttons = moveButtons(newButtons.map { it.copy(command = it.command?.removePrefix("/")) })
+ }
+ .pos(lastGuiRect.minX + 10, lastGuiRect.minY + 10)
+ .width(lastGuiRect.width - 20)
+ .build()
+ )
+ addRenderableWidget(
+ Button.builder(Component.translatable("firmament.inventory-buttons.load-preset")) {
val t = ClipboardUtils.getTextContents()
val newButtons = InventoryButtonTemplates.loadTemplate(t)
if (newButtons != null)
buttons = moveButtons(newButtons.map { it.copy(command = it.command?.removePrefix("/")) })
}
- .position(lastGuiRect.minX + 10, lastGuiRect.minY + 35)
+ .pos(lastGuiRect.minX + 10, lastGuiRect.minY + 35)
.width(lastGuiRect.width - 20)
.build()
)
- addDrawableChild(
- ButtonWidget.builder(Text.translatable("firmament.inventory-buttons.save-preset")) {
+ addRenderableWidget(
+ Button.builder(Component.translatable("firmament.inventory-buttons.save-preset")) {
ClipboardUtils.setTextContent(InventoryButtonTemplates.saveTemplate(buttons))
}
- .position(lastGuiRect.minX + 10, lastGuiRect.minY + 60)
+ .pos(lastGuiRect.minX + 10, lastGuiRect.minY + 60)
+ .width(lastGuiRect.width - 20)
+ .build()
+ )
+ addRenderableWidget(
+ Button.builder(Component.translatable("firmament.inventory-buttons.simple-preset")) {
+ // Preset from NEU
+ // Credit: https://github.com/NotEnoughUpdates/NotEnoughUpdates/blob/9b1fcfebc646e9fb69f99006327faa3e734e5f51/src/main/resources/assets/notenoughupdates/invbuttons/presets.json#L900-L1348
+ val newButtons = InventoryButtonTemplates.loadTemplate(
+ "TkVVQlVUVE9OUy9bIntcblx0XCJ4XCI6IDE2MCxcblx0XCJ5XCI6IC0yMCxcblx0XCJhbmNob3JSaWdodFwiOiBmYWxzZSxcblx0XCJhbmNob3JCb3R0b21cIjogZmFsc2UsXG5cdFwiaWNvblwiOiBcImJvbmVcIixcblx0XCJjb21tYW5kXCI6IFwicGV0c1wiXG59Iiwie1xuXHRcInhcIjogMTQwLFxuXHRcInlcIjogLTIwLFxuXHRcImFuY2hvclJpZ2h0XCI6IGZhbHNlLFxuXHRcImFuY2hvckJvdHRvbVwiOiBmYWxzZSxcblx0XCJpY29uXCI6IFwiYXJtb3Jfc3RhbmRcIixcblx0XCJjb21tYW5kXCI6IFwid2FyZHJvYmVcIlxufSIsIntcblx0XCJ4XCI6IDEyMCxcblx0XCJ5XCI6IC0yMCxcblx0XCJhbmNob3JSaWdodFwiOiBmYWxzZSxcblx0XCJhbmNob3JCb3R0b21cIjogZmFsc2UsXG5cdFwiaWNvblwiOiBcImVuZGVyX2NoZXN0XCIsXG5cdFwiY29tbWFuZFwiOiBcInN0b3JhZ2VcIlxufSIsIntcblx0XCJ4XCI6IDEwMCxcblx0XCJ5XCI6IC0yMCxcblx0XCJhbmNob3JSaWdodFwiOiBmYWxzZSxcblx0XCJhbmNob3JCb3R0b21cIjogZmFsc2UsXG5cdFwiaWNvblwiOiBcInNrdWxsOmQ3Y2M2Njg3NDIzZDA1NzBkNTU2YWM1M2UwNjc2Y2I1NjNiYmRkOTcxN2NkODI2OWJkZWJlZDZmNmQ0ZTdiZjhcIixcblx0XCJjb21tYW5kXCI6IFwid2FycCBpc2xhbmRcIlxufSIsIntcblx0XCJ4XCI6IDgwLFxuXHRcInlcIjogLTIwLFxuXHRcImFuY2hvclJpZ2h0XCI6IGZhbHNlLFxuXHRcImFuY2hvckJvdHRvbVwiOiBmYWxzZSxcblx0XCJpY29uXCI6IFwic2t1bGw6MzVmNGI0MGNlZjllMDE3Y2Q0MTEyZDI2YjYyNTU3ZjhjMWQ1YjE4OWRhMmU5OTUzNDIyMmJjOGNlYzdkOTE5NlwiLFxuXHRcImNvbW1hbmRcIjogXCJ3YXJwIGh1YlwiXG59Il0="
+ )
+ if (newButtons != null)
+ buttons = moveButtons(newButtons.map { it.copy(command = it.command?.removePrefix("/")) })
+ }
+ .pos(lastGuiRect.minX + 10, lastGuiRect.minY + 85)
+ .width(lastGuiRect.width - 20)
+ .build()
+ )
+ addRenderableWidget(
+ Button.builder(Component.translatable("firmament.inventory-buttons.all-warps-preset")) {
+ // Preset from NEU
+ // Credit: https://github.com/NotEnoughUpdates/NotEnoughUpdates/blob/9b1fcfebc646e9fb69f99006327faa3e734e5f51/src/main/resources/assets/notenoughupdates/invbuttons/presets.json#L1817-L2276
+ val newButtons = InventoryButtonTemplates.loadTemplate(
+ "TkVVQlVUVE9OUy9bIntcblx0XCJ4XCI6IDIsXG5cdFwieVwiOiAtODQsXG5cdFwiYW5jaG9yUmlnaHRcIjogdHJ1ZSxcblx0XCJhbmNob3JCb3R0b21cIjogdHJ1ZSxcblx0XCJpY29uXCI6IFwic2t1bGw6YzljODg4MWU0MjkxNWE5ZDI5YmI2MWExNmZiMjZkMDU5OTEzMjA0ZDI2NWRmNWI0MzliM2Q3OTJhY2Q1NlwiLFxuXHRcImNvbW1hbmRcIjogXCJ3YXJwIGhvbWVcIlxufSIsIntcblx0XCJ4XCI6IDIsXG5cdFwieVwiOiAtNjQsXG5cdFwiYW5jaG9yUmlnaHRcIjogdHJ1ZSxcblx0XCJhbmNob3JCb3R0b21cIjogdHJ1ZSxcblx0XCJpY29uXCI6IFwic2t1bGw6ZDdjYzY2ODc0MjNkMDU3MGQ1NTZhYzUzZTA2NzZjYjU2M2JiZGQ5NzE3Y2Q4MjY5YmRlYmVkNmY2ZDRlN2JmOFwiLFxuXHRcImNvbW1hbmRcIjogXCJ3YXJwIGh1YlwiXG59Iiwie1xuXHRcInhcIjogMixcblx0XCJ5XCI6IC00NCxcblx0XCJhbmNob3JSaWdodFwiOiB0cnVlLFxuXHRcImFuY2hvckJvdHRvbVwiOiB0cnVlLFxuXHRcImljb25cIjogXCJza3VsbDo5YjU2ODk1Yjk2NTk4OTZhZDY0N2Y1ODU5OTIzOGFmNTMyZDQ2ZGI5YzFiMDM4OWI4YmJlYjcwOTk5ZGFiMzNkXCIsXG5cdFwiY29tbWFuZFwiOiBcIndhcnAgZHVuZ2Vvbl9odWJcIlxufSIsIntcblx0XCJ4XCI6IDIsXG5cdFwieVwiOiAtMjQsXG5cdFwiYW5jaG9yUmlnaHRcIjogdHJ1ZSxcblx0XCJhbmNob3JCb3R0b21cIjogdHJ1ZSxcblx0XCJpY29uXCI6IFwic2t1bGw6Nzg0MGI4N2Q1MjI3MWQyYTc1NWRlZGM4Mjg3N2UwZWQzZGY2N2RjYzQyZWE0NzllYzE0NjE3NmIwMjc3OWE1XCIsXG5cdFwiY29tbWFuZFwiOiBcIndhcnAgZW5kXCJcbn0iLCJ7XG5cdFwieFwiOiAxMDksXG5cdFwieVwiOiAtMTksXG5cdFwiYW5jaG9yUmlnaHRcIjogZmFsc2UsXG5cdFwiYW5jaG9yQm90dG9tXCI6IGZhbHNlLFxuXHRcImljb25cIjogXCJza3VsbDo4NmYwNmVhYTMwMDRhZWVkMDliM2Q1YjQ1ZDk3NmRlNTg0ZTY5MWMwZTljYWRlMTMzNjM1ZGU5M2QyM2I5ZWRiXCIsXG5cdFwiY29tbWFuZFwiOiBcImhvdG1cIlxufSIsIntcblx0XCJ4XCI6IDEzMCxcblx0XCJ5XCI6IC0xOSxcblx0XCJhbmNob3JSaWdodFwiOiBmYWxzZSxcblx0XCJhbmNob3JCb3R0b21cIjogZmFsc2UsXG5cdFwiaWNvblwiOiBcIkVOREVSX0NIRVNUXCIsXG5cdFwiY29tbWFuZFwiOiBcInN0b3JhZ2VcIlxufSIsIntcblx0XCJ4XCI6IDE1MSxcblx0XCJ5XCI6IC0xOSxcblx0XCJhbmNob3JSaWdodFwiOiBmYWxzZSxcblx0XCJhbmNob3JCb3R0b21cIjogZmFsc2UsXG5cdFwiaWNvblwiOiBcIkJPTkVcIixcblx0XCJjb21tYW5kXCI6IFwicGV0c1wiXG59Iiwie1xuXHRcInhcIjogLTE5LFxuXHRcInlcIjogMixcblx0XCJhbmNob3JSaWdodFwiOiBmYWxzZSxcblx0XCJhbmNob3JCb3R0b21cIjogZmFsc2UsXG5cdFwiaWNvblwiOiBcIkdPTERfQkxPQ0tcIixcblx0XCJjb21tYW5kXCI6IFwiYWhcIlxufSIsIntcblx0XCJ4XCI6IC0xOSxcblx0XCJ5XCI6IDIyLFxuXHRcImFuY2hvclJpZ2h0XCI6IGZhbHNlLFxuXHRcImFuY2hvckJvdHRvbVwiOiBmYWxzZSxcblx0XCJpY29uXCI6IFwiR09MRF9CQVJESU5HXCIsXG5cdFwiY29tbWFuZFwiOiBcImJ6XCJcbn0iLCJ7XG5cdFwieFwiOiAtMTksXG5cdFwieVwiOiAtODQsXG5cdFwiYW5jaG9yUmlnaHRcIjogZmFsc2UsXG5cdFwiYW5jaG9yQm90dG9tXCI6IHRydWUsXG5cdFwiaWNvblwiOiBcInNrdWxsOjQzOGNmM2Y4ZTU0YWZjM2IzZjkxZDIwYTQ5ZjMyNGRjYTE0ODYwMDdmZTU0NTM5OTA1NTUyNGMxNzk0MWY0ZGNcIixcblx0XCJjb21tYW5kXCI6IFwid2FycCBtdXNldW1cIlxufSIsIntcblx0XCJ4XCI6IC0xOSxcblx0XCJ5XCI6IC02NCxcblx0XCJhbmNob3JSaWdodFwiOiBmYWxzZSxcblx0XCJhbmNob3JCb3R0b21cIjogdHJ1ZSxcblx0XCJpY29uXCI6IFwic2t1bGw6ZjQ4ODBkMmMxZTdiODZlODc1MjJlMjA4ODI2NTZmNDViYWZkNDJmOTQ5MzJiMmM1ZTBkNmVjYWE0OTBjYjRjXCIsXG5cdFwiY29tbWFuZFwiOiBcIndhcnAgZ2FyZGVuXCJcbn0iLCJ7XG5cdFwieFwiOiAtMTksXG5cdFwieVwiOiAtNDQsXG5cdFwiYW5jaG9yUmlnaHRcIjogZmFsc2UsXG5cdFwiYW5jaG9yQm90dG9tXCI6IHRydWUsXG5cdFwiaWNvblwiOiBcInNrdWxsOjRkM2E2YmQ5OGFjMTgzM2M2NjRjNDkwOWZmOGQyZGM2MmNlODg3YmRjZjNjYzViMzg0ODY1MWFlNWFmNmJcIixcblx0XCJjb21tYW5kXCI6IFwid2FycCBiYXJuXCJcbn0iLCJ7XG5cdFwieFwiOiAtMTksXG5cdFwieVwiOiAtMjQsXG5cdFwiYW5jaG9yUmlnaHRcIjogZmFsc2UsXG5cdFwiYW5jaG9yQm90dG9tXCI6IHRydWUsXG5cdFwiaWNvblwiOiBcInNrdWxsOjUxNTM5ZGRkZjllZDI1NWVjZTYzNDgxOTNjZDc1MDEyYzgyYzkzYWVjMzgxZjA1NTcyY2VjZjczNzk3MTFiM2JcIixcblx0XCJjb21tYW5kXCI6IFwid2FycCBkZXNlcnRcIlxufSIsIntcblx0XCJ4XCI6IDQsXG5cdFwieVwiOiAyLFxuXHRcImFuY2hvclJpZ2h0XCI6IGZhbHNlLFxuXHRcImFuY2hvckJvdHRvbVwiOiB0cnVlLFxuXHRcImljb25cIjogXCJza3VsbDo3M2JjOTY1ZDU3OWMzYzYwMzlmMGExN2ViN2MyZTZmYWY1MzhjN2E1ZGU4ZTYwZWM3YTcxOTM2MGQwYTg1N2E5XCIsXG5cdFwiY29tbWFuZFwiOiBcIndhcnAgZ29sZFwiXG59Iiwie1xuXHRcInhcIjogMjUsXG5cdFwieVwiOiAyLFxuXHRcImFuY2hvclJpZ2h0XCI6IGZhbHNlLFxuXHRcImFuY2hvckJvdHRvbVwiOiB0cnVlLFxuXHRcImljb25cIjogXCJza3VsbDo1NjlhMWYxMTQxNTFiNDUyMTM3M2YzNGJjMTRjMjk2M2E1MDExY2RjMjVhNjU1NGM0OGM3MDhjZDk2ZWJmY1wiLFxuXHRcImNvbW1hbmRcIjogXCJ3YXJwIGRlZXBcIlxufSIsIntcblx0XCJ4XCI6IDQ2LFxuXHRcInlcIjogMixcblx0XCJhbmNob3JSaWdodFwiOiBmYWxzZSxcblx0XCJhbmNob3JCb3R0b21cIjogdHJ1ZSxcblx0XCJpY29uXCI6IFwic2t1bGw6MjFkYmUzMGIwMjdhY2JjZWI2MTI1NjNiZDg3N2NkN2ViYjcxOWVhNmVkMTM5OTAyN2RjZWU1OGJiOTA0OWQ0YVwiLFxuXHRcImNvbW1hbmRcIjogXCJ3YXJwIGNyeXN0YWxzXCJcbn0iLCJ7XG5cdFwieFwiOiA2Nyxcblx0XCJ5XCI6IDIsXG5cdFwiYW5jaG9yUmlnaHRcIjogZmFsc2UsXG5cdFwiYW5jaG9yQm90dG9tXCI6IHRydWUsXG5cdFwiaWNvblwiOiBcInNrdWxsOjVjYmQ5ZjVlYzFlZDAwNzI1OTk5NjQ5MWU2OWZmNjQ5YTMxMDZjZjkyMDIyN2IxYmIzYTcxZWU3YTg5ODYzZlwiLFxuXHRcImNvbW1hbmRcIjogXCJ3YXJwIGZvcmdlXCJcbn0iLCJ7XG5cdFwieFwiOiA4OCxcblx0XCJ5XCI6IDIsXG5cdFwiYW5jaG9yUmlnaHRcIjogZmFsc2UsXG5cdFwiYW5jaG9yQm90dG9tXCI6IHRydWUsXG5cdFwiaWNvblwiOiBcInNrdWxsOjZiMjBiMjNjMWFhMmJlMDI3MGYwMTZiNGM5MGQ2ZWU2YjgzMzBhMTdjZmVmODc4NjlkNmFkNjBiMmZmYmYzYjVcIixcblx0XCJjb21tYW5kXCI6IFwid2FycCBtaW5lc1wiXG59Iiwie1xuXHRcInhcIjogMTA5LFxuXHRcInlcIjogMixcblx0XCJhbmNob3JSaWdodFwiOiBmYWxzZSxcblx0XCJhbmNob3JCb3R0b21cIjogdHJ1ZSxcblx0XCJpY29uXCI6IFwic2t1bGw6YTIyMWY4MTNkYWNlZTBmZWY4YzU5Zjc2ODk0ZGJiMjY0MTU0NzhkOWRkZmM0NGMyZTcwOGE2ZDNiNzU0OWJcIixcblx0XCJjb21tYW5kXCI6IFwid2FycCBwYXJrXCJcbn0iLCJ7XG5cdFwieFwiOiAxMzAsXG5cdFwieVwiOiAyLFxuXHRcImFuY2hvclJpZ2h0XCI6IGZhbHNlLFxuXHRcImFuY2hvckJvdHRvbVwiOiB0cnVlLFxuXHRcImljb25cIjogXCJza3VsbDo5ZDdlM2IxOWFjNGYzZGVlOWM1Njc3YzEzNTMzM2I5ZDM1YTdmNTY4YjYzZDFlZjRhZGE0YjA2OGI1YTI1XCIsXG5cdFwiY29tbWFuZFwiOiBcIndhcnAgc3BpZGVyXCJcbn0iLCJ7XG5cdFwieFwiOiAxNTEsXG5cdFwieVwiOiAyLFxuXHRcImFuY2hvclJpZ2h0XCI6IGZhbHNlLFxuXHRcImFuY2hvckJvdHRvbVwiOiB0cnVlLFxuXHRcImljb25cIjogXCJza3VsbDpjMzY4N2UyNWM2MzJiY2U4YWE2MWUwZDY0YzI0ZTY5NGMzZWVhNjI5ZWE5NDRmNGNmMzBkY2ZiNGZiY2UwNzFcIixcblx0XCJjb21tYW5kXCI6IFwid2FycCBuZXRoZXJcIlxufSJd"
+ )
+ if (newButtons != null)
+ buttons = moveButtons(newButtons.map { it.copy(command = it.command?.removePrefix("/")) })
+ }
+ .pos(lastGuiRect.minX + 10, lastGuiRect.minY + 110)
.width(lastGuiRect.width - 20)
.build()
)
@@ -83,14 +156,20 @@ class InventoryButtonEditor(
val movedButtons = mutableListOf<InventoryButton>()
for (button in buttons) {
if ((!button.anchorBottom && !button.anchorRight && button.x > 0 && button.y > 0)) {
- MC.sendChat(tr("firmament.inventory-buttons.button-moved",
- "One of your imported buttons intersects with the inventory and has been moved to the top left."))
- movedButtons.add(button.copy(
- x = 0,
- y = -InventoryButton.dimensions.width,
- anchorRight = false,
- anchorBottom = false
- ))
+ MC.sendChat(
+ tr(
+ "firmament.inventory-buttons.button-moved",
+ "One of your imported buttons intersects with the inventory and has been moved to the top left."
+ )
+ )
+ movedButtons.add(
+ button.copy(
+ x = 0,
+ y = -InventoryButton.dimensions.width,
+ anchorRight = false,
+ anchorBottom = false
+ )
+ )
} else {
newButtons.add(button)
}
@@ -99,9 +178,11 @@ class InventoryButtonEditor(
val zeroRect = Rectangle(0, 0, 1, 1)
for (movedButton in movedButtons) {
fun getPosition(button: InventoryButton, index: Int) =
- button.copy(x = (index % 10) * InventoryButton.dimensions.width,
- y = (index / 10) * -InventoryButton.dimensions.height,
- anchorRight = false, anchorBottom = false)
+ button.copy(
+ x = (index % 10) * InventoryButton.dimensions.width,
+ y = (index / 10) * -InventoryButton.dimensions.height,
+ anchorRight = false, anchorBottom = false
+ )
while (true) {
val newPos = getPosition(movedButton, i++)
val newBounds = newPos.getBounds(zeroRect)
@@ -114,35 +195,48 @@ class InventoryButtonEditor(
return newButtons
}
- override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
+ override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
+ context.pose().pushMatrix()
+ PanelComponent.DefaultBackgroundRenderer.VANILLA
+ .render(
+ MoulConfigRenderContext(context),
+ lastGuiRect.minX, lastGuiRect.minY,
+ lastGuiRect.width, lastGuiRect.height,
+ )
+ context.pose().popMatrix()
super.render(context, mouseX, mouseY, delta)
- context.matrices.push()
- context.matrices.translate(0f, 0f, -10f)
- context.fill(lastGuiRect.minX, lastGuiRect.minY, lastGuiRect.maxX, lastGuiRect.maxY, -1)
- context.matrices.pop()
for (button in buttons) {
val buttonPosition = button.getBounds(lastGuiRect)
- context.matrices.push()
- context.matrices.translate(buttonPosition.minX.toFloat(), buttonPosition.minY.toFloat(), 0F)
+ context.pose().pushMatrix()
+ context.pose().translate(buttonPosition.minX.toFloat(), buttonPosition.minY.toFloat())
button.render(context)
- context.matrices.pop()
+ context.pose().popMatrix()
}
+ renderPopup(context, mouseX, mouseY, delta)
}
- override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
- if (super.keyPressed(keyCode, scanCode, modifiers)) return true
- if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
- close()
+ override fun keyPressed(input: KeyEvent): Boolean {
+ if (super.keyPressed(input)) return true
+ if (input.input() == GLFW.GLFW_KEY_ESCAPE) {
+ onClose()
return true
}
return false
}
- override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
- if (super.mouseReleased(mouseX, mouseY, button)) return true
- val clickedButton = buttons.firstOrNull { it.getBounds(lastGuiRect).contains(Point(mouseX, mouseY)) }
+ override fun mouseReleased(click: MouseButtonEvent): Boolean {
+ if (super.mouseReleased(click)) return true
+ val clickedButton = buttons.firstOrNull { it.getBounds(lastGuiRect).contains(Point(click.x, click.y)) }
if (clickedButton != null && !justPerformedAClickAction) {
- createPopup(MoulConfigUtils.loadGui("button_editor_fragment", Editor(clickedButton)), Point(mouseX, mouseY))
+ if (InputConstants.isKeyDown(
+ MC.window,
+ InputConstants.KEY_LCONTROL
+ )
+ ) Editor(clickedButton).delete()
+ else createPopup(
+ MoulConfigUtils.loadGui("button_editor_fragment", Editor(clickedButton)),
+ Point(click.x, click.y)
+ )
return true
}
justPerformedAClickAction = false
@@ -150,19 +244,27 @@ class InventoryButtonEditor(
return false
}
- override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
- if (super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) return true
+ override fun mouseDragged(click: MouseButtonEvent, offsetX: Double, offsetY: Double): Boolean {
+ if (super.mouseDragged(click, offsetX, offsetY)) return true
- if (initialDragMousePosition.distanceSquared(Vec2f(mouseX.toFloat(), mouseY.toFloat())) >= 4 * 4) {
- initialDragMousePosition = Vec2f(-10F, -10F)
+ if (initialDragMousePosition.distanceToSqr(Vec2(click.x.toFloat(), click.y.toFloat())) >= 4 * 4) {
+ initialDragMousePosition = Vec2(-10F, -10F)
lastDraggedButton?.let { dragging ->
justPerformedAClickAction = true
- val (anchorRight, anchorBottom, offsetX, offsetY) = getCoordsForMouse(mouseX.toInt(), mouseY.toInt())
+ val (anchorRight, anchorBottom, offsetX, offsetY) = getCoordsForMouse(click.x.toInt(), click.y.toInt())
?: return true
dragging.x = offsetX
dragging.y = offsetY
dragging.anchorRight = anchorRight
dragging.anchorBottom = anchorBottom
+ if (!anchorRight && offsetX > -dragging.myDimension.width
+ && dragging.getBounds(lastGuiRect).intersects(lastGuiRect)
+ )
+ dragging.x = -dragging.myDimension.width
+ if (!anchorRight && offsetY > -dragging.myDimension.height
+ && dragging.getBounds(lastGuiRect).intersects(lastGuiRect)
+ )
+ dragging.y = -dragging.myDimension.height
}
}
return false
@@ -170,7 +272,7 @@ class InventoryButtonEditor(
var lastDraggedButton: InventoryButton? = null
var justPerformedAClickAction = false
- var initialDragMousePosition = Vec2f(-10F, -10F)
+ var initialDragMousePosition = Vec2(-10F, -10F)
data class AnchoredCoords(
val anchorRight: Boolean,
@@ -180,35 +282,30 @@ class InventoryButtonEditor(
)
fun getCoordsForMouse(mx: Int, my: Int): AnchoredCoords? {
- if (lastGuiRect.contains(mx, my) || lastGuiRect.contains(
- Point(
- mx + InventoryButton.dimensions.width,
- my + InventoryButton.dimensions.height,
- )
- )
- ) return null
-
val anchorRight = mx > lastGuiRect.maxX
val anchorBottom = my > lastGuiRect.maxY
var offsetX = mx - if (anchorRight) lastGuiRect.maxX else lastGuiRect.minX
var offsetY = my - if (anchorBottom) lastGuiRect.maxY else lastGuiRect.minY
- if (InputUtil.isKeyPressed(MC.window.handle, InputUtil.GLFW_KEY_LEFT_SHIFT)) {
- offsetX = MathHelper.floor(offsetX / 20F) * 20
- offsetY = MathHelper.floor(offsetY / 20F) * 20
+ if (InputConstants.isKeyDown(MC.window, InputConstants.KEY_LSHIFT)) {
+ offsetX = Mth.floor(offsetX / 20F) * 20
+ offsetY = Mth.floor(offsetY / 20F) * 20
}
- return AnchoredCoords(anchorRight, anchorBottom, offsetX, offsetY)
+ val rect = InventoryButton(offsetX, offsetY, anchorRight, anchorBottom).getBounds(lastGuiRect)
+ if (rect.intersects(lastGuiRect)) return null
+ val anchoredCoords = AnchoredCoords(anchorRight, anchorBottom, offsetX, offsetY)
+ return anchoredCoords
}
- override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
- if (super.mouseClicked(mouseX, mouseY, button)) return true
- val clickedButton = buttons.firstOrNull { it.getBounds(lastGuiRect).contains(Point(mouseX, mouseY)) }
+ override fun mouseClicked(click: MouseButtonEvent, doubled: Boolean): Boolean {
+ if (super.mouseClicked(click, doubled)) return true
+ val clickedButton = buttons.firstOrNull { it.getBounds(lastGuiRect).contains(click.x, click.y) }
if (clickedButton != null) {
lastDraggedButton = clickedButton
- initialDragMousePosition = Vec2f(mouseX.toFloat(), mouseY.toFloat())
+ initialDragMousePosition = Vec2(click.y.toFloat(), click.y.toFloat())
return true
}
- val mx = mouseX.toInt()
- val my = mouseY.toInt()
+ val mx = click.x.toInt()
+ val my = click.y.toInt()
val (anchorRight, anchorBottom, offsetX, offsetY) = getCoordsForMouse(mx, my) ?: return true
buttons.add(InventoryButton(offsetX, offsetY, anchorRight, anchorBottom, null, null))
justPerformedAClickAction = true
diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButtonTemplates.kt b/src/main/kotlin/features/inventory/buttons/InventoryButtonTemplates.kt
index d282157..c6ad14d 100644
--- a/src/main/kotlin/features/inventory/buttons/InventoryButtonTemplates.kt
+++ b/src/main/kotlin/features/inventory/buttons/InventoryButtonTemplates.kt
@@ -1,7 +1,6 @@
package moe.nea.firmament.features.inventory.buttons
-import kotlinx.serialization.encodeToString
-import net.minecraft.text.Text
+import net.minecraft.network.chat.Component
import moe.nea.firmament.Firmament
import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.MC
@@ -18,7 +17,7 @@ object InventoryButtonTemplates {
ErrorUtil.catch<InventoryButton?>("Could not import button") {
Firmament.json.decodeFromString<InventoryButton>(it).also {
if (it.icon?.startsWith("extra:") == true) {
- MC.sendChat(Text.translatable("firmament.inventory-buttons.import-failed"))
+ MC.sendChat(Component.translatable("firmament.inventory-buttons.import-failed"))
}
}
}.or {
diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt b/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt
index d5b5417..eaa6138 100644
--- a/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt
+++ b/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt
@@ -1,88 +1,118 @@
-
-
package moe.nea.firmament.features.inventory.buttons
import me.shedaniel.math.Rectangle
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import net.minecraft.client.gui.screens.inventory.InventoryScreen
+import net.minecraft.network.chat.Component
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HandledScreenClickEvent
import moe.nea.firmament.events.HandledScreenForegroundEvent
import moe.nea.firmament.events.HandledScreenPushREIEvent
-import moe.nea.firmament.features.FirmamentFeature
-import moe.nea.firmament.gui.config.ManagedConfig
+import moe.nea.firmament.impl.v1.FirmamentAPIImpl
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.ScreenUtil
+import moe.nea.firmament.util.TimeMark
+import moe.nea.firmament.util.accessors.getProperRectangle
+import moe.nea.firmament.util.data.Config
import moe.nea.firmament.util.data.DataHolder
-import moe.nea.firmament.util.accessors.getRectangle
+import moe.nea.firmament.util.data.ManagedConfig
+import moe.nea.firmament.util.gold
+
+object InventoryButtons {
+
+ @Config
+ object TConfig : ManagedConfig("inventory-buttons-config", Category.INVENTORY) {
+ val _openEditor by button("open-editor") {
+ openEditor()
+ }
+ val hoverText by toggle("hover-text") { true }
+ val onlyInv by toggle("only-inv") { false }
+ }
-object InventoryButtons : FirmamentFeature {
- override val identifier: String
- get() = "inventory-buttons"
+ @Config
+ object DConfig : DataHolder<Data>(serializer(), "inventory-buttons", ::Data)
- object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
- val _openEditor by button("open-editor") {
- openEditor()
- }
- }
+ @Serializable
+ data class Data(
+ var buttons: MutableList<InventoryButton> = mutableListOf()
+ )
- object DConfig : DataHolder<Data>(serializer(), identifier, ::Data)
+ fun getValidButtons(screen: AbstractContainerScreen<*>): Sequence<InventoryButton> {
+ if (TConfig.onlyInv && screen !is InventoryScreen) return emptySequence()
+ if (FirmamentAPIImpl.extensions.any { it.shouldHideInventoryButtons(screen) }) {
+ return emptySequence()
+ }
+ return DConfig.data.buttons.asSequence().filter(InventoryButton::isValid)
+ }
- @Serializable
- data class Data(
- var buttons: MutableList<InventoryButton> = mutableListOf()
- )
+ @Subscribe
+ fun onRectangles(it: HandledScreenPushREIEvent) {
+ val bounds = it.screen.getProperRectangle()
+ for (button in getValidButtons(it.screen)) {
+ val buttonBounds = button.getBounds(bounds)
+ it.block(buttonBounds)
+ }
+ }
- override val config: ManagedConfig
- get() = TConfig
+ @Subscribe
+ fun onClickScreen(it: HandledScreenClickEvent) {
+ val bounds = it.screen.getProperRectangle()
+ for (button in getValidButtons(it.screen)) {
+ val buttonBounds = button.getBounds(bounds)
+ if (buttonBounds.contains(it.mouseX, it.mouseY)) {
+ MC.sendCommand(button.command!! /* non null invariant covered by getValidButtons */)
+ break
+ }
+ }
+ }
- fun getValidButtons() = DConfig.data.buttons.asSequence().filter { it.isValid() }
+ var lastHoveredComponent: InventoryButton? = null
+ var lastMouseMove = TimeMark.farPast()
- @Subscribe
- fun onRectangles(it: HandledScreenPushREIEvent) {
- val bounds = it.screen.getRectangle()
- for (button in getValidButtons()) {
- val buttonBounds = button.getBounds(bounds)
- it.block(buttonBounds)
- }
- }
+ @Subscribe
+ fun onRenderForeground(it: HandledScreenForegroundEvent) {
+ val bounds = it.screen.getProperRectangle()
- @Subscribe
- fun onClickScreen(it: HandledScreenClickEvent) {
- val bounds = it.screen.getRectangle()
- for (button in getValidButtons()) {
- val buttonBounds = button.getBounds(bounds)
- if (buttonBounds.contains(it.mouseX, it.mouseY)) {
- MC.sendCommand(button.command!! /* non null invariant covered by getValidButtons */)
- break
- }
- }
- }
+ var hoveredComponent: InventoryButton? = null
+ for (button in getValidButtons(it.screen)) {
+ val buttonBounds = button.getBounds(bounds)
+ it.context.pose().pushMatrix()
+ it.context.pose().translate(buttonBounds.minX.toFloat(), buttonBounds.minY.toFloat())
+ button.render(it.context)
+ it.context.pose().popMatrix()
- @Subscribe
- fun onRenderForeground(it: HandledScreenForegroundEvent) {
- val bounds = it.screen.getRectangle()
- for (button in getValidButtons()) {
- val buttonBounds = button.getBounds(bounds)
- it.context.matrices.push()
- it.context.matrices.translate(buttonBounds.minX.toFloat(), buttonBounds.minY.toFloat(), 0F)
- button.render(it.context)
- it.context.matrices.pop()
- }
- lastRectangle = bounds
- }
+ if (buttonBounds.contains(it.mouseX, it.mouseY) && TConfig.hoverText && hoveredComponent == null) {
+ hoveredComponent = button
+ if (lastMouseMove.passedTime() > 0.6.seconds && lastHoveredComponent === button) {
+ it.context.setComponentTooltipForNextFrame(
+ MC.font,
+ listOf(Component.literal(button.command).gold()),
+ buttonBounds.minX - 15,
+ buttonBounds.maxY + 20,
+ )
+ }
+ }
+ }
+ if (hoveredComponent !== lastHoveredComponent)
+ lastMouseMove = TimeMark.now()
+ lastHoveredComponent = hoveredComponent
+ lastRectangle = bounds
+ }
- var lastRectangle: Rectangle? = null
- fun openEditor() {
- ScreenUtil.setScreenLater(
- InventoryButtonEditor(
- lastRectangle ?: Rectangle(
- MC.window.scaledWidth / 2 - 100,
- MC.window.scaledHeight / 2 - 100,
- 200, 200,
- )
- )
- )
- }
+ var lastRectangle: Rectangle? = null
+ fun openEditor() {
+ ScreenUtil.setScreenLater(
+ InventoryButtonEditor(
+ lastRectangle ?: Rectangle(
+ MC.window.guiScaledWidth / 2 - 88,
+ MC.window.guiScaledHeight / 2 - 83,
+ 176, 166,
+ )
+ )
+ )
+ }
}
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt
index 8fad4df..964f415 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt
@@ -4,9 +4,9 @@ package moe.nea.firmament.features.inventory.storageoverlay
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
-import net.minecraft.client.gui.screen.Screen
-import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
-import net.minecraft.screen.GenericContainerScreenHandler
+import net.minecraft.client.gui.screens.Screen
+import net.minecraft.client.gui.screens.inventory.ContainerScreen
+import net.minecraft.world.inventory.ChestMenu
import moe.nea.firmament.util.ifMatches
import moe.nea.firmament.util.unformattedString
@@ -16,24 +16,24 @@ import moe.nea.firmament.util.unformattedString
sealed interface StorageBackingHandle {
sealed interface HasBackingScreen {
- val handler: GenericContainerScreenHandler
+ val handler: ChestMenu
}
/**
* The main storage overview is open. Clicking on a slot will open that page. This page is accessible via `/storage`
*/
- data class Overview(override val handler: GenericContainerScreenHandler) : StorageBackingHandle, HasBackingScreen
+ data class Overview(override val handler: ChestMenu) : StorageBackingHandle, HasBackingScreen
/**
* An individual storage page is open. This may be a backpack or an enderchest page. This page is accessible via
* the [Overview] or via `/ec <index + 1>` for enderchest pages.
*/
- data class Page(override val handler: GenericContainerScreenHandler, val storagePageSlot: StoragePageSlot) :
+ data class Page(override val handler: ChestMenu, val storagePageSlot: StoragePageSlot) :
StorageBackingHandle, HasBackingScreen
companion object {
- private val enderChestName = "^Ender Chest \\(([1-9])/[1-9]\\)$".toRegex()
- private val backPackName = "^.+Backpack \\(Slot #([0-9]+)\\)$".toRegex()
+ private val enderChestName = "^Ender Chest (?:✦ )?\\(([1-9])/[1-9]\\)$".toRegex()
+ private val backPackName = "^.+Backpack (?:✦ )?\\(Slot #([0-9]+)\\)$".toRegex()
/**
* Parse a screen into a [StorageBackingHandle]. If this returns null it means that the screen is not
@@ -46,13 +46,13 @@ sealed interface StorageBackingHandle {
returnsNotNull() implies (screen != null)
}
if (screen == null) return null
- if (screen !is GenericContainerScreen) return null
+ if (screen !is ContainerScreen) return null
val title = screen.title.unformattedString
- if (title == "Storage") return Overview(screen.screenHandler)
+ if (title == "Storage") return Overview(screen.menu)
return title.ifMatches(enderChestName) {
- Page(screen.screenHandler, StoragePageSlot.ofEnderChestPage(it.groupValues[1].toInt()))
+ Page(screen.menu, StoragePageSlot.ofEnderChestPage(it.groupValues[1].toInt()))
} ?: title.ifMatches(backPackName) {
- Page(screen.screenHandler, StoragePageSlot.ofBackPackPage(it.groupValues[1].toInt()))
+ Page(screen.menu, StoragePageSlot.ofBackPackPage(it.groupValues[1].toInt()))
}
}
}
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt
index 2e807de..7f96637 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt
@@ -1,59 +1,106 @@
package moe.nea.firmament.features.inventory.storageoverlay
+import io.github.notenoughupdates.moulconfig.ChromaColour
import java.util.SortedMap
import kotlinx.serialization.serializer
-import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
-import net.minecraft.client.gui.screen.ingame.HandledScreen
-import net.minecraft.entity.player.PlayerInventory
-import net.minecraft.item.Items
-import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket
+import net.minecraft.client.gui.screens.inventory.ContainerScreen
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import net.minecraft.world.entity.player.Inventory
+import net.minecraft.world.item.Items
+import net.minecraft.network.protocol.game.ServerboundContainerClosePacket
+import net.minecraft.network.chat.Component
import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.ChestInventoryUpdateEvent
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.events.SlotClickEvent
+import moe.nea.firmament.events.SlotRenderEvents
import moe.nea.firmament.events.TickEvent
-import moe.nea.firmament.features.FirmamentFeature
-import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.async.discard
import moe.nea.firmament.util.customgui.customGui
+import moe.nea.firmament.util.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
-object StorageOverlay : FirmamentFeature {
-
+object StorageOverlay {
+ @Config
object Data : ProfileSpecificDataHolder<StorageData>(serializer(), "storage-data", ::StorageData)
- override val identifier: String
+ val identifier: String
get() = "storage-overlay"
+ @Config
object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
val alwaysReplace by toggle("always-replace") { true }
+ val outlineActiveStoragePage by toggle("outline-active-page") { false }
+ val outlineActiveStoragePageColour by colour("outline-active-page-colour") {
+ ChromaColour.fromRGB(
+ 255,
+ 255,
+ 0,
+ 0,
+ 255
+ )
+ }
+ val showInactivePageTooltips by toggle("inactive-page-tooltips") { false }
val columns by integer("rows", 1, 10) { 3 }
val height by integer("height", 80, 3000) { 3 * 18 * 6 }
+ val retainScroll by toggle("retain-scroll") { true }
val scrollSpeed by integer("scroll-speed", 1, 50) { 10 }
val inverseScroll by toggle("inverse-scroll") { false }
val padding by integer("padding", 1, 20) { 5 }
val margin by integer("margin", 1, 60) { 20 }
+ val itemsBlockScrolling by toggle("block-item-scrolling") { true }
+ val highlightSearchResults by toggle("highlight-search-results") { true }
+ val highlightSearchResultsColour by colour("highlight-search-results-colour") {
+ ChromaColour.fromRGB(
+ 0,
+ 176,
+ 0,
+ 0,
+ 255
+ )
+ }
+ }
+
+ @Subscribe
+ fun highlightSlots(event: SlotRenderEvents.Before) {
+ if (!TConfig.highlightSearchResults) return
+ val storageOverlayScreen =
+ (MC.screen as? StorageOverlayScreen)
+ ?: (MC.handledScreen?.customGui as? StorageOverlayCustom)?.overview
+ ?: return
+ val stack = event.slot.item ?: return
+ val search = storageOverlayScreen.searchText.get().takeIf { it.isNotBlank() } ?: return
+ if (storageOverlayScreen.matchesSearch(stack, search)) {
+ event.context.fill(
+ event.slot.x,
+ event.slot.y,
+ event.slot.x + 16,
+ event.slot.y + 16,
+ TConfig.highlightSearchResultsColour.getEffectiveColourRGB()
+ )
+ }
}
+
fun adjustScrollSpeed(amount: Double): Double {
return amount * TConfig.scrollSpeed * (if (TConfig.inverseScroll) 1 else -1)
}
- override val config: TConfig
- get() = TConfig
-
var lastStorageOverlay: StorageOverviewScreen? = null
var skipNextStorageOverlayBackflip = false
var currentHandler: StorageBackingHandle? = null
@Subscribe
- fun onTick(event: TickEvent) {
- rememberContent(currentHandler ?: return)
+ fun onChestContentUpdate(event: ChestInventoryUpdateEvent) {
+ rememberContent(currentHandler)
}
@Subscribe
fun onClick(event: SlotClickEvent) {
- if (lastStorageOverlay != null && event.slot.inventory !is PlayerInventory && event.slot.index < 9
+ if (lastStorageOverlay != null && event.slot.container !is Inventory && event.slot.containerSlot < 9
&& event.stack.item != Items.BLACK_STAINED_GLASS_PANE
) {
skipNextStorageOverlayBackflip = true
@@ -64,18 +111,18 @@ object StorageOverlay : FirmamentFeature {
fun onScreenChange(it: ScreenChangeEvent) {
if (it.old == null && it.new == null) return
val storageOverlayScreen = it.old as? StorageOverlayScreen
- ?: ((it.old as? HandledScreen<*>)?.customGui as? StorageOverlayCustom)?.overview
+ ?: ((it.old as? AbstractContainerScreen<*>)?.customGui as? StorageOverlayCustom)?.overview
var storageOverviewScreen = it.old as? StorageOverviewScreen
- val screen = it.new as? GenericContainerScreen
+ val screen = it.new as? ContainerScreen
+ rememberContent(currentHandler)
val oldHandler = currentHandler
currentHandler = StorageBackingHandle.fromScreen(screen)
- rememberContent(currentHandler)
if (storageOverviewScreen != null && oldHandler is StorageBackingHandle.HasBackingScreen) {
val player = MC.player
assert(player != null)
- player?.networkHandler?.sendPacket(CloseHandledScreenC2SPacket(oldHandler.handler.syncId))
- if (player?.currentScreenHandler === oldHandler.handler) {
- player.currentScreenHandler = player.playerScreenHandler
+ player?.connection?.send(ServerboundContainerClosePacket(oldHandler.handler.containerId))
+ if (player?.containerMenu === oldHandler.handler) {
+ player.containerMenu = player.inventoryMenu
}
}
storageOverviewScreen = storageOverviewScreen ?: lastStorageOverlay
@@ -100,25 +147,24 @@ object StorageOverlay : FirmamentFeature {
screen.customGui = StorageOverlayCustom(
currentHandler ?: return,
screen,
- storageOverlayScreen ?: (if (TConfig.alwaysReplace) StorageOverlayScreen() else return))
+ storageOverlayScreen ?: (if (TConfig.alwaysReplace) StorageOverlayScreen() else return)
+ )
}
fun rememberContent(handler: StorageBackingHandle?) {
handler ?: return
- // TODO: Make all of these functions work on deltas / updates instead of the entire contents
- val data = Data.data?.storageInventories ?: return
+ val data = Data.data.storageInventories
when (handler) {
is StorageBackingHandle.Overview -> rememberStorageOverview(handler, data)
is StorageBackingHandle.Page -> rememberPage(handler, data)
}
- Data.markDirty()
}
private fun rememberStorageOverview(
handler: StorageBackingHandle.Overview,
data: SortedMap<StoragePageSlot, StorageData.StorageInventory>
) {
- for ((index, stack) in handler.handler.stacks.withIndex()) {
+ for ((index, stack) in handler.handler.items.withIndex()) { // TODO: replace with slot iteration
// Ignore unloaded item stacks
if (stack.isEmpty) continue
val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue
@@ -132,15 +178,15 @@ object StorageOverlay : FirmamentFeature {
data[slot] = StorageData.StorageInventory(slot.defaultName(), slot, null)
}
}
+ Data.markDirty()
}
private fun rememberPage(
handler: StorageBackingHandle.Page,
data: SortedMap<StoragePageSlot, StorageData.StorageInventory>
) {
- // TODO: FIXME: FIXME NOW: Definitely don't copy all of this every tick into persistence
val newStacks =
- VirtualInventory(handler.handler.stacks.take(handler.handler.rows * 9).drop(9).map { it.copy() })
+ VirtualInventory(handler.handler.items.take(handler.handler.rowCount * 9).drop(9).map { it.copy() })
data.compute(handler.storagePageSlot) { slot, existingInventory ->
(existingInventory ?: StorageData.StorageInventory(
slot.defaultName(),
@@ -150,5 +196,6 @@ object StorageOverlay : FirmamentFeature {
it.inventory = newStacks
}
}
+ Data.markDirty(newStacks.serializationCache.discard())
}
}
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt
index 6092e26..98e8085 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt
@@ -2,21 +2,27 @@ package moe.nea.firmament.features.inventory.storageoverlay
import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
-import net.minecraft.client.MinecraftClient
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
-import net.minecraft.entity.player.PlayerInventory
-import net.minecraft.screen.slot.Slot
+import net.minecraft.client.Minecraft
+import net.minecraft.client.input.MouseButtonEvent
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.gui.screens.inventory.ContainerScreen
+import net.minecraft.client.input.CharacterEvent
+import net.minecraft.client.input.KeyEvent
+import net.minecraft.world.entity.player.Inventory
+import net.minecraft.world.inventory.Slot
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
+import moe.nea.firmament.util.accessors.castAccessor
import moe.nea.firmament.util.customgui.CustomGui
+import moe.nea.firmament.util.focusedItemStack
class StorageOverlayCustom(
- val handler: StorageBackingHandle,
- val screen: GenericContainerScreen,
- val overview: StorageOverlayScreen,
+ val handler: StorageBackingHandle,
+ val screen: ContainerScreen,
+ val overview: StorageOverlayScreen,
) : CustomGui() {
override fun onVoluntaryExit(): Boolean {
overview.isExiting = true
+ StorageOverlayScreen.resetScroll()
return super.onVoluntaryExit()
}
@@ -24,20 +30,20 @@ class StorageOverlayCustom(
return overview.getBounds()
}
- override fun afterSlotRender(context: DrawContext, slot: Slot) {
- if (slot.inventory !is PlayerInventory)
+ override fun afterSlotRender(context: GuiGraphics, slot: Slot) {
+ if (slot.container !is Inventory)
context.disableScissor()
}
- override fun beforeSlotRender(context: DrawContext, slot: Slot) {
- if (slot.inventory !is PlayerInventory)
+ override fun beforeSlotRender(context: GuiGraphics, slot: Slot) {
+ if (slot.container !is Inventory)
overview.createScissors(context)
}
override fun onInit() {
- overview.init(MinecraftClient.getInstance(), screen.width, screen.height)
+ overview.init(Minecraft.getInstance(), screen.width, screen.height)
overview.init()
- screen as AccessorHandledScreen
+ screen.castAccessor()
screen.x_Firmament = overview.measurements.x
screen.y_Firmament = overview.measurements.y
screen.backgroundWidth_Firmament = overview.measurements.totalWidth
@@ -47,7 +53,7 @@ class StorageOverlayCustom(
override fun isPointOverSlot(slot: Slot, xOffset: Int, yOffset: Int, pointX: Double, pointY: Double): Boolean {
if (!super.isPointOverSlot(slot, xOffset, yOffset, pointX, pointY))
return false
- if (slot.inventory !is PlayerInventory) {
+ if (slot.container !is Inventory) {
if (!overview.getScrollPanelInner().contains(pointX, pointY))
return false
}
@@ -58,48 +64,50 @@ class StorageOverlayCustom(
return false
}
- override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
- return overview.mouseReleased(mouseX, mouseY, button)
+ override fun mouseReleased(click: MouseButtonEvent): Boolean {
+ return overview.mouseReleased(click)
}
- override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
- return overview.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
+ override fun mouseDragged(click: MouseButtonEvent, offsetX: Double, offsetY: Double): Boolean {
+ return overview.mouseDragged(click, offsetX, offsetY)
}
- override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
- return overview.keyReleased(keyCode, scanCode, modifiers)
+ override fun keyReleased(input: KeyEvent): Boolean {
+ return overview.keyReleased(input)
}
- override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
- return overview.keyPressed(keyCode, scanCode, modifiers)
+ override fun keyPressed(input: KeyEvent): Boolean {
+ return overview.keyPressed(input)
}
- override fun charTyped(chr: Char, modifiers: Int): Boolean {
- return overview.charTyped(chr, modifiers)
+ override fun charTyped(input: CharacterEvent): Boolean {
+ return overview.charTyped(input)
}
- override fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean {
- return overview.mouseClicked(mouseX, mouseY, button, (handler as? StorageBackingHandle.Page)?.storagePageSlot)
+ override fun mouseClick(click: MouseButtonEvent, doubled: Boolean): Boolean {
+ return overview.mouseClicked(click, doubled, (handler as? StorageBackingHandle.Page)?.storagePageSlot)
}
- override fun render(drawContext: DrawContext, delta: Float, mouseX: Int, mouseY: Int) {
+ override fun render(drawContext: GuiGraphics, delta: Float, mouseX: Int, mouseY: Int) {
overview.drawBackgrounds(drawContext)
- overview.drawPages(drawContext,
- mouseX,
- mouseY,
- delta,
- (handler as? StorageBackingHandle.Page)?.storagePageSlot,
- screen.screenHandler.slots.take(screen.screenHandler.rows * 9).drop(9),
- Point((screen as AccessorHandledScreen).x_Firmament, screen.y_Firmament))
+ overview.drawPages(
+ drawContext,
+ mouseX,
+ mouseY,
+ delta,
+ (handler as? StorageBackingHandle.Page)?.storagePageSlot,
+ screen.menu.slots.take(screen.menu.rowCount * 9).drop(9),
+ Point((screen.castAccessor()).x_Firmament, screen.y_Firmament)
+ )
overview.drawScrollBar(drawContext)
overview.drawControls(drawContext, mouseX, mouseY)
}
override fun moveSlot(slot: Slot) {
- val index = slot.index
+ val index = slot.containerSlot
if (index in 0..<36) {
val (x, y) = overview.getPlayerInventorySlotPosition(index)
- slot.x = x - (screen as AccessorHandledScreen).x_Firmament
+ slot.x = x - (screen.castAccessor()).x_Firmament
slot.y = y - screen.y_Firmament
} else {
slot.x = -100000
@@ -113,6 +121,8 @@ class StorageOverlayCustom(
horizontalAmount: Double,
verticalAmount: Double
): Boolean {
+ if (screen.focusedItemStack != null && StorageOverlay.TConfig.itemsBlockScrolling)
+ return false
return overview.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)
}
}
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
index cf1cf1d..3e0bb4b 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt
@@ -13,13 +13,17 @@ import io.github.notenoughupdates.moulconfig.observer.Property
import java.util.TreeSet
import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.client.gui.screen.Screen
-import net.minecraft.client.gui.screen.ingame.HandledScreen
-import net.minecraft.item.ItemStack
-import net.minecraft.screen.slot.Slot
-import net.minecraft.text.Text
-import net.minecraft.util.Identifier
+import net.minecraft.client.input.MouseButtonEvent
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.gui.screens.Screen
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import net.minecraft.client.input.CharacterEvent
+import net.minecraft.client.input.KeyEvent
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.inventory.Slot
+import net.minecraft.network.chat.Component
+import net.minecraft.ChatFormatting
+import net.minecraft.resources.ResourceLocation
import moe.nea.firmament.events.SlotRenderEvents
import moe.nea.firmament.gui.EmptyComponent
import moe.nea.firmament.gui.FirmButtonComponent
@@ -35,10 +39,11 @@ import moe.nea.firmament.util.mc.FakeSlot
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.render.drawGuiTexture
+import moe.nea.firmament.util.render.enableScissorWithoutTranslation
import moe.nea.firmament.util.tr
import moe.nea.firmament.util.unformattedString
-class StorageOverlayScreen : Screen(Text.literal("")) {
+class StorageOverlayScreen : Screen(Component.literal("")) {
companion object {
val PLAYER_WIDTH = 184
@@ -46,19 +51,28 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
val PLAYER_Y_INSET = 3
val SLOT_SIZE = 18
val PADDING = 10
- val PAGE_WIDTH = SLOT_SIZE * 9
+ val PAGE_SLOTS_WIDTH = SLOT_SIZE * 9
+ val PAGE_WIDTH = PAGE_SLOTS_WIDTH + 4
val HOTBAR_X = 12
val HOTBAR_Y = 67
val MAIN_INVENTORY_Y = 9
val SCROLL_BAR_WIDTH = 8
val SCROLL_BAR_HEIGHT = 16
+ val CONTROL_X_INSET = 3
+ val CONTROL_Y_INSET = 5
val CONTROL_WIDTH = 70
- val CONTROL_BACKGROUND_WIDTH = CONTROL_WIDTH + PLAYER_Y_INSET
- val CONTROL_HEIGHT = 100
+ val CONTROL_BACKGROUND_WIDTH = CONTROL_WIDTH + CONTROL_X_INSET + 1
+ val CONTROL_HEIGHT = 50
+
+ var scroll: Float = 0F
+ var lastRenderedInnerHeight = 0
+
+ fun resetScroll() {
+ if (!StorageOverlay.TConfig.retainScroll) scroll = 0F
+ }
}
var isExiting: Boolean = false
- var scroll: Float = 0F
var pageWidthCount = StorageOverlay.TConfig.columns
inner class Measurements {
@@ -67,20 +81,20 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
val x = width / 2 - overviewWidth / 2
val overviewHeight = minOf(
height - PLAYER_HEIGHT - minOf(80, height / 10),
- StorageOverlay.TConfig.height)
+ StorageOverlay.TConfig.height
+ )
val innerScrollPanelHeight = overviewHeight - PADDING * 2
val y = height / 2 - (overviewHeight + PLAYER_HEIGHT) / 2
val playerX = width / 2 - PLAYER_WIDTH / 2
val playerY = y + overviewHeight - PLAYER_Y_INSET
- val controlX = x - CONTROL_WIDTH
- val controlY = y + overviewHeight / 2 - CONTROL_HEIGHT / 2
+ val controlX = playerX - CONTROL_WIDTH + CONTROL_X_INSET
+ val controlY = playerY - CONTROL_Y_INSET
val totalWidth = overviewWidth
val totalHeight = overviewHeight - PLAYER_Y_INSET + PLAYER_HEIGHT
}
var measurements = Measurements()
- var lastRenderedInnerHeight = 0
public override fun init() {
super.init()
pageWidthCount = StorageOverlay.TConfig.columns
@@ -99,6 +113,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
coerceScroll(StorageOverlay.adjustScrollSpeed(verticalAmount).toFloat())
return true
}
+
fun coerceScroll(offset: Float) {
scroll = (scroll + offset)
.coerceAtMost(getMaxScroll())
@@ -107,19 +122,20 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
fun getMaxScroll() = lastRenderedInnerHeight.toFloat() - getScrollPanelInner().height
- val playerInventorySprite = Identifier.of("firmament:storageoverlay/player_inventory")
- val upperBackgroundSprite = Identifier.of("firmament:storageoverlay/upper_background")
- val slotRowSprite = Identifier.of("firmament:storageoverlay/storage_row")
- val scrollbarBackground = Identifier.of("firmament:storageoverlay/scroll_bar_background")
- val scrollbarKnob = Identifier.of("firmament:storageoverlay/scroll_bar_knob")
- val controllerBackground = Identifier.of("firmament:storageoverlay/storage_controls")
+ val playerInventorySprite = ResourceLocation.parse("firmament:storageoverlay/player_inventory")
+ val upperBackgroundSprite = ResourceLocation.parse("firmament:storageoverlay/upper_background")
+ val slotRowSprite = ResourceLocation.parse("firmament:storageoverlay/storage_row")
+ val scrollbarBackground = ResourceLocation.parse("firmament:storageoverlay/scroll_bar_background")
+ val scrollbarKnob = ResourceLocation.parse("firmament:storageoverlay/scroll_bar_knob")
+ val controllerBackground = ResourceLocation.parse("firmament:storageoverlay/storage_controls")
- override fun close() {
+ override fun onClose() {
isExiting = true
- super.close()
+ resetScroll()
+ super.onClose()
}
- override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
+ override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
super.render(context, mouseX, mouseY, delta)
drawBackgrounds(context)
drawPages(context, mouseX, mouseY, delta, null, null, Point())
@@ -132,7 +148,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
return scroll / getMaxScroll()
}
- fun drawScrollBar(context: DrawContext) {
+ fun drawScrollBar(context: GuiGraphics) {
val sbRect = getScrollBarRect()
context.drawGuiTexture(
scrollbarBackground,
@@ -148,21 +164,29 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
fun editPages() {
isExiting = true
- val hs = MC.screen as? HandledScreen<*>
- if (StorageBackingHandle.fromScreen(hs) is StorageBackingHandle.Overview) {
- hs.customGui = null
- } else {
- MC.sendCommand("storage")
+ MC.instance.schedule {
+ val hs = MC.screen as? AbstractContainerScreen<*>
+ if (StorageBackingHandle.fromScreen(hs) is StorageBackingHandle.Overview) {
+ hs.customGui = null
+ hs.init(MC.instance, width, height)
+ } else {
+ MC.sendCommand("storage")
+ }
}
}
val guiContext = GuiContext(EmptyComponent())
private val knobStub = EmptyComponent()
- val editButton = FirmButtonComponent(TextComponent(tr("firmament.storage-overlay.edit-pages", "Edit Pages").string), action = ::editPages)
+ val editButton = FirmButtonComponent(
+ TextComponent(tr("firmament.storage-overlay.edit-pages", "Edit Pages").string),
+ action = ::editPages
+ )
val searchText = Property.of("") // TODO: sync with REI
- val searchField = TextFieldComponent(searchText, 100, GetSetter.constant(true),
- tr("firmament.storage-overlay.search.suggestion", "Search...").string,
- IMinecraft.instance.defaultFontRenderer)
+ val searchField = TextFieldComponent(
+ searchText, 100, GetSetter.constant(true),
+ tr("firmament.storage-overlay.search.suggestion", "Search...").string,
+ IMinecraft.INSTANCE.defaultFontRenderer
+ )
val controlComponent = PanelComponent(
ColumnComponent(
searchField,
@@ -180,30 +204,36 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
guiContext.adopt(controlComponent)
}
- fun drawControls(context: DrawContext, mouseX: Int, mouseY: Int) {
+ fun drawControls(context: GuiGraphics, mouseX: Int, mouseY: Int) {
context.drawGuiTexture(
controllerBackground,
measurements.controlX,
measurements.controlY,
- CONTROL_BACKGROUND_WIDTH, CONTROL_HEIGHT)
+ CONTROL_BACKGROUND_WIDTH, CONTROL_HEIGHT
+ )
context.drawMCComponentInPlace(
controlComponent,
measurements.controlX, measurements.controlY,
CONTROL_WIDTH, CONTROL_HEIGHT,
- mouseX, mouseY)
+ mouseX, mouseY
+ )
}
- fun drawBackgrounds(context: DrawContext) {
- context.drawGuiTexture(upperBackgroundSprite,
- measurements.x,
- measurements.y,
- measurements.overviewWidth,
- measurements.overviewHeight)
- context.drawGuiTexture(playerInventorySprite,
- measurements.playerX,
- measurements.playerY,
- PLAYER_WIDTH,
- PLAYER_HEIGHT)
+ fun drawBackgrounds(context: GuiGraphics) {
+ context.drawGuiTexture(
+ upperBackgroundSprite,
+ measurements.x,
+ measurements.y,
+ measurements.overviewWidth,
+ measurements.overviewHeight
+ )
+ context.drawGuiTexture(
+ playerInventorySprite,
+ measurements.playerX,
+ measurements.playerY,
+ PLAYER_WIDTH,
+ PLAYER_HEIGHT
+ )
}
fun getPlayerInventorySlotPosition(int: Int): Pair<Int, Int> {
@@ -216,55 +246,63 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
)
}
- fun drawPlayerInventory(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
- val items = MC.player?.inventory?.main ?: return
+ fun drawPlayerInventory(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
+ val items = MC.player?.inventory?.nonEquipmentItems ?: return
items.withIndex().forEach { (index, item) ->
val (x, y) = getPlayerInventorySlotPosition(index)
- context.drawItem(item, x, y, 0)
- context.drawStackOverlay(textRenderer, item, x, y)
+ context.renderItem(item, x, y, 0)
+ context.renderItemDecorations(font, item, x, y)
}
}
fun getScrollBarRect(): Rectangle {
- return Rectangle(measurements.x + PADDING + measurements.innerScrollPanelWidth + PADDING,
- measurements.y + PADDING,
- SCROLL_BAR_WIDTH,
- measurements.innerScrollPanelHeight)
+ return Rectangle(
+ measurements.x + PADDING + measurements.innerScrollPanelWidth + PADDING,
+ measurements.y + PADDING,
+ SCROLL_BAR_WIDTH,
+ measurements.innerScrollPanelHeight
+ )
}
fun getScrollPanelInner(): Rectangle {
- return Rectangle(measurements.x + PADDING,
- measurements.y + PADDING,
- measurements.innerScrollPanelWidth,
- measurements.innerScrollPanelHeight)
+ return Rectangle(
+ measurements.x + PADDING,
+ measurements.y + PADDING,
+ measurements.innerScrollPanelWidth,
+ measurements.innerScrollPanelHeight
+ )
}
- fun createScissors(context: DrawContext) {
+ fun createScissors(context: GuiGraphics) {
val rect = getScrollPanelInner()
- context.enableScissor(
- rect.minX, rect.minY,
- rect.maxX, rect.maxY
+ context.enableScissorWithoutTranslation(
+ rect.minX.toFloat(), rect.minY.toFloat(),
+ rect.maxX.toFloat(), rect.maxY.toFloat(),
)
}
fun drawPages(
- context: DrawContext, mouseX: Int, mouseY: Int, delta: Float,
- excluding: StoragePageSlot?,
- slots: List<Slot>?,
- slotOffset: Point
+ context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float,
+ excluding: StoragePageSlot?,
+ slots: List<Slot>?,
+ slotOffset: Point
) {
createScissors(context)
val data = StorageOverlay.Data.data ?: StorageData()
layoutedForEach(data) { rect, page, inventory ->
- drawPage(context,
- rect.x,
- rect.y,
- page, inventory,
- if (excluding == page) slots else null,
- slotOffset
+ drawPage(
+ context,
+ rect.x,
+ rect.y,
+ page, inventory,
+ if (excluding == page) slots else null,
+ slotOffset,
+ mouseX,
+ mouseY
)
}
context.disableScissor()
+
}
@@ -272,40 +310,45 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
get() = guiContext.focusedElement == knobStub
set(value) = knobStub.setFocus(value)
- override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
- return mouseClicked(mouseX, mouseY, button, null)
+ override fun mouseClicked(click: MouseButtonEvent, doubled: Boolean): Boolean {
+ return mouseClicked(click, doubled, null)
}
- override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ override fun mouseReleased(click: MouseButtonEvent): Boolean {
if (knobGrabbed) {
knobGrabbed = false
return true
}
- if (clickMCComponentInPlace(controlComponent,
- measurements.controlX, measurements.controlY,
- CONTROL_WIDTH, CONTROL_HEIGHT,
- mouseX.toInt(), mouseY.toInt(),
- MouseEvent.Click(button, false))
+ if (clickMCComponentInPlace(
+ controlComponent,
+ measurements.controlX, measurements.controlY,
+ CONTROL_WIDTH, CONTROL_HEIGHT,
+ click.x.toInt(), click.y.toInt(),
+ MouseEvent.Click(click.button(), false)
+ )
) return true
- return super.mouseReleased(mouseX, mouseY, button)
+ return super.mouseReleased(click)
}
- override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
+ override fun mouseDragged(click: MouseButtonEvent, offsetX: Double, offsetY: Double): Boolean {
if (knobGrabbed) {
val sbRect = getScrollBarRect()
- val percentage = (mouseY - sbRect.getY()) / sbRect.getHeight()
+ val percentage = (click.x - sbRect.getY()) / sbRect.getHeight()
scroll = (getMaxScroll() * percentage).toFloat()
mouseScrolled(0.0, 0.0, 0.0, 0.0)
return true
}
- return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
+ return super.mouseDragged(click, offsetX, offsetY)
}
- fun mouseClicked(mouseX: Double, mouseY: Double, button: Int, activePage: StoragePageSlot?): Boolean {
+ fun mouseClicked(click: MouseButtonEvent, doubled: Boolean, activePage: StoragePageSlot?): Boolean {
+ guiContext.setFocusedElement(null) // Blur all elements. They will be refocused by clickMCComponentInPlace if in doubt, and we don't have any double click components.
+ val mouseX = click.x
+ val mouseY = click.y
if (getScrollPanelInner().contains(mouseX, mouseY)) {
- val data = StorageOverlay.Data.data ?: StorageData()
+ val data = StorageOverlay.Data.data
layoutedForEach(data) { rect, page, _ ->
- if (rect.contains(mouseX, mouseY) && activePage != page && button == 0) {
+ if (rect.contains(mouseX, mouseY) && activePage != page && click.button() == 0) {
page.navigateTo()
return true
}
@@ -320,52 +363,58 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
knobGrabbed = true
return true
}
- if (clickMCComponentInPlace(controlComponent,
- measurements.controlX, measurements.controlY,
- CONTROL_WIDTH, CONTROL_HEIGHT,
- mouseX.toInt(), mouseY.toInt(),
- MouseEvent.Click(button, true))
+ if (clickMCComponentInPlace(
+ controlComponent,
+ measurements.controlX, measurements.controlY,
+ CONTROL_WIDTH, CONTROL_HEIGHT,
+ mouseX.toInt(), mouseY.toInt(),
+ MouseEvent.Click(click.button(), true)
+ )
) return true
return false
}
- override fun charTyped(chr: Char, modifiers: Int): Boolean {
+ override fun charTyped(input: CharacterEvent): Boolean {
if (typeMCComponentInPlace(
controlComponent,
measurements.controlX, measurements.controlY,
CONTROL_WIDTH, CONTROL_HEIGHT,
- KeyboardEvent.CharTyped(chr)
+ KeyboardEvent.CharTyped(input.codepointAsString().first()) // TODO: i dont like this .first()
)
) {
return true
}
- return super.charTyped(chr, modifiers)
+ return super.charTyped(input)
}
- override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ override fun keyReleased(input: KeyEvent): Boolean {
if (typeMCComponentInPlace(
controlComponent,
measurements.controlX, measurements.controlY,
CONTROL_WIDTH, CONTROL_HEIGHT,
- KeyboardEvent.KeyPressed(keyCode, false)
+ KeyboardEvent.KeyPressed(input.input(), input.scancode, false)
)
) {
return true
}
- return super.keyReleased(keyCode, scanCode, modifiers)
+ return super.keyReleased(input)
+ }
+
+ override fun shouldCloseOnEsc(): Boolean {
+ return this === MC.screen // Fixes this UI closing the handled screen on Escape press.
}
- override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ override fun keyPressed(input: KeyEvent): Boolean {
if (typeMCComponentInPlace(
controlComponent,
measurements.controlX, measurements.controlY,
CONTROL_WIDTH, CONTROL_HEIGHT,
- KeyboardEvent.KeyPressed(keyCode, true)
+ KeyboardEvent.KeyPressed(input.input(), input.scancode, true)
)
) {
return true
}
- return super.keyPressed(keyCode, scanCode, modifiers)
+ return super.keyPressed(input)
}
@@ -414,7 +463,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
val filter = getFilteredPages()
for ((page, inventory) in data.storageInventories.entries) {
if (page !in filter) continue
- val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 4 + textRenderer.fontHeight }
+ val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 6 + font.lineHeight }
?: 18
maxHeight = maxOf(maxHeight, currentHeight)
val rect = Rectangle(
@@ -435,61 +484,102 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
}
fun drawPage(
- context: DrawContext,
- x: Int,
- y: Int,
- page: StoragePageSlot,
- inventory: StorageData.StorageInventory,
- slots: List<Slot>?,
- slotOffset: Point,
+ context: GuiGraphics,
+ x: Int,
+ y: Int,
+ page: StoragePageSlot,
+ inventory: StorageData.StorageInventory,
+ slots: List<Slot>?,
+ slotOffset: Point,
+ mouseX: Int,
+ mouseY: Int,
): Int {
val inv = inventory.inventory
if (inv == null) {
context.drawGuiTexture(upperBackgroundSprite, x, y, PAGE_WIDTH, 18)
- context.drawText(textRenderer,
- Text.literal("TODO: open this page"),
- x + 4,
- y + 4,
- -1,
- true)
+ context.drawString(
+ font,
+ Component.literal("TODO: open this page"),
+ x + 4,
+ y + 4,
+ -1,
+ true
+ )
return 18
}
assertTrueOr(slots == null || slots.size == inv.stacks.size) { return 0 }
- val name = page.defaultName()
- context.drawText(textRenderer, Text.literal(name), x + 4, y + 2,
- if (slots == null) 0xFFFFFFFF.toInt() else 0xFFFFFF00.toInt(), true)
- context.drawGuiTexture(slotRowSprite, x, y + 4 + textRenderer.fontHeight, PAGE_WIDTH, inv.rows * SLOT_SIZE)
+ val name = inventory.title
+ val pageHeight = inv.rows * SLOT_SIZE + 8 + font.lineHeight
+ if (slots != null && StorageOverlay.TConfig.outlineActiveStoragePage)
+ context.submitOutline(
+ x,
+ y + 3 + font.lineHeight,
+ PAGE_WIDTH,
+ inv.rows * SLOT_SIZE + 4,
+ StorageOverlay.TConfig.outlineActiveStoragePageColour.getEffectiveColourRGB()
+ )
+ context.drawString(
+ font, Component.literal(name), x + 6, y + 3,
+ if (slots == null) 0xFFFFFFFF.toInt() else 0xFFFFFF00.toInt(), true
+ )
+ context.drawGuiTexture(
+ slotRowSprite,
+ x + 2,
+ y + 5 + font.lineHeight,
+ PAGE_SLOTS_WIDTH,
+ inv.rows * SLOT_SIZE
+ )
inv.stacks.forEachIndexed { index, stack ->
- val slotX = (index % 9) * SLOT_SIZE + x + 1
- val slotY = (index / 9) * SLOT_SIZE + y + 4 + textRenderer.fontHeight + 1
- val fakeSlot = FakeSlot(stack, slotX, slotY)
+ val slotX = (index % 9) * SLOT_SIZE + x + 3
+ val slotY = (index / 9) * SLOT_SIZE + y + 5 + font.lineHeight + 1
if (slots == null) {
+ val fakeSlot = FakeSlot(stack, slotX, slotY)
SlotRenderEvents.Before.publish(SlotRenderEvents.Before(context, fakeSlot))
- context.drawItem(stack, slotX, slotY)
- context.drawStackOverlay(textRenderer, stack, slotX, slotY)
+ context.renderItem(stack, slotX, slotY)
+ context.renderItemDecorations(font, stack, slotX, slotY)
SlotRenderEvents.After.publish(SlotRenderEvents.After(context, fakeSlot))
+ val rect = getScrollPanelInner()
+ if (StorageOverlay.TConfig.showInactivePageTooltips && !stack.isEmpty &&
+ mouseX >= slotX && mouseY >= slotY &&
+ mouseX <= slotX + 16 && mouseY <= slotY + 16 &&
+ mouseY >= rect.minY && mouseY <= rect.maxY) {
+ try {
+ context.setTooltipForNextFrame(font, stack, mouseX, mouseY)
+ } catch (e: IllegalStateException) {
+ context.setComponentTooltipForNextFrame(font, listOf(Component.nullToEmpty(ChatFormatting.RED.toString() +
+ "Error Getting Tooltip!"), Component.nullToEmpty(ChatFormatting.YELLOW.toString() +
+ "Open page to fix" + ChatFormatting.RESET)), mouseX, mouseY)
+ }
+ }
} else {
val slot = slots[index]
slot.x = slotX - slotOffset.x
slot.y = slotY - slotOffset.y
}
}
- return inv.rows * SLOT_SIZE + 4 + textRenderer.fontHeight
+ return pageHeight + 6
}
fun getBounds(): List<Rectangle> {
return listOf(
- Rectangle(measurements.x,
- measurements.y,
- measurements.overviewWidth,
- measurements.overviewHeight),
- Rectangle(measurements.playerX,
- measurements.playerY,
- PLAYER_WIDTH,
- PLAYER_HEIGHT),
- Rectangle(measurements.controlX,
- measurements.controlY,
- CONTROL_WIDTH,
- CONTROL_HEIGHT))
+ Rectangle(
+ measurements.x,
+ measurements.y,
+ measurements.overviewWidth,
+ measurements.overviewHeight
+ ),
+ Rectangle(
+ measurements.playerX,
+ measurements.playerY,
+ PLAYER_WIDTH,
+ PLAYER_HEIGHT
+ ),
+ Rectangle(
+ measurements.controlX,
+ measurements.controlY,
+ CONTROL_WIDTH,
+ CONTROL_HEIGHT
+ )
+ )
}
}
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverviewScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverviewScreen.kt
index 9112fab..3c40fc6 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverviewScreen.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverviewScreen.kt
@@ -4,17 +4,19 @@ package moe.nea.firmament.features.inventory.storageoverlay
import org.lwjgl.glfw.GLFW
import kotlin.math.max
-import net.minecraft.block.Blocks
-import net.minecraft.client.gui.DrawContext
-import net.minecraft.client.gui.screen.Screen
-import net.minecraft.item.Item
-import net.minecraft.item.Items
-import net.minecraft.text.Text
-import net.minecraft.util.DyeColor
+import net.minecraft.world.level.block.Blocks
+import net.minecraft.client.input.MouseButtonEvent
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.gui.screens.Screen
+import net.minecraft.client.input.KeyEvent
+import net.minecraft.world.item.Item
+import net.minecraft.world.item.Items
+import net.minecraft.network.chat.Component
+import net.minecraft.world.item.DyeColor
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.toShedaniel
-class StorageOverviewScreen() : Screen(Text.empty()) {
+class StorageOverviewScreen() : Screen(Component.empty()) {
companion object {
val emptyStorageSlotItems = listOf<Item>(
Blocks.RED_STAINED_GLASS_PANE.asItem(),
@@ -22,39 +24,49 @@ class StorageOverviewScreen() : Screen(Text.empty()) {
Items.GRAY_DYE
)
val pageWidth get() = 19 * 9
+
+ var scroll = 0
+ var lastRenderedHeight = 0
}
val content = StorageOverlay.Data.data ?: StorageData()
var isClosing = false
- var scroll = 0
- var lastRenderedHeight = 0
+ override fun init() {
+ super.init()
+ scroll = scroll.coerceAtMost(getMaxScroll()).coerceAtLeast(0)
+ }
+
+ override fun onClose() {
+ if (!StorageOverlay.TConfig.retainScroll) scroll = 0
+ super.onClose()
+ }
- override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
+ override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
super.render(context, mouseX, mouseY, delta)
context.fill(0, 0, width, height, 0x90000000.toInt())
layoutedForEach { (key, value), offsetX, offsetY ->
- context.matrices.push()
- context.matrices.translate(offsetX.toFloat(), offsetY.toFloat(), 0F)
+ context.pose().pushMatrix()
+ context.pose().translate(offsetX.toFloat(), offsetY.toFloat())
renderStoragePage(context, value, mouseX - offsetX, mouseY - offsetY)
- context.matrices.pop()
+ context.pose().popMatrix()
}
}
inline fun layoutedForEach(onEach: (data: Pair<StoragePageSlot, StorageData.StorageInventory>, offsetX: Int, offsetY: Int) -> Unit) {
var offsetY = 0
- var currentMaxHeight = StorageOverlay.config.margin - StorageOverlay.config.padding - scroll
+ var currentMaxHeight = StorageOverlay.TConfig.margin - StorageOverlay.TConfig.padding - scroll
var totalHeight = -currentMaxHeight
content.storageInventories.onEachIndexed { index, (key, value) ->
- val pageX = (index % StorageOverlay.config.columns)
+ val pageX = (index % StorageOverlay.TConfig.columns)
if (pageX == 0) {
- currentMaxHeight += StorageOverlay.config.padding
+ currentMaxHeight += StorageOverlay.TConfig.padding
offsetY += currentMaxHeight
totalHeight += currentMaxHeight
currentMaxHeight = 0
}
val xPosition =
- width / 2 - (StorageOverlay.config.columns * (pageWidth + StorageOverlay.config.padding) - StorageOverlay.config.padding) / 2 + pageX * (pageWidth + StorageOverlay.config.padding)
+ width / 2 - (StorageOverlay.TConfig.columns * (pageWidth + StorageOverlay.TConfig.padding) - StorageOverlay.TConfig.padding) / 2 + pageX * (pageWidth + StorageOverlay.TConfig.padding)
onEach(Pair(key, value), xPosition, offsetY)
val height = getStorePageHeight(value)
currentMaxHeight = max(currentMaxHeight, height)
@@ -62,22 +74,22 @@ class StorageOverviewScreen() : Screen(Text.empty()) {
lastRenderedHeight = totalHeight + currentMaxHeight
}
- override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ override fun mouseClicked(click: MouseButtonEvent, doubled: Boolean): Boolean {
layoutedForEach { (k, p), x, y ->
- val rx = mouseX - x
- val ry = mouseY - y
+ val rx = click.x - x
+ val ry = click.y - y
if (rx in (0.0..pageWidth.toDouble()) && ry in (0.0..getStorePageHeight(p).toDouble())) {
- close()
+ onClose()
StorageOverlay.lastStorageOverlay = this
k.navigateTo()
return true
}
}
- return super.mouseClicked(mouseX, mouseY, button)
+ return super.mouseClicked(click, doubled)
}
fun getStorePageHeight(page: StorageData.StorageInventory): Int {
- return page.inventory?.rows?.let { it * 19 + MC.font.fontHeight + 2 } ?: 60
+ return page.inventory?.rows?.let { it * 19 + MC.font.lineHeight + 2 } ?: 60
}
override fun mouseScrolled(
@@ -88,36 +100,38 @@ class StorageOverviewScreen() : Screen(Text.empty()) {
): Boolean {
scroll =
(scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toInt()
- .coerceAtMost(lastRenderedHeight - height + 2 * StorageOverlay.config.margin).coerceAtLeast(0)
+ .coerceAtMost(getMaxScroll()).coerceAtLeast(0)
return true
}
- private fun renderStoragePage(context: DrawContext, page: StorageData.StorageInventory, mouseX: Int, mouseY: Int) {
- context.drawText(MC.font, page.title, 2, 2, -1, true)
+ private fun getMaxScroll() = lastRenderedHeight - height + 2 * StorageOverlay.TConfig.margin
+
+ private fun renderStoragePage(context: GuiGraphics, page: StorageData.StorageInventory, mouseX: Int, mouseY: Int) {
+ context.drawString(MC.font, page.title, 2, 2, -1, true)
val inventory = page.inventory
if (inventory == null) {
// TODO: Missing texture
context.fill(0, 0, pageWidth, 60, DyeColor.RED.toShedaniel().darker(4.0).color)
- context.drawCenteredTextWithShadow(MC.font, Text.literal("Not loaded yet"), pageWidth / 2, 30, -1)
+ context.drawCenteredString(MC.font, Component.literal("Not loaded yet"), pageWidth / 2, 30, -1)
return
}
for ((index, stack) in inventory.stacks.withIndex()) {
val x = (index % 9) * 19
- val y = (index / 9) * 19 + MC.font.fontHeight + 2
+ val y = (index / 9) * 19 + MC.font.lineHeight + 2
if (((mouseX - x) in 0 until 18) && ((mouseY - y) in 0 until 18)) {
context.fill(x, y, x + 18, y + 18, 0x80808080.toInt())
} else {
context.fill(x, y, x + 18, y + 18, 0x40808080.toInt())
}
- context.drawItem(stack, x + 1, y + 1)
- context.drawStackOverlay(MC.font, stack, x + 1, y + 1)
+ context.renderItem(stack, x + 1, y + 1)
+ context.renderItemDecorations(MC.font, stack, x + 1, y + 1)
}
}
- override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
- if (keyCode == GLFW.GLFW_KEY_ESCAPE)
+ override fun keyPressed(input: KeyEvent): Boolean {
+ if (input.input() == GLFW.GLFW_KEY_ESCAPE)
isClosing = true
- return super.keyPressed(keyCode, scanCode, modifiers)
+ return super.keyPressed(input)
}
}
diff --git a/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt b/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt
index 3b86184..69d686f 100644
--- a/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt
+++ b/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt
@@ -1,9 +1,10 @@
package moe.nea.firmament.features.inventory.storageoverlay
-import io.ktor.util.decodeBase64Bytes
-import io.ktor.util.encodeBase64
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
+import java.util.Base64
+import java.util.concurrent.CompletableFuture
+import kotlinx.coroutines.async
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
@@ -11,13 +12,16 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
-import net.minecraft.item.ItemStack
-import net.minecraft.nbt.NbtCompound
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.world.item.ItemStack
+import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtIo
-import net.minecraft.nbt.NbtList
+import net.minecraft.nbt.ListTag
import net.minecraft.nbt.NbtOps
-import net.minecraft.nbt.NbtSizeTracker
-import net.minecraft.registry.RegistryOps
+import net.minecraft.nbt.NbtAccounter
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.features.inventory.storageoverlay.VirtualInventory.Serializer.writeToByteArray
+import moe.nea.firmament.util.Base64Util
import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.mc.TolerantRegistriesOps
@@ -28,6 +32,10 @@ data class VirtualInventory(
) {
val rows = stacks.size / 9
+ val serializationCache = CompletableFuture.supplyAsync {
+ writeToByteArray(this)
+ }
+
init {
assert(stacks.size % 9 == 0)
assert(stacks.size / 9 in 1..5)
@@ -35,41 +43,47 @@ data class VirtualInventory(
object Serializer : KSerializer<VirtualInventory> {
+ fun writeToByteArray(value: VirtualInventory): ByteArray {
+ val list = ListTag()
+ val ops = getOps()
+ value.stacks.forEach {
+ if (it.isEmpty) list.add(CompoundTag())
+ else list.add(ErrorUtil.catch("Could not serialize item") {
+ ItemStack.CODEC.encode(
+ it,
+ ops,
+ CompoundTag()
+ ).orThrow
+ }
+ .or { CompoundTag() })
+ }
+ val baos = ByteArrayOutputStream()
+ NbtIo.writeCompressed(CompoundTag().also { it.put(INVENTORY, list) }, baos)
+ return baos.toByteArray()
+ }
+
const val INVENTORY = "INVENTORY"
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("VirtualInventory", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): VirtualInventory {
val s = decoder.decodeString()
- val n = NbtIo.readCompressed(ByteArrayInputStream(s.decodeBase64Bytes()), NbtSizeTracker.of(100_000_000))
- val items = n.getList(INVENTORY, NbtCompound.COMPOUND_TYPE.toInt())
+ val n = NbtIo.readCompressed(ByteArrayInputStream(Base64Util.decodeBytes(s)), NbtAccounter.create(100_000_000))
+ val items = n.getList(INVENTORY).getOrNull()
val ops = getOps()
- return VirtualInventory(items.map {
- it as NbtCompound
+ return VirtualInventory(items?.map {
+ it as CompoundTag
if (it.isEmpty) ItemStack.EMPTY
else ErrorUtil.catch("Could not deserialize item") {
ItemStack.CODEC.parse(ops, it).orThrow
}.or { ItemStack.EMPTY }
- })
+ } ?: listOf())
}
- fun getOps() = TolerantRegistriesOps(NbtOps.INSTANCE, MC.currentOrDefaultRegistries)
+ fun getOps() = MC.currentOrDefaultRegistryNbtOps
override fun serialize(encoder: Encoder, value: VirtualInventory) {
- val list = NbtList()
- val ops = getOps()
- value.stacks.forEach {
- if (it.isEmpty) list.add(NbtCompound())
- else list.add(ErrorUtil.catch("Could not serialize item") {
- ItemStack.CODEC.encode(it,
- ops,
- NbtCompound()).orThrow
- }
- .or { NbtCompound() })
- }
- val baos = ByteArrayOutputStream()
- NbtIo.writeCompressed(NbtCompound().also { it.put(INVENTORY, list) }, baos)
- encoder.encodeString(baos.toByteArray().encodeBase64())
+ encoder.encodeString(Base64Util.encodeToString(value.serializationCache.get()))
}
}
}