Add edit backpacks button to /firm storage

This commit is contained in:
Linnea Gräf
2024-10-16 19:24:24 +02:00
parent 854ec336cc
commit 7de0e8e7e0
11 changed files with 473 additions and 301 deletions

View File

@@ -1,7 +1,9 @@
@file:OptIn(ExperimentalContracts::class)
package moe.nea.firmament.features.inventory.storageoverlay 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.Screen
import net.minecraft.client.gui.screen.ingame.GenericContainerScreen import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
import net.minecraft.screen.GenericContainerScreenHandler import net.minecraft.screen.GenericContainerScreenHandler
@@ -39,6 +41,9 @@ sealed interface StorageBackingHandle {
* selection screen. * selection screen.
*/ */
fun fromScreen(screen: Screen?): StorageBackingHandle? { fun fromScreen(screen: Screen?): StorageBackingHandle? {
contract {
returnsNotNull() implies (screen != null)
}
if (screen == null) return null if (screen == null) return null
if (screen !is GenericContainerScreen) return null if (screen !is GenericContainerScreen) return null
val title = screen.title.unformattedString val title = screen.title.unformattedString

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.features.inventory.storageoverlay package moe.nea.firmament.features.inventory.storageoverlay
import java.util.SortedMap import java.util.SortedMap
@@ -22,133 +20,134 @@ import moe.nea.firmament.util.data.ProfileSpecificDataHolder
object StorageOverlay : FirmamentFeature { object StorageOverlay : FirmamentFeature {
object Data : ProfileSpecificDataHolder<StorageData>(serializer(), "storage-data", ::StorageData) object Data : ProfileSpecificDataHolder<StorageData>(serializer(), "storage-data", ::StorageData)
override val identifier: String override val identifier: String
get() = "storage-overlay" get() = "storage-overlay"
object TConfig : ManagedConfig(identifier, Category.INVENTORY) { object TConfig : ManagedConfig(identifier, Category.INVENTORY) {
val alwaysReplace by toggle("always-replace") { true } val alwaysReplace by toggle("always-replace") { true }
val columns by integer("rows", 1, 10) { 3 } val columns by integer("rows", 1, 10) { 3 }
val scrollSpeed by integer("scroll-speed", 1, 50) { 10 } val scrollSpeed by integer("scroll-speed", 1, 50) { 10 }
val inverseScroll by toggle("inverse-scroll") { false } val inverseScroll by toggle("inverse-scroll") { false }
val padding by integer("padding", 1, 20) { 5 } val padding by integer("padding", 1, 20) { 5 }
val margin by integer("margin", 1, 60) { 20 } val margin by integer("margin", 1, 60) { 20 }
} }
fun adjustScrollSpeed(amount: Double): Double { fun adjustScrollSpeed(amount: Double): Double {
return amount * TConfig.scrollSpeed * (if (TConfig.inverseScroll) 1 else -1) return amount * TConfig.scrollSpeed * (if (TConfig.inverseScroll) 1 else -1)
} }
override val config: TConfig override val config: TConfig
get() = TConfig get() = TConfig
var lastStorageOverlay: StorageOverviewScreen? = null var lastStorageOverlay: StorageOverviewScreen? = null
var skipNextStorageOverlayBackflip = false var skipNextStorageOverlayBackflip = false
var currentHandler: StorageBackingHandle? = null var currentHandler: StorageBackingHandle? = null
@Subscribe @Subscribe
fun onTick(event: TickEvent) { fun onTick(event: TickEvent) {
rememberContent(currentHandler ?: return) rememberContent(currentHandler ?: return)
} }
@Subscribe @Subscribe
fun onClick(event: SlotClickEvent) { fun onClick(event: SlotClickEvent) {
if (lastStorageOverlay != null && event.slot.inventory !is PlayerInventory && event.slot.index < 9 if (lastStorageOverlay != null && event.slot.inventory !is PlayerInventory && event.slot.index < 9
&& event.stack.item != Items.BLACK_STAINED_GLASS_PANE && event.stack.item != Items.BLACK_STAINED_GLASS_PANE
) { ) {
skipNextStorageOverlayBackflip = true skipNextStorageOverlayBackflip = true
} }
} }
@Subscribe @Subscribe
fun onScreenChange(it: ScreenChangeEvent) { fun onScreenChange(it: ScreenChangeEvent) {
if (it.old == null && it.new == null) return if (it.old == null && it.new == null) return
val storageOverlayScreen = it.old as? StorageOverlayScreen val storageOverlayScreen = it.old as? StorageOverlayScreen
?: ((it.old as? HandledScreen<*>)?.customGui as? StorageOverlayCustom)?.overview ?: ((it.old as? HandledScreen<*>)?.customGui as? StorageOverlayCustom)?.overview
var storageOverviewScreen = it.old as? StorageOverviewScreen var storageOverviewScreen = it.old as? StorageOverviewScreen
val screen = it.new as? GenericContainerScreen val screen = it.new as? GenericContainerScreen
val oldHandler = currentHandler val oldHandler = currentHandler
currentHandler = StorageBackingHandle.fromScreen(screen) currentHandler = StorageBackingHandle.fromScreen(screen)
rememberContent(currentHandler) rememberContent(currentHandler)
if (storageOverviewScreen != null && oldHandler is StorageBackingHandle.HasBackingScreen) { if (storageOverviewScreen != null && oldHandler is StorageBackingHandle.HasBackingScreen) {
val player = MC.player val player = MC.player
assert(player != null) assert(player != null)
player?.networkHandler?.sendPacket(CloseHandledScreenC2SPacket(oldHandler.handler.syncId)) player?.networkHandler?.sendPacket(CloseHandledScreenC2SPacket(oldHandler.handler.syncId))
if (player?.currentScreenHandler === oldHandler.handler) { if (player?.currentScreenHandler === oldHandler.handler) {
player.currentScreenHandler = player.playerScreenHandler player.currentScreenHandler = player.playerScreenHandler
} }
} }
storageOverviewScreen = storageOverviewScreen ?: lastStorageOverlay storageOverviewScreen = storageOverviewScreen ?: lastStorageOverlay
if (it.new == null && storageOverlayScreen != null && !storageOverlayScreen.isExiting) { if (it.new == null && storageOverlayScreen != null && !storageOverlayScreen.isExiting) {
it.overrideScreen = storageOverlayScreen it.overrideScreen = storageOverlayScreen
return return
} }
if (storageOverviewScreen != null if (storageOverviewScreen != null
&& !storageOverviewScreen.isClosing && !storageOverviewScreen.isClosing
&& (currentHandler is StorageBackingHandle.Overview || currentHandler == null) && (currentHandler is StorageBackingHandle.Overview || currentHandler == null)
) { ) {
if (skipNextStorageOverlayBackflip) { if (skipNextStorageOverlayBackflip) {
skipNextStorageOverlayBackflip = false skipNextStorageOverlayBackflip = false
} else { } else {
it.overrideScreen = storageOverviewScreen it.overrideScreen = storageOverviewScreen
lastStorageOverlay = null lastStorageOverlay = null
} }
return return
} }
screen ?: return screen ?: return
screen.customGui = StorageOverlayCustom( if (storageOverlayScreen?.isExiting == true) return
currentHandler ?: return, screen.customGui = StorageOverlayCustom(
screen, currentHandler ?: return,
storageOverlayScreen ?: (if (TConfig.alwaysReplace) StorageOverlayScreen() else return)) screen,
} storageOverlayScreen ?: (if (TConfig.alwaysReplace) StorageOverlayScreen() else return))
}
fun rememberContent(handler: StorageBackingHandle?) { fun rememberContent(handler: StorageBackingHandle?) {
handler ?: return handler ?: return
// TODO: Make all of these functions work on deltas / updates instead of the entire contents // TODO: Make all of these functions work on deltas / updates instead of the entire contents
val data = Data.data?.storageInventories ?: return val data = Data.data?.storageInventories ?: return
when (handler) { when (handler) {
is StorageBackingHandle.Overview -> rememberStorageOverview(handler, data) is StorageBackingHandle.Overview -> rememberStorageOverview(handler, data)
is StorageBackingHandle.Page -> rememberPage(handler, data) is StorageBackingHandle.Page -> rememberPage(handler, data)
} }
Data.markDirty() Data.markDirty()
} }
private fun rememberStorageOverview( private fun rememberStorageOverview(
handler: StorageBackingHandle.Overview, handler: StorageBackingHandle.Overview,
data: SortedMap<StoragePageSlot, StorageData.StorageInventory> data: SortedMap<StoragePageSlot, StorageData.StorageInventory>
) { ) {
for ((index, stack) in handler.handler.stacks.withIndex()) { for ((index, stack) in handler.handler.stacks.withIndex()) {
// Ignore unloaded item stacks // Ignore unloaded item stacks
if (stack.isEmpty) continue if (stack.isEmpty) continue
val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue val slot = StoragePageSlot.fromOverviewSlotIndex(index) ?: continue
val isEmpty = stack.item in StorageOverviewScreen.emptyStorageSlotItems val isEmpty = stack.item in StorageOverviewScreen.emptyStorageSlotItems
if (slot in data) { if (slot in data) {
if (isEmpty) if (isEmpty)
data.remove(slot) data.remove(slot)
continue continue
} }
if (!isEmpty) { if (!isEmpty) {
data[slot] = StorageData.StorageInventory(slot.defaultName(), slot, null) data[slot] = StorageData.StorageInventory(slot.defaultName(), slot, null)
} }
} }
} }
private fun rememberPage( private fun rememberPage(
handler: StorageBackingHandle.Page, handler: StorageBackingHandle.Page,
data: SortedMap<StoragePageSlot, StorageData.StorageInventory> data: SortedMap<StoragePageSlot, StorageData.StorageInventory>
) { ) {
// TODO: FIXME: FIXME NOW: Definitely don't copy all of this every tick into persistence // TODO: FIXME: FIXME NOW: Definitely don't copy all of this every tick into persistence
val newStacks = val newStacks =
VirtualInventory(handler.handler.stacks.take(handler.handler.rows * 9).drop(9).map { it.copy() }) VirtualInventory(handler.handler.stacks.take(handler.handler.rows * 9).drop(9).map { it.copy() })
data.compute(handler.storagePageSlot) { slot, existingInventory -> data.compute(handler.storagePageSlot) { slot, existingInventory ->
(existingInventory ?: StorageData.StorageInventory( (existingInventory ?: StorageData.StorageInventory(
slot.defaultName(), slot.defaultName(),
slot, slot,
null null
)).also { )).also {
it.inventory = newStacks it.inventory = newStacks
} }
} }
} }
} }

View File

@@ -80,6 +80,7 @@ class StorageOverlayCustom(
screen.screenHandler.slots.take(screen.screenHandler.rows * 9).drop(9), screen.screenHandler.slots.take(screen.screenHandler.rows * 9).drop(9),
Point((screen as AccessorHandledScreen).x_Firmament, screen.y_Firmament)) Point((screen as AccessorHandledScreen).x_Firmament, screen.y_Firmament))
overview.drawScrollBar(drawContext) overview.drawScrollBar(drawContext)
overview.drawControls(drawContext, mouseX, mouseY)
} }
override fun moveSlot(slot: Slot) { override fun moveSlot(slot: Slot) {

View File

@@ -1,14 +1,26 @@
package moe.nea.firmament.features.inventory.storageoverlay 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.Point
import me.shedaniel.math.Rectangle import me.shedaniel.math.Rectangle
import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.Slot
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.util.Identifier 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.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.assertTrueOr
import moe.nea.firmament.util.customgui.customGui
class StorageOverlayScreen : Screen(Text.literal("")) { class StorageOverlayScreen : Screen(Text.literal("")) {
@@ -24,6 +36,9 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
val MAIN_INVENTORY_Y = 9 val MAIN_INVENTORY_Y = 9
val SCROLL_BAR_WIDTH = 8 val SCROLL_BAR_WIDTH = 8
val SCROLL_BAR_HEIGHT = 16 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 var isExiting: Boolean = false
@@ -39,6 +54,8 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
val y = height / 2 - (overviewHeight + PLAYER_HEIGHT) / 2 val y = height / 2 - (overviewHeight + PLAYER_HEIGHT) / 2
val playerX = width / 2 - PLAYER_WIDTH / 2 val playerX = width / 2 - PLAYER_WIDTH / 2
val playerY = y + overviewHeight - PLAYER_Y_INSET val playerY = y + overviewHeight - PLAYER_Y_INSET
val controlX = x - CONTROL_WIDTH
val controlY = y + overviewHeight / 2 - CONTROL_HEIGHT / 2
val totalWidth = overviewWidth val totalWidth = overviewWidth
val totalHeight = overviewHeight - PLAYER_Y_INSET + PLAYER_HEIGHT 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 slotRowSprite = Identifier.of("firmament:storageoverlay/storage_row")
val scrollbarBackground = Identifier.of("firmament:storageoverlay/scroll_bar_background") val scrollbarBackground = Identifier.of("firmament:storageoverlay/scroll_bar_background")
val scrollbarKnob = Identifier.of("firmament:storageoverlay/scroll_bar_knob") val scrollbarKnob = Identifier.of("firmament:storageoverlay/scroll_bar_knob")
val controllerBackground = Identifier.of("firmament:storageoverlay/storage_controls")
override fun close() { override fun close() {
isExiting = true isExiting = true
@@ -86,6 +104,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
drawPages(context, mouseX, mouseY, delta, null, null, Point()) drawPages(context, mouseX, mouseY, delta, null, null, Point())
drawScrollBar(context) drawScrollBar(context)
drawPlayerInventory(context, mouseX, mouseY, delta) drawPlayerInventory(context, mouseX, mouseY, delta)
drawControls(context, mouseX, mouseY)
} }
fun getScrollbarPercentage(): Float { 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) { fun drawBackgrounds(context: DrawContext) {
context.drawGuiTexture(upperBackgroundSprite, context.drawGuiTexture(upperBackgroundSprite,
measurements.x, measurements.x,
@@ -182,7 +234,10 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
context.disableScissor() 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 { override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
return mouseClicked(mouseX, mouseY, button, null) return mouseClicked(mouseX, mouseY, button, null)
@@ -193,6 +248,12 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
knobGrabbed = false knobGrabbed = false
return true 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) return super.mouseReleased(mouseX, mouseY, button)
} }
@@ -226,6 +287,12 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
knobGrabbed = true knobGrabbed = true
return 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 return false
} }
@@ -309,6 +376,10 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
Rectangle(measurements.playerX, Rectangle(measurements.playerX,
measurements.playerY, measurements.playerY,
PLAYER_WIDTH, PLAYER_WIDTH,
PLAYER_HEIGHT)) PLAYER_HEIGHT),
Rectangle(measurements.controlX,
measurements.controlY,
CONTROL_WIDTH,
CONTROL_HEIGHT))
} }
} }

View File

@@ -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) {
}
}

View File

@@ -1,12 +1,15 @@
package moe.nea.firmament.util package moe.nea.firmament.util
import io.github.notenoughupdates.moulconfig.common.IMinecraft
import io.github.notenoughupdates.moulconfig.common.MyResourceLocation import io.github.notenoughupdates.moulconfig.common.MyResourceLocation
import io.github.notenoughupdates.moulconfig.gui.CloseEventListener 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.GuiComponentWrapper
import io.github.notenoughupdates.moulconfig.gui.GuiContext 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.observer.GetSetter
import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
import io.github.notenoughupdates.moulconfig.xml.ChildCount import io.github.notenoughupdates.moulconfig.xml.ChildCount
import io.github.notenoughupdates.moulconfig.xml.XMLContext import io.github.notenoughupdates.moulconfig.xml.XMLContext
import io.github.notenoughupdates.moulconfig.xml.XMLGuiLoader import io.github.notenoughupdates.moulconfig.xml.XMLGuiLoader
@@ -19,6 +22,7 @@ import me.shedaniel.math.Color
import org.w3c.dom.Element import org.w3c.dom.Element
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.screen.Screen
import moe.nea.firmament.gui.BarComponent import moe.nea.firmament.gui.BarComponent
import moe.nea.firmament.gui.FirmButtonComponent 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.FixedComponent
import moe.nea.firmament.gui.ImageComponent import moe.nea.firmament.gui.ImageComponent
import moe.nea.firmament.gui.TickComponent import moe.nea.firmament.gui.TickComponent
import moe.nea.firmament.util.render.isUntranslatedGuiDrawContext
object MoulConfigUtils { object MoulConfigUtils {
val firmUrl = "http://firmament.nea.moe/moulconfig" val firmUrl = "http://firmament.nea.moe/moulconfig"
val universe = XMLUniverse.getDefaultUniverse().also { uni -> val universe = XMLUniverse.getDefaultUniverse().also { uni ->
uni.registerMapper(java.awt.Color::class.java) { uni.registerMapper(java.awt.Color::class.java) {
if (it.startsWith("#")) { if (it.startsWith("#")) {
val hexString = it.substring(1) val hexString = it.substring(1)
val hex = hexString.toInt(16) val hex = hexString.toInt(16)
if (hexString.length == 6) { if (hexString.length == 6) {
return@registerMapper java.awt.Color(hex) return@registerMapper java.awt.Color(hex)
} }
if (hexString.length == 8) { if (hexString.length == 8) {
return@registerMapper java.awt.Color(hex, true) return@registerMapper java.awt.Color(hex, true)
} }
error("Hexcolor $it needs to be exactly 6 or 8 hex digits long") error("Hexcolor $it needs to be exactly 6 or 8 hex digits long")
} }
return@registerMapper java.awt.Color(it.toInt(), true) return@registerMapper java.awt.Color(it.toInt(), true)
} }
uni.registerMapper(Color::class.java) { uni.registerMapper(Color::class.java) {
val color = uni.mapXMLObject(it, java.awt.Color::class.java) val color = uni.mapXMLObject(it, java.awt.Color::class.java)
Color.ofRGBA(color.red, color.green, color.blue, color.alpha) Color.ofRGBA(color.red, color.green, color.blue, color.alpha)
} }
uni.registerLoader(object : XMLGuiLoader.Basic<BarComponent> { uni.registerLoader(object : XMLGuiLoader.Basic<BarComponent> {
override fun getName(): QName { override fun getName(): QName {
return QName(firmUrl, "Bar") return QName(firmUrl, "Bar")
} }
override fun createInstance(context: XMLContext<*>, element: Element): BarComponent { override fun createInstance(context: XMLContext<*>, element: Element): BarComponent {
return BarComponent( return BarComponent(
context.getPropertyFromAttribute(element, QName("progress"), Double::class.java)!!, context.getPropertyFromAttribute(element, QName("progress"), Double::class.java)!!,
context.getPropertyFromAttribute(element, QName("total"), Double::class.java)!!, context.getPropertyFromAttribute(element, QName("total"), Double::class.java)!!,
context.getPropertyFromAttribute(element, QName("fillColor"), Color::class.java)!!.get(), context.getPropertyFromAttribute(element, QName("fillColor"), Color::class.java)!!.get(),
context.getPropertyFromAttribute(element, QName("emptyColor"), Color::class.java)!!.get(), context.getPropertyFromAttribute(element, QName("emptyColor"), Color::class.java)!!.get(),
) )
} }
override fun getChildCount(): ChildCount { override fun getChildCount(): ChildCount {
return ChildCount.NONE return ChildCount.NONE
} }
override fun getAttributeNames(): Map<String, Boolean> { override fun getAttributeNames(): Map<String, Boolean> {
return mapOf("progress" to true, "total" to true, "emptyColor" to true, "fillColor" to true) return mapOf("progress" to true, "total" to true, "emptyColor" to true, "fillColor" to true)
} }
}) })
uni.registerLoader(object : XMLGuiLoader.Basic<FirmHoverComponent> { uni.registerLoader(object : XMLGuiLoader.Basic<FirmHoverComponent> {
override fun createInstance(context: XMLContext<*>, element: Element): FirmHoverComponent { override fun createInstance(context: XMLContext<*>, element: Element): FirmHoverComponent {
return FirmHoverComponent( return FirmHoverComponent(
context.getChildFragment(element), context.getChildFragment(element),
context.getPropertyFromAttribute(element, QName("lines"), List::class.java) as Supplier<List<String>>, context.getPropertyFromAttribute(element,
context.getPropertyFromAttribute(element, QName("delay"), Duration::class.java, 0.6.seconds), QName("lines"),
) List::class.java) as Supplier<List<String>>,
} context.getPropertyFromAttribute(element, QName("delay"), Duration::class.java, 0.6.seconds),
)
}
override fun getName(): QName { override fun getName(): QName {
return QName(firmUrl, "Hover") return QName(firmUrl, "Hover")
} }
override fun getChildCount(): ChildCount { override fun getChildCount(): ChildCount {
return ChildCount.ONE return ChildCount.ONE
} }
override fun getAttributeNames(): Map<String, Boolean> { override fun getAttributeNames(): Map<String, Boolean> {
return mapOf( return mapOf(
"lines" to true, "lines" to true,
"delay" to false, "delay" to false,
) )
} }
}) })
uni.registerLoader(object : XMLGuiLoader.Basic<FirmButtonComponent> { uni.registerLoader(object : XMLGuiLoader.Basic<FirmButtonComponent> {
override fun getName(): QName { override fun getName(): QName {
return QName(firmUrl, "Button") return QName(firmUrl, "Button")
} }
override fun createInstance(context: XMLContext<*>, element: Element): FirmButtonComponent { override fun createInstance(context: XMLContext<*>, element: Element): FirmButtonComponent {
return FirmButtonComponent( return FirmButtonComponent(
context.getChildFragment(element), context.getChildFragment(element),
context.getPropertyFromAttribute(element, QName("enabled"), Boolean::class.java) context.getPropertyFromAttribute(element, QName("enabled"), Boolean::class.java)
?: GetSetter.constant(true), ?: GetSetter.constant(true),
context.getPropertyFromAttribute(element, QName("noBackground"), Boolean::class.java, false), context.getPropertyFromAttribute(element, QName("noBackground"), Boolean::class.java, false),
context.getMethodFromAttribute(element, QName("onClick")), context.getMethodFromAttribute(element, QName("onClick")),
) )
} }
override fun getChildCount(): ChildCount { override fun getChildCount(): ChildCount {
return ChildCount.ONE return ChildCount.ONE
} }
override fun getAttributeNames(): Map<String, Boolean> { override fun getAttributeNames(): Map<String, Boolean> {
return mapOf("onClick" to true, "enabled" to false, "noBackground" to false) return mapOf("onClick" to true, "enabled" to false, "noBackground" to false)
} }
}) })
uni.registerLoader(object : XMLGuiLoader.Basic<ImageComponent> { uni.registerLoader(object : XMLGuiLoader.Basic<ImageComponent> {
override fun createInstance(context: XMLContext<*>, element: Element): ImageComponent { override fun createInstance(context: XMLContext<*>, element: Element): ImageComponent {
return ImageComponent( return ImageComponent(
context.getPropertyFromAttribute(element, QName("width"), Int::class.java)!!.get(), context.getPropertyFromAttribute(element, QName("width"), Int::class.java)!!.get(),
context.getPropertyFromAttribute(element, QName("height"), 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("resource"), MyResourceLocation::class.java)!!,
context.getPropertyFromAttribute(element, QName("u1"), Float::class.java, 0f), context.getPropertyFromAttribute(element, QName("u1"), Float::class.java, 0f),
context.getPropertyFromAttribute(element, QName("u2"), Float::class.java, 1f), context.getPropertyFromAttribute(element, QName("u2"), Float::class.java, 1f),
context.getPropertyFromAttribute(element, QName("v1"), Float::class.java, 0f), context.getPropertyFromAttribute(element, QName("v1"), Float::class.java, 0f),
context.getPropertyFromAttribute(element, QName("v2"), Float::class.java, 1f), context.getPropertyFromAttribute(element, QName("v2"), Float::class.java, 1f),
) )
} }
override fun getName(): QName { override fun getName(): QName {
return QName(firmUrl, "Image") return QName(firmUrl, "Image")
} }
override fun getChildCount(): ChildCount { override fun getChildCount(): ChildCount {
return ChildCount.NONE return ChildCount.NONE
} }
override fun getAttributeNames(): Map<String, Boolean> { override fun getAttributeNames(): Map<String, Boolean> {
return mapOf( return mapOf(
"width" to true, "height" to true, "width" to true, "height" to true,
"resource" to true, "resource" to true,
"u1" to false, "u1" to false,
"u2" to false, "u2" to false,
"v1" to false, "v1" to false,
"v2" to false, "v2" to false,
) )
} }
}) })
uni.registerLoader(object : XMLGuiLoader.Basic<TickComponent> { uni.registerLoader(object : XMLGuiLoader.Basic<TickComponent> {
override fun createInstance(context: XMLContext<*>, element: Element): TickComponent { override fun createInstance(context: XMLContext<*>, element: Element): TickComponent {
return TickComponent(context.getMethodFromAttribute(element, QName("tick"))) return TickComponent(context.getMethodFromAttribute(element, QName("tick")))
} }
override fun getName(): QName { override fun getName(): QName {
return QName(firmUrl, "Tick") return QName(firmUrl, "Tick")
} }
override fun getChildCount(): ChildCount { override fun getChildCount(): ChildCount {
return ChildCount.NONE return ChildCount.NONE
} }
override fun getAttributeNames(): Map<String, Boolean> { override fun getAttributeNames(): Map<String, Boolean> {
return mapOf("tick" to true) return mapOf("tick" to true)
} }
}) })
uni.registerLoader(object : XMLGuiLoader.Basic<FixedComponent> { uni.registerLoader(object : XMLGuiLoader.Basic<FixedComponent> {
override fun createInstance(context: XMLContext<*>, element: Element): FixedComponent { override fun createInstance(context: XMLContext<*>, element: Element): FixedComponent {
return FixedComponent( return FixedComponent(
context.getPropertyFromAttribute(element, QName("width"), Int::class.java) context.getPropertyFromAttribute(element, QName("width"), Int::class.java)
?: error("Requires width specified"), ?: error("Requires width specified"),
context.getPropertyFromAttribute(element, QName("height"), Int::class.java) context.getPropertyFromAttribute(element, QName("height"), Int::class.java)
?: error("Requires height specified"), ?: error("Requires height specified"),
context.getChildFragment(element) context.getChildFragment(element)
) )
} }
override fun getName(): QName { override fun getName(): QName {
return QName(firmUrl, "Fixed") return QName(firmUrl, "Fixed")
} }
override fun getChildCount(): ChildCount { override fun getChildCount(): ChildCount {
return ChildCount.ONE return ChildCount.ONE
} }
override fun getAttributeNames(): Map<String, Boolean> { override fun getAttributeNames(): Map<String, Boolean> {
return mapOf("width" to true, "height" to true) return mapOf("width" to true, "height" to true)
} }
}) })
} }
fun generateXSD( fun generateXSD(
file: File, file: File,
namespace: String namespace: String
) { ) {
val generator = XSDGenerator(universe, namespace) val generator = XSDGenerator(universe, namespace)
generator.writeAll() generator.writeAll()
generator.dumpToFile(file) generator.dumpToFile(file)
} }
@JvmStatic @JvmStatic
fun main(args: Array<out String>) { fun main(args: Array<out String>) {
generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS) generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS)
generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl) generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl)
File("wrapper.xsd").writeText(""" File("wrapper.xsd").writeText("""
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:import namespace="http://notenoughupdates.org/moulconfig" schemaLocation="MoulConfig.xsd"/> <xs:import namespace="http://notenoughupdates.org/moulconfig" schemaLocation="MoulConfig.xsd"/>
<xs:import namespace="http://firmament.nea.moe/moulconfig" schemaLocation="MoulConfig.Firmament.xsd"/> <xs:import namespace="http://firmament.nea.moe/moulconfig" schemaLocation="MoulConfig.Firmament.xsd"/>
</xs:schema> </xs:schema>
""".trimIndent()) """.trimIndent())
} }
fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen { fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen {
return object : GuiComponentWrapper(loadGui(name, bindTo)) { return object : GuiComponentWrapper(loadGui(name, bindTo)) {
override fun close() { override fun close() {
if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) { if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) {
client!!.setScreen(parent) client!!.setScreen(parent)
} }
} }
} }
} }
fun loadGui(name: String, bindTo: Any): GuiContext { // TODO: move this utility into moulconfig (also rework guicontext into an interface so i can make this mesh better into vanilla)
return GuiContext(universe.load(bindTo, MyResourceLocation("firmament", "gui/$name.xml"))) 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")))
}
} }

View File

@@ -1,11 +1,18 @@
@file:OptIn(ExperimentalContracts::class)
package moe.nea.firmament.util 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. * 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 { inline fun <T : Any> assertNotNullOr(obj: T?, message: String? = null, block: () -> T): T {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
if (message == null) if (message == null)
assert(obj != null) assert(obj != null)
else 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. * Less aggressive version of `require(condition)`, which fails in devenv but continues at runtime.
*/ */
inline fun assertTrueOr(condition: Boolean, block: () -> Unit) { inline fun assertTrueOr(condition: Boolean, block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
assert(condition) assert(condition)
if (!condition) block() if (!condition) block()
} }

View File

@@ -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
}

View File

@@ -1,12 +1,10 @@
package moe.nea.firmament.util package moe.nea.firmament.util
import java.math.BigInteger import java.math.BigInteger
import java.util.UUID import java.util.UUID
fun parseDashlessUUID(dashlessUuid: String): UUID { fun parseDashlessUUID(dashlessUuid: String): UUID {
val most = BigInteger(dashlessUuid.substring(0, 16), 16) val most = BigInteger(dashlessUuid.substring(0, 16), 16)
val least = BigInteger(dashlessUuid.substring(16, 32), 16) val least = BigInteger(dashlessUuid.substring(16, 32), 16)
return UUID(most.toLong(), least.toLong()) return UUID(most.toLong(), least.toLong())
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,10 @@
{
"gui": {
"scaling": {
"type": "nine_slice",
"width": 91,
"height": 184,
"border": 7
}
}
}