diff options
author | Linnea Gräf <nea@nea.moe> | 2024-07-10 01:34:37 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-07-10 03:14:55 +0200 |
commit | 986ce538f123cdec7e0da12ed89ba7225539df0a (patch) | |
tree | dd4a3448878c2af26be3bca81bbc8aa710bdc112 /src/main/kotlin | |
parent | 4e1cda1d64ce60719f6ff6e60c6af4881d81fdd2 (diff) | |
download | firmament-986ce538f123cdec7e0da12ed89ba7225539df0a.tar.gz firmament-986ce538f123cdec7e0da12ed89ba7225539df0a.tar.bz2 firmament-986ce538f123cdec7e0da12ed89ba7225539df0a.zip |
Add interactive storage overlay
Diffstat (limited to 'src/main/kotlin')
13 files changed, 648 insertions, 151 deletions
diff --git a/src/main/kotlin/moe/nea/firmament/commands/rome.kt b/src/main/kotlin/moe/nea/firmament/commands/rome.kt index 6ec09f2..4211963 100644 --- a/src/main/kotlin/moe/nea/firmament/commands/rome.kt +++ b/src/main/kotlin/moe/nea/firmament/commands/rome.kt @@ -15,7 +15,7 @@ import net.minecraft.text.Text import moe.nea.firmament.apis.UrsaManager import moe.nea.firmament.events.CommandEvent import moe.nea.firmament.features.inventory.buttons.InventoryButtons -import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen +import moe.nea.firmament.features.inventory.storageoverlay.StorageOverviewScreen import moe.nea.firmament.features.world.FairySouls import moe.nea.firmament.gui.config.AllConfigsGui import moe.nea.firmament.gui.config.BooleanHandler @@ -107,7 +107,7 @@ fun firmamentCommand() = literal("firmament") { } thenLiteral("storage") { thenExecute { - ScreenUtil.setScreenLater(StorageOverlayScreen()) + ScreenUtil.setScreenLater(StorageOverviewScreen()) MC.player?.networkHandler?.sendChatCommand("storage") } } diff --git a/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt index c6b0a7f..4683fac 100644 --- a/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt +++ b/src/main/kotlin/moe/nea/firmament/events/ScreenChangeEvent.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -9,5 +10,6 @@ package moe.nea.firmament.events import net.minecraft.client.gui.screen.Screen data class ScreenChangeEvent(val old: Screen?, val new: Screen?) : FirmamentEvent.Cancellable() { + var overrideScreen: Screen? = null companion object : FirmamentEventBus<ScreenChangeEvent>() } diff --git a/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt b/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt index a4542e7..bf29543 100644 --- a/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt +++ b/src/main/kotlin/moe/nea/firmament/events/subscription/Subscription.kt @@ -15,7 +15,7 @@ interface SubscriptionOwner { } data class Subscription<T : FirmamentEvent>( - val owner: SubscriptionOwner, + val owner: Any, val invoke: (T) -> Unit, val eventBus: FirmamentEventBus<T>, ) diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt index f2b2d25..f047ad3 100644 --- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt +++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt @@ -98,11 +98,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature } private fun <T : FirmamentEvent> subscribeSingleEvent(it: Subscription<T>) { - if (it.owner.delegateFeature in features.values) { // TODO: better check here, somehow. probably implement some interface method - it.eventBus.subscribe(false, it.invoke) // TODO: pass through receivesCancelled from the annotation - } else { - Firmament.logger.error("Ignoring event listener for ${it.eventBus} in ${it.owner}") - } + it.eventBus.subscribe(false, it.invoke) } fun loadFeature(feature: FirmamentFeature) { diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt index 006ad54..a38896f 100644 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageBackingHandle.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -22,11 +23,6 @@ sealed interface StorageBackingHandle { } /** - * No open "server side" screen. - */ - object None : StorageBackingHandle - - /** * 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 @@ -48,7 +44,7 @@ sealed interface StorageBackingHandle { * selection screen. */ fun fromScreen(screen: Screen?): StorageBackingHandle? { - if (screen == null) return None + if (screen == null) return null if (screen !is GenericContainerScreen) return null val title = screen.title.unformattedString if (title == "Storage") return Overview(screen.screenHandler) diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt index 0426e34..f3a0f47 100644 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlay.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -9,12 +10,14 @@ package moe.nea.firmament.features.inventory.storageoverlay import java.util.SortedMap import kotlinx.serialization.serializer import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen +import net.minecraft.client.gui.screen.ingame.HandledScreen import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.ScreenChangeEvent import moe.nea.firmament.events.TickEvent import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig -import moe.nea.firmament.util.ScreenUtil.setScreenLater +import moe.nea.firmament.util.customgui.customGui import moe.nea.firmament.util.data.ProfileSpecificDataHolder object StorageOverlay : FirmamentFeature { @@ -33,13 +36,15 @@ object StorageOverlay : FirmamentFeature { val margin by integer("margin", 1, 60) { 20 } } + fun adjustScrollSpeed(amount: Double): Double { + return amount * TConfig.scrollSpeed * (if (TConfig.inverseScroll) 1 else -1) + } + override val config: TConfig get() = TConfig var lastStorageOverlay: Screen? = null - var shouldReturnToStorageOverlayFrom: Screen? = null - var shouldReturnToStorageOverlay: Screen? = null - var currentHandler: StorageBackingHandle? = StorageBackingHandle.None + var currentHandler: StorageBackingHandle? = null @Subscribe fun onTick(event: TickEvent) { @@ -47,35 +52,25 @@ object StorageOverlay : FirmamentFeature { } @Subscribe - fun onScreenChangeLegacy(event: ScreenChangeEvent) { - currentHandler = StorageBackingHandle.fromScreen(event.new) - if (event.old is StorageOverlayScreen && !event.old.isClosing) { - event.old.setHandler(currentHandler) - if (currentHandler != null) - // TODO: Consider instead only replacing rendering? might make a lot of stack handling easier - event.cancel() - } - } - - @Subscribe fun onScreenChange(it: ScreenChangeEvent) { - if (lastStorageOverlay != null && it.new != null) { - shouldReturnToStorageOverlay = lastStorageOverlay - shouldReturnToStorageOverlayFrom = it.new - lastStorageOverlay = null - } else if (it.old === shouldReturnToStorageOverlayFrom) { - if (shouldReturnToStorageOverlay != null && it.new == null) - setScreenLater(shouldReturnToStorageOverlay) - shouldReturnToStorageOverlay = null - shouldReturnToStorageOverlayFrom = null + val storageOverlayScreen = it.old as? StorageOverlayScreen + ?: ((it.old as? HandledScreen<*>)?.customGui as? StorageOverlayCustom)?.overview + if (it.new == null && storageOverlayScreen != null && !storageOverlayScreen.isExiting) { + it.overrideScreen = storageOverlayScreen + return } + val screen = it.new as? GenericContainerScreen ?: return + currentHandler = StorageBackingHandle.fromScreen(screen) + screen.customGui = StorageOverlayCustom( + currentHandler as? StorageBackingHandle.Page ?: return, + screen, + storageOverlayScreen ?: return) } - private fun rememberContent(handler: StorageBackingHandle) { + fun rememberContent(handler: StorageBackingHandle) { // TODO: Make all of these functions work on deltas / updates instead of the entire contents val data = Data.data?.storageInventories ?: return when (handler) { - StorageBackingHandle.None -> {} is StorageBackingHandle.Overview -> rememberStorageOverview(handler, data) is StorageBackingHandle.Page -> rememberPage(handler, data) } @@ -90,7 +85,7 @@ object StorageOverlay : FirmamentFeature { // Ignore unloaded item stacks if (stack.isEmpty) continue val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue - val isEmpty = stack.item in StorageOverlayScreen.emptyStorageSlotItems + val isEmpty = stack.item in StorageOverviewScreen.emptyStorageSlotItems if (slot in data) { if (isEmpty) data.remove(slot) diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt new file mode 100644 index 0000000..d1cdef2 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayCustom.kt @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +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 moe.nea.firmament.mixins.accessor.AccessorHandledScreen +import moe.nea.firmament.util.customgui.CustomGui + +class StorageOverlayCustom( + val handler: StorageBackingHandle.Page, + val screen: GenericContainerScreen, + val overview: StorageOverlayScreen, +) : CustomGui() { + override fun onVoluntaryExit(): Boolean { + overview.isExiting = true + return super.onVoluntaryExit() + } + + override fun getBounds(): List<Rectangle> { + return overview.getBounds() + } + + override fun afterSlotRender(context: DrawContext, slot: Slot) { + if (slot.inventory !is PlayerInventory) + context.disableScissor() + } + + override fun beforeSlotRender(context: DrawContext, slot: Slot) { + if (slot.inventory !is PlayerInventory) + overview.createScissors(context) + } + + override fun onInit() { + overview.init(MinecraftClient.getInstance(), screen.width, screen.height) + } + + 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 (!overview.getScrollPanelInner().contains(pointX, pointY)) + return false + } + return true + } + + override fun shouldDrawForeground(): Boolean { + return false + } + + override fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean { + return overview.mouseClicked(mouseX, mouseY, button, handler.storagePageSlot) + } + + override fun render(drawContext: DrawContext, delta: Float, mouseX: Int, mouseY: Int) { + overview.drawBackgrounds(drawContext) + overview.drawPages(drawContext, + mouseX, + mouseY, + delta, + handler.storagePageSlot, + screen.screenHandler.slots.take(screen.screenHandler.rows * 9).drop(9), + Point((screen as AccessorHandledScreen).x_Firmament, screen.y_Firmament)) + overview.drawScrollBar(drawContext) + } + + override fun moveSlot(slot: Slot) { + val index = slot.index + if (index in 0..<36) { + val (x, y) = overview.getPlayerInventorySlotPosition(index) + slot.x = x - (screen as AccessorHandledScreen).x_Firmament + slot.y = y - screen.y_Firmament + } else { + slot.x = -100000 + slot.y = -100000 + } + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + return overview.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) + } +} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt index e0cd3c8..ac89f8c 100644 --- a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverlayScreen.kt @@ -1,147 +1,310 @@ /* - * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> * * SPDX-License-Identifier: GPL-3.0-or-later */ package moe.nea.firmament.features.inventory.storageoverlay -import moe.nea.firmament.util.MC -import moe.nea.firmament.util.assertNotNullOr -import moe.nea.firmament.util.toShedaniel -import net.minecraft.block.Blocks +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.item.Item -import net.minecraft.item.Items -import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket +import net.minecraft.screen.slot.Slot import net.minecraft.text.Text -import net.minecraft.util.DyeColor -import kotlin.math.max +import net.minecraft.util.Identifier +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.ScreenUtil +import moe.nea.firmament.util.assertTrueOr -class StorageOverlayScreen() : Screen(Text.empty()) { - companion object { - val emptyStorageSlotItems = listOf<Item>( - Blocks.RED_STAINED_GLASS_PANE.asItem(), - Blocks.BROWN_STAINED_GLASS_PANE.asItem(), - Items.GRAY_DYE - ) - val pageWidth get() = 19 * 9 - } +class StorageOverlayScreen : Screen(Text.literal("")) { - private var handler: StorageBackingHandle = StorageBackingHandle.None - val content = StorageOverlay.Data.data ?: StorageData() - var isClosing = false + companion object { + val PLAYER_WIDTH = 184 + val PLAYER_HEIGHT = 91 + val PLAYER_Y_INSET = 3 + val SLOT_SIZE = 18 + val PADDING = 10 + val PAGE_WIDTH = SLOT_SIZE * 9 + val HOTBAR_X = 12 + val HOTBAR_Y = 67 + val MAIN_INVENTORY_Y = 9 + val SCROLL_BAR_WIDTH = 8 + val SCROLL_BAR_HEIGHT = 16 - private fun discardOldHandle() { - val player = assertNotNullOr(MC.player) { return } - val handle = this.handler - if (handle is StorageBackingHandle.HasBackingScreen) { - player.networkHandler.sendPacket(CloseHandledScreenC2SPacket(handle.handler.syncId)) - if (player.currentScreenHandler === handle.handler) { - player.currentScreenHandler = player.playerScreenHandler + @Subscribe + fun onCommand(event: CommandEvent.SubCommand) { + event.subcommand("teststorage") { + executes { + ScreenUtil.setScreenLater(StorageOverlayScreen()) + MC.sendCommand("ec") + 0 + } } } } - fun setHandler(handler: StorageBackingHandle?) { - discardOldHandle() - if (handler != null) - this.handler = handler + var isExiting: Boolean = false + var scroll: Float = 0F + var pageWidthCount = StorageOverlay.TConfig.rows + + inner class Measurements { + val innerScrollPanelWidth = PAGE_WIDTH * pageWidthCount + (pageWidthCount - 1) * PADDING + val overviewWidth = innerScrollPanelWidth + 3 * PADDING + SCROLL_BAR_WIDTH + val x = width / 2 - overviewWidth / 2 + val overviewHeight = minOf(3 * 18 * 6, height - PLAYER_HEIGHT - minOf(80, height / 10)) + 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 } - var scroll = 0 - var lastRenderedHeight = 0 + var measurements = Measurements() + + var lastRenderedInnerHeight = 0 + override fun init() { + super.init() + pageWidthCount = StorageOverlay.TConfig.rows + .coerceAtMost((width - PADDING) / (PAGE_WIDTH + PADDING)) + .coerceAtLeast(1) + measurements = Measurements() + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + scroll = (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toFloat() + .coerceAtMost(getMaxScroll()) + .coerceAtLeast(0F) + return true + } + + 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") + + override fun close() { + isExiting = true + super.close() + } override fun render(context: DrawContext, 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) - renderStoragePage(context, value, mouseX - offsetX, mouseY - offsetY) - context.matrices.pop() + drawBackgrounds(context) + drawPages(context, mouseX, mouseY, delta, null, null, Point()) + drawScrollBar(context) + drawPlayerInventory(context, mouseX, mouseY, delta) + } + + fun getScrollbarPercentage(): Float { + return scroll / getMaxScroll() + } + + fun drawScrollBar(context: DrawContext) { + val sbRect = getScrollBarRect() + context.drawGuiTexture( + scrollbarBackground, + sbRect.minX, sbRect.minY, + sbRect.width, sbRect.height, + ) + context.drawGuiTexture( + scrollbarKnob, + sbRect.minX, sbRect.minY + (getScrollbarPercentage() * (sbRect.height - SCROLL_BAR_HEIGHT)).toInt(), + SCROLL_BAR_WIDTH, SCROLL_BAR_HEIGHT + ) + } + + fun drawBackgrounds(context: DrawContext) { + context.drawGuiTexture(upperBackgroundSprite, + measurements.x, + measurements.y, + 0, + measurements.overviewWidth, + measurements.overviewHeight) + context.drawGuiTexture(playerInventorySprite, + measurements.playerX, + measurements.playerY, + 0, + PLAYER_WIDTH, + PLAYER_HEIGHT) + } + + fun getPlayerInventorySlotPosition(int: Int): Pair<Int, Int> { + if (int < 9) { + return Pair(measurements.playerX + int * SLOT_SIZE + HOTBAR_X, HOTBAR_Y + measurements.playerY) } + return Pair( + measurements.playerX + (int % 9) * SLOT_SIZE + HOTBAR_X, + measurements.playerY + (int / 9 - 1) * SLOT_SIZE + MAIN_INVENTORY_Y + ) } - 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 totalHeight = -currentMaxHeight - content.storageInventories.onEachIndexed { index, (key, value) -> - val pageX = (index % StorageOverlay.config.rows) - if (pageX == 0) { - currentMaxHeight += StorageOverlay.config.padding - offsetY += currentMaxHeight - totalHeight += currentMaxHeight - currentMaxHeight = 0 - } - val xPosition = - width / 2 - (StorageOverlay.config.rows * (pageWidth + StorageOverlay.config.padding) - StorageOverlay.config.padding) / 2 + pageX * (pageWidth + StorageOverlay.config.padding) - onEach(Pair(key, value), xPosition, offsetY) - val height = getStorePageHeight(value) - currentMaxHeight = max(currentMaxHeight, height) + fun drawPlayerInventory(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + val items = MC.player?.inventory?.main ?: return + items.withIndex().forEach { (index, item) -> + val (x, y) = getPlayerInventorySlotPosition(index) + context.drawItem(item, x, y, 0) + context.drawItemInSlot(textRenderer, item, x, y) } - lastRenderedHeight = totalHeight + currentMaxHeight } - override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { - layoutedForEach { (k, p), x, y -> - val rx = mouseX - x - val ry = mouseY - y - if (rx in (0.0..pageWidth.toDouble()) && ry in (0.0..getStorePageHeight(p).toDouble())) { - close() - StorageOverlay.lastStorageOverlay = this - k.navigateTo() - return true - } + fun getScrollBarRect(): Rectangle { + 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) + } + + fun createScissors(context: DrawContext) { + val rect = getScrollPanelInner() + context.enableScissor( + rect.minX, rect.minY, + rect.maxX, rect.maxY + ) + } + + fun drawPages( + context: DrawContext, 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 + ) } - return super.mouseClicked(mouseX, mouseY, button) + context.disableScissor() } - fun getStorePageHeight(page: StorageData.StorageInventory): Int { - return page.inventory?.rows?.let { it * 19 + MC.font.fontHeight + 2 } ?: 60 + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + return mouseClicked(mouseX, mouseY, button, null) } - override fun mouseScrolled( - mouseX: Double, - mouseY: Double, - horizontalAmount: Double, - verticalAmount: Double - ): Boolean { - scroll = - (scroll + verticalAmount * StorageOverlay.config.scrollSpeed * - (if (StorageOverlay.config.inverseScroll) 1 else -1)).toInt() - .coerceAtMost(lastRenderedHeight - height + 2 * StorageOverlay.config.margin).coerceAtLeast(0) - return true + fun mouseClicked(mouseX: Double, mouseY: Double, button: Int, activePage: StoragePageSlot?): Boolean { + if (getScrollPanelInner().contains(mouseX, mouseY)) { + val data = StorageOverlay.Data.data ?: StorageData() + layoutedForEach(data) { rect, page, _ -> + if (rect.contains(mouseX, mouseY) && activePage != page && button == 0) { + page.navigateTo() + return true + } + } + return false + } + val sbRect = getScrollBarRect() + if (sbRect.contains(mouseX, mouseY)) { + // TODO: support dragging of the mouse and such + val percentage = (mouseY - sbRect.getY()) / sbRect.getHeight() + scroll = (getMaxScroll() * percentage).toFloat() + mouseScrolled(0.0, 0.0, 0.0, 0.0) + return true + } + return false } - private fun renderStoragePage(context: DrawContext, page: StorageData.StorageInventory, mouseX: Int, mouseY: Int) { - context.drawText(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) - return + private inline fun layoutedForEach( + data: StorageData, + func: ( + rectangle: Rectangle, + page: StoragePageSlot, inventory: StorageData.StorageInventory, + ) -> Unit + ) { + var yOffset = -scroll.toInt() + var xOffset = 0 + var maxHeight = 0 + for ((page, inventory) in data.storageInventories.entries) { + val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 4 + textRenderer.fontHeight } + ?: 18 + maxHeight = maxOf(maxHeight, currentHeight) + val rect = Rectangle( + measurements.x + PADDING + (PAGE_WIDTH + PADDING) * xOffset, + yOffset + measurements.y + PADDING, + PAGE_WIDTH, + currentHeight + ) + func(rect, page, inventory) + xOffset++ + if (xOffset >= pageWidthCount) { + yOffset += maxHeight + xOffset = 0 + maxHeight = 0 + } } + lastRenderedInnerHeight = maxHeight + yOffset + scroll.toInt() + } - for ((index, stack) in inventory.stacks.withIndex()) { - val x = (index % 9) * 19 - val y = (index / 9) * 19 + MC.font.fontHeight + 2 - if (((mouseX - x) in 0 until 18) && ((mouseY - y) in 0 until 18)) { - context.fill(x, y, x + 18, y + 18, 0x80808080.toInt()) + fun drawPage( + context: DrawContext, + x: Int, + y: Int, + page: StoragePageSlot, + inventory: StorageData.StorageInventory, + slots: List<Slot>?, + slotOffset: Point, + ): 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) + 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) + inv.stacks.forEachIndexed { index, stack -> + val slotX = (index % 9) * SLOT_SIZE + x + 1 + val slotY = (index / 9) * SLOT_SIZE + y + 4 + textRenderer.fontHeight + 1 + if (slots == null) { + context.drawItem(stack, slotX, slotY) + context.drawItemInSlot(textRenderer, stack, slotX, slotY) } else { - context.fill(x, y, x + 18, y + 18, 0x40808080.toInt()) + val slot = slots[index] + slot.x = slotX - slotOffset.x + slot.y = slotY - slotOffset.y } - context.drawItem(stack, x + 1, y + 1) - context.drawItemInSlot(MC.font, stack, x + 1, y + 1) } + return inv.rows * SLOT_SIZE + 4 + textRenderer.fontHeight } - override fun close() { - discardOldHandle() - isClosing = true - super.close() + fun getBounds(): List<Rectangle> { + return listOf( + Rectangle(measurements.x, + measurements.y, + measurements.overviewWidth, + measurements.overviewHeight), + Rectangle(measurements.playerX, + measurements.playerY, + PLAYER_WIDTH, + PLAYER_HEIGHT)) } } diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt new file mode 100644 index 0000000..22b612b --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/storageoverlay/StorageOverviewScreen.kt @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.inventory.storageoverlay + +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 moe.nea.firmament.util.MC +import moe.nea.firmament.util.toShedaniel + +class StorageOverviewScreen() : Screen(Text.empty()) { + companion object { + val emptyStorageSlotItems = listOf<Item>( + Blocks.RED_STAINED_GLASS_PANE.asItem(), + Blocks.BROWN_STAINED_GLASS_PANE.asItem(), + Items.GRAY_DYE + ) + val pageWidth get() = 19 * 9 + } + + val content = StorageOverlay.Data.data ?: StorageData() + var isClosing = false + + var scroll = 0 + var lastRenderedHeight = 0 + + override fun render(context: DrawContext, 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) + renderStoragePage(context, value, mouseX - offsetX, mouseY - offsetY) + context.matrices.pop() + } + } + + 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 totalHeight = -currentMaxHeight + content.storageInventories.onEachIndexed { index, (key, value) -> + val pageX = (index % StorageOverlay.config.rows) + if (pageX == 0) { + currentMaxHeight += StorageOverlay.config.padding + offsetY += currentMaxHeight + totalHeight += currentMaxHeight + currentMaxHeight = 0 + } + val xPosition = + width / 2 - (StorageOverlay.config.rows * (pageWidth + StorageOverlay.config.padding) - StorageOverlay.config.padding) / 2 + pageX * (pageWidth + StorageOverlay.config.padding) + onEach(Pair(key, value), xPosition, offsetY) + val height = getStorePageHeight(value) + currentMaxHeight = max(currentMaxHeight, height) + } + lastRenderedHeight = totalHeight + currentMaxHeight + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + layoutedForEach { (k, p), x, y -> + val rx = mouseX - x + val ry = mouseY - y + if (rx in (0.0..pageWidth.toDouble()) && ry in (0.0..getStorePageHeight(p).toDouble())) { + close() + StorageOverlay.lastStorageOverlay = this + k.navigateTo() + return true + } + } + return super.mouseClicked(mouseX, mouseY, button) + } + + fun getStorePageHeight(page: StorageData.StorageInventory): Int { + return page.inventory?.rows?.let { it * 19 + MC.font.fontHeight + 2 } ?: 60 + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + scroll = + (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toInt() + .coerceAtMost(lastRenderedHeight - height + 2 * StorageOverlay.config.margin).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) + 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) + return + } + + for ((index, stack) in inventory.stacks.withIndex()) { + val x = (index % 9) * 19 + val y = (index / 9) * 19 + MC.font.fontHeight + 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.drawItemInSlot(MC.font, stack, x + 1, y + 1) + } + } + + override fun close() { + isClosing = true + super.close() + } +} diff --git a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt index a916b3e..85efd15 100644 --- a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt +++ b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt @@ -19,20 +19,21 @@ import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry import me.shedaniel.rei.api.common.entry.EntryStack import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.item.ItemStack +import net.minecraft.text.Text +import net.minecraft.util.Identifier import moe.nea.firmament.events.HandledScreenPushREIEvent import moe.nea.firmament.features.inventory.CraftingOverlay +import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen import moe.nea.firmament.rei.recipes.SBCraftingRecipe import moe.nea.firmament.rei.recipes.SBForgeRecipe +import moe.nea.firmament.rei.recipes.SBMobDropRecipe import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.skyblockId import moe.nea.firmament.util.unformattedString -import net.minecraft.client.gui.screen.ingame.GenericContainerScreen -import net.minecraft.client.gui.screen.ingame.HandledScreen -import net.minecraft.item.ItemStack -import net.minecraft.text.Text -import net.minecraft.util.Identifier -import moe.nea.firmament.rei.recipes.SBMobDropRecipe class FirmamentReiPlugin : REIClientPlugin { @@ -44,6 +45,7 @@ class FirmamentReiPlugin : REIClientPlugin { val SKYBLOCK_ITEM_TYPE_ID = Identifier.of("firmament", "skyblockitems") } + override fun registerTransferHandlers(registry: TransferHandlerRegistry) { registry.register(TransferHandler { context -> val screen = context.containerScreen @@ -69,6 +71,7 @@ class FirmamentReiPlugin : REIClientPlugin { override fun registerExclusionZones(zones: ExclusionZones) { zones.register(HandledScreen::class.java) { HandledScreenPushREIEvent.publish(HandledScreenPushREIEvent(it)).rectangles } + zones.register(StorageOverlayScreen::class.java) { it.getBounds() } } override fun registerDisplays(registry: DisplayRegistry) { @@ -80,7 +83,8 @@ class FirmamentReiPlugin : REIClientPlugin { SBForgeRecipe.Category.categoryIdentifier, SkyblockForgeRecipeDynamicGenerator ) - registry.registerDisplayGenerator(SBMobDropRecipe.Category.categoryIdentifier, SkyblockMobDropRecipeDynamicGenerator) + registry.registerDisplayGenerator(SBMobDropRecipe.Category.categoryIdentifier, + SkyblockMobDropRecipeDynamicGenerator) } override fun registerCollapsibleEntries(registry: CollapsibleEntryRegistry) { diff --git a/src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt b/src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt new file mode 100644 index 0000000..cf290af --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/customgui/CoordRememberingSlot.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util.customgui + +import net.minecraft.screen.slot.Slot + +interface CoordRememberingSlot { + fun rememberCoords_firmament() + fun restoreCoords_firmament() + fun getOriginalX_firmament(): Int + fun getOriginalY_firmament(): Int +} + +val Slot.originalX get() = (this as CoordRememberingSlot).getOriginalX_firmament() +val Slot.originalY get() = (this as CoordRememberingSlot).getOriginalY_firmament() diff --git a/src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt b/src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt new file mode 100644 index 0000000..40c9ade --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/customgui/CustomGui.kt @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util.customgui + +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.DrawContext +import net.minecraft.screen.slot.Slot +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HandledScreenPushREIEvent + +abstract class CustomGui { + + abstract fun getBounds(): List<Rectangle> + + open fun moveSlot(slot: Slot) { + // TODO: return a Pair maybe? worth an investigation + } + + companion object { + @Subscribe + fun onExclusionZone(event: HandledScreenPushREIEvent) { + val customGui = event.screen.customGui ?: return + event.rectangles.addAll(customGui.getBounds()) + } + } + + open fun render( + drawContext: DrawContext, + delta: Float, + mouseX: Int, + mouseY: Int + ) { + } + + open fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean { + return false + } + + open fun afterSlotRender(context: DrawContext, slot: Slot) {} + open fun beforeSlotRender(context: DrawContext, slot: Slot) {} + open fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean { + return false + } + + open fun isClickOutsideBounds(mouseX: Double, mouseY: Double): Boolean { + return getBounds().none { it.contains(mouseX, mouseY) } + } + + open fun isPointWithinBounds( + x: Int, + y: Int, + width: Int, + height: Int, + pointX: Double, + pointY: Double, + ): Boolean { + return getBounds().any { it.contains(pointX, pointY) } && + Rectangle(x, y, width, height).contains(pointX, pointY) + } + + open fun isPointOverSlot(slot: Slot, xOffset: Int, yOffset: Int, pointX: Double, pointY: Double): Boolean { + return isPointWithinBounds(slot.x + xOffset, slot.y + yOffset, 16, 16, pointX, pointY) + } + + open fun onInit() {} + open fun shouldDrawForeground(): Boolean { + return true + } + + open fun onVoluntaryExit(): Boolean { + return true + } +} diff --git a/src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt b/src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt new file mode 100644 index 0000000..54eb50d --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/customgui/HasCustomGui.kt @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util.customgui + +import net.minecraft.client.gui.screen.ingame.HandledScreen + +@Suppress("FunctionName") +interface HasCustomGui { + fun getCustomGui_Firmament(): CustomGui? + fun setCustomGui_Firmament(gui: CustomGui?) +} + +var <T : HandledScreen<*>> T.customGui: CustomGui? + get() = (this as HasCustomGui).getCustomGui_Firmament() + set(value) { + (this as HasCustomGui).setCustomGui_Firmament(value) + } + |