diff options
Diffstat (limited to 'src/main/kotlin/features/inventory')
14 files changed, 591 insertions, 225 deletions
diff --git a/src/main/kotlin/features/inventory/CraftingOverlay.kt b/src/main/kotlin/features/inventory/CraftingOverlay.kt index d2c79fd..f823086 100644 --- a/src/main/kotlin/features/inventory/CraftingOverlay.kt +++ b/src/main/kotlin/features/inventory/CraftingOverlay.kt @@ -8,6 +8,7 @@ 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 @@ -45,6 +46,7 @@ object CraftingOverlay : FirmamentFeature { override val identifier: String get() = "crafting-overlay" + @OptIn(ExpensiveItemCacheApi::class) @Subscribe fun onSlotRender(event: SlotRenderEvents.After) { val slot = event.slot diff --git a/src/main/kotlin/features/inventory/ItemHotkeys.kt b/src/main/kotlin/features/inventory/ItemHotkeys.kt index 4aa8202..e826b31 100644 --- a/src/main/kotlin/features/inventory/ItemHotkeys.kt +++ b/src/main/kotlin/features/inventory/ItemHotkeys.kt @@ -3,12 +3,14 @@ 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.focusedItemStack import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.skyblock.SBItemUtil.getSearchName @@ -18,6 +20,7 @@ object ItemHotkeys { val openGlobalTradeInterface by keyBindingWithDefaultUnbound("global-trade-interface") } + @OptIn(ExpensiveItemCacheApi::class) @Subscribe fun onHandledInventoryPress(event: HandledScreenKeyPressedEvent) { if (!event.matches(TConfig.openGlobalTradeInterface)) { @@ -26,7 +29,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/PetFeatures.kt b/src/main/kotlin/features/inventory/PetFeatures.kt index 5ca10f7..bb39fbc 100644 --- a/src/main/kotlin/features/inventory/PetFeatures.kt +++ b/src/main/kotlin/features/inventory/PetFeatures.kt @@ -1,14 +1,24 @@ package moe.nea.firmament.features.inventory -import net.minecraft.util.Identifier +import moe.nea.jarvis.api.Point +import net.minecraft.item.ItemStack +import net.minecraft.text.Text +import net.minecraft.util.Formatting +import moe.nea.firmament.Firmament import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HudRenderEvent import moe.nea.firmament.events.SlotRenderEvents import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig +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.petData import moe.nea.firmament.util.render.drawGuiTexture +import moe.nea.firmament.util.skyblock.Rarity +import moe.nea.firmament.util.titleCase import moe.nea.firmament.util.useMatch +import moe.nea.firmament.util.withColor object PetFeatures : FirmamentFeature { override val identifier: String @@ -19,9 +29,12 @@ object PetFeatures : FirmamentFeature { object TConfig : ManagedConfig(identifier, Category.INVENTORY) { val highlightEquippedPet by toggle("highlight-pet") { true } + var petOverlay by toggle("pet-overlay") { false } + val petOverlayHud by position("pet-overlay-hud", 80, 10) { Point(0.5, 1.0) } } val petMenuTitle = "Pets(?: \\([0-9]+/[0-9]+\\))?".toPattern() + var petItemStack: ItemStack? = null @Subscribe fun onSlotRender(event: SlotRenderEvents.Before) { @@ -29,12 +42,44 @@ object PetFeatures : FirmamentFeature { val stack = event.slot.stack if (stack.petData?.active == true) petMenuTitle.useMatch(MC.screenName ?: return) { - event.context.drawGuiTexture( - event.slot.x, event.slot.y, 0, 16, 16, - Identifier.of("firmament:selected_pet_background") - ) - } + petItemStack = stack + event.context.drawGuiTexture( + Firmament.identifier("selected_pet_background"), + event.slot.x, event.slot.y, 16, 16, + ) + } } + @Subscribe + fun onRenderHud(it: HudRenderEvent) { + if (!TConfig.petOverlay) return + val itemStack = petItemStack ?: return + val petData = petItemStack?.petData ?: return + val rarity = Rarity.fromNeuRepo(petData.tier) + val rarityCode = Rarity.colourMap[rarity] ?: Formatting.WHITE + val xp = petData.level + val petType = titleCase(petData.type) + val heldItem = petData.heldItem?.let { item -> "Held Item: ${titleCase(item)}" } + + it.context.matrices.push() + TConfig.petOverlayHud.applyTransformations(it.context.matrices) + + val lines = mutableListOf<Text>() + it.context.matrices.push() + it.context.matrices.translate(-0.5, -0.5, 0.0) + it.context.matrices.scale(2f, 2f, 1f) + it.context.drawItem(itemStack, 0, 0) + it.context.matrices.pop() + lines.add(Text.literal("[Lvl ${xp.currentLevel}] ").append(Text.literal(petType).withColor(rarityCode))) + if (heldItem != null) lines.add(Text.literal(heldItem)) + if (xp.currentLevel != xp.maxLevel) lines.add(Text.literal("Required L${xp.currentLevel + 1}: ${shortFormat(xp.expInCurrentLevel.toDouble())}/${shortFormat(xp.expRequiredForNextLevel.toDouble())} (${formatPercent(xp.percentageToNextLevel.toDouble())})")) + lines.add(Text.literal("Required L100: ${shortFormat(xp.expTotal.toDouble())}/${shortFormat(xp.expRequiredForMaxLevel.toDouble())} (${formatPercent(xp.percentageToMaxLevel.toDouble())})")) + + for ((index, line) in lines.withIndex()) { + it.context.drawText(MC.font, line.copy().withColor(Formatting.GRAY), 36, MC.font.fontHeight * index, -1, true) + } + + it.context.matrices.pop() + } } diff --git a/src/main/kotlin/features/inventory/PriceData.kt b/src/main/kotlin/features/inventory/PriceData.kt index 4477203..92bfc58 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 org.lwjgl.glfw.GLFW import net.minecraft.text.Text +import net.minecraft.util.StringIdentifiable 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.gold import moe.nea.firmament.util.skyBlockId +import moe.nea.firmament.util.tr +import moe.nea.firmament.util.yellow object PriceData : FirmamentFeature { - override val identifier: String - get() = "price-data" - - object TConfig : ManagedConfig(identifier, Category.INVENTORY) { - val tooltipEnabled by toggle("enable-always") { true } - val enableKeybinding by keyBindingWithDefaultUnbound("enable-keybind") - } - - override val config get() = TConfig - - @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)) - ) - } - } + override val identifier: String + get() = "price-data" + + 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 + } + } + + enum class AvgLowestBin : StringIdentifiable { + OFF, + ONEDAYAVGLOWESTBIN, + THREEDAYAVGLOWESTBIN, + SEVENDAYAVGLOWESTBIN; + + override fun asString(): String { + return name + } + } + + override val config get() = TConfig + + fun formatPrice(label: Text, price: Double): Text { + return Text.literal("") + .yellow() + .bold() + .append(label) + .append(": ") + .append( + Text.literal(formatCommas(price, fractionalDigits = 1)) + .append(if (price != 1.0) " coins" else " coin") + .gold() + .bold() + ) + } + + @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.count + 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(Text.literal("")) + it.lines.add(multiplierText) + it.lines.add( + formatPrice( + tr("firmament.tooltip.bazaar.sell-order", "Bazaar Sell Order"), + bazaarData.quickStatus.sellPrice * multiplier + ) + ) + it.lines.add( + formatPrice( + tr("firmament.tooltip.bazaar.buy-order", "Bazaar Buy Order"), + bazaarData.quickStatus.buyPrice * multiplier + ) + ) + } else if (lowestBin != null) { + it.lines.add(Text.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 7d88dd1..476759a 100644 --- a/src/main/kotlin/features/inventory/REIDependencyWarner.kt +++ b/src/main/kotlin/features/inventory/REIDependencyWarner.kt @@ -52,6 +52,7 @@ object REIDependencyWarner { @Subscribe fun checkREIDependency(event: SkyblockServerUpdateEvent) { if (!SBData.isOnSkyblock) return + if (!RepoManager.Config.warnForMissingItemListMod) return if (hasREI) return if (sentWarning) return sentWarning = true diff --git a/src/main/kotlin/features/inventory/TimerInLore.kt b/src/main/kotlin/features/inventory/TimerInLore.kt index 309ea61..cc1df9a 100644 --- a/src/main/kotlin/features/inventory/TimerInLore.kt +++ b/src/main/kotlin/features/inventory/TimerInLore.kt @@ -16,12 +16,14 @@ import moe.nea.firmament.util.SBData import moe.nea.firmament.util.aqua 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 { 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 } } @@ -81,6 +83,9 @@ object TimerInLore { CHOCOLATEFACTORY("Next Charge", "Available at"), STONKSAUCTION("Auction ends in", "Ends 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"); } @@ -88,6 +93,14 @@ object TimerInLore { "(?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 @@ -108,9 +121,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 @@ -120,10 +137,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, + Text.literal("${countdownType.label}: ") + .grey() + .append(Text.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..d797600 --- /dev/null +++ b/src/main/kotlin/features/inventory/WardrobeKeybinds.kt @@ -0,0 +1,56 @@ +package moe.nea.firmament.features.inventory + +import org.lwjgl.glfw.GLFW +import net.minecraft.item.Items +import net.minecraft.screen.slot.SlotActionType +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HandledScreenKeyPressedEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.mc.SlotUtils.clickLeftMouseButton +import moe.nea.firmament.util.mc.SlotUtils.clickMiddleMouseButton + +object WardrobeKeybinds : FirmamentFeature { + override val identifier: String + get() = "wardrobe-keybinds" + + object TConfig : ManagedConfig(identifier, Category.INVENTORY) { + val wardrobeKeybinds by toggle("wardrobe-keybinds") { false } + val slotKeybinds = (1..9).map { + keyBinding("slot-$it") { GLFW.GLFW_KEY_0 + it } + } + } + + override val config: ManagedConfig? + get() = TConfig + + 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 + + val regex = Regex("Wardrobe \\([12]/2\\)") + if (!regex.matches(event.screen.title.string)) return + if (!TConfig.wardrobeKeybinds) return + + val slot = + slotKeybindsWithSlot + .find { event.matches(it.second.get()) } + ?.first ?: return + + event.cancel() + + val handler = event.screen.screenHandler + val invSlot = handler.getSlot(slot) + + val itemStack = invSlot.stack + if (itemStack.item != Items.PINK_DYE && itemStack.item != Items.LIME_DYE) 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..955ae88 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 @@ -13,74 +11,93 @@ import net.minecraft.command.argument.ItemStackArgumentType import net.minecraft.item.ItemStack import net.minecraft.resource.featuretoggle.FeatureFlags import net.minecraft.util.Identifier +import moe.nea.firmament.repo.ExpensiveItemCacheApi import moe.nea.firmament.repo.ItemCache.asItemStack import moe.nea.firmament.repo.RepoManager 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? = "", ) { - 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 - } - } + 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) + @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)).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) - } + fun render(context: DrawContext) { + context.drawGuiTexture( + 0, + 0, + 0, + dimensions.width, + dimensions.height, + Identifier.of("firmament:inventory_button_background") + ) + context.drawItem(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), dimensions) + } - 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 c4ea519..eecbd17 100644 --- a/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt +++ b/src/main/kotlin/features/inventory/buttons/InventoryButtonEditor.kt @@ -1,7 +1,9 @@ package moe.nea.firmament.features.inventory.buttons import io.github.notenoughupdates.moulconfig.common.IItemStack +import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent import io.github.notenoughupdates.moulconfig.platform.ModernItemStack +import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext import io.github.notenoughupdates.moulconfig.xml.Bind import me.shedaniel.math.Point import me.shedaniel.math.Rectangle @@ -9,6 +11,7 @@ import org.lwjgl.glfw.GLFW import net.minecraft.client.MinecraftClient import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.widget.ButtonWidget +import net.minecraft.client.gui.widget.TextWidget import net.minecraft.client.util.InputUtil import net.minecraft.text.Text import net.minecraft.util.math.MathHelper @@ -57,13 +60,46 @@ class InventoryButtonEditor( } override fun resize(client: MinecraftClient, width: Int, height: Int) { - lastGuiRect.move(MC.window.scaledWidth / 2 - lastGuiRect.width / 2, MC.window.scaledHeight / 2 - lastGuiRect.height / 2) + lastGuiRect.move( + MC.window.scaledWidth / 2 - lastGuiRect.width / 2, + MC.window.scaledHeight / 2 - lastGuiRect.height / 2 + ) super.resize(client, width, height) } override fun init() { super.init() addDrawableChild( + TextWidget( + lastGuiRect.minX, + 25, + lastGuiRect.width, + 9, + Text.translatable("firmament.inventory-buttons.delete"), + MC.font + ).alignCenter() + ) + addDrawableChild( + TextWidget( + lastGuiRect.minX, + 40, + lastGuiRect.width, + 9, + Text.translatable("firmament.inventory-buttons.info"), + MC.font + ).alignCenter() + ) + addDrawableChild( + ButtonWidget.builder(Text.translatable("firmament.inventory-buttons.reset")) { + val newButtons = InventoryButtonTemplates.loadTemplate("TkVVQlVUVE9OUy9bXQ==") + if (newButtons != null) + buttons = moveButtons(newButtons.map { it.copy(command = it.command?.removePrefix("/")) }) + } + .position(lastGuiRect.minX + 10, lastGuiRect.minY + 10) + .width(lastGuiRect.width - 20) + .build() + ) + addDrawableChild( ButtonWidget.builder(Text.translatable("firmament.inventory-buttons.load-preset")) { val t = ClipboardUtils.getTextContents() val newButtons = InventoryButtonTemplates.loadTemplate(t) @@ -82,6 +118,30 @@ class InventoryButtonEditor( .width(lastGuiRect.width - 20) .build() ) + addDrawableChild( + ButtonWidget.builder(Text.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("/")) }) + } + .position(lastGuiRect.minX + 10, lastGuiRect.minY + 85) + .width(lastGuiRect.width - 20) + .build() + ) + addDrawableChild( + ButtonWidget.builder(Text.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("/")) }) + } + .position(lastGuiRect.minX + 10, lastGuiRect.minY + 110) + .width(lastGuiRect.width - 20) + .build() + ) } private fun moveButtons(buttons: List<InventoryButton>): MutableList<InventoryButton> { @@ -89,14 +149,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) } @@ -105,9 +171,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) @@ -131,7 +199,12 @@ class InventoryButtonEditor( 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) + PanelComponent.DefaultBackgroundRenderer.VANILLA + .render( + ModernRenderContext(context), + lastGuiRect.minX, lastGuiRect.minY, + lastGuiRect.width, lastGuiRect.height, + ) context.matrices.pop() for (button in buttons) { val buttonPosition = button.getBounds(lastGuiRect) @@ -155,7 +228,8 @@ class InventoryButtonEditor( if (super.mouseReleased(mouseX, mouseY, button)) return true val clickedButton = buttons.firstOrNull { it.getBounds(lastGuiRect).contains(Point(mouseX, mouseY)) } if (clickedButton != null && !justPerformedAClickAction) { - createPopup(MoulConfigUtils.loadGui("button_editor_fragment", Editor(clickedButton)), Point(mouseX, mouseY)) + if (InputUtil.isKeyPressed(MC.window.handle, InputUtil.GLFW_KEY_LEFT_CONTROL)) Editor(clickedButton).delete() + else createPopup(MoulConfigUtils.loadGui("button_editor_fragment", Editor(clickedButton)), Point(mouseX, mouseY)) return true } justPerformedAClickAction = false @@ -193,14 +267,6 @@ 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 @@ -209,7 +275,10 @@ class InventoryButtonEditor( offsetX = MathHelper.floor(offsetX / 20F) * 20 offsetY = MathHelper.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 { diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt b/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt index 92640c8..ab80d97 100644 --- a/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt +++ b/src/main/kotlin/features/inventory/buttons/InventoryButtons.kt @@ -5,44 +5,50 @@ 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.screen.ingame.HandledScreen +import net.minecraft.client.gui.screen.ingame.InventoryScreen +import net.minecraft.text.Text 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.util.MC import moe.nea.firmament.util.ScreenUtil +import moe.nea.firmament.util.TimeMark import moe.nea.firmament.util.data.DataHolder import moe.nea.firmament.util.accessors.getRectangle +import moe.nea.firmament.util.gold -object InventoryButtons : FirmamentFeature { - override val identifier: String - get() = "inventory-buttons" +object InventoryButtons { - object TConfig : ManagedConfig(identifier, Category.INVENTORY) { + 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 DConfig : DataHolder<Data>(serializer(), identifier, ::Data) + object DConfig : DataHolder<Data>(serializer(), "inventory-buttons", ::Data) @Serializable data class Data( var buttons: MutableList<InventoryButton> = mutableListOf() ) + fun getValidButtons(screen: HandledScreen<*>): Sequence<InventoryButton> { + return DConfig.data.buttons.asSequence().filter { button -> + button.isValid() && (!TConfig.onlyInv || screen is InventoryScreen) + } + } - override val config: ManagedConfig - get() = TConfig - - fun getValidButtons() = DConfig.data.buttons.asSequence().filter { it.isValid() } @Subscribe fun onRectangles(it: HandledScreenPushREIEvent) { val bounds = it.screen.getRectangle() - for (button in getValidButtons()) { + for (button in getValidButtons(it.screen)) { val buttonBounds = button.getBounds(bounds) it.block(buttonBounds) } @@ -51,7 +57,7 @@ object InventoryButtons : FirmamentFeature { @Subscribe fun onClickScreen(it: HandledScreenClickEvent) { val bounds = it.screen.getRectangle() - for (button in getValidButtons()) { + 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 */) @@ -60,16 +66,36 @@ object InventoryButtons : FirmamentFeature { } } + var lastHoveredComponent: InventoryButton? = null + var lastMouseMove = TimeMark.farPast() + @Subscribe fun onRenderForeground(it: HandledScreenForegroundEvent) { - val bounds = it.screen.getRectangle() - for (button in getValidButtons()) { + val bounds = it.screen.getRectangle() + + var hoveredComponent: InventoryButton? = null + for (button in getValidButtons(it.screen)) { 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() + + if (buttonBounds.contains(it.mouseX, it.mouseY) && TConfig.hoverText && hoveredComponent == null) { + hoveredComponent = button + if (lastMouseMove.passedTime() > 0.6.seconds && lastHoveredComponent === button) { + it.context.drawTooltip( + MC.font, + listOf(Text.literal(button.command).gold()), + buttonBounds.minX - 15, + buttonBounds.maxY + 20, + ) + } + } } + if (hoveredComponent !== lastHoveredComponent) + lastMouseMove = TimeMark.now() + lastHoveredComponent = hoveredComponent lastRectangle = bounds } diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt index 8fad4df..d7346c2 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt @@ -32,8 +32,8 @@ sealed interface StorageBackingHandle { 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 diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt index 2e807de..ec62aa6 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt @@ -27,12 +27,14 @@ object StorageOverlay : FirmamentFeature { object TConfig : ManagedConfig(identifier, Category.INVENTORY) { val alwaysReplace by toggle("always-replace") { true } + val outlineActiveStoragePage by toggle("outline-active-page") { false } val columns by integer("rows", 1, 10) { 3 } val height by integer("height", 80, 3000) { 3 * 18 * 6 } 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 } } fun adjustScrollSpeed(amount: Double): Double { @@ -100,7 +102,8 @@ 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?) { diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt index 6092e26..81f058e 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt @@ -9,6 +9,7 @@ import net.minecraft.entity.player.PlayerInventory import net.minecraft.screen.slot.Slot import moe.nea.firmament.mixins.accessor.AccessorHandledScreen import moe.nea.firmament.util.customgui.CustomGui +import moe.nea.firmament.util.focusedItemStack class StorageOverlayCustom( val handler: StorageBackingHandle, @@ -113,6 +114,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 22f4dab..84d0f2b 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt @@ -47,15 +47,18 @@ 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 isExiting: Boolean = false @@ -68,13 +71,14 @@ 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 } @@ -100,6 +104,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) { coerceScroll(StorageOverlay.adjustScrollSpeed(verticalAmount).toFloat()) return true } + fun coerceScroll(offset: Float) { scroll = (scroll + offset) .coerceAtMost(getMaxScroll()) @@ -149,21 +154,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.send { + val hs = MC.screen as? HandledScreen<*> + 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, @@ -186,25 +199,31 @@ class StorageOverlayScreen : Screen(Text.literal("")) { 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) + 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> { @@ -227,17 +246,21 @@ class StorageOverlayScreen : Screen(Text.literal("")) { } 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) { @@ -257,12 +280,13 @@ class StorageOverlayScreen : Screen(Text.literal("")) { 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 ) } context.disableScissor() @@ -282,11 +306,13 @@ class StorageOverlayScreen : Screen(Text.literal("")) { 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, + mouseX.toInt(), mouseY.toInt(), + MouseEvent.Click(button, false) + ) ) return true return super.mouseReleased(mouseX, mouseY, button) } @@ -322,11 +348,13 @@ 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(button, true) + ) ) return true return false } @@ -420,7 +448,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 + textRenderer.fontHeight } ?: 18 maxHeight = maxOf(maxHeight, currentHeight) val rect = Rectangle( @@ -452,22 +480,41 @@ class StorageOverlayScreen : Screen(Text.literal("")) { 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.drawText( + textRenderer, + Text.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 pageHeight = inv.rows * SLOT_SIZE + 8 + textRenderer.fontHeight + if (slots != null && StorageOverlay.TConfig.outlineActiveStoragePage) + context.drawBorder( + x, + y + 3 + textRenderer.fontHeight, + PAGE_WIDTH, + inv.rows * SLOT_SIZE + 4, + 0xFFFF00FF.toInt() + ) + context.drawText( + textRenderer, Text.literal(name), x + 6, y + 3, + if (slots == null) 0xFFFFFFFF.toInt() else 0xFFFFFF00.toInt(), true + ) + context.drawGuiTexture( + slotRowSprite, + x + 2, + y + 5 + textRenderer.fontHeight, + 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 slotX = (index % 9) * SLOT_SIZE + x + 3 + val slotY = (index / 9) * SLOT_SIZE + y + 5 + textRenderer.fontHeight + 1 val fakeSlot = FakeSlot(stack, slotX, slotY) if (slots == null) { SlotRenderEvents.Before.publish(SlotRenderEvents.Before(context, fakeSlot)) @@ -480,22 +527,29 @@ class StorageOverlayScreen : Screen(Text.literal("")) { 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 + ) + ) } } |