diff options
author | Linnea Gräf <nea@nea.moe> | 2024-10-16 19:24:24 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-10-16 19:24:24 +0200 |
commit | 7de0e8e7e09e3428c17ca9717c21c02469c31b76 (patch) | |
tree | f8c3d32a286521967a27877231c1ee74fa8c87ab /src/main/kotlin | |
parent | 854ec336cc6a0a3bb60f33acfac28ed45b491a15 (diff) | |
download | Firmament-7de0e8e7e09e3428c17ca9717c21c02469c31b76.tar.gz Firmament-7de0e8e7e09e3428c17ca9717c21c02469c31b76.tar.bz2 Firmament-7de0e8e7e09e3428c17ca9717c21c02469c31b76.zip |
Add edit backpacks button to /firm storage
Diffstat (limited to 'src/main/kotlin')
9 files changed, 497 insertions, 335 deletions
diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt index 1015578..5c1ac34 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt @@ -1,7 +1,9 @@ - +@file:OptIn(ExperimentalContracts::class) 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 @@ -39,6 +41,9 @@ sealed interface StorageBackingHandle { * selection screen. */ fun fromScreen(screen: Screen?): StorageBackingHandle? { + contract { + returnsNotNull() implies (screen != null) + } if (screen == null) return null if (screen !is GenericContainerScreen) return null val title = screen.title.unformattedString diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt index 333b0fb..7de0fff 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt @@ -1,5 +1,3 @@ - - package moe.nea.firmament.features.inventory.storageoverlay import java.util.SortedMap @@ -22,133 +20,134 @@ import moe.nea.firmament.util.data.ProfileSpecificDataHolder object StorageOverlay : FirmamentFeature { - object Data : ProfileSpecificDataHolder<StorageData>(serializer(), "storage-data", ::StorageData) - - override val identifier: String - get() = "storage-overlay" - - object TConfig : ManagedConfig(identifier, Category.INVENTORY) { - val alwaysReplace by toggle("always-replace") { true } - val columns by integer("rows", 1, 10) { 3 } - 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 } - } - - 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) - } - - @Subscribe - fun onClick(event: SlotClickEvent) { - if (lastStorageOverlay != null && event.slot.inventory !is PlayerInventory && event.slot.index < 9 - && event.stack.item != Items.BLACK_STAINED_GLASS_PANE - ) { - skipNextStorageOverlayBackflip = true - } - } - - @Subscribe - 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 - var storageOverviewScreen = it.old as? StorageOverviewScreen - val screen = it.new as? GenericContainerScreen - 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 - } - } - storageOverviewScreen = storageOverviewScreen ?: lastStorageOverlay - if (it.new == null && storageOverlayScreen != null && !storageOverlayScreen.isExiting) { - it.overrideScreen = storageOverlayScreen - return - } - if (storageOverviewScreen != null - && !storageOverviewScreen.isClosing - && (currentHandler is StorageBackingHandle.Overview || currentHandler == null) - ) { - if (skipNextStorageOverlayBackflip) { - skipNextStorageOverlayBackflip = false - } else { - it.overrideScreen = storageOverviewScreen - lastStorageOverlay = null - } - return - } - screen ?: return - screen.customGui = StorageOverlayCustom( - currentHandler ?: return, - screen, - 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 - 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()) { - // Ignore unloaded item stacks - if (stack.isEmpty) continue - val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue - val isEmpty = stack.item in StorageOverviewScreen.emptyStorageSlotItems - if (slot in data) { - if (isEmpty) - data.remove(slot) - continue - } - if (!isEmpty) { - data[slot] = StorageData.StorageInventory(slot.defaultName(), slot, null) - } - } - } - - 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() }) - data.compute(handler.storagePageSlot) { slot, existingInventory -> - (existingInventory ?: StorageData.StorageInventory( - slot.defaultName(), - slot, - null - )).also { - it.inventory = newStacks - } - } - } + object Data : ProfileSpecificDataHolder<StorageData>(serializer(), "storage-data", ::StorageData) + + override val identifier: String + get() = "storage-overlay" + + object TConfig : ManagedConfig(identifier, Category.INVENTORY) { + val alwaysReplace by toggle("always-replace") { true } + val columns by integer("rows", 1, 10) { 3 } + 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 } + } + + 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) + } + + @Subscribe + fun onClick(event: SlotClickEvent) { + if (lastStorageOverlay != null && event.slot.inventory !is PlayerInventory && event.slot.index < 9 + && event.stack.item != Items.BLACK_STAINED_GLASS_PANE + ) { + skipNextStorageOverlayBackflip = true + } + } + + @Subscribe + 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 + var storageOverviewScreen = it.old as? StorageOverviewScreen + val screen = it.new as? GenericContainerScreen + 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 + } + } + storageOverviewScreen = storageOverviewScreen ?: lastStorageOverlay + if (it.new == null && storageOverlayScreen != null && !storageOverlayScreen.isExiting) { + it.overrideScreen = storageOverlayScreen + return + } + if (storageOverviewScreen != null + && !storageOverviewScreen.isClosing + && (currentHandler is StorageBackingHandle.Overview || currentHandler == null) + ) { + if (skipNextStorageOverlayBackflip) { + skipNextStorageOverlayBackflip = false + } else { + it.overrideScreen = storageOverviewScreen + lastStorageOverlay = null + } + return + } + screen ?: return + if (storageOverlayScreen?.isExiting == true) return + screen.customGui = StorageOverlayCustom( + currentHandler ?: return, + screen, + 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 + 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()) { + // Ignore unloaded item stacks + if (stack.isEmpty) continue + val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue + val isEmpty = stack.item in StorageOverviewScreen.emptyStorageSlotItems + if (slot in data) { + if (isEmpty) + data.remove(slot) + continue + } + if (!isEmpty) { + data[slot] = StorageData.StorageInventory(slot.defaultName(), slot, null) + } + } + } + + 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() }) + data.compute(handler.storagePageSlot) { slot, existingInventory -> + (existingInventory ?: StorageData.StorageInventory( + slot.defaultName(), + slot, + null + )).also { + it.inventory = newStacks + } + } + } } diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt index fc3c4ef..2be798b 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt @@ -80,6 +80,7 @@ class StorageOverlayCustom( screen.screenHandler.slots.take(screen.screenHandler.rows * 9).drop(9), Point((screen as AccessorHandledScreen).x_Firmament, screen.y_Firmament)) overview.drawScrollBar(drawContext) + overview.drawControls(drawContext, mouseX, mouseY) } override fun moveSlot(slot: Slot) { diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt index 58c894a..f81315d 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt @@ -1,14 +1,26 @@ package moe.nea.firmament.features.inventory.storageoverlay +import io.github.notenoughupdates.moulconfig.gui.GuiContext +import io.github.notenoughupdates.moulconfig.gui.MouseEvent +import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent +import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent 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.screen.slot.Slot import net.minecraft.text.Text import net.minecraft.util.Identifier +import moe.nea.firmament.gui.EmptyComponent +import moe.nea.firmament.gui.FirmButtonComponent import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MoulConfigUtils.adopt +import moe.nea.firmament.util.MoulConfigUtils.clickMCComponentInPlace +import moe.nea.firmament.util.MoulConfigUtils.drawMCComponentInPlace import moe.nea.firmament.util.assertTrueOr +import moe.nea.firmament.util.customgui.customGui class StorageOverlayScreen : Screen(Text.literal("")) { @@ -24,6 +36,9 @@ class StorageOverlayScreen : Screen(Text.literal("")) { val MAIN_INVENTORY_Y = 9 val SCROLL_BAR_WIDTH = 8 val SCROLL_BAR_HEIGHT = 16 + val CONTROL_WIDTH = 70 + val CONTROL_BACKGROUND_WIDTH = CONTROL_WIDTH + PLAYER_Y_INSET + val CONTROL_HEIGHT = 100 } var isExiting: Boolean = false @@ -39,6 +54,8 @@ class StorageOverlayScreen : Screen(Text.literal("")) { 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 totalWidth = overviewWidth val totalHeight = overviewHeight - PLAYER_Y_INSET + PLAYER_HEIGHT } @@ -74,6 +91,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) { 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") override fun close() { isExiting = true @@ -86,6 +104,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) { drawPages(context, mouseX, mouseY, delta, null, null, Point()) drawScrollBar(context) drawPlayerInventory(context, mouseX, mouseY, delta) + drawControls(context, mouseX, mouseY) } fun getScrollbarPercentage(): Float { @@ -106,6 +125,39 @@ 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") + } + } + + val guiContext = GuiContext(EmptyComponent()) + private val knobStub = EmptyComponent() + val editButton = PanelComponent(ColumnComponent(FirmButtonComponent(TextComponent("Edit"), action = ::editPages)), + 8, PanelComponent.DefaultBackgroundRenderer.TRANSPARENT) + + init { + guiContext.adopt(editButton) + guiContext.adopt(knobStub) + } + + fun drawControls(context: DrawContext, mouseX: Int, mouseY: Int) { + context.drawGuiTexture( + controllerBackground, + measurements.controlX, + measurements.controlY, + CONTROL_BACKGROUND_WIDTH, CONTROL_HEIGHT) + context.drawMCComponentInPlace( + editButton, + measurements.controlX, measurements.controlY, + CONTROL_WIDTH, CONTROL_HEIGHT, + mouseX, mouseY) + } + fun drawBackgrounds(context: DrawContext) { context.drawGuiTexture(upperBackgroundSprite, measurements.x, @@ -182,7 +234,10 @@ class StorageOverlayScreen : Screen(Text.literal("")) { context.disableScissor() } - var knobGrabbed = false + + var knobGrabbed: Boolean + 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) @@ -193,6 +248,12 @@ class StorageOverlayScreen : Screen(Text.literal("")) { knobGrabbed = false return true } + if (clickMCComponentInPlace(editButton, + measurements.controlX, measurements.controlY, + CONTROL_WIDTH, CONTROL_HEIGHT, + mouseX.toInt(), mouseY.toInt(), + MouseEvent.Click(button, false)) + ) return true return super.mouseReleased(mouseX, mouseY, button) } @@ -226,6 +287,12 @@ class StorageOverlayScreen : Screen(Text.literal("")) { knobGrabbed = true return true } + if (clickMCComponentInPlace(editButton, + measurements.controlX, measurements.controlY, + CONTROL_WIDTH, CONTROL_HEIGHT, + mouseX.toInt(), mouseY.toInt(), + MouseEvent.Click(button, true)) + ) return true return false } @@ -309,6 +376,10 @@ class StorageOverlayScreen : Screen(Text.literal("")) { Rectangle(measurements.playerX, measurements.playerY, PLAYER_WIDTH, - PLAYER_HEIGHT)) + PLAYER_HEIGHT), + Rectangle(measurements.controlX, + measurements.controlY, + CONTROL_WIDTH, + CONTROL_HEIGHT)) } } diff --git a/src/main/kotlin/gui/EmptyComponent.kt b/src/main/kotlin/gui/EmptyComponent.kt new file mode 100644 index 0000000..13efa89 --- /dev/null +++ b/src/main/kotlin/gui/EmptyComponent.kt @@ -0,0 +1,17 @@ +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext + +class EmptyComponent : GuiComponent() { + override fun getWidth(): Int { + return 0 + } + + override fun getHeight(): Int { + return 0 + } + + override fun render(context: GuiImmediateContext) { + } +} diff --git a/src/main/kotlin/util/MoulConfigUtils.kt b/src/main/kotlin/util/MoulConfigUtils.kt index 00561d1..54528dd 100644 --- a/src/main/kotlin/util/MoulConfigUtils.kt +++ b/src/main/kotlin/util/MoulConfigUtils.kt @@ -1,12 +1,15 @@ - - package moe.nea.firmament.util +import io.github.notenoughupdates.moulconfig.common.IMinecraft import io.github.notenoughupdates.moulconfig.common.MyResourceLocation import io.github.notenoughupdates.moulconfig.gui.CloseEventListener +import io.github.notenoughupdates.moulconfig.gui.GuiComponent import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper import io.github.notenoughupdates.moulconfig.gui.GuiContext +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.MouseEvent import io.github.notenoughupdates.moulconfig.observer.GetSetter +import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext import io.github.notenoughupdates.moulconfig.xml.ChildCount import io.github.notenoughupdates.moulconfig.xml.XMLContext import io.github.notenoughupdates.moulconfig.xml.XMLGuiLoader @@ -19,6 +22,7 @@ import me.shedaniel.math.Color import org.w3c.dom.Element import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds +import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.screen.Screen import moe.nea.firmament.gui.BarComponent import moe.nea.firmament.gui.FirmButtonComponent @@ -26,205 +30,254 @@ import moe.nea.firmament.gui.FirmHoverComponent import moe.nea.firmament.gui.FixedComponent import moe.nea.firmament.gui.ImageComponent import moe.nea.firmament.gui.TickComponent +import moe.nea.firmament.util.render.isUntranslatedGuiDrawContext object MoulConfigUtils { - val firmUrl = "http://firmament.nea.moe/moulconfig" - val universe = XMLUniverse.getDefaultUniverse().also { uni -> - uni.registerMapper(java.awt.Color::class.java) { - if (it.startsWith("#")) { - val hexString = it.substring(1) - val hex = hexString.toInt(16) - if (hexString.length == 6) { - return@registerMapper java.awt.Color(hex) - } - if (hexString.length == 8) { - return@registerMapper java.awt.Color(hex, true) - } - error("Hexcolor $it needs to be exactly 6 or 8 hex digits long") - } - return@registerMapper java.awt.Color(it.toInt(), true) - } - uni.registerMapper(Color::class.java) { - val color = uni.mapXMLObject(it, java.awt.Color::class.java) - Color.ofRGBA(color.red, color.green, color.blue, color.alpha) - } - uni.registerLoader(object : XMLGuiLoader.Basic<BarComponent> { - override fun getName(): QName { - return QName(firmUrl, "Bar") - } - - override fun createInstance(context: XMLContext<*>, element: Element): BarComponent { - return BarComponent( - context.getPropertyFromAttribute(element, QName("progress"), Double::class.java)!!, - context.getPropertyFromAttribute(element, QName("total"), Double::class.java)!!, - context.getPropertyFromAttribute(element, QName("fillColor"), Color::class.java)!!.get(), - context.getPropertyFromAttribute(element, QName("emptyColor"), Color::class.java)!!.get(), - ) - } - - override fun getChildCount(): ChildCount { - return ChildCount.NONE - } - - override fun getAttributeNames(): Map<String, Boolean> { - return mapOf("progress" to true, "total" to true, "emptyColor" to true, "fillColor" to true) - } - }) - uni.registerLoader(object : XMLGuiLoader.Basic<FirmHoverComponent> { - override fun createInstance(context: XMLContext<*>, element: Element): FirmHoverComponent { - return FirmHoverComponent( - context.getChildFragment(element), - context.getPropertyFromAttribute(element, QName("lines"), List::class.java) as Supplier<List<String>>, - context.getPropertyFromAttribute(element, QName("delay"), Duration::class.java, 0.6.seconds), - ) - } - - override fun getName(): QName { - return QName(firmUrl, "Hover") - } - - override fun getChildCount(): ChildCount { - return ChildCount.ONE - } - - override fun getAttributeNames(): Map<String, Boolean> { - return mapOf( - "lines" to true, - "delay" to false, - ) - } - - }) - uni.registerLoader(object : XMLGuiLoader.Basic<FirmButtonComponent> { - override fun getName(): QName { - return QName(firmUrl, "Button") - } - - override fun createInstance(context: XMLContext<*>, element: Element): FirmButtonComponent { - return FirmButtonComponent( - context.getChildFragment(element), - context.getPropertyFromAttribute(element, QName("enabled"), Boolean::class.java) - ?: GetSetter.constant(true), - context.getPropertyFromAttribute(element, QName("noBackground"), Boolean::class.java, false), - context.getMethodFromAttribute(element, QName("onClick")), - ) - } - - override fun getChildCount(): ChildCount { - return ChildCount.ONE - } - - override fun getAttributeNames(): Map<String, Boolean> { - return mapOf("onClick" to true, "enabled" to false, "noBackground" to false) - } - }) - uni.registerLoader(object : XMLGuiLoader.Basic<ImageComponent> { - override fun createInstance(context: XMLContext<*>, element: Element): ImageComponent { - return ImageComponent( - context.getPropertyFromAttribute(element, QName("width"), Int::class.java)!!.get(), - context.getPropertyFromAttribute(element, QName("height"), Int::class.java)!!.get(), - context.getPropertyFromAttribute(element, QName("resource"), MyResourceLocation::class.java)!!, - context.getPropertyFromAttribute(element, QName("u1"), Float::class.java, 0f), - context.getPropertyFromAttribute(element, QName("u2"), Float::class.java, 1f), - context.getPropertyFromAttribute(element, QName("v1"), Float::class.java, 0f), - context.getPropertyFromAttribute(element, QName("v2"), Float::class.java, 1f), - ) - } - - override fun getName(): QName { - return QName(firmUrl, "Image") - } - - override fun getChildCount(): ChildCount { - return ChildCount.NONE - } - - override fun getAttributeNames(): Map<String, Boolean> { - return mapOf( - "width" to true, "height" to true, - "resource" to true, - "u1" to false, - "u2" to false, - "v1" to false, - "v2" to false, - ) - } - }) - uni.registerLoader(object : XMLGuiLoader.Basic<TickComponent> { - override fun createInstance(context: XMLContext<*>, element: Element): TickComponent { - return TickComponent(context.getMethodFromAttribute(element, QName("tick"))) - } - - override fun getName(): QName { - return QName(firmUrl, "Tick") - } - - override fun getChildCount(): ChildCount { - return ChildCount.NONE - } - - override fun getAttributeNames(): Map<String, Boolean> { - return mapOf("tick" to true) - } - }) - uni.registerLoader(object : XMLGuiLoader.Basic<FixedComponent> { - override fun createInstance(context: XMLContext<*>, element: Element): FixedComponent { - return FixedComponent( - context.getPropertyFromAttribute(element, QName("width"), Int::class.java) - ?: error("Requires width specified"), - context.getPropertyFromAttribute(element, QName("height"), Int::class.java) - ?: error("Requires height specified"), - context.getChildFragment(element) - ) - } - - override fun getName(): QName { - return QName(firmUrl, "Fixed") - } - - override fun getChildCount(): ChildCount { - return ChildCount.ONE - } - - override fun getAttributeNames(): Map<String, Boolean> { - return mapOf("width" to true, "height" to true) - } - }) - } - - fun generateXSD( - file: File, - namespace: String - ) { - val generator = XSDGenerator(universe, namespace) - generator.writeAll() - generator.dumpToFile(file) - } - - @JvmStatic - fun main(args: Array<out String>) { - generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS) - generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl) - File("wrapper.xsd").writeText(""" + val firmUrl = "http://firmament.nea.moe/moulconfig" + val universe = XMLUniverse.getDefaultUniverse().also { uni -> + uni.registerMapper(java.awt.Color::class.java) { + if (it.startsWith("#")) { + val hexString = it.substring(1) + val hex = hexString.toInt(16) + if (hexString.length == 6) { + return@registerMapper java.awt.Color(hex) + } + if (hexString.length == 8) { + return@registerMapper java.awt.Color(hex, true) + } + error("Hexcolor $it needs to be exactly 6 or 8 hex digits long") + } + return@registerMapper java.awt.Color(it.toInt(), true) + } + uni.registerMapper(Color::class.java) { + val color = uni.mapXMLObject(it, java.awt.Color::class.java) + Color.ofRGBA(color.red, color.green, color.blue, color.alpha) + } + uni.registerLoader(object : XMLGuiLoader.Basic<BarComponent> { + override fun getName(): QName { + return QName(firmUrl, "Bar") + } + + override fun createInstance(context: XMLContext<*>, element: Element): BarComponent { + return BarComponent( + context.getPropertyFromAttribute(element, QName("progress"), Double::class.java)!!, + context.getPropertyFromAttribute(element, QName("total"), Double::class.java)!!, + context.getPropertyFromAttribute(element, QName("fillColor"), Color::class.java)!!.get(), + context.getPropertyFromAttribute(element, QName("emptyColor"), Color::class.java)!!.get(), + ) + } + + override fun getChildCount(): ChildCount { + return ChildCount.NONE + } + + override fun getAttributeNames(): Map<String, Boolean> { + return mapOf("progress" to true, "total" to true, "emptyColor" to true, "fillColor" to true) + } + }) + uni.registerLoader(object : XMLGuiLoader.Basic<FirmHoverComponent> { + override fun createInstance(context: XMLContext<*>, element: Element): FirmHoverComponent { + return FirmHoverComponent( + context.getChildFragment(element), + context.getPropertyFromAttribute(element, + QName("lines"), + List::class.java) as Supplier<List<String>>, + context.getPropertyFromAttribute(element, QName("delay"), Duration::class.java, 0.6.seconds), + ) + } + + override fun getName(): QName { + return QName(firmUrl, "Hover") + } + + override fun getChildCount(): ChildCount { + return ChildCount.ONE + } + + override fun getAttributeNames(): Map<String, Boolean> { + return mapOf( + "lines" to true, + "delay" to false, + ) + } + + }) + uni.registerLoader(object : XMLGuiLoader.Basic<FirmButtonComponent> { + override fun getName(): QName { + return QName(firmUrl, "Button") + } + + override fun createInstance(context: XMLContext<*>, element: Element): FirmButtonComponent { + return FirmButtonComponent( + context.getChildFragment(element), + context.getPropertyFromAttribute(element, QName("enabled"), Boolean::class.java) + ?: GetSetter.constant(true), + context.getPropertyFromAttribute(element, QName("noBackground"), Boolean::class.java, false), + context.getMethodFromAttribute(element, QName("onClick")), + ) + } + + override fun getChildCount(): ChildCount { + return ChildCount.ONE + } + + override fun getAttributeNames(): Map<String, Boolean> { + return mapOf("onClick" to true, "enabled" to false, "noBackground" to false) + } + }) + uni.registerLoader(object : XMLGuiLoader.Basic<ImageComponent> { + override fun createInstance(context: XMLContext<*>, element: Element): ImageComponent { + return ImageComponent( + context.getPropertyFromAttribute(element, QName("width"), Int::class.java)!!.get(), + context.getPropertyFromAttribute(element, QName("height"), Int::class.java)!!.get(), + context.getPropertyFromAttribute(element, QName("resource"), MyResourceLocation::class.java)!!, + context.getPropertyFromAttribute(element, QName("u1"), Float::class.java, 0f), + context.getPropertyFromAttribute(element, QName("u2"), Float::class.java, 1f), + context.getPropertyFromAttribute(element, QName("v1"), Float::class.java, 0f), + context.getPropertyFromAttribute(element, QName("v2"), Float::class.java, 1f), + ) + } + + override fun getName(): QName { + return QName(firmUrl, "Image") + } + + override fun getChildCount(): ChildCount { + return ChildCount.NONE + } + + override fun getAttributeNames(): Map<String, Boolean> { + return mapOf( + "width" to true, "height" to true, + "resource" to true, + "u1" to false, + "u2" to false, + "v1" to false, + "v2" to false, + ) + } + }) + uni.registerLoader(object : XMLGuiLoader.Basic<TickComponent> { + override fun createInstance(context: XMLContext<*>, element: Element): TickComponent { + return TickComponent(context.getMethodFromAttribute(element, QName("tick"))) + } + + override fun getName(): QName { + return QName(firmUrl, "Tick") + } + + override fun getChildCount(): ChildCount { + return ChildCount.NONE + } + + override fun getAttributeNames(): Map<String, Boolean> { + return mapOf("tick" to true) + } + }) + uni.registerLoader(object : XMLGuiLoader.Basic<FixedComponent> { + override fun createInstance(context: XMLContext<*>, element: Element): FixedComponent { + return FixedComponent( + context.getPropertyFromAttribute(element, QName("width"), Int::class.java) + ?: error("Requires width specified"), + context.getPropertyFromAttribute(element, QName("height"), Int::class.java) + ?: error("Requires height specified"), + context.getChildFragment(element) + ) + } + + override fun getName(): QName { + return QName(firmUrl, "Fixed") + } + + override fun getChildCount(): ChildCount { + return ChildCount.ONE + } + + override fun getAttributeNames(): Map<String, Boolean> { + return mapOf("width" to true, "height" to true) + } + }) + } + + fun generateXSD( + file: File, + namespace: String + ) { + val generator = XSDGenerator(universe, namespace) + generator.writeAll() + generator.dumpToFile(file) + } + + @JvmStatic + fun main(args: Array<out String>) { + generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS) + generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl) + File("wrapper.xsd").writeText(""" <?xml version="1.0" encoding="UTF-8" ?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:import namespace="http://notenoughupdates.org/moulconfig" schemaLocation="MoulConfig.xsd"/> <xs:import namespace="http://firmament.nea.moe/moulconfig" schemaLocation="MoulConfig.Firmament.xsd"/> </xs:schema> """.trimIndent()) - } - - fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen { - return object : GuiComponentWrapper(loadGui(name, bindTo)) { - override fun close() { - if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) { - client!!.setScreen(parent) - } - } - } - } - - fun loadGui(name: String, bindTo: Any): GuiContext { - return GuiContext(universe.load(bindTo, MyResourceLocation("firmament", "gui/$name.xml"))) - } + } + + fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen { + return object : GuiComponentWrapper(loadGui(name, bindTo)) { + override fun close() { + if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) { + client!!.setScreen(parent) + } + } + } + } + + // TODO: move this utility into moulconfig (also rework guicontext into an interface so i can make this mesh better into vanilla) + fun GuiContext.adopt(element: GuiComponent) = element.foldRecursive(Unit, { comp, unit -> comp.context = this }) + + fun clickMCComponentInPlace( + component: GuiComponent, + x: Int, + y: Int, + w: Int, + h: Int, + mouseX: Int, mouseY: Int, + mouseEvent: MouseEvent + ): Boolean { + val immContext = createInPlaceFullContext(null, mouseX, mouseY) + return component.mouseEvent(mouseEvent, immContext.translated(x, y, w, h)) + } + + fun createInPlaceFullContext(drawContext: DrawContext?, mouseX: Int, mouseY: Int): GuiImmediateContext { + assert(drawContext?.isUntranslatedGuiDrawContext() != false) + val context = drawContext?.let(::ModernRenderContext) + ?: IMinecraft.instance.provideTopLevelRenderContext() + val immContext = GuiImmediateContext(context, + 0, 0, 0, 0, + mouseX, mouseY, + mouseX, mouseY, + mouseX.toFloat(), + mouseY.toFloat()) + return immContext + } + + fun DrawContext.drawMCComponentInPlace( + component: GuiComponent, + x: Int, + y: Int, + w: Int, + h: Int, + mouseX: Int, + mouseY: Int + ) { + val immContext = createInPlaceFullContext(this, mouseX, mouseY) + matrices.push() + matrices.translate(x.toFloat(), y.toFloat(), 0F) + component.render(immContext.translated(x, y, w, h)) + matrices.pop() + } + + + fun loadGui(name: String, bindTo: Any): GuiContext { + return GuiContext(universe.load(bindTo, MyResourceLocation("firmament", "gui/$name.xml"))) + } } diff --git a/src/main/kotlin/util/assertions.kt b/src/main/kotlin/util/assertions.kt index 6f2ed19..86982be 100644 --- a/src/main/kotlin/util/assertions.kt +++ b/src/main/kotlin/util/assertions.kt @@ -1,11 +1,18 @@ - +@file:OptIn(ExperimentalContracts::class) package moe.nea.firmament.util +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + /** * Less aggressive version of `require(obj != null)`, which fails in devenv but continues at runtime. */ inline fun <T : Any> assertNotNullOr(obj: T?, message: String? = null, block: () -> T): T { + contract { + callsInPlace(block, InvocationKind.AT_MOST_ONCE) + } if (message == null) assert(obj != null) else @@ -18,6 +25,9 @@ inline fun <T : Any> assertNotNullOr(obj: T?, message: String? = null, block: () * Less aggressive version of `require(condition)`, which fails in devenv but continues at runtime. */ inline fun assertTrueOr(condition: Boolean, block: () -> Unit) { + contract { + callsInPlace(block, InvocationKind.AT_MOST_ONCE) + } assert(condition) if (!condition) block() } diff --git a/src/main/kotlin/util/render/DrawContextExt.kt b/src/main/kotlin/util/render/DrawContextExt.kt new file mode 100644 index 0000000..48698b4 --- /dev/null +++ b/src/main/kotlin/util/render/DrawContextExt.kt @@ -0,0 +1,8 @@ +package moe.nea.firmament.util.render + +import org.joml.Matrix4f +import net.minecraft.client.gui.DrawContext + +fun DrawContext.isUntranslatedGuiDrawContext(): Boolean { + return (matrices.peek().positionMatrix.properties() and Matrix4f.PROPERTY_IDENTITY.toInt()) != 0 +} diff --git a/src/main/kotlin/util/uuid.kt b/src/main/kotlin/util/uuid.kt index 4aa0749..cccfdd2 100644 --- a/src/main/kotlin/util/uuid.kt +++ b/src/main/kotlin/util/uuid.kt @@ -1,12 +1,10 @@ - - package moe.nea.firmament.util import java.math.BigInteger import java.util.UUID fun parseDashlessUUID(dashlessUuid: String): UUID { - val most = BigInteger(dashlessUuid.substring(0, 16), 16) - val least = BigInteger(dashlessUuid.substring(16, 32), 16) - return UUID(most.toLong(), least.toLong()) + val most = BigInteger(dashlessUuid.substring(0, 16), 16) + val least = BigInteger(dashlessUuid.substring(16, 32), 16) + return UUID(most.toLong(), least.toLong()) } |