diff options
Diffstat (limited to 'src/main/kotlin/gui')
44 files changed, 1352 insertions, 1193 deletions
diff --git a/src/main/kotlin/gui/BarComponent.kt b/src/main/kotlin/gui/BarComponent.kt index b82c666..4c0a52d 100644 --- a/src/main/kotlin/gui/BarComponent.kt +++ b/src/main/kotlin/gui/BarComponent.kt @@ -1,16 +1,14 @@ package moe.nea.firmament.gui -import com.mojang.blaze3d.systems.RenderSystem import io.github.notenoughupdates.moulconfig.common.MyResourceLocation -import io.github.notenoughupdates.moulconfig.common.RenderContext import io.github.notenoughupdates.moulconfig.gui.GuiComponent import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext import io.github.notenoughupdates.moulconfig.observer.GetSetter -import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext +import io.github.notenoughupdates.moulconfig.platform.MoulConfigRenderContext import me.shedaniel.math.Color -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.render.RenderLayer -import net.minecraft.util.Identifier +import net.minecraft.client.renderer.RenderPipelines +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.resources.ResourceLocation import moe.nea.firmament.Firmament class BarComponent( @@ -27,13 +25,13 @@ class BarComponent( } data class Texture( - val identifier: Identifier, - val u1: Float, val v1: Float, - val u2: Float, val v2: Float, + val identifier: ResourceLocation, + val u1: Float, val v1: Float, + val u2: Float, val v2: Float, ) { - fun draw(context: DrawContext, x: Int, y: Int, width: Int, height: Int, color: Color) { - context.drawTexturedQuad( - RenderLayer::getGuiTextured, + fun draw(context: GuiGraphics, x: Int, y: Int, width: Int, height: Int, color: Color) { + context.innerBlit( + RenderPipelines.GUI_TEXTURED, identifier, x, y, x + width, x + height, u1, u2, v1, v2, @@ -51,13 +49,13 @@ class BarComponent( } private fun drawSection( - context: DrawContext, - texture: Texture, - x: Int, - y: Int, - width: Int, - sectionStart: Double, - sectionEnd: Double + context: GuiGraphics, + texture: Texture, + x: Int, + y: Int, + width: Int, + sectionStart: Double, + sectionEnd: Double ) { if (sectionEnd < progress.get() && width == 4) { texture.draw(context, x, y, 4, 8, fillColor) @@ -81,7 +79,7 @@ class BarComponent( } override fun render(context: GuiImmediateContext) { - val renderContext = (context.renderContext as ModernRenderContext).drawContext + val renderContext = (context.renderContext as MoulConfigRenderContext).drawContext var i = 0 val x = 0 val y = 0 @@ -104,20 +102,11 @@ class BarComponent( (context.width - 4) * total.get() / context.width, total.get() ) - RenderSystem.setShaderColor(1F, 1F, 1F, 1F) } } -fun Identifier.toMoulConfig(): MyResourceLocation { +fun ResourceLocation.toMoulConfig(): MyResourceLocation { return MyResourceLocation(this.namespace, this.path) } - -fun RenderContext.color(color: Color) { - color(color.red, color.green, color.blue, color.alpha) -} - -fun RenderContext.color(red: Int, green: Int, blue: Int, alpha: Int) { - color(red / 255f, green / 255f, blue / 255f, alpha / 255f) -} diff --git a/src/main/kotlin/gui/CheckboxComponent.kt b/src/main/kotlin/gui/CheckboxComponent.kt index 761c086..da3e5c8 100644 --- a/src/main/kotlin/gui/CheckboxComponent.kt +++ b/src/main/kotlin/gui/CheckboxComponent.kt @@ -4,8 +4,8 @@ import io.github.notenoughupdates.moulconfig.gui.GuiComponent 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 net.minecraft.client.render.RenderLayer +import io.github.notenoughupdates.moulconfig.platform.MoulConfigRenderContext +import net.minecraft.client.renderer.RenderPipelines import moe.nea.firmament.Firmament class CheckboxComponent<T>( @@ -25,11 +25,11 @@ class CheckboxComponent<T>( } override fun render(context: GuiImmediateContext) { - val ctx = (context.renderContext as ModernRenderContext).drawContext - ctx.drawGuiTexture( - RenderLayer::getGuiTextured, - if (isEnabled()) Firmament.identifier("firmament:widget/checkbox_checked") - else Firmament.identifier("firmament:widget/checkbox_unchecked"), + val ctx = (context.renderContext as MoulConfigRenderContext).drawContext + ctx.blitSprite( + RenderPipelines.GUI_TEXTURED, + if (isEnabled()) Firmament.identifier("widget/checkbox_checked") + else Firmament.identifier("widget/checkbox_unchecked"), 0, 0, 16, 16 ) @@ -43,6 +43,7 @@ class CheckboxComponent<T>( isClicking = false if (context.isHovered) state.set(value) + blur() return true } if (mouseEvent.mouseState && mouseEvent.mouseButton == 0 && context.isHovered) { diff --git a/src/main/kotlin/gui/FirmButtonComponent.kt b/src/main/kotlin/gui/FirmButtonComponent.kt index 82e5b05..1469c09 100644 --- a/src/main/kotlin/gui/FirmButtonComponent.kt +++ b/src/main/kotlin/gui/FirmButtonComponent.kt @@ -1,4 +1,3 @@ - package moe.nea.firmament.gui import io.github.notenoughupdates.moulconfig.common.MyResourceLocation @@ -11,71 +10,78 @@ import io.github.notenoughupdates.moulconfig.observer.GetSetter open class FirmButtonComponent( - child: GuiComponent, - val isEnabled: GetSetter<Boolean> = GetSetter.constant(true), - val noBackground: Boolean = false, - val action: Runnable, + child: GuiComponent, + val isEnabled: GetSetter<Boolean> = GetSetter.constant(true), + val noBackground: Boolean = false, + val action: (mouseButton: Int) -> Unit, ) : PanelComponent(child, if (noBackground) 0 else 2, DefaultBackgroundRenderer.TRANSPARENT) { - /* TODO: make use of vanillas built in nine slicer */ - val hoveredBg = - NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button_highlighted.png")) - .cornerSize(5) - .cornerUv(5 / 200F, 5 / 20F) - .mode(NinePatch.Mode.STRETCHING) - .build() - val unhoveredBg = NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button.png")) - .cornerSize(5) - .cornerUv(5 / 200F, 5 / 20F) - .mode(NinePatch.Mode.STRETCHING) - .build() - val disabledBg = - NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button_disabled.png")) - .cornerSize(5) - .cornerUv(5 / 200F, 5 / 20F) - .mode(NinePatch.Mode.STRETCHING) - .build() - val activeBg = NinePatch.builder(MyResourceLocation("firmament", "textures/gui/sprites/widget/button_active.png")) - .cornerSize(5) - .cornerUv(5 / 200F, 5 / 20F) - .mode(NinePatch.Mode.STRETCHING) - .build() - var isClicking = false - override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { - if (!isEnabled.get()) return false - if (isClicking) { - if (mouseEvent is MouseEvent.Click && !mouseEvent.mouseState && mouseEvent.mouseButton == 0) { - isClicking = false - if (context.isHovered) { - action.run() - } - return true - } - } - if (!context.isHovered) return false - if (mouseEvent !is MouseEvent.Click) return false - if (mouseEvent.mouseState && mouseEvent.mouseButton == 0) { - requestFocus() - isClicking = true - return true - } - return false - } + constructor( + child: GuiComponent, + isEnabled: GetSetter<Boolean> = GetSetter.constant(true), + noBackground: Boolean = false, + action: Runnable, + ) : this(child, isEnabled, noBackground, { action.run() }) + + /* TODO: make use of vanillas built in nine slicer */ + val hoveredBg = + NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button_highlighted.png")) + .cornerSize(5) + .cornerUv(5 / 200F, 5 / 20F) + .mode(NinePatch.Mode.STRETCHING) + .build() + val unhoveredBg = NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button.png")) + .cornerSize(5) + .cornerUv(5 / 200F, 5 / 20F) + .mode(NinePatch.Mode.STRETCHING) + .build() + val disabledBg = + NinePatch.builder(MyResourceLocation("minecraft", "textures/gui/sprites/widget/button_disabled.png")) + .cornerSize(5) + .cornerUv(5 / 200F, 5 / 20F) + .mode(NinePatch.Mode.STRETCHING) + .build() + val activeBg = NinePatch.builder(MyResourceLocation("firmament", "textures/gui/sprites/widget/button_active.png")) + .cornerSize(5) + .cornerUv(5 / 200F, 5 / 20F) + .mode(NinePatch.Mode.STRETCHING) + .build() + var isClicking = false + override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { + if (!isEnabled.get()) return false + if (isClicking) { + if (mouseEvent is MouseEvent.Click && !mouseEvent.mouseState) { + isClicking = false + if (context.isHovered) { + action.invoke(mouseEvent.mouseButton) + } + return true + } + } + if (!context.isHovered) return false + if (mouseEvent !is MouseEvent.Click) return false + if (mouseEvent.mouseState) { + requestFocus() + isClicking = true + return true + } + return false + } - open fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> = - if (!isEnabled.get()) disabledBg - else if (context.isHovered || isClicking) hoveredBg - else unhoveredBg + open fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> = + if (!isEnabled.get()) disabledBg + else if (context.isHovered || isClicking) hoveredBg + else unhoveredBg - override fun render(context: GuiImmediateContext) { - context.renderContext.pushMatrix() - if (!noBackground) - context.renderContext.drawNinePatch( - getBackground(context), - 0f, 0f, context.width, context.height - ) - context.renderContext.translate(insets.toFloat(), insets.toFloat(), 0f) - element.render(getChildContext(context)) - context.renderContext.popMatrix() - } + override fun render(context: GuiImmediateContext) { + context.renderContext.pushMatrix() + if (!noBackground) + context.renderContext.drawNinePatch( + getBackground(context), + 0f, 0f, context.width, context.height + ) + context.renderContext.translate(insets.toFloat(), insets.toFloat()) + element.render(getChildContext(context)) + context.renderContext.popMatrix() + } } diff --git a/src/main/kotlin/gui/FirmHoverComponent.kt b/src/main/kotlin/gui/FirmHoverComponent.kt index b1792ce..eed795a 100644 --- a/src/main/kotlin/gui/FirmHoverComponent.kt +++ b/src/main/kotlin/gui/FirmHoverComponent.kt @@ -1,5 +1,6 @@ package moe.nea.firmament.gui +import io.github.notenoughupdates.moulconfig.common.text.StructuredText import io.github.notenoughupdates.moulconfig.gui.GuiComponent import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent @@ -10,50 +11,51 @@ import kotlin.time.Duration import moe.nea.firmament.util.TimeMark class FirmHoverComponent( - val child: GuiComponent, - val hoverLines: Supplier<List<String>>, - val hoverDelay: Duration, + val child: GuiComponent, + val hoverLines: Supplier<List<String>>, + val hoverDelay: Duration, ) : GuiComponent() { - override fun getWidth(): Int { - return child.width - } - - override fun getHeight(): Int { - return child.height - } - - override fun <T : Any?> foldChildren( - initial: T, - visitor: BiFunction<GuiComponent, T, T> - ): T { - return visitor.apply(child, initial) - } - - override fun render(context: GuiImmediateContext) { - if (context.isHovered && (permaHover || lastMouseMove.passedTime() > hoverDelay)) { - context.renderContext.scheduleDrawTooltip(hoverLines.get()) - permaHover = true - } else { - permaHover = false - } - if (!context.isHovered) { - lastMouseMove = TimeMark.now() - } - child.render(context) - - } - - var permaHover = false - var lastMouseMove = TimeMark.farPast() - - override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { - if (mouseEvent is MouseEvent.Move) { - lastMouseMove = TimeMark.now() - } - return child.mouseEvent(mouseEvent, context) - } - - override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { - return child.keyboardEvent(event, context) - } + override fun getWidth(): Int { + return child.width + } + + override fun getHeight(): Int { + return child.height + } + + override fun <T : Any?> foldChildren( + initial: T, + visitor: BiFunction<GuiComponent, T, T> + ): T { + return visitor.apply(child, initial) + } + + override fun render(context: GuiImmediateContext) { + if (context.isHovered && (permaHover || lastMouseMove.passedTime() > hoverDelay)) { + context.renderContext.scheduleDrawTooltip(context.mouseX, context.mouseY, hoverLines.get() + .map { it -> StructuredText.of(it) }) + permaHover = true + } else { + permaHover = false + } + if (!context.isHovered) { + lastMouseMove = TimeMark.now() + } + child.render(context) + + } + + var permaHover = false + var lastMouseMove = TimeMark.farPast() + + override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { + if (mouseEvent is MouseEvent.Move) { + lastMouseMove = TimeMark.now() + } + return child.mouseEvent(mouseEvent, context) + } + + override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { + return child.keyboardEvent(event, context) + } } diff --git a/src/main/kotlin/gui/ImageComponent.kt b/src/main/kotlin/gui/ImageComponent.kt index bba7dee..695c0ed 100644 --- a/src/main/kotlin/gui/ImageComponent.kt +++ b/src/main/kotlin/gui/ImageComponent.kt @@ -6,28 +6,30 @@ import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext import java.util.function.Supplier class ImageComponent( - private val width: Int, - private val height: Int, - val resourceLocation: Supplier<MyResourceLocation>, - val u1: Float, - val u2: Float, - val v1: Float, - val v2: Float, + private val width: Int, + private val height: Int, + val resourceLocation: Supplier<MyResourceLocation>, + val u1: Float, + val u2: Float, + val v1: Float, + val v2: Float, ) : GuiComponent() { - override fun getWidth(): Int { - return width - } + override fun getWidth(): Int { + return width + } - override fun getHeight(): Int { - return height - } + override fun getHeight(): Int { + return height + } - override fun render(context: GuiImmediateContext) { - context.renderContext.bindTexture(resourceLocation.get()) - context.renderContext.drawTexturedRect( - 0f, 0f, - context.width.toFloat(), context.height.toFloat(), - u1, v1, u2, v2 - ) - } + override fun render(context: GuiImmediateContext) { + context.renderContext.drawComplexTexture( + resourceLocation.get(), + 0f, 0f, + context.width.toFloat(), context.height.toFloat(), + { + it.uv(u1, v1, u2, v2) + } + ) + } } diff --git a/src/main/kotlin/gui/config/AllConfigsGui.kt b/src/main/kotlin/gui/config/AllConfigsGui.kt index 73ff444..60711ca 100644 --- a/src/main/kotlin/gui/config/AllConfigsGui.kt +++ b/src/main/kotlin/gui/config/AllConfigsGui.kt @@ -2,11 +2,19 @@ package moe.nea.firmament.gui.config import io.github.notenoughupdates.moulconfig.observer.ObservableList import io.github.notenoughupdates.moulconfig.xml.Bind -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.Text +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.RestArgumentType +import moe.nea.firmament.commands.get +import moe.nea.firmament.commands.thenArgument +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.events.CommandEvent import moe.nea.firmament.util.MC import moe.nea.firmament.util.MoulConfigUtils import moe.nea.firmament.util.ScreenUtil.setScreenLater +import moe.nea.firmament.util.data.Config +import moe.nea.firmament.util.data.ManagedConfig object AllConfigsGui { // @@ -15,9 +23,11 @@ object AllConfigsGui { // RepoManager.Config // ) + FeatureManager.allFeatures.mapNotNull { it.config } + @Config object ConfigConfig : ManagedConfig("configconfig", Category.META) { val enableYacl by toggle("enable-yacl") { false } val enableMoulConfig by toggle("enable-moulconfig") { true } + val enableWideMC by toggle("wide-moulconfig") { false } } fun <T> List<T>.toObservableList(): ObservableList<T> = ObservableList(this) @@ -27,16 +37,16 @@ object AllConfigsGui { val configs = category.configs.map { EntryMapping(it) }.toObservableList() @Bind - fun name() = category.labelText.string + fun name() = category.labelText @Bind fun close() { - MC.screen?.close() + MC.screen?.onClose() } class EntryMapping(val config: ManagedConfig) { @Bind - fun name() = Text.translatable("firmament.config.${config.name}").string + fun name() = Component.translatable("firmament.config.${config.name}") @Bind fun openEditor() { @@ -53,7 +63,7 @@ object AllConfigsGui { class CategoryEntry(val category: ManagedConfig.Category) { @Bind - fun name() = category.labelText.string + fun name() = category.labelText @Bind fun open() { @@ -66,7 +76,7 @@ object AllConfigsGui { return MoulConfigUtils.loadScreen("config/main", CategoryView(), parent) } - fun makeScreen(parent: Screen? = null): Screen { + fun makeScreen(search: String? = null, parent: Screen? = null): Screen { val wantedKey = when { ConfigConfig.enableMoulConfig -> "moulconfig" ConfigConfig.enableYacl -> "yacl" @@ -74,10 +84,23 @@ object AllConfigsGui { } val provider = FirmamentConfigScreenProvider.providers.find { it.key == wantedKey } ?: FirmamentConfigScreenProvider.providers.first() - return provider.open(parent) + return provider.open(search, parent) } fun showAllGuis() { setScreenLater(makeScreen()) } + + @Subscribe + fun registerCommands(event: CommandEvent.SubCommand) { + event.subcommand("search") { + thenArgument("search", RestArgumentType) { search -> + thenExecute { + val search = this[search] + setScreenLater(makeScreen(search = search)) + } + } + } + } + } diff --git a/src/main/kotlin/gui/config/BooleanHandler.kt b/src/main/kotlin/gui/config/BooleanHandler.kt index 8592777..b954401 100644 --- a/src/main/kotlin/gui/config/BooleanHandler.kt +++ b/src/main/kotlin/gui/config/BooleanHandler.kt @@ -9,6 +9,7 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.boolean import kotlinx.serialization.json.jsonPrimitive +import moe.nea.firmament.util.data.ManagedConfig class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Boolean> { override fun toJson(element: Boolean): JsonElement? { @@ -29,7 +30,7 @@ class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Bo override fun set(newValue: Boolean) { opt.set(newValue) - config.save() + config.markDirty() } }, 200) )) diff --git a/src/main/kotlin/gui/config/BuiltInConfigScreenProvider.kt b/src/main/kotlin/gui/config/BuiltInConfigScreenProvider.kt index 19e7383..6495e68 100644 --- a/src/main/kotlin/gui/config/BuiltInConfigScreenProvider.kt +++ b/src/main/kotlin/gui/config/BuiltInConfigScreenProvider.kt @@ -1,14 +1,14 @@ package moe.nea.firmament.gui.config import com.google.auto.service.AutoService -import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.screens.Screen @AutoService(FirmamentConfigScreenProvider::class) class BuiltInConfigScreenProvider : FirmamentConfigScreenProvider { override val key: String get() = "builtin" - override fun open(parent: Screen?): Screen { + override fun open(search: String?, parent: Screen?): Screen { return AllConfigsGui.makeBuiltInScreen(parent) } } diff --git a/src/main/kotlin/gui/config/ChoiceHandler.kt b/src/main/kotlin/gui/config/ChoiceHandler.kt index 2ea3efc..494d08a 100644 --- a/src/main/kotlin/gui/config/ChoiceHandler.kt +++ b/src/main/kotlin/gui/config/ChoiceHandler.kt @@ -7,16 +7,17 @@ import io.github.notenoughupdates.moulconfig.gui.component.RowComponent import io.github.notenoughupdates.moulconfig.gui.component.TextComponent import kotlinx.serialization.json.JsonElement import kotlin.jvm.optionals.getOrNull -import net.minecraft.util.StringIdentifiable +import net.minecraft.util.StringRepresentable import moe.nea.firmament.gui.CheckboxComponent import moe.nea.firmament.util.ErrorUtil +import moe.nea.firmament.util.data.ManagedConfig import moe.nea.firmament.util.json.KJsonOps class ChoiceHandler<E>( val enumClass: Class<E>, val universe: List<E>, -) : ManagedConfig.OptionHandler<E> where E : Enum<E>, E : StringIdentifiable { - val codec = StringIdentifiable.createCodec { +) : ManagedConfig.OptionHandler<E> where E : Enum<E>, E : StringRepresentable { + val codec = StringRepresentable.fromEnum { @Suppress("UNCHECKED_CAST", "PLATFORM_CLASS_MAPPED_TO_KOTLIN") (universe as java.util.List<*>).toArray(arrayOfNulls<Enum<E>>(0)) as Array<E> } diff --git a/src/main/kotlin/gui/config/ClickHandler.kt b/src/main/kotlin/gui/config/ClickHandler.kt index fa1c621..9ea83aa 100644 --- a/src/main/kotlin/gui/config/ClickHandler.kt +++ b/src/main/kotlin/gui/config/ClickHandler.kt @@ -5,6 +5,7 @@ package moe.nea.firmament.gui.config import io.github.notenoughupdates.moulconfig.gui.component.TextComponent import kotlinx.serialization.json.JsonElement import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.util.data.ManagedConfig class ClickHandler(val config: ManagedConfig, val runnable: () -> Unit) : ManagedConfig.OptionHandler<Unit> { override fun toJson(element: Unit): JsonElement? { diff --git a/src/main/kotlin/gui/config/ColourHandler.kt b/src/main/kotlin/gui/config/ColourHandler.kt new file mode 100644 index 0000000..33daa6d --- /dev/null +++ b/src/main/kotlin/gui/config/ColourHandler.kt @@ -0,0 +1,83 @@ +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.ChromaColour +import io.github.notenoughupdates.moulconfig.gui.component.ColorSelectComponent +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import moe.nea.firmament.util.data.ManagedConfig + +class ColourHandler(val config: ManagedConfig) : + ManagedConfig.OptionHandler<ChromaColour> { + @Serializable + data class ChromaDelegate( + @SerialName("h") + val hue: Float, + @SerialName("s") + val saturation: Float, + @SerialName("b") + val brightness: Float, + @SerialName("a") + val alpha: Int, + @SerialName("c") + val timeForFullRotationInMillis: Int, + ) { + constructor(delegate: ChromaColour) : this( + delegate.hue, + delegate.saturation, + delegate.brightness, + delegate.alpha, + delegate.timeForFullRotationInMillis + ) + + fun into(): ChromaColour = ChromaColour(hue, saturation, brightness, timeForFullRotationInMillis, alpha) + } + + object ChromaSerializer : KSerializer<ChromaColour> { + override val descriptor: SerialDescriptor + get() = SerialDescriptor("FirmChromaColour", ChromaDelegate.serializer().descriptor) + + override fun serialize( + encoder: Encoder, + value: ChromaColour + ) { + encoder.encodeSerializableValue(ChromaDelegate.serializer(), ChromaDelegate(value)) + } + + override fun deserialize(decoder: Decoder): ChromaColour { + return decoder.decodeSerializableValue(ChromaDelegate.serializer()).into() + } + } + + override fun toJson(element: ChromaColour): JsonElement? { + return Json.encodeToJsonElement(ChromaSerializer, element) + } + + override fun fromJson(element: JsonElement): ChromaColour { + return Json.decodeFromJsonElement(ChromaSerializer, element) + } + + override fun emitGuiElements( + opt: ManagedOption<ChromaColour>, + guiAppender: GuiAppender + ) { + guiAppender.appendLabeledRow( + opt.labelText, + ColorSelectComponent( + 0, + 0, + opt.value.toLegacyString(), + { + opt.value = ChromaColour.forLegacyString(it) + config.markDirty() + }, + { } + ) + ) + } +} diff --git a/src/main/kotlin/gui/config/DurationHandler.kt b/src/main/kotlin/gui/config/DurationHandler.kt index 8d485b1..0fc945f 100644 --- a/src/main/kotlin/gui/config/DurationHandler.kt +++ b/src/main/kotlin/gui/config/DurationHandler.kt @@ -3,6 +3,7 @@ package moe.nea.firmament.gui.config import io.github.notenoughupdates.moulconfig.common.IMinecraft +import io.github.notenoughupdates.moulconfig.common.text.StructuredText import io.github.notenoughupdates.moulconfig.gui.component.RowComponent import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent import io.github.notenoughupdates.moulconfig.gui.component.TextComponent @@ -14,8 +15,8 @@ import kotlinx.serialization.json.long import kotlin.time.Duration import kotlin.time.DurationUnit import kotlin.time.toDuration -import net.minecraft.text.Text import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.data.ManagedConfig class DurationHandler(val config: ManagedConfig, val min: Duration, val max: Duration) : ManagedConfig.OptionHandler<Duration> { @@ -31,8 +32,8 @@ class DurationHandler(val config: ManagedConfig, val min: Duration, val max: Dur guiAppender.appendLabeledRow( opt.labelText, RowComponent( - TextComponent(IMinecraft.instance.defaultFontRenderer, - { FirmFormatters.formatTimespan(opt.value) }, + TextComponent(IMinecraft.INSTANCE.defaultFontRenderer, + { StructuredText.of(FirmFormatters.formatTimespan(opt.value)) }, 40, TextComponent.TextAlignment.CENTER, true, diff --git a/src/main/kotlin/gui/config/EnumRenderer.kt b/src/main/kotlin/gui/config/EnumRenderer.kt index 3b80b7e..a2dee69 100644 --- a/src/main/kotlin/gui/config/EnumRenderer.kt +++ b/src/main/kotlin/gui/config/EnumRenderer.kt @@ -1,14 +1,14 @@ package moe.nea.firmament.gui.config -import net.minecraft.text.Text +import net.minecraft.network.chat.Component interface EnumRenderer<E : Any> { - fun getName(option: ManagedOption<E>, value: E): Text + fun getName(option: ManagedOption<E>, value: E): Component companion object { fun <E : Enum<E>> default() = object : EnumRenderer<E> { - override fun getName(option: ManagedOption<E>, value: E): Text { - return Text.translatable(option.rawLabelText + ".choice." + value.name.lowercase()) + override fun getName(option: ManagedOption<E>, value: E): Component { + return Component.translatable(option.rawLabelText + ".choice." + value.name.lowercase()) } } } diff --git a/src/main/kotlin/gui/config/FirmamentConfigScreenProvider.kt b/src/main/kotlin/gui/config/FirmamentConfigScreenProvider.kt index faad1cc..d2a8ab6 100644 --- a/src/main/kotlin/gui/config/FirmamentConfigScreenProvider.kt +++ b/src/main/kotlin/gui/config/FirmamentConfigScreenProvider.kt @@ -1,13 +1,13 @@ package moe.nea.firmament.gui.config -import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.screens.Screen import moe.nea.firmament.util.compatloader.CompatLoader interface FirmamentConfigScreenProvider { val key: String val isEnabled: Boolean get() = true - fun open(parent: Screen?): Screen + fun open(search: String?, parent: Screen?): Screen companion object : CompatLoader<FirmamentConfigScreenProvider>(FirmamentConfigScreenProvider::class) { val providers by lazy { diff --git a/src/main/kotlin/gui/config/GuiAppender.kt b/src/main/kotlin/gui/config/GuiAppender.kt index 329319d..ba28400 100644 --- a/src/main/kotlin/gui/config/GuiAppender.kt +++ b/src/main/kotlin/gui/config/GuiAppender.kt @@ -6,8 +6,8 @@ import io.github.notenoughupdates.moulconfig.gui.GuiComponent import io.github.notenoughupdates.moulconfig.gui.component.RowComponent import io.github.notenoughupdates.moulconfig.gui.component.TextComponent import io.github.notenoughupdates.moulconfig.observer.GetSetter -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.Text +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component import moe.nea.firmament.gui.FixedComponent class GuiAppender(val width: Int, val screenAccessor: () -> Screen) { @@ -18,7 +18,7 @@ class GuiAppender(val width: Int, val screenAccessor: () -> Screen) { reloadables.add(reloadable) } - fun appendLabeledRow(label: Text, right: GuiComponent) { + fun appendLabeledRow(label: Component, right: GuiComponent) { appendSplitRow( TextComponent(label.string), right diff --git a/src/main/kotlin/gui/config/HudMetaHandler.kt b/src/main/kotlin/gui/config/HudMetaHandler.kt index a9659ee..915dcf3 100644 --- a/src/main/kotlin/gui/config/HudMetaHandler.kt +++ b/src/main/kotlin/gui/config/HudMetaHandler.kt @@ -5,21 +5,29 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.encodeToJsonElement -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.MutableText -import net.minecraft.text.Text +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.MutableComponent +import net.minecraft.network.chat.Component +import moe.nea.firmament.Firmament import moe.nea.firmament.gui.FirmButtonComponent import moe.nea.firmament.jarvis.JarvisIntegration import moe.nea.firmament.util.MC +import moe.nea.firmament.util.data.ManagedConfig -class HudMetaHandler(val config: ManagedConfig, val label: MutableText, val width: Int, val height: Int) : +class HudMetaHandler( + val config: ManagedConfig, + val propertyName: String, + val label: MutableComponent, + val width: Int, + val height: Int +) : ManagedConfig.OptionHandler<HudMeta> { override fun toJson(element: HudMeta): JsonElement? { return Json.encodeToJsonElement(element.position) } override fun fromJson(element: JsonElement): HudMeta { - return HudMeta(Json.decodeFromJsonElement(element), label, width, height) + return HudMeta(Json.decodeFromJsonElement(element), Firmament.identifier(propertyName), label, width, height) } fun openEditor(option: ManagedOption<HudMeta>, oldScreen: Screen) { @@ -34,7 +42,8 @@ class HudMetaHandler(val config: ManagedConfig, val label: MutableText, val widt opt.labelText, FirmButtonComponent( TextComponent( - Text.stringifiedTranslatable("firmament.hud.edit", label).string), + Component.translatableEscape("firmament.hud.edit", label).string + ), ) { openEditor(opt, guiAppender.screenAccessor()) }) diff --git a/src/main/kotlin/gui/config/IntegerHandler.kt b/src/main/kotlin/gui/config/IntegerHandler.kt index 31ce90f..ab0237a 100644 --- a/src/main/kotlin/gui/config/IntegerHandler.kt +++ b/src/main/kotlin/gui/config/IntegerHandler.kt @@ -3,6 +3,7 @@ package moe.nea.firmament.gui.config import io.github.notenoughupdates.moulconfig.common.IMinecraft +import io.github.notenoughupdates.moulconfig.common.text.StructuredText import io.github.notenoughupdates.moulconfig.gui.component.RowComponent import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent import io.github.notenoughupdates.moulconfig.gui.component.TextComponent @@ -12,6 +13,7 @@ import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonPrimitive import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.data.ManagedConfig class IntegerHandler(val config: ManagedConfig, val min: Int, val max: Int) : ManagedConfig.OptionHandler<Int> { override fun toJson(element: Int): JsonElement? { @@ -26,8 +28,8 @@ class IntegerHandler(val config: ManagedConfig, val min: Int, val max: Int) : Ma guiAppender.appendLabeledRow( opt.labelText, RowComponent( - TextComponent(IMinecraft.instance.defaultFontRenderer, - { FirmFormatters.formatCommas(opt.value, 0) }, + TextComponent(IMinecraft.INSTANCE.defaultFontRenderer, + { StructuredText.of(FirmFormatters.formatCommas(opt.value, 0)) }, 40, TextComponent.TextAlignment.CENTER, true, diff --git a/src/main/kotlin/gui/config/JAnyHud.kt b/src/main/kotlin/gui/config/JAnyHud.kt index 35c4eb2..63975c6 100644 --- a/src/main/kotlin/gui/config/JAnyHud.kt +++ b/src/main/kotlin/gui/config/JAnyHud.kt @@ -1,48 +1,67 @@ - - package moe.nea.firmament.gui.config import moe.nea.jarvis.api.JarvisHud -import moe.nea.jarvis.api.JarvisScalable +import org.joml.Matrix3x2f +import org.joml.Vector2i +import org.joml.Vector2ic import kotlinx.serialization.Serializable -import net.minecraft.text.Text +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import moe.nea.firmament.jarvis.JarvisIntegration @Serializable data class HudPosition( - var x: Double, - var y: Double, - var scale: Float, + var x: Int, + var y: Int, + var scale: Float, ) data class HudMeta( val position: HudPosition, - private val label: Text, + private val id: ResourceLocation, + private val label: Component, private val width: Int, private val height: Int, -) : JarvisScalable, JarvisHud { - override fun getX(): Double = position.x +) : JarvisHud, JarvisHud.Scalable { + override fun getLabel(): Component = label + override fun getUnscaledWidth(): Int { + return width + } + + override fun getUnscaledHeight(): Int { + return height + } - override fun setX(newX: Double) { - position.x = newX - } + override fun getHudId(): ResourceLocation { + return id + } - override fun getY(): Double = position.y + override fun getPosition(): Vector2ic { + return Vector2i(position.x, position.y) + } - override fun setY(newY: Double) { - position.y = newY - } + override fun setPosition(p0: Vector2ic) { + position.x = p0.x() + position.y = p0.y() + } - override fun getLabel(): Text = label + override fun isEnabled(): Boolean { + return true // TODO: this should be actually truthful, if possible + } - override fun getWidth(): Int = width + override fun isVisible(): Boolean { + return true // TODO: this should be actually truthful, if possible + } - override fun getHeight(): Int = height + override fun getScale(): Float = position.scale - override fun getScale(): Float = position.scale + override fun setScale(newScale: Float) { + position.scale = newScale + } - override fun setScale(newScale: Float) { - position.scale = newScale - } + fun applyTransformations(matrix4f: Matrix3x2f) { + applyTransformations(JarvisIntegration.jarvis, matrix4f) + } } diff --git a/src/main/kotlin/gui/config/KeyBindingHandler.kt b/src/main/kotlin/gui/config/KeyBindingHandler.kt index d7d0b47..3c08da2 100644 --- a/src/main/kotlin/gui/config/KeyBindingHandler.kt +++ b/src/main/kotlin/gui/config/KeyBindingHandler.kt @@ -1,11 +1,5 @@ package moe.nea.firmament.gui.config -import io.github.notenoughupdates.moulconfig.common.IMinecraft -import io.github.notenoughupdates.moulconfig.common.MyResourceLocation -import io.github.notenoughupdates.moulconfig.deps.libninepatch.NinePatch -import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext -import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.decodeFromJsonElement @@ -13,6 +7,7 @@ import kotlinx.serialization.json.encodeToJsonElement import moe.nea.firmament.gui.FirmButtonComponent import moe.nea.firmament.keybindings.FirmamentKeyBindings import moe.nea.firmament.keybindings.SavedKeyBinding +import moe.nea.firmament.util.data.ManagedConfig class KeyBindingHandler(val name: String, val managedConfig: ManagedConfig) : ManagedConfig.OptionHandler<SavedKeyBinding> { @@ -35,39 +30,12 @@ class KeyBindingHandler(val name: String, val managedConfig: ManagedConfig) : { opt.value }, { opt.value = it - opt.element.save() + opt.element.markDirty() }, { button.blur() }, { button.requestFocus() } ) - button = object : FirmButtonComponent( - TextComponent( - IMinecraft.instance.defaultFontRenderer, - { sm.label.string }, - 130, - TextComponent.TextAlignment.LEFT, - false, - false - ), action = { - sm.onClick() - }) { - override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { - if (event is KeyboardEvent.KeyPressed) { - return sm.keyboardEvent(event.keycode, event.pressed) - } - return super.keyboardEvent(event, context) - } - - override fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> { - if (sm.editing) return activeBg - return super.getBackground(context) - } - - - override fun onLostFocus() { - sm.onLostFocus() - } - } + button = sm.createButton() sm.updateLabel() return button } diff --git a/src/main/kotlin/gui/config/KeyBindingStateManager.kt b/src/main/kotlin/gui/config/KeyBindingStateManager.kt index cc8178d..9cf2771 100644 --- a/src/main/kotlin/gui/config/KeyBindingStateManager.kt +++ b/src/main/kotlin/gui/config/KeyBindingStateManager.kt @@ -1,8 +1,18 @@ package moe.nea.firmament.gui.config +import io.github.notenoughupdates.moulconfig.common.IMinecraft +import io.github.notenoughupdates.moulconfig.common.MyResourceLocation +import io.github.notenoughupdates.moulconfig.deps.libninepatch.NinePatch +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import io.github.notenoughupdates.moulconfig.platform.MoulConfigPlatform import org.lwjgl.glfw.GLFW -import net.minecraft.text.Text -import net.minecraft.util.Formatting +import net.minecraft.network.chat.Component +import net.minecraft.ChatFormatting +import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.keybindings.GenericInputButton +import moe.nea.firmament.keybindings.InputModifiers import moe.nea.firmament.keybindings.SavedKeyBinding class KeyBindingStateManager( @@ -12,73 +22,65 @@ class KeyBindingStateManager( val requestFocus: () -> Unit, ) { var editing = false - var lastPressed = 0 - var lastPressedNonModifier = 0 - var label: Text = Text.literal("") + var lastPressed: GenericInputButton? = null + var label: Component = Component.literal("") - fun onClick() { + fun onClick(mouseButton: Int) { if (editing) { - editing = false - blur() - } else { + keyboardEvent(GenericInputButton.mouse(mouseButton), true) + } else if (mouseButton == GLFW.GLFW_MOUSE_BUTTON_LEFT) { editing = true requestFocus() } updateLabel() } - fun keyboardEvent(keyCode: Int, pressed: Boolean): Boolean { - return if (pressed) onKeyPressed(keyCode, SavedKeyBinding.getModInt()) - else onKeyReleased(keyCode, SavedKeyBinding.getModInt()) + fun keyboardEvent(keyCode: GenericInputButton, pressed: Boolean): Boolean { + return if (pressed) onKeyPressed(keyCode, InputModifiers.current()) + else onKeyReleased(keyCode, InputModifiers.current()) } - fun onKeyPressed(ch: Int, modifiers: Int): Boolean { + fun onKeyPressed( + ch: GenericInputButton, + modifiers: InputModifiers + ): Boolean { // TODO !!!!!: genericify this method to allow for other inputs if (!editing) { return false } - if (ch == GLFW.GLFW_KEY_ESCAPE) { - lastPressedNonModifier = 0 + if (ch == GenericInputButton.escape()) { editing = false - lastPressed = 0 - setValue(SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN)) + lastPressed = null + setValue(SavedKeyBinding.unbound()) updateLabel() blur() return true } - if (ch == GLFW.GLFW_KEY_LEFT_SHIFT || ch == GLFW.GLFW_KEY_RIGHT_SHIFT - || ch == GLFW.GLFW_KEY_LEFT_ALT || ch == GLFW.GLFW_KEY_RIGHT_ALT - || ch == GLFW.GLFW_KEY_LEFT_CONTROL || ch == GLFW.GLFW_KEY_RIGHT_CONTROL - ) { + if (ch.isModifier()) { lastPressed = ch } else { - setValue(SavedKeyBinding( - ch, modifiers - )) + setValue(SavedKeyBinding(ch, modifiers)) editing = false blur() - lastPressed = 0 - lastPressedNonModifier = 0 + lastPressed = null } updateLabel() return true } fun onLostFocus() { - lastPressedNonModifier = 0 editing = false - lastPressed = 0 + lastPressed = null updateLabel() } - fun onKeyReleased(ch: Int, modifiers: Int): Boolean { + fun onKeyReleased(ch: GenericInputButton, modifiers: InputModifiers): Boolean { if (!editing) return false - if (lastPressedNonModifier == ch || (lastPressedNonModifier == 0 && ch == lastPressed)) { + if (ch == lastPressed) { // TODO: check modifiers dont duplicate (CTRL+CTRL) setValue(SavedKeyBinding(ch, modifiers)) editing = false blur() - lastPressed = 0 - lastPressedNonModifier = 0 + lastPressed = null } updateLabel() return true @@ -87,22 +89,51 @@ class KeyBindingStateManager( fun updateLabel() { var stroke = value().format() if (editing) { - stroke = Text.literal("") - val (shift, ctrl, alt) = SavedKeyBinding.getMods(SavedKeyBinding.getModInt()) - if (shift) { - stroke.append("SHIFT + ") - } - if (alt) { - stroke.append("ALT + ") - } - if (ctrl) { - stroke.append("CTRL + ") + stroke = Component.empty() + val modifiers = InputModifiers.current() + if (!modifiers.isEmpty()) { + stroke.append(modifiers.format()) + stroke.append(" + ") } stroke.append("???") - stroke.styled { it.withColor(Formatting.YELLOW) } + stroke.withStyle { it.withColor(ChatFormatting.YELLOW) } } label = stroke } + fun createButton(): FirmButtonComponent { + return object : FirmButtonComponent( + TextComponent( + IMinecraft.INSTANCE.defaultFontRenderer, + { MoulConfigPlatform.wrap(this@KeyBindingStateManager.label) }, + 130, + TextComponent.TextAlignment.LEFT, + false, + false + ), action = { + this@KeyBindingStateManager.onClick(it) + }) { + override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { + if (event is KeyboardEvent.KeyPressed) { + return this@KeyBindingStateManager.keyboardEvent( + GenericInputButton.ofKeyAndScan( + event.keycode, + event.scancode + ), event.pressed + ) + } + return super.keyboardEvent(event, context) + } + override fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> { + if (this@KeyBindingStateManager.editing) return activeBg + return super.getBackground(context) + } + + + override fun onLostFocus() { + this@KeyBindingStateManager.onLostFocus() + } + } + } } diff --git a/src/main/kotlin/gui/config/ManagedConfig.kt b/src/main/kotlin/gui/config/ManagedConfig.kt deleted file mode 100644 index 7ddda9e..0000000 --- a/src/main/kotlin/gui/config/ManagedConfig.kt +++ /dev/null @@ -1,252 +0,0 @@ -package moe.nea.firmament.gui.config - -import com.mojang.serialization.Codec -import io.github.notenoughupdates.moulconfig.gui.CloseEventListener -import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper -import io.github.notenoughupdates.moulconfig.gui.GuiContext -import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent -import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent -import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent -import io.github.notenoughupdates.moulconfig.gui.component.RowComponent -import io.github.notenoughupdates.moulconfig.gui.component.ScrollPanelComponent -import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import moe.nea.jarvis.api.Point -import org.lwjgl.glfw.GLFW -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlin.io.path.createDirectories -import kotlin.io.path.readText -import kotlin.io.path.writeText -import kotlin.time.Duration -import net.minecraft.client.gui.screen.Screen -import net.minecraft.text.Text -import net.minecraft.util.StringIdentifiable -import moe.nea.firmament.Firmament -import moe.nea.firmament.gui.FirmButtonComponent -import moe.nea.firmament.keybindings.SavedKeyBinding -import moe.nea.firmament.util.ScreenUtil.setScreenLater -import moe.nea.firmament.util.collections.InstanceList - -abstract class ManagedConfig( - override val name: String, - val category: Category, - // TODO: allow vararg secondaryCategories: Category, -) : ManagedConfigElement() { - enum class Category { - // Böse Kategorie, nicht benutzten lol - MISC, - CHAT, - INVENTORY, - MINING, - EVENTS, - INTEGRATIONS, - META, - DEV, - ; - - val labelText: Text = Text.translatable("firmament.config.category.${name.lowercase()}") - val description: Text = Text.translatable("firmament.config.category.${name.lowercase()}.description") - val configs: MutableList<ManagedConfig> = mutableListOf() - } - - companion object { - val allManagedConfigs = InstanceList<ManagedConfig>("ManagedConfig") - } - - interface OptionHandler<T : Any> { - fun initOption(opt: ManagedOption<T>) {} - fun toJson(element: T): JsonElement? - fun fromJson(element: JsonElement): T - fun emitGuiElements(opt: ManagedOption<T>, guiAppender: GuiAppender) - } - - init { - allManagedConfigs.getAll().forEach { - require(it.name != name) { "Duplicate name '$name' used for config" } - } - allManagedConfigs.add(this) - category.configs.add(this) - } - - val file = Firmament.CONFIG_DIR.resolve("$name.json") - val data: JsonObject by lazy { - try { - Firmament.json.decodeFromString( - file.readText() - ) - } catch (e: Exception) { - Firmament.logger.info("Could not read config $name. Loading empty config.") - JsonObject(mutableMapOf()) - } - } - - fun save() { - val data = JsonObject(allOptions.mapNotNull { (key, value) -> - value.toJson()?.let { - key to it - } - }.toMap()) - file.parent.createDirectories() - file.writeText(Firmament.json.encodeToString(data)) - } - - - val allOptions = mutableMapOf<String, ManagedOption<*>>() - val sortedOptions = mutableListOf<ManagedOption<*>>() - - private var latestGuiAppender: GuiAppender? = null - - protected fun <T : Any> option( - propertyName: String, - default: () -> T, - handler: OptionHandler<T> - ): ManagedOption<T> { - if (propertyName in allOptions) error("Cannot register the same name twice") - return ManagedOption(this, propertyName, default, handler).also { - it.handler.initOption(it) - it.load(data) - allOptions[propertyName] = it - sortedOptions.add(it) - } - } - - protected fun toggle(propertyName: String, default: () -> Boolean): ManagedOption<Boolean> { - return option(propertyName, default, BooleanHandler(this)) - } - - protected fun <E> choice( - propertyName: String, - enumClass: Class<E>, - default: () -> E - ): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable { - return option(propertyName, default, ChoiceHandler(enumClass, enumClass.enumConstants.toList())) - } - - protected inline fun <reified E> choice( - propertyName: String, - noinline default: () -> E - ): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable { - return choice(propertyName, E::class.java, default) - } - - private fun <E> createStringIdentifiable(x: () -> Array<out E>): Codec<E> where E : Enum<E>, E : StringIdentifiable { - return StringIdentifiable.createCodec { x() } - } - - // TODO: wait on https://youtrack.jetbrains.com/issue/KT-73434 -// protected inline fun <reified E> choice( -// propertyName: String, -// noinline default: () -> E -// ): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable { -// return choice( -// propertyName, -// enumEntries<E>().toList(), -// StringIdentifiable.createCodec { enumValues<E>() }, -// EnumRenderer.default(), -// default -// ) -// } - open fun onChange(option: ManagedOption<*>) { - } - - protected fun duration( - propertyName: String, - min: Duration, - max: Duration, - default: () -> Duration, - ): ManagedOption<Duration> { - return option(propertyName, default, DurationHandler(this, min, max)) - } - - - protected fun position( - propertyName: String, - width: Int, - height: Int, - default: () -> Point, - ): ManagedOption<HudMeta> { - val label = Text.translatable("firmament.config.${name}.${propertyName}") - return option(propertyName, { - val p = default() - HudMeta(HudPosition(p.x, p.y, 1F), label, width, height) - }, HudMetaHandler(this, label, width, height)) - } - - protected fun keyBinding( - propertyName: String, - default: () -> Int, - ): ManagedOption<SavedKeyBinding> = keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(default()) } - - protected fun keyBindingWithOutDefaultModifiers( - propertyName: String, - default: () -> SavedKeyBinding, - ): ManagedOption<SavedKeyBinding> { - return option(propertyName, default, KeyBindingHandler("firmament.config.${name}.${propertyName}", this)) - } - - protected fun keyBindingWithDefaultUnbound( - propertyName: String, - ): ManagedOption<SavedKeyBinding> { - return keyBindingWithOutDefaultModifiers(propertyName) { SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN) } - } - - protected fun integer( - propertyName: String, - min: Int, - max: Int, - default: () -> Int, - ): ManagedOption<Int> { - return option(propertyName, default, IntegerHandler(this, min, max)) - } - - protected fun button(propertyName: String, runnable: () -> Unit): ManagedOption<Unit> { - return option(propertyName, { }, ClickHandler(this, runnable)) - } - - protected fun string(propertyName: String, default: () -> String): ManagedOption<String> { - return option(propertyName, default, StringHandler(this)) - } - - - fun reloadGui() { - latestGuiAppender?.reloadables?.forEach { it() } - } - - val translationKey get() = "firmament.config.${name}" - val labelText: Text = Text.translatable(translationKey) - - fun getConfigEditor(parent: Screen? = null): Screen { - var screen: Screen? = null - val guiapp = GuiAppender(400) { requireNotNull(screen) { "Screen Accessor called too early" } } - latestGuiAppender = guiapp - guiapp.appendFullRow(RowComponent( - FirmButtonComponent(TextComponent("←")) { - if (parent != null) { - save() - setScreenLater(parent) - } else { - AllConfigsGui.showAllGuis() - } - } - )) - sortedOptions.forEach { it.appendToGui(guiapp) } - guiapp.reloadables.forEach { it() } - val component = CenterComponent(PanelComponent(ScrollPanelComponent(400, 300, ColumnComponent(guiapp.panel)), - 10, - PanelComponent.DefaultBackgroundRenderer.VANILLA)) - screen = object : GuiComponentWrapper(GuiContext(component)) { - override fun close() { - if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) { - client!!.setScreen(parent) - } - } - } - return screen - } - - fun showConfigEditor(parent: Screen? = null) { - setScreenLater(getConfigEditor(parent)) - } - -} diff --git a/src/main/kotlin/gui/config/ManagedConfigElement.kt b/src/main/kotlin/gui/config/ManagedConfigElement.kt deleted file mode 100644 index 28cd6b8..0000000 --- a/src/main/kotlin/gui/config/ManagedConfigElement.kt +++ /dev/null @@ -1,8 +0,0 @@ - - -package moe.nea.firmament.gui.config - -abstract class ManagedConfigElement { - abstract val name: String - -} diff --git a/src/main/kotlin/gui/config/ManagedOption.kt b/src/main/kotlin/gui/config/ManagedOption.kt index 383f392..4c228de 100644 --- a/src/main/kotlin/gui/config/ManagedOption.kt +++ b/src/main/kotlin/gui/config/ManagedOption.kt @@ -5,8 +5,9 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -import net.minecraft.text.Text +import net.minecraft.network.chat.Component import moe.nea.firmament.util.ErrorUtil +import moe.nea.firmament.util.data.ManagedConfig class ManagedOption<T : Any>( val element: ManagedConfig, @@ -23,15 +24,15 @@ class ManagedOption<T : Any>( } val rawLabelText = "firmament.config.${element.name}.${propertyName}" - val labelText: Text = Text.translatable(rawLabelText) + val labelText: Component = Component.translatable(rawLabelText) val descriptionTranslationKey = "firmament.config.${element.name}.${propertyName}.description" - val labelDescription: Text = Text.translatable(descriptionTranslationKey) + val labelDescription: Component = Component.translatable(descriptionTranslationKey) - private var actualValue: T? = null + var _actualValue: T? = null var value: T - get() = actualValue ?: error("Lateinit variable not initialized") + get() = _actualValue ?: error("Lateinit variable not initialized") set(value) { - actualValue = value + _actualValue = value element.onChange(this) } @@ -49,7 +50,7 @@ class ManagedOption<T : Any>( value = handler.fromJson(root[propertyName]!!) return } catch (e: Exception) { - ErrorUtil.softError( + ErrorUtil.logError( "Exception during loading of config file ${element.name}. This will reset this config.", e ) diff --git a/src/main/kotlin/gui/config/StringHandler.kt b/src/main/kotlin/gui/config/StringHandler.kt index a326abb..17bb981 100644 --- a/src/main/kotlin/gui/config/StringHandler.kt +++ b/src/main/kotlin/gui/config/StringHandler.kt @@ -7,7 +7,8 @@ import io.github.notenoughupdates.moulconfig.observer.GetSetter import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonPrimitive -import net.minecraft.text.Text +import net.minecraft.network.chat.Component +import moe.nea.firmament.util.data.ManagedConfig class StringHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<String> { override fun toJson(element: String): JsonElement? { @@ -25,11 +26,11 @@ class StringHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Str object : GetSetter<String> by opt { override fun set(newValue: String) { opt.set(newValue) - config.save() + config.markDirty() } }, 130, - suggestion = Text.translatableWithFallback(opt.rawLabelText + ".hint", "").string + suggestion = Component.translatableWithFallback(opt.rawLabelText + ".hint", "").string ), ) } diff --git a/src/main/kotlin/gui/config/storage/ConfigLoadContext.kt b/src/main/kotlin/gui/config/storage/ConfigLoadContext.kt new file mode 100644 index 0000000..4a06ec6 --- /dev/null +++ b/src/main/kotlin/gui/config/storage/ConfigLoadContext.kt @@ -0,0 +1,98 @@ +package moe.nea.firmament.gui.config.storage + +import java.io.PrintWriter +import java.nio.file.Path +import org.apache.commons.io.output.StringBuilderWriter +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.OnErrorResult +import kotlin.io.path.Path +import kotlin.io.path.copyToRecursively +import kotlin.io.path.createParentDirectories +import kotlin.io.path.writeText +import moe.nea.firmament.Firmament + +data class ConfigLoadContext( + val loadId: String, +) : AutoCloseable { + val backupPath = Path("backups").resolve(Firmament.MOD_ID) + .resolve("config-$loadId") + .toAbsolutePath() + val logFile = Path("logs") + .resolve(Firmament.MOD_ID) + .resolve("config-$loadId.log") + .toAbsolutePath() + val logBuffer = StringBuilder() + + var shouldSaveLogBuffer = false + fun markShouldSaveLogBuffer() { + shouldSaveLogBuffer = true + } + + fun logDebug(message: String) { + logBuffer.append("[DEBUG] ").append(message).appendLine() + } + + fun logInfo(message: String) { + if (Firmament.DEBUG) + Firmament.logger.info("[ConfigUpgrade] $message") + logBuffer.append("[INFO] ").append(message).appendLine() + } + + fun logError(message: String, exception: Throwable) { + markShouldSaveLogBuffer() + if (Firmament.DEBUG) + Firmament.logger.error("[ConfigUpgrade] $message", exception) + logBuffer.append("[ERROR] ").append(message).appendLine() + PrintWriter(StringBuilderWriter(logBuffer)).use { + exception.printStackTrace(it) + } + logBuffer.appendLine() + } + + fun logError(message: String) { + markShouldSaveLogBuffer() + Firmament.logger.error("[ConfigUpgrade] $message") + logBuffer.append("[ERROR] ").append(message).appendLine() + } + + fun ensureWritable(path: Path) { + path.createParentDirectories() + } + + fun use(block: (ConfigLoadContext) -> Unit) { + try { + block(this) + } catch (ex: Exception) { + logError("Caught exception on CLC", ex) + } finally { + close() + } + } + + override fun close() { + logInfo("Closing out config load.") + if (shouldSaveLogBuffer) { + try { + ensureWritable(logFile) + logFile.writeText(logBuffer.toString()) + } catch (ex: Exception) { + logError("Could not save config load log", ex) + } + } + } + + @OptIn(ExperimentalPathApi::class) + fun createBackup(folder: Path, string: String) { + val backupDestination = backupPath.resolve("$string-${System.currentTimeMillis()}") + logError("Creating backup of $folder in $backupDestination") + folder.copyToRecursively( + backupDestination.createParentDirectories(), + onError = { source: Path, target: Path, exception: Exception -> + logError("Failed to copy subtree $source to $target", exception) + OnErrorResult.SKIP_SUBTREE + }, + followLinks = false, + overwrite = false + ) + } +} diff --git a/src/main/kotlin/gui/config/storage/ConfigStorageClass.kt b/src/main/kotlin/gui/config/storage/ConfigStorageClass.kt new file mode 100644 index 0000000..8258fe7 --- /dev/null +++ b/src/main/kotlin/gui/config/storage/ConfigStorageClass.kt @@ -0,0 +1,8 @@ +package moe.nea.firmament.gui.config.storage + +enum class ConfigStorageClass { // TODO: make this encode type info somehow + PROFILE, + STORAGE, + CONFIG, +} + diff --git a/src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt b/src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt new file mode 100644 index 0000000..0292721 --- /dev/null +++ b/src/main/kotlin/gui/config/storage/FirmamentConfigLoader.kt @@ -0,0 +1,252 @@ +package moe.nea.firmament.gui.config.storage + +import java.util.UUID +import java.util.concurrent.CompletableFuture +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonObject +import kotlin.io.path.Path +import kotlin.io.path.exists +import kotlin.io.path.forEachDirectoryEntry +import kotlin.io.path.isDirectory +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.name +import kotlin.io.path.readText +import kotlin.io.path.writeText +import kotlin.time.Duration.Companion.seconds +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.features.debug.DebugLogger +import moe.nea.firmament.util.SBData.NULL_UUID +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.data.IConfigProvider +import moe.nea.firmament.util.data.IDataHolder +import moe.nea.firmament.util.data.ProfileKeyedConfig +import moe.nea.firmament.util.json.intoGson +import moe.nea.firmament.util.json.intoKotlinJson + +object FirmamentConfigLoader { + val currentConfigVersion = 1000 + val configFolder = Path("config/firmament") + .toAbsolutePath() + val storageFolder = configFolder.resolve("storage") + val profilePath = configFolder.resolve("profiles") + val tagLines = listOf( + "<- your config version here", + "I'm a teapot", + "mail.example.com ESMTP", + "Apples" + ) + val configVersionFile = configFolder.resolve("config.version") + + fun loadConfig() { + if (configFolder.exists()) { + if (!configVersionFile.exists()) { + LegacyImporter.importFromLegacy() + } + updateConfigs() + } + + ConfigLoadContext("load-${System.currentTimeMillis()}").use { loadContext -> + val configData = FirstLevelSplitJsonFolder(loadContext, configFolder).load() + loadConfigFromData(configData, Unit, ConfigStorageClass.CONFIG) + val storageData = FirstLevelSplitJsonFolder(loadContext, storageFolder).load() + loadConfigFromData(storageData, Unit, ConfigStorageClass.STORAGE) + var profileData = + profilePath.takeIf { it.exists() } + ?.listDirectoryEntries() + ?.filter { it.isDirectory() } + ?.mapNotNull { + val uuid= runCatching { UUID.fromString(it.name) }.getOrNull() ?: return@mapNotNull null + uuid to FirstLevelSplitJsonFolder(loadContext, it).load() + } + ?.toMap() + if (profileData.isNullOrEmpty()) + profileData = mapOf(NULL_UUID to JsonObject(mapOf())) + profileData.forEach { (key, value) -> + loadConfigFromData(value, key, ConfigStorageClass.PROFILE) + } + } + } + + fun <T> loadConfigFromData( + configData: JsonObject, + key: T?, + storageClass: ConfigStorageClass + ) { + for (holder in allConfigs) { + if (holder.storageClass == storageClass) { + val h = (holder as IDataHolder<T>) + if (key == null) { + h.explicitDefaultLoad() + } else { + h.loadFrom(key, configData) + } + } + } + } + + fun <T> collectConfigFromData( + key: T, + storageClass: ConfigStorageClass, + ): JsonObject { + var json = JsonObject(mapOf()) + for (holder in allConfigs) { + if (holder.storageClass == storageClass) { + json = mergeJson(json, (holder as IDataHolder<T>).saveTo(key)) + } + } + return json + } + + fun <T> saveStorage( + storageClass: ConfigStorageClass, + key: T, + firstLevelSplitJsonFolder: FirstLevelSplitJsonFolder, + ) { + firstLevelSplitJsonFolder.save( + collectConfigFromData(key, storageClass) + ) + } + + fun collectAllProfileIds(): Set<UUID> { + return allConfigs + .filter { it.storageClass == ConfigStorageClass.PROFILE } + .flatMapTo(mutableSetOf()) { + (it as ProfileKeyedConfig<*>).keys() + } + } + + fun saveAll() { + ConfigLoadContext("save-${System.currentTimeMillis()}").use { context -> + saveStorage( + ConfigStorageClass.CONFIG, + Unit, + FirstLevelSplitJsonFolder(context, configFolder) + ) + saveStorage( + ConfigStorageClass.STORAGE, + Unit, + FirstLevelSplitJsonFolder(context, storageFolder) + ) + collectAllProfileIds().forEach { profileId -> + saveStorage( + ConfigStorageClass.PROFILE, + profileId, + FirstLevelSplitJsonFolder(context, profilePath.resolve(profileId.toString())) + ) + } + writeConfigVersion() + } + } + + fun mergeJson(a: JsonObject, b: JsonObject): JsonObject { + fun mergeInner(a: JsonElement?, b: JsonElement?): JsonElement { + if (a == null) + return b!! + if (b == null) + return a + a as JsonObject + b as JsonObject + return buildJsonObject { + (a.keys + b.keys) + .forEach { + put(it, mergeInner(a[it], b[it])) + } + } + } + return mergeInner(a, b) as JsonObject + } + + val allConfigs: List<IDataHolder<*>> = IConfigProvider.providers.allValidInstances.flatMap { it.configs } + + fun updateConfigs() { + val startVersion = configVersionFile.readText() + .substringBefore(' ') + .trim() + .toInt() + ConfigLoadContext("update-from-$startVersion-to-$currentConfigVersion-${System.currentTimeMillis()}") + .use { loadContext -> + updateOneConfig( + loadContext, + startVersion, + ConfigStorageClass.CONFIG, + FirstLevelSplitJsonFolder(loadContext, configFolder) + ) + updateOneConfig( + loadContext, + startVersion, + ConfigStorageClass.STORAGE, + FirstLevelSplitJsonFolder(loadContext, storageFolder) + ) + profilePath.forEachDirectoryEntry { + updateOneConfig( + loadContext, + startVersion, + ConfigStorageClass.PROFILE, + FirstLevelSplitJsonFolder(loadContext, it) + ) + } + writeConfigVersion() + } + } + + fun writeConfigVersion() { + configVersionFile.writeText("$currentConfigVersion ${tagLines.random()}") + } + + private fun updateOneConfig( + loadContext: ConfigLoadContext, + startVersion: Int, + storageClass: ConfigStorageClass, + firstLevelSplitJsonFolder: FirstLevelSplitJsonFolder + ) { + if (startVersion == currentConfigVersion) { + loadContext.logDebug("Skipping upgrade to ") + return + } + loadContext.logInfo("Starting upgrade from at ${firstLevelSplitJsonFolder.folder} ($storageClass) to $startVersion") + var data = firstLevelSplitJsonFolder.load() + for (nextVersion in (startVersion + 1)..currentConfigVersion) { + data = updateOneConfigOnce(nextVersion, storageClass, data) + } + firstLevelSplitJsonFolder.save(data) + } + + private fun updateOneConfigOnce( + nextVersion: Int, + storageClass: ConfigStorageClass, + data: JsonObject + ): JsonObject { + return ConfigFixEvent.publish(ConfigFixEvent(storageClass, nextVersion, data.intoGson().asJsonObject)) + .data.intoKotlinJson().jsonObject + } + + @Subscribe + fun onTick(event: TickEvent) { + val config = configPromise ?: return + val passedTime = saveDebounceStart.passedTime() + if (passedTime < 1.seconds) + return + if (!config.isDone && passedTime < 3.seconds) + return + debugLogger.log("Performing config save") + configPromise = null + saveAll() + } + + val debugLogger = DebugLogger("config") + + var configPromise: CompletableFuture<Void?>? = null + var saveDebounceStart: TimeMark = TimeMark.farPast() + fun markDirty( + holder: IDataHolder<*>, + timeoutPromise: CompletableFuture<Void?>? = null + ) { + debugLogger.log("Config marked dirty") + this.saveDebounceStart = TimeMark.now() + this.configPromise = timeoutPromise ?: CompletableFuture.completedFuture(null) + } + +} diff --git a/src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt b/src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt new file mode 100644 index 0000000..b92488a --- /dev/null +++ b/src/main/kotlin/gui/config/storage/FirstLevelSplitJsonFolder.kt @@ -0,0 +1,109 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package moe.nea.firmament.gui.config.storage + +import java.nio.file.Path +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.decodeFromStream +import kotlinx.serialization.json.encodeToStream +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteExisting +import kotlin.io.path.exists +import kotlin.io.path.inputStream +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.outputStream +import moe.nea.firmament.Firmament + +// TODO: make this class write / read async +class FirstLevelSplitJsonFolder( + val context: ConfigLoadContext, + val folder: Path +) { + + var hasCreatedBackup = false + + fun backup(cause: String) { + if (hasCreatedBackup) return + hasCreatedBackup = true + context.createBackup(folder, cause) + } + + fun load(): JsonObject { + context.logInfo("Loading FLSJF from $folder") + if (!folder.exists()) + return JsonObject(mapOf()) + return try { + folder.listDirectoryEntries("*.json") + .mapNotNull(::loadIndividualFile) + .toMap() + .let(::JsonObject) + .also { context.logInfo("FLSJF from $folder - Voller Erfolg!") } + } catch (ex: Exception) { + context.logError("Could not load files from $folder", ex) + backup("failed-load") + JsonObject(mapOf()) + } + } + + fun loadIndividualFile(path: Path): Pair<String, JsonElement>? { + context.logDebug("Loading partial file from $path") + return try { + path.inputStream().use { + path.nameWithoutExtension to Firmament.json.decodeFromStream(JsonElement.serializer(), it) + } + } catch (ex: Exception) { + context.logError("Could not load file from $path", ex) + backup("failed-load") + null + } + } + + fun save(value: JsonObject) { + context.logInfo("Saving FLSJF to $folder") + context.logDebug("Current value:\n$value") + if (!folder.exists()) { + context.logInfo("Creating folder $folder") + folder.createDirectories() + } + val entries = folder.listDirectoryEntries("*.json") + .toMutableList() + for ((name, element) in value) { + val path = saveIndividualFile(name, element) + if (path != null) { + entries.remove(path) + } + } + if (entries.isNotEmpty()) { + context.logInfo("Deleting additional files.") + for (path in entries) { + context.logInfo("Deleting $path") + backup("save-deletion") + try { + path.deleteExisting() + } catch (ex: Exception) { + context.logError("Could not delete $path", ex) + } + } + } + context.logInfo("FLSJF to $folder - Voller Erfolg!") + } + + fun saveIndividualFile(name: String, element: JsonElement): Path? { + try { + context.logDebug("Saving partial file with name $name") + val path = folder.resolve("$name.json") + context.ensureWritable(path) + path.outputStream().use { + Firmament.json.encodeToStream(JsonElement.serializer(), element, it) + } + return path + } catch (ex: Exception) { + context.logError("Could not save $name with value $element", ex) + backup("failed-save") + return null + } + } +} diff --git a/src/main/kotlin/gui/config/storage/LegacyImporter.kt b/src/main/kotlin/gui/config/storage/LegacyImporter.kt new file mode 100644 index 0000000..c1f8b90 --- /dev/null +++ b/src/main/kotlin/gui/config/storage/LegacyImporter.kt @@ -0,0 +1,68 @@ +package moe.nea.firmament.gui.config.storage + +import java.nio.file.Path +import kotlin.io.path.copyTo +import kotlin.io.path.createDirectories +import kotlin.io.path.createParentDirectories +import kotlin.io.path.exists +import kotlin.io.path.forEachDirectoryEntry +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.moveTo +import kotlin.io.path.name +import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.writeText +import moe.nea.firmament.gui.config.storage.FirmamentConfigLoader.configFolder +import moe.nea.firmament.gui.config.storage.FirmamentConfigLoader.configVersionFile +import moe.nea.firmament.gui.config.storage.FirmamentConfigLoader.storageFolder + +object LegacyImporter { + val legacyConfigVersion = 995 + val backupPath = configFolder.resolveSibling("firmament-legacy-config-${System.currentTimeMillis()}") + + fun copyIf(from: Path, to: Path) { + if (from.exists()) { + to.createParentDirectories() + from.copyTo(to) + } + } + + val legacyStorage = listOf( + "inventory-buttons", + "macros", + ) + + fun importFromLegacy() { + if (!configFolder.exists()) return + configFolder.moveTo(backupPath) + configFolder.createDirectories() + + legacyStorage.forEach { + copyIf( + backupPath.resolve("$it.json"), + storageFolder.resolve("$it.json") + ) + } + + backupPath.listDirectoryEntries("*.json") + .filter { it.nameWithoutExtension !in legacyStorage } + .forEach { path -> + val name = path.name + path.copyTo(configFolder.resolve(name)) + } + + backupPath.resolve("profiles") + .takeIf { it.exists() } + ?.forEachDirectoryEntry { category -> + category.forEachDirectoryEntry { profile -> + copyIf( + profile, + FirmamentConfigLoader.profilePath + .resolve(profile.nameWithoutExtension) + .resolve(category.name + ".json") + ) + } + } + + configVersionFile.writeText("$legacyConfigVersion LEGACY") + } +} diff --git a/src/main/kotlin/gui/config/storage/README.md b/src/main/kotlin/gui/config/storage/README.md new file mode 100644 index 0000000..aad4afe --- /dev/null +++ b/src/main/kotlin/gui/config/storage/README.md @@ -0,0 +1,68 @@ +<!-- +SPDX-FileCopyrightText: 2025 Linnea Gräf <nea@nea.moe> + +SPDX-License-Identifier: CC0-1.0 +--> + +# Plan for the 2026 Config Renewal of Firmament + +The current config system in Firmament is not growing at a reasonable pace. Here is a list of my grievances with it: + +- the config files are split, resulting in making migrations between different config files (which might load in + different order) difficult +- it is difficult to detect extraneous properties / files, because not all files are loaded and consumed at once +- profile specific data should be in a different hierarchy. the current hierarchy of `profiles/topic/<uuid>.json` orders + data from different profiles to be closer than data from the same profile. this also contributes to the two former + problems. + +## Goals + +- i want to retain having multiple different files for different topics, as well as a folder structure that makes sense + for profiles. +- i want to split up "storage" type data, with "config" type data +- i want to support partial loads with some broken files (resetting the files that are broken) +- i want to support backups on any detected error (or simply at will) + - notably i do not care about the structure of the backups much. even just a all json files merged backup is fine + for me, for now. + +## Implementation + +### FirstLevelSplitJsonFolder + +One of the basic components of this new config folder is a `FirstLevelSplitJsonFolder`. A `FLSJF` takes in a folder +containing multiple JSON-files and loads all of them unconditionally. Each file is then inserted side by side into a +json object, to be processed further by other mechanisms. + +In essence the `FLSJF` takes a folder structure like this: + +``` +file-1.json +file-2.json +file-3.json +``` + +and turns it into a single merged json object: + +```json +{ + "file-1": "the json content of file-1.json", + "file-2": "the json content of file-2.json", + "file-3": "the json content of file-3.json" +} +``` + +As with any stage of the implementation, any unparsable files shall be copied over to a backup spot and discarded. + +Nota bene: Folders are wholesale ignored. + +### Config folders + +Firmament stores all configs and data in the root config folder `./config/firmament`. + +- Any config data is stored as an [`FLSJF`](#firstlevelsplitjsonfolder) in the root config folder +- Any generic storage data is stored as an [`FLSJF`](#firstlevelsplitjsonfolder) in `${rootConfigFolder}/storage/`. +- Any profile specific storage data is stored as an [`FLSJF`](#firstlevelsplitjsonfolder) for each profile in `${rootConfigFolder}/profileStorage/${profileUuid}/`. +- Any backup data is stored in `${rootConfigFolder}/backups/${launchId}/${loadId}/${fileName}`. + - Where `launchId` is `${currentLaunchTimestamp}-${random()}` to avoid collisions. + - Where `loadId` depends on which stage of the config load we are doing (`merge`/`upgrade`/etc.) and what type of config we are loading (`profileSpecific`/`config`/etc.). + - And where `fileName` may be a relative filename of where this data was originally found or some internal descriptor for the merged data stage we are on. diff --git a/src/main/kotlin/gui/entity/EntityModifier.kt b/src/main/kotlin/gui/entity/EntityModifier.kt index 9623070..4915ebb 100644 --- a/src/main/kotlin/gui/entity/EntityModifier.kt +++ b/src/main/kotlin/gui/entity/EntityModifier.kt @@ -2,7 +2,7 @@ package moe.nea.firmament.gui.entity import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity +import net.minecraft.world.entity.LivingEntity fun interface EntityModifier { fun apply(entity: LivingEntity, info: JsonObject): LivingEntity diff --git a/src/main/kotlin/gui/entity/EntityRenderer.kt b/src/main/kotlin/gui/entity/EntityRenderer.kt index fd7a0c4..4972709 100644 --- a/src/main/kotlin/gui/entity/EntityRenderer.kt +++ b/src/main/kotlin/gui/entity/EntityRenderer.kt @@ -3,17 +3,18 @@ package moe.nea.firmament.gui.entity import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonObject +import me.shedaniel.math.Dimension import org.joml.Quaternionf import org.joml.Vector3f import kotlin.math.atan -import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.screen.ingame.InventoryScreen -import net.minecraft.entity.Entity -import net.minecraft.entity.EntityType -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.SpawnReason -import net.minecraft.util.Identifier -import net.minecraft.world.World +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.screens.inventory.InventoryScreen +import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.EntityType +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.EntitySpawnReason +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.level.Level import moe.nea.firmament.util.ErrorUtil import moe.nea.firmament.util.MC import moe.nea.firmament.util.iterate @@ -21,47 +22,87 @@ import moe.nea.firmament.util.openFirmamentResource import moe.nea.firmament.util.render.enableScissorWithTranslation object EntityRenderer { - val fakeWorld: World get() = MC.lastWorld!! + val fakeWorld: Level get() = MC.lastWorld!! private fun <T : Entity> t(entityType: EntityType<T>): () -> T { - return { entityType.create(fakeWorld, SpawnReason.LOAD)!! } + return { entityType.create(fakeWorld, EntitySpawnReason.LOAD)!! } } val entityIds: Map<String, () -> LivingEntity> = mapOf( - "Zombie" to t(EntityType.ZOMBIE), + "Armadillo" to t(EntityType.ARMADILLO), + "ArmorStand" to t(EntityType.ARMOR_STAND), + "Axolotl" to t(EntityType.AXOLOTL), + "Bat" to t(EntityType.BAT), + "Bee" to t(EntityType.BEE), + "Blaze" to t(EntityType.BLAZE), + "Bogged" to t(EntityType.BOGGED), + "Breeze" to t(EntityType.BREEZE), + "CaveSpider" to t(EntityType.CAVE_SPIDER), "Chicken" to t(EntityType.CHICKEN), - "Slime" to t(EntityType.SLIME), - "Wolf" to t(EntityType.WOLF), - "Skeleton" to t(EntityType.SKELETON), + "Cod" to t(EntityType.COD), + "Cow" to t(EntityType.COW), + "Creaking" to t(EntityType.CREAKING), "Creeper" to t(EntityType.CREEPER), + "Dolphin" to t(EntityType.DOLPHIN), + "Donkey" to t(EntityType.DONKEY), + "Dragon" to t(EntityType.ENDER_DRAGON), + "Drowned" to t(EntityType.DROWNED), + "Eisengolem" to t(EntityType.IRON_GOLEM), + "Enderman" to t(EntityType.ENDERMAN), + "Endermite" to t(EntityType.ENDERMITE), + "Evoker" to t(EntityType.EVOKER), + "Fox" to t(EntityType.FOX), + "Frog" to t(EntityType.FROG), + "Ghast" to t(EntityType.GHAST), + "Giant" to t(EntityType.GIANT), + "GlowSquid" to t(EntityType.GLOW_SQUID), + "Goat" to t(EntityType.GOAT), + "Guardian" to t(EntityType.GUARDIAN), + "Horse" to t(EntityType.HORSE), + "Husk" to t(EntityType.HUSK), + "Illusioner" to t(EntityType.ILLUSIONER), + "LLama" to t(EntityType.LLAMA), + "MagmaCube" to t(EntityType.MAGMA_CUBE), + "Mooshroom" to t(EntityType.MOOSHROOM), + "Mule" to t(EntityType.MULE), "Ocelot" to t(EntityType.OCELOT), - "Blaze" to t(EntityType.BLAZE), + "Panda" to t(EntityType.PANDA), + "Phantom" to t(EntityType.PHANTOM), + "Pig" to t(EntityType.PIG), + "Piglin" to t(EntityType.PIGLIN), + "PiglinBrute" to t(EntityType.PIGLIN_BRUTE), + "Pigman" to t(EntityType.ZOMBIFIED_PIGLIN), + "Pillager" to t(EntityType.PILLAGER), + "Player" to { makeGuiPlayer(fakeWorld) }, + "PolarBear" to t(EntityType.POLAR_BEAR), + "Pufferfish" to t(EntityType.PUFFERFISH), "Rabbit" to t(EntityType.RABBIT), + "Salmom" to t(EntityType.SALMON), + "Salmon" to t(EntityType.SALMON), "Sheep" to t(EntityType.SHEEP), - "Horse" to t(EntityType.HORSE), - "Eisengolem" to t(EntityType.IRON_GOLEM), + "Shulker" to t(EntityType.SHULKER), "Silverfish" to t(EntityType.SILVERFISH), - "Witch" to t(EntityType.WITCH), - "Endermite" to t(EntityType.ENDERMITE), + "Skeleton" to t(EntityType.SKELETON), + "Slime" to t(EntityType.SLIME), + "Sniffer" to t(EntityType.SNIFFER), "Snowman" to t(EntityType.SNOW_GOLEM), - "Villager" to t(EntityType.VILLAGER), - "Guardian" to t(EntityType.GUARDIAN), - "ArmorStand" to t(EntityType.ARMOR_STAND), - "Squid" to t(EntityType.SQUID), - "Bat" to t(EntityType.BAT), "Spider" to t(EntityType.SPIDER), - "CaveSpider" to t(EntityType.CAVE_SPIDER), - "Pigman" to t(EntityType.ZOMBIFIED_PIGLIN), - "Ghast" to t(EntityType.GHAST), - "MagmaCube" to t(EntityType.MAGMA_CUBE), + "Squid" to t(EntityType.SQUID), + "Stray" to t(EntityType.STRAY), + "Strider" to t(EntityType.STRIDER), + "Tadpole" to t(EntityType.TADPOLE), + "TropicalFish" to t(EntityType.TROPICAL_FISH), + "Turtle" to t(EntityType.TURTLE), + "Vex" to t(EntityType.VEX), + "Villager" to t(EntityType.VILLAGER), + "Vindicator" to t(EntityType.VINDICATOR), + "Warden" to t(EntityType.WARDEN), + "Witch" to t(EntityType.WITCH), "Wither" to t(EntityType.WITHER), - "Enderman" to t(EntityType.ENDERMAN), - "Mooshroom" to t(EntityType.MOOSHROOM), "WitherSkeleton" to t(EntityType.WITHER_SKELETON), - "Cow" to t(EntityType.COW), - "Dragon" to t(EntityType.ENDER_DRAGON), - "Player" to { makeGuiPlayer(fakeWorld) }, - "Pig" to t(EntityType.PIG), - "Giant" to t(EntityType.GIANT), + "Wolf" to t(EntityType.WOLF), + "Zoglin" to t(EntityType.ZOGLIN), + "Zombie" to t(EntityType.ZOMBIE), + "ZombieVillager" to t(EntityType.ZOMBIE_VILLAGER) ) val entityModifiers: Map<String, EntityModifier> = mapOf( "playerdata" to ModifyPlayerSkin, @@ -83,7 +124,8 @@ object EntityRenderer { for (modifierJson in modifiers) { val modifier = ErrorUtil.notNullOr( modifierJson["type"]?.asString?.let(entityModifiers::get), - "Could not create entity with id $entityId. Failed to apply modifier $modifierJson") { return null } + "Could not create entity with id $entityId. Failed to apply modifier $modifierJson" + ) { return null } entity = modifier.apply(entity, modifierJson) } return entity @@ -98,7 +140,7 @@ object EntityRenderer { } private val gson = Gson() - fun constructEntity(location: Identifier): LivingEntity? { + fun constructEntity(location: ResourceLocation): LivingEntity? { return constructEntity( gson.fromJson( location.openFirmamentResource().bufferedReader(), JsonObject::class.java @@ -108,7 +150,7 @@ object EntityRenderer { fun renderEntity( entity: LivingEntity, - renderContext: DrawContext, + renderContext: GuiGraphics, posX: Int, posY: Int, // TODO: Add width, height properties here @@ -121,10 +163,10 @@ object EntityRenderer { var bottomOffset = 0.0 var currentEntity = entity val maxSize = entity.iterate { it.firstPassenger as? LivingEntity } - .map { it.height } + .map { it.bbHeight } .sum() while (true) { - currentEntity.age = MC.player?.age ?: 0 + currentEntity.tickCount = MC.player?.tickCount ?: 0 drawEntity( renderContext, posX, @@ -138,14 +180,14 @@ object EntityRenderer { currentEntity ) val next = currentEntity.firstPassenger as? LivingEntity ?: break - bottomOffset += currentEntity.getPassengerRidingPos(next).y.toFloat() * 0.75F + bottomOffset += currentEntity.getPassengerRidingPosition(next).y.toFloat() * 0.75F currentEntity = next } } fun drawEntity( - context: DrawContext, + context: GuiGraphics, x1: Int, y1: Int, x2: Int, @@ -162,38 +204,38 @@ object EntityRenderer { val hw = (x2 - x1) / 2 val hh = (y2 - y1) / 2 val targetYaw = atan(((centerX - mouseX) / hw)).toFloat() - val targetPitch = atan(((centerY - mouseY) / hh)).toFloat() + val targetPitch = atan(((centerY - mouseY) / hh - entity.eyeHeight * hh / 40)).toFloat() val rotateToFaceTheFront = Quaternionf().rotateZ(Math.PI.toFloat()) val rotateToFaceTheCamera = Quaternionf().rotateX(targetPitch * 20.0f * (Math.PI.toFloat() / 180)) rotateToFaceTheFront.mul(rotateToFaceTheCamera) - val oldBodyYaw = entity.bodyYaw - val oldYaw = entity.yaw - val oldPitch = entity.pitch - val oldPrevHeadYaw = entity.prevHeadYaw - val oldHeadYaw = entity.headYaw - entity.bodyYaw = 180.0f + targetYaw * 20.0f - entity.yaw = 180.0f + targetYaw * 40.0f - entity.pitch = -targetPitch * 20.0f - entity.headYaw = entity.yaw - entity.prevHeadYaw = entity.yaw - val vector3f = Vector3f(0.0f, (entity.height / 2.0f + bottomOffset).toFloat(), 0.0f) - InventoryScreen.drawEntity( + val oldBodyYaw = entity.yBodyRot + val oldYaw = entity.yRot + val oldPitch = entity.xRot + val oldPrevHeadYaw = entity.yHeadRotO + val oldHeadYaw = entity.yHeadRot + entity.yBodyRot = 180.0f + targetYaw * 20.0f + entity.yRot = 180.0f + targetYaw * 40.0f + entity.xRot = -targetPitch * 20.0f + entity.yHeadRot = entity.yRot + entity.yHeadRotO = entity.yRot + val vector3f = Vector3f(0.0f, (entity.bbHeight / 2.0f + bottomOffset).toFloat(), 0.0f) + InventoryScreen.renderEntityInInventory( // TODO: fix multiple entities rendering the same entity context, - centerX, - centerY, + x1, y1, + x2, y2, size.toFloat(), vector3f, rotateToFaceTheFront, rotateToFaceTheCamera, entity ) - entity.bodyYaw = oldBodyYaw - entity.yaw = oldYaw - entity.pitch = oldPitch - entity.prevHeadYaw = oldPrevHeadYaw - entity.headYaw = oldHeadYaw + entity.yBodyRot = oldBodyYaw + entity.yRot = oldYaw + entity.xRot = oldPitch + entity.yHeadRotO = oldPrevHeadYaw + entity.yHeadRot = oldHeadYaw context.disableScissor() } - + val defaultSize = Dimension(50, 80) } diff --git a/src/main/kotlin/gui/entity/FakeWorld.kt b/src/main/kotlin/gui/entity/FakeWorld.kt deleted file mode 100644 index ccf6b60..0000000 --- a/src/main/kotlin/gui/entity/FakeWorld.kt +++ /dev/null @@ -1,343 +0,0 @@ -package moe.nea.firmament.gui.entity - -import java.util.UUID -import java.util.function.BooleanSupplier -import java.util.function.Consumer -import net.minecraft.block.Block -import net.minecraft.block.BlockState -import net.minecraft.client.gui.screen.world.SelectWorldScreen -import net.minecraft.component.type.MapIdComponent -import net.minecraft.entity.Entity -import net.minecraft.entity.boss.dragon.EnderDragonPart -import net.minecraft.entity.damage.DamageSource -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.fluid.Fluid -import net.minecraft.item.FuelRegistry -import net.minecraft.item.map.MapState -import net.minecraft.particle.ParticleEffect -import net.minecraft.recipe.BrewingRecipeRegistry -import net.minecraft.recipe.RecipeManager -import net.minecraft.recipe.RecipePropertySet -import net.minecraft.recipe.StonecuttingRecipe -import net.minecraft.recipe.display.CuttingRecipeDisplay -import net.minecraft.registry.DynamicRegistryManager -import net.minecraft.registry.Registries -import net.minecraft.registry.RegistryKey -import net.minecraft.registry.RegistryKeys -import net.minecraft.registry.ServerDynamicRegistryType -import net.minecraft.registry.entry.RegistryEntry -import net.minecraft.resource.DataConfiguration -import net.minecraft.resource.ResourcePackManager -import net.minecraft.resource.featuretoggle.FeatureFlags -import net.minecraft.resource.featuretoggle.FeatureSet -import net.minecraft.scoreboard.Scoreboard -import net.minecraft.server.SaveLoading -import net.minecraft.server.command.CommandManager -import net.minecraft.sound.SoundCategory -import net.minecraft.sound.SoundEvent -import net.minecraft.util.Identifier -import net.minecraft.util.TypeFilter -import net.minecraft.util.function.LazyIterationConsumer -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Box -import net.minecraft.util.math.ChunkPos -import net.minecraft.util.math.Direction -import net.minecraft.util.math.Vec3d -import net.minecraft.world.BlockView -import net.minecraft.world.Difficulty -import net.minecraft.world.MutableWorldProperties -import net.minecraft.world.World -import net.minecraft.world.biome.Biome -import net.minecraft.world.biome.BiomeKeys -import net.minecraft.world.chunk.Chunk -import net.minecraft.world.chunk.ChunkManager -import net.minecraft.world.chunk.ChunkStatus -import net.minecraft.world.chunk.EmptyChunk -import net.minecraft.world.chunk.light.LightingProvider -import net.minecraft.world.entity.EntityLookup -import net.minecraft.world.event.GameEvent -import net.minecraft.world.explosion.ExplosionBehavior -import net.minecraft.world.tick.OrderedTick -import net.minecraft.world.tick.QueryableTickScheduler -import net.minecraft.world.tick.TickManager -import moe.nea.firmament.util.MC - -fun createDynamicRegistry(): DynamicRegistryManager.Immutable { - // TODO: use SaveLoading.load() to properly load a full registry - return DynamicRegistryManager.of(Registries.REGISTRIES) -} - -class FakeWorld( - registries: DynamicRegistryManager.Immutable = createDynamicRegistry(), -) : World( - Properties, - RegistryKey.of(RegistryKeys.WORLD, Identifier.of("firmament", "fakeworld")), - registries, - MC.defaultRegistries.getOrThrow(RegistryKeys.DIMENSION_TYPE) - .getOrThrow(RegistryKey.of(RegistryKeys.DIMENSION_TYPE, Identifier.of("minecraft", "overworld"))), - true, - false, - 0L, - 0 -) { - object Properties : MutableWorldProperties { - override fun getSpawnPos(): BlockPos { - return BlockPos.ORIGIN - } - - override fun getSpawnAngle(): Float { - return 0F - } - - override fun getTime(): Long { - return 0 - } - - override fun getTimeOfDay(): Long { - return 0 - } - - override fun isThundering(): Boolean { - return false - } - - override fun isRaining(): Boolean { - return false - } - - override fun setRaining(raining: Boolean) { - } - - override fun isHardcore(): Boolean { - return false - } - - override fun getDifficulty(): Difficulty { - return Difficulty.HARD - } - - override fun isDifficultyLocked(): Boolean { - return false - } - - override fun setSpawnPos(pos: BlockPos?, angle: Float) {} - } - - override fun getPlayers(): List<PlayerEntity> { - return emptyList() - } - - override fun getBrightness(direction: Direction?, shaded: Boolean): Float { - return 1f - } - - override fun getGeneratorStoredBiome(biomeX: Int, biomeY: Int, biomeZ: Int): RegistryEntry<Biome> { - return registryManager.getOptionalEntry(BiomeKeys.PLAINS).get() - } - - override fun getSeaLevel(): Int { - return 0 - } - - override fun getEnabledFeatures(): FeatureSet { - return FeatureFlags.VANILLA_FEATURES - } - - class FakeTickScheduler<T> : QueryableTickScheduler<T> { - override fun scheduleTick(orderedTick: OrderedTick<T>?) { - } - - override fun isQueued(pos: BlockPos?, type: T): Boolean { - return true - } - - override fun getTickCount(): Int { - return 0 - } - - override fun isTicking(pos: BlockPos?, type: T): Boolean { - return true - } - - } - - override fun getBlockTickScheduler(): QueryableTickScheduler<Block> { - return FakeTickScheduler() - } - - override fun getFluidTickScheduler(): QueryableTickScheduler<Fluid> { - return FakeTickScheduler() - } - - - class FakeChunkManager(val world: FakeWorld) : ChunkManager() { - override fun getChunk(x: Int, z: Int, leastStatus: ChunkStatus?, create: Boolean): Chunk { - return EmptyChunk( - world, - ChunkPos(x, z), - world.registryManager.getOptionalEntry(BiomeKeys.PLAINS).get() - ) - } - - override fun getWorld(): BlockView { - return world - } - - override fun tick(shouldKeepTicking: BooleanSupplier?, tickChunks: Boolean) { - } - - override fun getDebugString(): String { - return "FakeChunkManager" - } - - override fun getLoadedChunkCount(): Int { - return 0 - } - - override fun getLightingProvider(): LightingProvider { - return FakeLightingProvider(this) - } - } - - class FakeLightingProvider(chunkManager: FakeChunkManager) : LightingProvider(chunkManager, false, false) - - override fun getChunkManager(): ChunkManager { - return FakeChunkManager(this) - } - - override fun playSound( - source: PlayerEntity?, - x: Double, - y: Double, - z: Double, - sound: RegistryEntry<SoundEvent>?, - category: SoundCategory?, - volume: Float, - pitch: Float, - seed: Long - ) { - } - - override fun syncWorldEvent(player: PlayerEntity?, eventId: Int, pos: BlockPos?, data: Int) { - } - - override fun emitGameEvent(event: RegistryEntry<GameEvent>?, emitterPos: Vec3d?, emitter: GameEvent.Emitter?) { - } - - override fun updateListeners(pos: BlockPos?, oldState: BlockState?, newState: BlockState?, flags: Int) { - } - - override fun playSoundFromEntity( - source: PlayerEntity?, - entity: Entity?, - sound: RegistryEntry<SoundEvent>?, - category: SoundCategory?, - volume: Float, - pitch: Float, - seed: Long - ) { - } - - override fun createExplosion( - entity: Entity?, - damageSource: DamageSource?, - behavior: ExplosionBehavior?, - x: Double, - y: Double, - z: Double, - power: Float, - createFire: Boolean, - explosionSourceType: ExplosionSourceType?, - smallParticle: ParticleEffect?, - largeParticle: ParticleEffect?, - soundEvent: RegistryEntry<SoundEvent>? - ) { - TODO("Not yet implemented") - } - - override fun asString(): String { - return "FakeWorld" - } - - override fun getEntityById(id: Int): Entity? { - return null - } - - override fun getEnderDragonParts(): MutableCollection<EnderDragonPart> { - return mutableListOf() - } - - override fun getTickManager(): TickManager { - return TickManager() - } - - override fun getMapState(id: MapIdComponent?): MapState? { - return null - } - - override fun putMapState(id: MapIdComponent?, state: MapState?) { - } - - override fun increaseAndGetMapId(): MapIdComponent { - return MapIdComponent(0) - } - - override fun setBlockBreakingInfo(entityId: Int, pos: BlockPos?, progress: Int) { - } - - override fun getScoreboard(): Scoreboard { - return Scoreboard() - } - - override fun getRecipeManager(): RecipeManager { - return object : RecipeManager { - override fun getPropertySet(key: RegistryKey<RecipePropertySet>?): RecipePropertySet { - return RecipePropertySet.EMPTY - } - - override fun getStonecutterRecipes(): CuttingRecipeDisplay.Grouping<StonecuttingRecipe> { - return CuttingRecipeDisplay.Grouping.empty() - } - } - } - - object FakeEntityLookup : EntityLookup<Entity> { - override fun get(id: Int): Entity? { - return null - } - - override fun get(uuid: UUID?): Entity? { - return null - } - - override fun iterate(): MutableIterable<Entity> { - return mutableListOf() - } - - override fun <U : Entity?> forEachIntersects( - filter: TypeFilter<Entity, U>?, - box: Box?, - consumer: LazyIterationConsumer<U>? - ) { - } - - override fun forEachIntersects(box: Box?, action: Consumer<Entity>?) { - } - - override fun <U : Entity?> forEach(filter: TypeFilter<Entity, U>?, consumer: LazyIterationConsumer<U>?) { - } - - } - - override fun getEntityLookup(): EntityLookup<Entity> { - return FakeEntityLookup - } - - override fun getBrewingRecipeRegistry(): BrewingRecipeRegistry { - return BrewingRecipeRegistry.EMPTY - } - - override fun getFuelRegistry(): FuelRegistry { - TODO("Not yet implemented") - } -} diff --git a/src/main/kotlin/gui/entity/GuiPlayer.kt b/src/main/kotlin/gui/entity/GuiPlayer.kt index f728dbf..b53f68c 100644 --- a/src/main/kotlin/gui/entity/GuiPlayer.kt +++ b/src/main/kotlin/gui/entity/GuiPlayer.kt @@ -1,62 +1,28 @@ package moe.nea.firmament.gui.entity -import com.mojang.authlib.GameProfile -import java.util.UUID -import net.minecraft.client.network.AbstractClientPlayerEntity -import net.minecraft.client.util.DefaultSkinHelper -import net.minecraft.client.util.SkinTextures -import net.minecraft.client.util.SkinTextures.Model -import net.minecraft.client.world.ClientWorld -import net.minecraft.util.Identifier -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Vec3d -import net.minecraft.world.World +import net.minecraft.client.entity.ClientMannequin +import net.minecraft.client.resources.DefaultPlayerSkin +import net.minecraft.client.multiplayer.ClientLevel +import net.minecraft.world.entity.player.PlayerSkin +import net.minecraft.world.level.Level +import moe.nea.firmament.util.MC -/** - * @see moe.nea.firmament.init.EarlyRiser - */ -fun makeGuiPlayer(world: World): GuiPlayer { - val constructor = GuiPlayer::class.java.getDeclaredConstructor( - World::class.java, - BlockPos::class.java, - Float::class.javaPrimitiveType, - GameProfile::class.java - ) - val player = constructor.newInstance(world, BlockPos.ORIGIN, 0F, GameProfile(UUID.randomUUID(), "Linnea")) - player.postInit() +fun makeGuiPlayer(world: Level): GuiPlayer { + val player = GuiPlayer(MC.instance.level!!) return player } -class GuiPlayer(world: ClientWorld?, profile: GameProfile?) : AbstractClientPlayerEntity(world, profile) { +class GuiPlayer(world: ClientLevel?) : ClientMannequin(world, MC.instance.playerSkinRenderCache()) { override fun isSpectator(): Boolean { return false } - fun postInit() { - skinTexture = DefaultSkinHelper.getSkinTextures(this.getUuid()).texture - lastVelocity = Vec3d.ZERO - model = Model.WIDE - } - - override fun isCreative(): Boolean { - return false - } - - override fun shouldRenderName(): Boolean { + override fun shouldShowName(): Boolean { return false } - lateinit var skinTexture: Identifier - var capeTexture: Identifier? = null - var model: Model = Model.WIDE - override fun getSkinTextures(): SkinTextures { - return SkinTextures( - skinTexture, - null, - capeTexture, - null, - model, - true - ) + var skinTextures: PlayerSkin = DefaultPlayerSkin.get(this.uuid) // TODO: 1.21.10 + override fun getSkin(): PlayerSkin { + return skinTextures } } diff --git a/src/main/kotlin/gui/entity/ModifyAge.kt b/src/main/kotlin/gui/entity/ModifyAge.kt index a65c368..99154ef 100644 --- a/src/main/kotlin/gui/entity/ModifyAge.kt +++ b/src/main/kotlin/gui/entity/ModifyAge.kt @@ -2,19 +2,19 @@ package moe.nea.firmament.gui.entity import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.decoration.ArmorStandEntity -import net.minecraft.entity.mob.ZombieEntity -import net.minecraft.entity.passive.PassiveEntity +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.decoration.ArmorStand +import net.minecraft.world.entity.monster.Zombie +import net.minecraft.world.entity.AgeableMob object ModifyAge : EntityModifier { override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { val isBaby = info["baby"]?.asBoolean ?: false - if (entity is PassiveEntity) { - entity.breedingAge = if (isBaby) -1 else 1 - } else if (entity is ZombieEntity) { + if (entity is AgeableMob) { + entity.age = if (isBaby) -1 else 1 + } else if (entity is Zombie) { entity.isBaby = isBaby - } else if (entity is ArmorStandEntity) { + } else if (entity is ArmorStand) { entity.isSmall = isBaby } else { error("Cannot set age for $entity") diff --git a/src/main/kotlin/gui/entity/ModifyCharged.kt b/src/main/kotlin/gui/entity/ModifyCharged.kt index d22f6e3..23fd495 100644 --- a/src/main/kotlin/gui/entity/ModifyCharged.kt +++ b/src/main/kotlin/gui/entity/ModifyCharged.kt @@ -2,13 +2,13 @@ package moe.nea.firmament.gui.entity import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.mob.CreeperEntity +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.monster.Creeper object ModifyCharged : EntityModifier { override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - require(entity is CreeperEntity) - entity.dataTracker.set(CreeperEntity.CHARGED, true) + require(entity is Creeper) + entity.entityData.set(Creeper.DATA_IS_POWERED, true) return entity } } diff --git a/src/main/kotlin/gui/entity/ModifyEquipment.kt b/src/main/kotlin/gui/entity/ModifyEquipment.kt index a558936..9c43e73 100644 --- a/src/main/kotlin/gui/entity/ModifyEquipment.kt +++ b/src/main/kotlin/gui/entity/ModifyEquipment.kt @@ -1,17 +1,18 @@ package moe.nea.firmament.gui.entity import com.google.gson.JsonObject -import net.minecraft.component.DataComponentTypes -import net.minecraft.component.type.DyedColorComponent -import net.minecraft.entity.EquipmentSlot -import net.minecraft.entity.LivingEntity -import net.minecraft.item.Item -import net.minecraft.item.ItemStack -import net.minecraft.item.Items +import net.minecraft.core.component.DataComponents +import net.minecraft.world.item.component.DyedItemColor +import net.minecraft.world.entity.EquipmentSlot +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import moe.nea.firmament.repo.ExpensiveItemCacheApi import moe.nea.firmament.repo.SBItemStack import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.mc.arbitraryUUID import moe.nea.firmament.util.mc.setEncodedSkullOwner -import moe.nea.firmament.util.mc.zeroUUID object ModifyEquipment : EntityModifier { val names = mapOf( @@ -25,18 +26,19 @@ object ModifyEquipment : EntityModifier { override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { names.forEach { (key, slot) -> info[key]?.let { - entity.equipStack(slot, createItem(it.asString)) + entity.setItemSlot(slot, createItem(it.asString)) } } return entity } + @OptIn(ExpensiveItemCacheApi::class) private fun createItem(item: String): ItemStack { val split = item.split("#") if (split.size != 2) return SBItemStack(SkyblockId(item)).asImmutableItemStack() val (type, data) = split return when (type) { - "SKULL" -> ItemStack(Items.PLAYER_HEAD).also { it.setEncodedSkullOwner(zeroUUID, data) } + "SKULL" -> ItemStack(Items.PLAYER_HEAD).also { it.setEncodedSkullOwner(arbitraryUUID, data) } "LEATHER_LEGGINGS" -> coloredLeatherArmor(Items.LEATHER_LEGGINGS, data) "LEATHER_BOOTS" -> coloredLeatherArmor(Items.LEATHER_BOOTS, data) "LEATHER_HELMET" -> coloredLeatherArmor(Items.LEATHER_HELMET, data) @@ -47,7 +49,7 @@ object ModifyEquipment : EntityModifier { private fun coloredLeatherArmor(leatherArmor: Item, data: String): ItemStack { val stack = ItemStack(leatherArmor) - stack.set(DataComponentTypes.DYED_COLOR, DyedColorComponent(data.toInt(16), false)) + stack.set(DataComponents.DYED_COLOR, DyedItemColor(data.toInt(16))) return stack } } diff --git a/src/main/kotlin/gui/entity/ModifyHorse.kt b/src/main/kotlin/gui/entity/ModifyHorse.kt index f094ca4..a870bf1 100644 --- a/src/main/kotlin/gui/entity/ModifyHorse.kt +++ b/src/main/kotlin/gui/entity/ModifyHorse.kt @@ -1,62 +1,58 @@ - package moe.nea.firmament.gui.entity import com.google.gson.JsonNull import com.google.gson.JsonObject -import kotlin.experimental.and -import kotlin.experimental.inv -import kotlin.experimental.or -import net.minecraft.entity.EntityType -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.SpawnReason -import net.minecraft.entity.passive.AbstractHorseEntity -import net.minecraft.item.ItemStack -import net.minecraft.item.Items +import net.minecraft.world.entity.EntityType +import net.minecraft.world.entity.EquipmentSlot +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.EntitySpawnReason +import net.minecraft.world.entity.animal.horse.AbstractHorse +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items import moe.nea.firmament.gui.entity.EntityRenderer.fakeWorld object ModifyHorse : EntityModifier { - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - require(entity is AbstractHorseEntity) - var entity: AbstractHorseEntity = entity - info["kind"]?.let { - entity = when (it.asString) { - "skeleton" -> EntityType.SKELETON_HORSE.create(fakeWorld, SpawnReason.LOAD)!! - "zombie" -> EntityType.ZOMBIE_HORSE.create(fakeWorld, SpawnReason.LOAD)!! - "mule" -> EntityType.MULE.create(fakeWorld, SpawnReason.LOAD)!! - "donkey" -> EntityType.DONKEY.create(fakeWorld, SpawnReason.LOAD)!! - "horse" -> EntityType.HORSE.create(fakeWorld, SpawnReason.LOAD)!! - else -> error("Unknown horse kind $it") - } - } - info["armor"]?.let { - if (it is JsonNull) { - entity.setHorseArmor(ItemStack.EMPTY) - } else { - when (it.asString) { - "iron" -> entity.setHorseArmor(ItemStack(Items.IRON_HORSE_ARMOR)) - "golden" -> entity.setHorseArmor(ItemStack(Items.GOLDEN_HORSE_ARMOR)) - "diamond" -> entity.setHorseArmor(ItemStack(Items.DIAMOND_HORSE_ARMOR)) - else -> error("Unknown horse armor $it") - } - } - } - info["saddled"]?.let { - entity.setIsSaddled(it.asBoolean) - } - return entity - } + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is AbstractHorse) + var entity: AbstractHorse = entity + info["kind"]?.let { + entity = when (it.asString) { + "skeleton" -> EntityType.SKELETON_HORSE.create(fakeWorld, EntitySpawnReason.LOAD)!! + "zombie" -> EntityType.ZOMBIE_HORSE.create(fakeWorld, EntitySpawnReason.LOAD)!! + "mule" -> EntityType.MULE.create(fakeWorld, EntitySpawnReason.LOAD)!! + "donkey" -> EntityType.DONKEY.create(fakeWorld, EntitySpawnReason.LOAD)!! + "horse" -> EntityType.HORSE.create(fakeWorld, EntitySpawnReason.LOAD)!! + else -> error("Unknown horse kind $it") + } + } + info["armor"]?.let { + if (it is JsonNull) { + entity.setHorseArmor(ItemStack.EMPTY) + } else { + when (it.asString) { + "iron" -> entity.setHorseArmor(ItemStack(Items.IRON_HORSE_ARMOR)) + "golden" -> entity.setHorseArmor(ItemStack(Items.GOLDEN_HORSE_ARMOR)) + "diamond" -> entity.setHorseArmor(ItemStack(Items.DIAMOND_HORSE_ARMOR)) + else -> error("Unknown horse armor $it") + } + } + } + info["saddled"]?.let { + entity.setIsSaddled(it.asBoolean) + } + return entity + } } -fun AbstractHorseEntity.setIsSaddled(shouldBeSaddled: Boolean) { - val oldFlag = dataTracker.get(AbstractHorseEntity.HORSE_FLAGS) - dataTracker.set( - AbstractHorseEntity.HORSE_FLAGS, - if (shouldBeSaddled) oldFlag or AbstractHorseEntity.SADDLED_FLAG.toByte() - else oldFlag and AbstractHorseEntity.SADDLED_FLAG.toByte().inv() - ) +fun AbstractHorse.setIsSaddled(shouldBeSaddled: Boolean) { + this.setItemSlot( + EquipmentSlot.SADDLE, + if (shouldBeSaddled) ItemStack(Items.SADDLE) + else ItemStack.EMPTY + ) } -fun AbstractHorseEntity.setHorseArmor(itemStack: ItemStack) { - items.setStack(1, itemStack) +fun AbstractHorse.setHorseArmor(itemStack: ItemStack) { + bodyArmorItem = itemStack } diff --git a/src/main/kotlin/gui/entity/ModifyInvisible.kt b/src/main/kotlin/gui/entity/ModifyInvisible.kt index 8d36991..7a54ab1 100644 --- a/src/main/kotlin/gui/entity/ModifyInvisible.kt +++ b/src/main/kotlin/gui/entity/ModifyInvisible.kt @@ -2,7 +2,7 @@ package moe.nea.firmament.gui.entity import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity +import net.minecraft.world.entity.LivingEntity object ModifyInvisible : EntityModifier { override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { diff --git a/src/main/kotlin/gui/entity/ModifyName.kt b/src/main/kotlin/gui/entity/ModifyName.kt index a03da96..6def0b4 100644 --- a/src/main/kotlin/gui/entity/ModifyName.kt +++ b/src/main/kotlin/gui/entity/ModifyName.kt @@ -2,12 +2,12 @@ package moe.nea.firmament.gui.entity import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity -import net.minecraft.text.Text +import net.minecraft.world.entity.LivingEntity +import net.minecraft.network.chat.Component object ModifyName : EntityModifier { override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - entity.customName = Text.literal(info.get("name").asString) + entity.customName = Component.literal(info.get("name").asString) return entity } diff --git a/src/main/kotlin/gui/entity/ModifyPlayerSkin.kt b/src/main/kotlin/gui/entity/ModifyPlayerSkin.kt index 28f0070..0b393bb 100644 --- a/src/main/kotlin/gui/entity/ModifyPlayerSkin.kt +++ b/src/main/kotlin/gui/entity/ModifyPlayerSkin.kt @@ -1,47 +1,57 @@ - package moe.nea.firmament.gui.entity import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import kotlin.experimental.and import kotlin.experimental.or -import net.minecraft.client.util.SkinTextures -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.entity.player.PlayerModelPart -import net.minecraft.util.Identifier +import net.minecraft.client.entity.ClientAvatarEntity +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.Avatar +import net.minecraft.world.entity.player.Player +import net.minecraft.world.entity.player.PlayerModelPart +import net.minecraft.world.entity.player.PlayerModelType +import net.minecraft.world.entity.player.PlayerSkin +import net.minecraft.core.ClientAsset +import net.minecraft.resources.ResourceLocation object ModifyPlayerSkin : EntityModifier { - val playerModelPartIndex = PlayerModelPart.entries.associateBy { it.getName() } - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - require(entity is GuiPlayer) - info["cape"]?.let { - entity.capeTexture = Identifier.of(it.asString) - } - info["skin"]?.let { - entity.skinTexture = Identifier.of(it.asString) - } - info["slim"]?.let { - entity.model = if (it.asBoolean) SkinTextures.Model.SLIM else SkinTextures.Model.WIDE - } - info["parts"]?.let { - var trackedData = entity.dataTracker.get(PlayerEntity.PLAYER_MODEL_PARTS) - if (it is JsonPrimitive && it.isBoolean) { - trackedData = (if (it.asBoolean) -1 else 0).toByte() - } else { - val obj = it.asJsonObject - for ((k, v) in obj.entrySet()) { - val part = playerModelPartIndex[k]!! - trackedData = if (v.asBoolean) { - trackedData and (part.bitFlag.inv().toByte()) - } else { - trackedData or (part.bitFlag.toByte()) - } - } - } - entity.dataTracker.set(PlayerEntity.PLAYER_MODEL_PARTS, trackedData) - } - return entity - } + val playerModelPartIndex = PlayerModelPart.entries.associateBy { it.id } + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is GuiPlayer) + var capeTexture = entity.skinTextures.cape + var model = entity.skinTextures.model + var bodyTexture = entity.skinTextures.body + fun mkTexAsset(id: ResourceLocation) = ClientAsset.ResourceTexture(id, id) + info["cape"]?.let { + capeTexture = mkTexAsset(ResourceLocation.parse(it.asString)) + } + info["skin"]?.let { + bodyTexture = mkTexAsset(ResourceLocation.parse(it.asString)) + } + info["slim"]?.let { + model = if (it.asBoolean) PlayerModelType.SLIM else PlayerModelType.WIDE + } + info["parts"]?.let { + var trackedData = entity.entityData.get(Avatar.DATA_PLAYER_MODE_CUSTOMISATION) + if (it is JsonPrimitive && it.isBoolean) { + trackedData = (if (it.asBoolean) -1 else 0).toByte() + } else { + val obj = it.asJsonObject + for ((k, v) in obj.entrySet()) { + val part = playerModelPartIndex[k]!! + trackedData = if (v.asBoolean) { + trackedData and (part.mask.inv().toByte()) + } else { + trackedData or (part.mask.toByte()) + } + } + } + entity.entityData.set(Player.DATA_PLAYER_MODE_CUSTOMISATION, trackedData) + } + entity.skinTextures = PlayerSkin( + bodyTexture, capeTexture, null, model, true + ) + return entity + } } diff --git a/src/main/kotlin/gui/entity/ModifyRiding.kt b/src/main/kotlin/gui/entity/ModifyRiding.kt index 5c4c78d..51fcfae 100644 --- a/src/main/kotlin/gui/entity/ModifyRiding.kt +++ b/src/main/kotlin/gui/entity/ModifyRiding.kt @@ -2,13 +2,13 @@ package moe.nea.firmament.gui.entity import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity +import net.minecraft.world.entity.LivingEntity object ModifyRiding : EntityModifier { override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { val newEntity = EntityRenderer.constructEntity(info) require(newEntity != null) - newEntity.startRiding(entity, true) + newEntity.startRiding(entity, true, false) return entity } diff --git a/src/main/kotlin/gui/entity/ModifyWither.kt b/src/main/kotlin/gui/entity/ModifyWither.kt index 6083d88..67252b8 100644 --- a/src/main/kotlin/gui/entity/ModifyWither.kt +++ b/src/main/kotlin/gui/entity/ModifyWither.kt @@ -2,14 +2,14 @@ package moe.nea.firmament.gui.entity import com.google.gson.JsonObject -import net.minecraft.entity.LivingEntity -import net.minecraft.entity.boss.WitherEntity +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.boss.wither.WitherBoss object ModifyWither : EntityModifier { override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - require(entity is WitherEntity) + require(entity is WitherBoss) info["tiny"]?.let { - entity.setInvulTimer(if (it.asBoolean) 800 else 0) + entity.invulnerableTicks = if (it.asBoolean) 800 else 0 } info["armored"]?.let { entity.health = if (it.asBoolean) 1F else entity.maxHealth diff --git a/src/main/kotlin/gui/hud/MoulConfigHud.kt b/src/main/kotlin/gui/hud/MoulConfigHud.kt index e99b069..b5d7cf7 100644 --- a/src/main/kotlin/gui/hud/MoulConfigHud.kt +++ b/src/main/kotlin/gui/hud/MoulConfigHud.kt @@ -1,66 +1,68 @@ - package moe.nea.firmament.gui.hud -import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper import io.github.notenoughupdates.moulconfig.gui.GuiContext import io.github.notenoughupdates.moulconfig.gui.component.TextComponent -import net.minecraft.resource.ResourceManager -import net.minecraft.resource.SynchronousResourceReloader +import io.github.notenoughupdates.moulconfig.platform.MoulConfigScreenComponent +import net.minecraft.server.packs.resources.ResourceManager +import net.minecraft.server.packs.resources.ResourceManagerReloadListener +import net.minecraft.network.chat.Component import moe.nea.firmament.events.FinalizeResourceManagerEvent import moe.nea.firmament.events.HudRenderEvent import moe.nea.firmament.gui.config.HudMeta +import moe.nea.firmament.jarvis.JarvisIntegration import moe.nea.firmament.util.MC import moe.nea.firmament.util.MoulConfigUtils abstract class MoulConfigHud( - val name: String, - val hudMeta: HudMeta, + val name: String, + val hudMeta: HudMeta, ) { - companion object { - private val componentWrapper by lazy { - object : GuiComponentWrapper(GuiContext(TextComponent("§cERROR"))) { - init { - this.client = MC.instance - } - } - } - } + companion object { + private val componentWrapper by lazy { + object : MoulConfigScreenComponent(Component.empty(), GuiContext(TextComponent("§cERROR")), null) { + init { + this.minecraft = MC.instance + } + } + } + } - private var fragment: GuiContext? = null + private var fragment: GuiContext? = null - fun forceInit() { - } + fun forceInit() { + } - open fun shouldRender(): Boolean { - return true - } + open fun shouldRender(): Boolean { + return true + } - init { - require(name.matches("^[a-z_/]+$".toRegex())) - HudRenderEvent.subscribe("MoulConfigHud:render") { - if (!shouldRender()) return@subscribe - val renderContext = componentWrapper.createContext(it.context) - if (fragment == null) - loadFragment() - it.context.matrices.push() - hudMeta.applyTransformations(it.context.matrices) - val renderContextTranslated = - renderContext.translated(hudMeta.absoluteX, hudMeta.absoluteY, hudMeta.width, hudMeta.height) - .scaled(hudMeta.scale) - fragment!!.root.render(renderContextTranslated) - it.context.matrices.pop() - } - FinalizeResourceManagerEvent.subscribe("MoulConfigHud:finalizeResourceManager") { - MC.resourceManager.registerReloader(object : SynchronousResourceReloader { - override fun reload(manager: ResourceManager?) { - fragment = null - } - }) - } - } + init { + require(name.matches("^[a-z_/]+$".toRegex())) + HudRenderEvent.subscribe("MoulConfigHud:render") { + if (!shouldRender()) return@subscribe + val renderContext = componentWrapper.createContext(it.context) + if (fragment == null) + loadFragment() + it.context.pose().pushMatrix() + hudMeta.applyTransformations(it.context.pose()) + val pos = hudMeta.getEffectivePosition(JarvisIntegration.jarvis) + val renderContextTranslated = + renderContext.translated(pos.x(), pos.y(), hudMeta.effectiveWidth, hudMeta.effectiveHeight) + .scaled(hudMeta.scale) + fragment!!.root.render(renderContextTranslated) + it.context.pose().popMatrix() + } + FinalizeResourceManagerEvent.subscribe("MoulConfigHud:finalizeResourceManager") { + MC.resourceManager.registerReloadListener(object : ResourceManagerReloadListener { + override fun onResourceManagerReload(manager: ResourceManager?) { + fragment = null + } + }) + } + } - fun loadFragment() { - fragment = MoulConfigUtils.loadGui(name, this) - } + fun loadFragment() { + fragment = MoulConfigUtils.loadGui(name, this) + } } |
