diff options
author | Linnea Gräf <nea@nea.moe> | 2024-08-28 19:04:24 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-08-28 19:04:24 +0200 |
commit | d2f240ff0ca0d27f417f837e706c781a98c31311 (patch) | |
tree | 0db7aff6cc14deaf36eed83889d59fd6b3a6f599 /src/main/kotlin/gui | |
parent | a6906308163aa3b2d18fa1dc1aa71ac9bbcc83ab (diff) | |
download | Firmament-d2f240ff0ca0d27f417f837e706c781a98c31311.tar.gz Firmament-d2f240ff0ca0d27f417f837e706c781a98c31311.tar.bz2 Firmament-d2f240ff0ca0d27f417f837e706c781a98c31311.zip |
Refactor source layout
Introduce compat source sets and move all kotlin sources to the main directory
[no changelog]
Diffstat (limited to 'src/main/kotlin/gui')
34 files changed, 2249 insertions, 0 deletions
diff --git a/src/main/kotlin/gui/BarComponent.kt b/src/main/kotlin/gui/BarComponent.kt new file mode 100644 index 0000000..8ef0753 --- /dev/null +++ b/src/main/kotlin/gui/BarComponent.kt @@ -0,0 +1,125 @@ + +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 me.shedaniel.math.Color +import net.minecraft.client.gui.DrawContext +import net.minecraft.util.Identifier +import moe.nea.firmament.Firmament + +class BarComponent( + val progress: GetSetter<Double>, val total: GetSetter<Double>, + val fillColor: Color, + val emptyColor: Color, +) : GuiComponent() { + override fun getWidth(): Int { + return 80 + } + + override fun getHeight(): Int { + return 8 + } + + data class Texture( + val identifier: Identifier, + 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( + identifier, + x, y, x + width, x + height, 0, + u1, u2, v1, v2, + color.red / 255F, + color.green / 255F, + color.blue / 255F, + color.alpha / 255F, + ) + } + } + + companion object { + val resource = Firmament.identifier("textures/gui/bar.png") + val left = Texture(resource, 0 / 64F, 0 / 64F, 4 / 64F, 8 / 64F) + val middle = Texture(resource, 4 / 64F, 0 / 64F, 8 / 64F, 8 / 64F) + val right = Texture(resource, 8 / 64F, 0 / 64F, 12 / 64F, 8 / 64F) + val segmentOverlay = Texture(resource, 12 / 64F, 0 / 64F, 15 / 64F, 8 / 64F) + } + + private fun drawSection( + context: DrawContext, + 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) + return + } + if (sectionStart > progress.get() && width == 4) { + texture.draw(context, x, y, 4, 8, emptyColor) + return + } + val increasePerPixel = (sectionEnd - sectionStart) / width + var valueAtPixel = sectionStart + for (i in (0 until width)) { + val newTex = + Texture(texture.identifier, texture.u1 + i / 64F, texture.v1, texture.u1 + (i + 1) / 64F, texture.v2) + newTex.draw( + context, x + i, y, 1, 8, + if (valueAtPixel < progress.get()) fillColor else emptyColor + ) + valueAtPixel += increasePerPixel + } + } + + override fun render(context: GuiImmediateContext) { + val renderContext = (context.renderContext as ModernRenderContext).drawContext + var i = 0 + val x = 0 + val y = 0 + while (i < context.width - 4) { + drawSection( + renderContext, + if (i == 0) left else middle, + x + i, y, + (context.width - (i + 4)).coerceAtMost(4), + i * total.get() / context.width, (i + 4) * total.get() / context.width + ) + i += 4 + } + drawSection( + renderContext, + right, + x + context.width - 4, + y, + 4, + (context.width - 4) * total.get() / context.width, + total.get() + ) + RenderSystem.setShaderColor(1F, 1F, 1F, 1F) + + } + +} + +fun Identifier.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/FirmButtonComponent.kt b/src/main/kotlin/gui/FirmButtonComponent.kt new file mode 100644 index 0000000..82e5b05 --- /dev/null +++ b/src/main/kotlin/gui/FirmButtonComponent.kt @@ -0,0 +1,81 @@ + +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.common.MyResourceLocation +import io.github.notenoughupdates.moulconfig.deps.libninepatch.NinePatch +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.gui.component.PanelComponent +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, +) : 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 + } + + 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() + } +} diff --git a/src/main/kotlin/gui/FirmHoverComponent.kt b/src/main/kotlin/gui/FirmHoverComponent.kt new file mode 100644 index 0000000..b1792ce --- /dev/null +++ b/src/main/kotlin/gui/FirmHoverComponent.kt @@ -0,0 +1,59 @@ +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent +import io.github.notenoughupdates.moulconfig.gui.MouseEvent +import java.util.function.BiFunction +import java.util.function.Supplier +import kotlin.time.Duration +import moe.nea.firmament.util.TimeMark + +class FirmHoverComponent( + 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) + } +} diff --git a/src/main/kotlin/gui/FixedComponent.kt b/src/main/kotlin/gui/FixedComponent.kt new file mode 100644 index 0000000..ae1da2d --- /dev/null +++ b/src/main/kotlin/gui/FixedComponent.kt @@ -0,0 +1,38 @@ + +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent +import io.github.notenoughupdates.moulconfig.gui.MouseEvent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import java.util.function.BiFunction + +class FixedComponent( + val fixedWidth: GetSetter<Int>?, + val fixedHeight: GetSetter<Int>?, + val component: GuiComponent, +) : GuiComponent() { + override fun getWidth(): Int = fixedWidth?.get() ?: component.width + + override fun getHeight(): Int = fixedHeight?.get() ?: component.height + + override fun <T : Any?> foldChildren(initial: T, visitor: BiFunction<GuiComponent, T, T>): T { + return visitor.apply(component, initial) + } + + fun fixContext(context: GuiImmediateContext): GuiImmediateContext = + context.translated(0, 0, width, height) + + override fun render(context: GuiImmediateContext) { + component.render(fixContext(context)) + } + + override fun mouseEvent(mouseEvent: MouseEvent, context: GuiImmediateContext): Boolean { + return component.mouseEvent(mouseEvent, fixContext(context)) + } + + override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { + return component.keyboardEvent(event, fixContext(context)) + } +} diff --git a/src/main/kotlin/gui/ImageComponent.kt b/src/main/kotlin/gui/ImageComponent.kt new file mode 100644 index 0000000..bba7dee --- /dev/null +++ b/src/main/kotlin/gui/ImageComponent.kt @@ -0,0 +1,33 @@ +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.common.MyResourceLocation +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +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, +) : GuiComponent() { + override fun getWidth(): Int { + return width + } + + 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 + ) + } +} diff --git a/src/main/kotlin/gui/TickComponent.kt b/src/main/kotlin/gui/TickComponent.kt new file mode 100644 index 0000000..d1879b1 --- /dev/null +++ b/src/main/kotlin/gui/TickComponent.kt @@ -0,0 +1,18 @@ +package moe.nea.firmament.gui + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext + +class TickComponent(val onTick: Runnable) : GuiComponent() { + override fun getWidth(): Int { + return 0 + } + + override fun getHeight(): Int { + return 0 + } + + override fun render(context: GuiImmediateContext) { + onTick.run() + } +} diff --git a/src/main/kotlin/gui/config/AllConfigsGui.kt b/src/main/kotlin/gui/config/AllConfigsGui.kt new file mode 100644 index 0000000..4f7731c --- /dev/null +++ b/src/main/kotlin/gui/config/AllConfigsGui.kt @@ -0,0 +1,46 @@ + + +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 moe.nea.firmament.features.FeatureManager +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MoulConfigUtils +import moe.nea.firmament.util.ScreenUtil.setScreenLater + +object AllConfigsGui { + + val allConfigs + get() = listOf( + RepoManager.Config + ) + FeatureManager.allFeatures.mapNotNull { it.config } + + fun <T> List<T>.toObservableList(): ObservableList<T> = ObservableList(this) + + class MainMapping(val allConfigs: List<ManagedConfig>) { + @get:Bind("configs") + val configs = allConfigs.map { EntryMapping(it) }.toObservableList() + + class EntryMapping(val config: ManagedConfig) { + @Bind + fun name() = Text.translatable("firmament.config.${config.name}").string + + @Bind + fun openEditor() { + config.showConfigEditor(MC.screen) + } + } + } + + fun makeScreen(parent: Screen? = null): Screen { + return MoulConfigUtils.loadScreen("config/main", MainMapping(allConfigs), parent) + } + + fun showAllGuis() { + setScreenLater(makeScreen()) + } +} diff --git a/src/main/kotlin/gui/config/BooleanHandler.kt b/src/main/kotlin/gui/config/BooleanHandler.kt new file mode 100644 index 0000000..8592777 --- /dev/null +++ b/src/main/kotlin/gui/config/BooleanHandler.kt @@ -0,0 +1,37 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent +import io.github.notenoughupdates.moulconfig.gui.component.SwitchComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.jsonPrimitive + +class BooleanHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<Boolean> { + override fun toJson(element: Boolean): JsonElement? { + return JsonPrimitive(element) + } + + override fun fromJson(element: JsonElement): Boolean { + return element.jsonPrimitive.boolean + } + + override fun emitGuiElements(opt: ManagedOption<Boolean>, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + CenterComponent(SwitchComponent(object : GetSetter<Boolean> { + override fun get(): Boolean { + return opt.get() + } + + override fun set(newValue: Boolean) { + opt.set(newValue) + config.save() + } + }, 200) + )) + } +} diff --git a/src/main/kotlin/gui/config/ClickHandler.kt b/src/main/kotlin/gui/config/ClickHandler.kt new file mode 100644 index 0000000..fa1c621 --- /dev/null +++ b/src/main/kotlin/gui/config/ClickHandler.kt @@ -0,0 +1,24 @@ + + +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 + +class ClickHandler(val config: ManagedConfig, val runnable: () -> Unit) : ManagedConfig.OptionHandler<Unit> { + override fun toJson(element: Unit): JsonElement? { + return null + } + + override fun fromJson(element: JsonElement) {} + + override fun emitGuiElements(opt: ManagedOption<Unit>, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + FirmButtonComponent( + TextComponent(opt.labelText.string), + action = runnable), + ) + } +} diff --git a/src/main/kotlin/gui/config/DurationHandler.kt b/src/main/kotlin/gui/config/DurationHandler.kt new file mode 100644 index 0000000..8d485b1 --- /dev/null +++ b/src/main/kotlin/gui/config/DurationHandler.kt @@ -0,0 +1,58 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.common.IMinecraft +import io.github.notenoughupdates.moulconfig.gui.component.RowComponent +import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.jsonPrimitive +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 + +class DurationHandler(val config: ManagedConfig, val min: Duration, val max: Duration) : + ManagedConfig.OptionHandler<Duration> { + override fun toJson(element: Duration): JsonElement? { + return JsonPrimitive(element.inWholeMilliseconds) + } + + override fun fromJson(element: JsonElement): Duration { + return element.jsonPrimitive.long.toDuration(DurationUnit.MILLISECONDS) + } + + override fun emitGuiElements(opt: ManagedOption<Duration>, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + RowComponent( + TextComponent(IMinecraft.instance.defaultFontRenderer, + { FirmFormatters.formatTimespan(opt.value) }, + 40, + TextComponent.TextAlignment.CENTER, + true, + false), + SliderComponent( + object : GetSetter<Float> { + override fun get(): Float { + return opt.value.toDouble(DurationUnit.SECONDS).toFloat() + } + + override fun set(newValue: Float) { + opt.value = newValue.toDouble().toDuration(DurationUnit.SECONDS) + } + }, + min.toDouble(DurationUnit.SECONDS).toFloat(), + max.toDouble(DurationUnit.SECONDS).toFloat(), + 0.1F, + 130 + ) + )) + } + +} diff --git a/src/main/kotlin/gui/config/GuiAppender.kt b/src/main/kotlin/gui/config/GuiAppender.kt new file mode 100644 index 0000000..329319d --- /dev/null +++ b/src/main/kotlin/gui/config/GuiAppender.kt @@ -0,0 +1,40 @@ + + +package moe.nea.firmament.gui.config + +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 moe.nea.firmament.gui.FixedComponent + +class GuiAppender(val width: Int, val screenAccessor: () -> Screen) { + val panel = mutableListOf<GuiComponent>() + internal val reloadables = mutableListOf<(() -> Unit)>() + + fun onReload(reloadable: () -> Unit) { + reloadables.add(reloadable) + } + + fun appendLabeledRow(label: Text, right: GuiComponent) { + appendSplitRow( + TextComponent(label.string), + right + ) + } + + fun appendSplitRow(left: GuiComponent, right: GuiComponent) { + // TODO: make this more dynamic + // i could just make a component that allows for using half the available size + appendFullRow(RowComponent( + FixedComponent(GetSetter.constant(width / 2), null, left), + FixedComponent(GetSetter.constant(width / 2), null, right), + )) + } + + fun appendFullRow(widget: GuiComponent) { + panel.add(widget) + } +} diff --git a/src/main/kotlin/gui/config/HudMetaHandler.kt b/src/main/kotlin/gui/config/HudMetaHandler.kt new file mode 100644 index 0000000..35c9d51 --- /dev/null +++ b/src/main/kotlin/gui/config/HudMetaHandler.kt @@ -0,0 +1,39 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.encodeToJsonElement +import net.minecraft.text.MutableText +import net.minecraft.text.Text +import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.jarvis.JarvisIntegration +import moe.nea.firmament.util.MC + +class HudMetaHandler(val config: ManagedConfig, val label: MutableText, 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) + } + + override fun emitGuiElements(opt: ManagedOption<HudMeta>, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + FirmButtonComponent( + TextComponent( + Text.stringifiedTranslatable("firmament.hud.edit", label).string), + ) { + MC.screen = JarvisIntegration.jarvis.getHudEditor( + guiAppender.screenAccessor.invoke(), + listOf(opt.value) + ) + }) + } +} diff --git a/src/main/kotlin/gui/config/IntegerHandler.kt b/src/main/kotlin/gui/config/IntegerHandler.kt new file mode 100644 index 0000000..31ce90f --- /dev/null +++ b/src/main/kotlin/gui/config/IntegerHandler.kt @@ -0,0 +1,54 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.common.IMinecraft +import io.github.notenoughupdates.moulconfig.gui.component.RowComponent +import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import moe.nea.firmament.util.FirmFormatters + +class IntegerHandler(val config: ManagedConfig, val min: Int, val max: Int) : ManagedConfig.OptionHandler<Int> { + override fun toJson(element: Int): JsonElement? { + return JsonPrimitive(element) + } + + override fun fromJson(element: JsonElement): Int { + return element.jsonPrimitive.int + } + + override fun emitGuiElements(opt: ManagedOption<Int>, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + RowComponent( + TextComponent(IMinecraft.instance.defaultFontRenderer, + { FirmFormatters.formatCommas(opt.value, 0) }, + 40, + TextComponent.TextAlignment.CENTER, + true, + false), + SliderComponent( + object : GetSetter<Float> { + override fun get(): Float { + return opt.value.toFloat() + } + + override fun set(newValue: Float) { + opt.value = newValue.toInt() + } + }, + min.toFloat(), + max.toFloat(), + 0.1F, + 130 + ) + )) + + } + +} diff --git a/src/main/kotlin/gui/config/JAnyHud.kt b/src/main/kotlin/gui/config/JAnyHud.kt new file mode 100644 index 0000000..35c4eb2 --- /dev/null +++ b/src/main/kotlin/gui/config/JAnyHud.kt @@ -0,0 +1,48 @@ + + +package moe.nea.firmament.gui.config + +import moe.nea.jarvis.api.JarvisHud +import moe.nea.jarvis.api.JarvisScalable +import kotlinx.serialization.Serializable +import net.minecraft.text.Text + +@Serializable +data class HudPosition( + var x: Double, + var y: Double, + var scale: Float, +) + + +data class HudMeta( + val position: HudPosition, + private val label: Text, + private val width: Int, + private val height: Int, +) : JarvisScalable, JarvisHud { + override fun getX(): Double = position.x + + override fun setX(newX: Double) { + position.x = newX + } + + override fun getY(): Double = position.y + + override fun setY(newY: Double) { + position.y = newY + } + + override fun getLabel(): Text = label + + override fun getWidth(): Int = width + + override fun getHeight(): Int = height + + override fun getScale(): Float = position.scale + + override fun setScale(newScale: Float) { + position.scale = newScale + } + +} diff --git a/src/main/kotlin/gui/config/KeyBindingHandler.kt b/src/main/kotlin/gui/config/KeyBindingHandler.kt new file mode 100644 index 0000000..c389cc9 --- /dev/null +++ b/src/main/kotlin/gui/config/KeyBindingHandler.kt @@ -0,0 +1,149 @@ + + +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 org.lwjgl.glfw.GLFW +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.encodeToJsonElement +import net.minecraft.text.Text +import net.minecraft.util.Formatting +import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.keybindings.FirmamentKeyBindings +import moe.nea.firmament.keybindings.SavedKeyBinding + +class KeyBindingHandler(val name: String, val managedConfig: ManagedConfig) : + ManagedConfig.OptionHandler<SavedKeyBinding> { + + override fun initOption(opt: ManagedOption<SavedKeyBinding>) { + FirmamentKeyBindings.registerKeyBinding(name, opt) + } + + override fun toJson(element: SavedKeyBinding): JsonElement? { + return Json.encodeToJsonElement(element) + } + + override fun fromJson(element: JsonElement): SavedKeyBinding { + return Json.decodeFromJsonElement(element) + } + + override fun emitGuiElements(opt: ManagedOption<SavedKeyBinding>, guiAppender: GuiAppender) { + var editing = false + var lastPressed = 0 + var lastPressedNonModifier = 0 + var label: String = "" + var button: FirmButtonComponent? = null + fun updateLabel() { + var stroke = opt.value.format() + if (editing) { + stroke = Text.literal("") + val (shift, alt, ctrl) = SavedKeyBinding.getMods(SavedKeyBinding.getModInt()) + if (shift) { + stroke.append("SHIFT + ") + } + if (alt) { + stroke.append("ALT + ") + } + if (ctrl) { + stroke.append("CTRL + ") + } + stroke.append("???") + stroke.styled { it.withColor(Formatting.YELLOW) } + } + label = (stroke).string + managedConfig.save() + } + button = object : FirmButtonComponent( + TextComponent( + IMinecraft.instance.defaultFontRenderer, + { label }, + 130, + TextComponent.TextAlignment.LEFT, + false, + false + ), action = { + if (editing) { + button!!.blur() + } else { + editing = true + button!!.requestFocus() + updateLabel() + } + }) { + override fun keyboardEvent(event: KeyboardEvent, context: GuiImmediateContext): Boolean { + if (event is KeyboardEvent.KeyPressed) { + return if (event.pressed) onKeyPressed(event.keycode, SavedKeyBinding.getModInt()) + else onKeyReleased(event.keycode, SavedKeyBinding.getModInt()) + } + return super.keyboardEvent(event, context) + } + + override fun getBackground(context: GuiImmediateContext): NinePatch<MyResourceLocation> { + if (editing) return activeBg + return super.getBackground(context) + } + + fun onKeyPressed(ch: Int, modifiers: Int): Boolean { + if (!editing) { + return false + } + if (ch == GLFW.GLFW_KEY_ESCAPE) { + lastPressedNonModifier = 0 + editing = false + lastPressed = 0 + opt.value = SavedKeyBinding(GLFW.GLFW_KEY_UNKNOWN) + 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 + ) { + lastPressed = ch + } else { + opt.value = SavedKeyBinding( + ch, modifiers + ) + editing = false + blur() + lastPressed = 0 + lastPressedNonModifier = 0 + } + updateLabel() + return true + } + + override fun onLostFocus() { + lastPressedNonModifier = 0 + editing = false + lastPressed = 0 + updateLabel() + } + + fun onKeyReleased(ch: Int, modifiers: Int): Boolean { + if (!editing) + return false + if (lastPressedNonModifier == ch || (lastPressedNonModifier == 0 && ch == lastPressed)) { + opt.value = SavedKeyBinding(ch, modifiers) + editing = false + blur() + lastPressed = 0 + lastPressedNonModifier = 0 + } + updateLabel() + return true + } + } + updateLabel() + guiAppender.appendLabeledRow(opt.labelText, button) + } + +} diff --git a/src/main/kotlin/gui/config/ManagedConfig.kt b/src/main/kotlin/gui/config/ManagedConfig.kt new file mode 100644 index 0000000..aa6e3c8 --- /dev/null +++ b/src/main/kotlin/gui/config/ManagedConfig.kt @@ -0,0 +1,181 @@ + + +package moe.nea.firmament.gui.config + +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 moe.nea.firmament.Firmament +import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.keybindings.SavedKeyBinding +import moe.nea.firmament.util.ScreenUtil.setScreenLater + +abstract class ManagedConfig(override val name: String) : ManagedConfigElement() { + + 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) + } + + 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 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 labelText = Text.translatable("firmament.config.${name}") + + 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 new file mode 100644 index 0000000..28cd6b8 --- /dev/null +++ b/src/main/kotlin/gui/config/ManagedConfigElement.kt @@ -0,0 +1,8 @@ + + +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 new file mode 100644 index 0000000..b7264e8 --- /dev/null +++ b/src/main/kotlin/gui/config/ManagedOption.kt @@ -0,0 +1,62 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty +import net.minecraft.text.Text +import moe.nea.firmament.Firmament + +class ManagedOption<T : Any>( + val element: ManagedConfigElement, + val propertyName: String, + val default: () -> T, + val handler: ManagedConfig.OptionHandler<T> +) : ReadWriteProperty<Any?, T>, GetSetter<T> { + override fun set(newValue: T) { + this.value = newValue + } + + override fun get(): T { + return this.value + } + + val rawLabelText = "firmament.config.${element.name}.${propertyName}" + val labelText = Text.translatable(rawLabelText) + + lateinit var value: T + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + this.value = value + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + return value + } + + fun load(root: JsonElement) { + if (root is JsonObject && root.containsKey(propertyName)) { + try { + value = handler.fromJson(root[propertyName]!!) + return + } catch (e: Exception) { + Firmament.logger.error( + "Exception during loading of config file ${element.name}. This will reset this config.", + e + ) + } + } + value = default() + } + + fun toJson(): JsonElement? { + return handler.toJson(value) + } + + fun appendToGui(guiapp: GuiAppender) { + handler.emitGuiElements(this, guiapp) + } +} diff --git a/src/main/kotlin/gui/config/StringHandler.kt b/src/main/kotlin/gui/config/StringHandler.kt new file mode 100644 index 0000000..a326abb --- /dev/null +++ b/src/main/kotlin/gui/config/StringHandler.kt @@ -0,0 +1,36 @@ + + +package moe.nea.firmament.gui.config + +import io.github.notenoughupdates.moulconfig.gui.component.TextFieldComponent +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 + +class StringHandler(val config: ManagedConfig) : ManagedConfig.OptionHandler<String> { + override fun toJson(element: String): JsonElement? { + return JsonPrimitive(element) + } + + override fun fromJson(element: JsonElement): String { + return element.jsonPrimitive.content + } + + override fun emitGuiElements(opt: ManagedOption<String>, guiAppender: GuiAppender) { + guiAppender.appendLabeledRow( + opt.labelText, + TextFieldComponent( + object : GetSetter<String> by opt { + override fun set(newValue: String) { + opt.set(newValue) + config.save() + } + }, + 130, + suggestion = Text.translatableWithFallback(opt.rawLabelText + ".hint", "").string + ), + ) + } +} diff --git a/src/main/kotlin/gui/entity/EntityModifier.kt b/src/main/kotlin/gui/entity/EntityModifier.kt new file mode 100644 index 0000000..9623070 --- /dev/null +++ b/src/main/kotlin/gui/entity/EntityModifier.kt @@ -0,0 +1,9 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.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 new file mode 100644 index 0000000..8c7428d --- /dev/null +++ b/src/main/kotlin/gui/entity/EntityRenderer.kt @@ -0,0 +1,197 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import org.apache.logging.log4j.LogManager +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.util.Identifier +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.assertNotNullOr +import moe.nea.firmament.util.iterate +import moe.nea.firmament.util.openFirmamentResource +import moe.nea.firmament.util.render.enableScissorWithTranslation + +object EntityRenderer { + val fakeWorld = FakeWorld() + private fun <T : Entity> t(entityType: EntityType<T>): () -> T { + return { entityType.create(fakeWorld)!! } + } + + val entityIds: Map<String, () -> LivingEntity> = mapOf( + "Zombie" to t(EntityType.ZOMBIE), + "Chicken" to t(EntityType.CHICKEN), + "Slime" to t(EntityType.SLIME), + "Wolf" to t(EntityType.WOLF), + "Skeleton" to t(EntityType.SKELETON), + "Creeper" to t(EntityType.CREEPER), + "Ocelot" to t(EntityType.OCELOT), + "Blaze" to t(EntityType.BLAZE), + "Rabbit" to t(EntityType.RABBIT), + "Sheep" to t(EntityType.SHEEP), + "Horse" to t(EntityType.HORSE), + "Eisengolem" to t(EntityType.IRON_GOLEM), + "Silverfish" to t(EntityType.SILVERFISH), + "Witch" to t(EntityType.WITCH), + "Endermite" to t(EntityType.ENDERMITE), + "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), + "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), + ) + val entityModifiers: Map<String, EntityModifier> = mapOf( + "playerdata" to ModifyPlayerSkin, + "equipment" to ModifyEquipment, + "riding" to ModifyRiding, + "charged" to ModifyCharged, + "witherdata" to ModifyWither, + "invisible" to ModifyInvisible, + "age" to ModifyAge, + "horse" to ModifyHorse, + "name" to ModifyName, + ) + + val logger = LogManager.getLogger("Firmament.Entity") + fun applyModifiers(entityId: String, modifiers: List<JsonObject>): LivingEntity? { + val entityType = assertNotNullOr(entityIds[entityId]) { + logger.error("Could not create entity with id $entityId") + return null + } + var entity = entityType() + for (modifierJson in modifiers) { + val modifier = assertNotNullOr(modifierJson["type"]?.asString?.let(entityModifiers::get)) { + logger.error("Unknown modifier $modifierJson") + return null + } + entity = modifier.apply(entity, modifierJson) + } + return entity + } + + fun constructEntity(info: JsonObject): LivingEntity? { + val modifiers = (info["modifiers"] as JsonArray?)?.map { it.asJsonObject } ?: emptyList() + val entityType = assertNotNullOr(info["entity"]?.asString) { + logger.error("Missing entity type on entity object") + return null + } + return applyModifiers(entityType, modifiers) + } + + private val gson = Gson() + fun constructEntity(location: Identifier): LivingEntity? { + return constructEntity( + gson.fromJson( + location.openFirmamentResource().bufferedReader(), JsonObject::class.java + ) + ) + } + + fun renderEntity( + entity: LivingEntity, + renderContext: DrawContext, + posX: Int, + posY: Int, + mouseX: Float, + mouseY: Float + ) { + var bottomOffset = 0.0F + var currentEntity = entity + val maxSize = entity.iterate { it.firstPassenger as? LivingEntity } + .map { it.height } + .sum() + while (true) { + currentEntity.age = MC.player?.age ?: 0 + drawEntity( + renderContext, + posX, + posY, + posX + 50, + posY + 80, + minOf(2F / maxSize, 1F) * 30, + -bottomOffset, + mouseX, + mouseY, + currentEntity + ) + val next = currentEntity.firstPassenger as? LivingEntity ?: break + bottomOffset += currentEntity.getPassengerRidingPos(next).y.toFloat() * 0.75F + currentEntity = next + } + } + + + fun drawEntity( + context: DrawContext, + x1: Int, + y1: Int, + x2: Int, + y2: Int, + size: Float, + bottomOffset: Float, + mouseX: Float, + mouseY: Float, + entity: LivingEntity + ) { + context.enableScissorWithTranslation(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat()) + val centerX = (x1 + x2) / 2f + val centerY = (y1 + y2) / 2f + val targetYaw = atan(((centerX - mouseX) / 40.0f).toDouble()).toFloat() + val targetPitch = atan(((centerY - mouseY) / 40.0f).toDouble()).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, 0.0f) + InventoryScreen.drawEntity( + context, + centerX, + centerY, + size, + vector3f, + rotateToFaceTheFront, + rotateToFaceTheCamera, + entity + ) + entity.bodyYaw = oldBodyYaw + entity.yaw = oldYaw + entity.pitch = oldPitch + entity.prevHeadYaw = oldPrevHeadYaw + entity.headYaw = oldHeadYaw + context.disableScissor() + } + + +} diff --git a/src/main/kotlin/gui/entity/EntityWidget.kt b/src/main/kotlin/gui/entity/EntityWidget.kt new file mode 100644 index 0000000..2e49072 --- /dev/null +++ b/src/main/kotlin/gui/entity/EntityWidget.kt @@ -0,0 +1,35 @@ + +package moe.nea.firmament.gui.entity + +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.Element +import net.minecraft.entity.LivingEntity + +class EntityWidget(val entity: LivingEntity, val point: Point) : WidgetWithBounds() { + override fun children(): List<Element> { + return emptyList() + } + + var hasErrored = false + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + try { + if (!hasErrored) + EntityRenderer.renderEntity(entity, context, point.x, point.y, mouseX.toFloat(), mouseY.toFloat()) + } catch (ex: Exception) { + EntityRenderer.logger.error("Failed to render constructed entity: $entity", ex) + hasErrored = true + } + if (hasErrored) { + context.fill(point.x, point.y, point.x + 50, point.y + 80, 0xFFAA2222.toInt()) + } + } + + override fun getBounds(): Rectangle { + return Rectangle(point, Dimension(50, 80)) + } +} diff --git a/src/main/kotlin/gui/entity/FakeWorld.kt b/src/main/kotlin/gui/entity/FakeWorld.kt new file mode 100644 index 0000000..f354d5a --- /dev/null +++ b/src/main/kotlin/gui/entity/FakeWorld.kt @@ -0,0 +1,488 @@ + +package moe.nea.firmament.gui.entity + +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Lifecycle +import java.util.* +import java.util.function.BooleanSupplier +import java.util.function.Consumer +import java.util.stream.Stream +import kotlin.jvm.optionals.getOrNull +import kotlin.streams.asSequence +import net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.component.type.MapIdComponent +import net.minecraft.entity.Entity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.fluid.Fluid +import net.minecraft.item.map.MapState +import net.minecraft.recipe.BrewingRecipeRegistry +import net.minecraft.recipe.Ingredient +import net.minecraft.recipe.RecipeManager +import net.minecraft.registry.BuiltinRegistries +import net.minecraft.registry.DynamicRegistryManager +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.registry.RegistryWrapper +import net.minecraft.registry.entry.RegistryEntry +import net.minecraft.registry.entry.RegistryEntryInfo +import net.minecraft.registry.entry.RegistryEntryList +import net.minecraft.registry.entry.RegistryEntryOwner +import net.minecraft.registry.tag.TagKey +import net.minecraft.resource.featuretoggle.FeatureFlags +import net.minecraft.resource.featuretoggle.FeatureSet +import net.minecraft.scoreboard.Scoreboard +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.util.math.random.Random +import net.minecraft.util.profiler.DummyProfiler +import net.minecraft.world.BlockView +import net.minecraft.world.Difficulty +import net.minecraft.world.GameRules +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.tick.OrderedTick +import net.minecraft.world.tick.QueryableTickScheduler +import net.minecraft.world.tick.TickManager + +fun <T> makeRegistry(registryWrapper: RegistryWrapper.Impl<T>, key: RegistryKey<out Registry<T>>): Registry<T> { + val inverseLookup = registryWrapper.streamEntries() + .asSequence().map { it.value() to it.registryKey() } + .toMap() + val idLookup = registryWrapper.streamEntries() + .asSequence() + .map { it.registryKey() } + .withIndex() + .associate { it.value to it.index } + val map = registryWrapper.streamEntries().asSequence().map { it.registryKey() to it.value() }.toMap(mutableMapOf()) + val inverseIdLookup = idLookup.asIterable().associate { (k, v) -> v to k } + return object : Registry<T> { + override fun get(key: RegistryKey<T>?): T? { + return registryWrapper.getOptional(key).getOrNull()?.value() + } + + override fun iterator(): MutableIterator<T> { + return object : MutableIterator<T> { + val iterator = registryWrapper.streamEntries().iterator() + override fun hasNext(): Boolean { + return iterator.hasNext() + } + + override fun next(): T { + return iterator.next().value() + } + + override fun remove() { + TODO("Not yet implemented") + } + } + } + + override fun getRawId(value: T?): Int { + return idLookup[inverseLookup[value ?: return -1] ?: return -1] ?: return -1 + } + + override fun get(id: Identifier?): T? { + return get(RegistryKey.of(key, id)) + } + + override fun get(index: Int): T? { + return get(inverseIdLookup[index] ?: return null) + } + + override fun size(): Int { + return idLookup.size + } + + override fun getKey(): RegistryKey<out Registry<T>> { + return key + } + + override fun getEntryInfo(key: RegistryKey<T>?): Optional<RegistryEntryInfo> { + TODO("Not yet implemented") + } + + override fun getLifecycle(): Lifecycle { + return Lifecycle.stable() + } + + override fun getDefaultEntry(): Optional<RegistryEntry.Reference<T>> { + return Optional.empty() + } + + override fun getIds(): MutableSet<Identifier> { + return idLookup.keys.mapTo(mutableSetOf()) { it.value } + } + + override fun getEntrySet(): MutableSet<MutableMap.MutableEntry<RegistryKey<T>, T>> { + return map.entries + } + + override fun getKeys(): MutableSet<RegistryKey<T>> { + return map.keys + } + + override fun getRandom(random: Random?): Optional<RegistryEntry.Reference<T>> { + return registryWrapper.streamEntries().findFirst() + } + + override fun containsId(id: Identifier?): Boolean { + return idLookup.containsKey(RegistryKey.of(key, id ?: return false)) + } + + override fun freeze(): Registry<T> { + return this + } + + override fun getEntry(rawId: Int): Optional<RegistryEntry.Reference<T>> { + val x = inverseIdLookup[rawId] ?: return Optional.empty() + return Optional.of(RegistryEntry.Reference.standAlone(registryWrapper, x)) + } + + override fun streamEntries(): Stream<RegistryEntry.Reference<T>> { + return registryWrapper.streamEntries() + } + + override fun streamTagsAndEntries(): Stream<Pair<TagKey<T>, RegistryEntryList.Named<T>>> { + return streamTags().map { Pair(it, getOrCreateEntryList(it)) } + } + + override fun streamTags(): Stream<TagKey<T>> { + return registryWrapper.streamTagKeys() + } + + override fun clearTags() { + } + + override fun getEntryOwner(): RegistryEntryOwner<T> { + return registryWrapper + } + + override fun getReadOnlyWrapper(): RegistryWrapper.Impl<T> { + return registryWrapper + } + + override fun populateTags(tagEntries: MutableMap<TagKey<T>, MutableList<RegistryEntry<T>>>?) { + } + + override fun getOrCreateEntryList(tag: TagKey<T>?): RegistryEntryList.Named<T> { + return getEntryList(tag).orElseGet { RegistryEntryList.of(registryWrapper, tag) } + } + + override fun getEntryList(tag: TagKey<T>?): Optional<RegistryEntryList.Named<T>> { + return registryWrapper.getOptional(tag ?: return Optional.empty()) + } + + override fun getEntry(value: T): RegistryEntry<T> { + return registryWrapper.getOptional(inverseLookup[value]!!).get() + } + + override fun getEntry(key: RegistryKey<T>?): Optional<RegistryEntry.Reference<T>> { + return registryWrapper.getOptional(key ?: return Optional.empty()) + } + + override fun getEntry(id: Identifier?): Optional<RegistryEntry.Reference<T>> { + TODO("Not yet implemented") + } + + override fun createEntry(value: T): RegistryEntry.Reference<T> { + TODO("Not yet implemented") + } + + override fun contains(key: RegistryKey<T>?): Boolean { + return getEntry(key).isPresent + } + + override fun getId(value: T): Identifier? { + return (inverseLookup[value] ?: return null).value + } + + override fun getKey(entry: T): Optional<RegistryKey<T>> { + return Optional.ofNullable(inverseLookup[entry ?: return Optional.empty()]) + } + } +} + +fun createDynamicRegistry(): DynamicRegistryManager.Immutable { + val wrapperLookup = BuiltinRegistries.createWrapperLookup() + return object : DynamicRegistryManager.Immutable { + override fun <E : Any?> getOptional(key: RegistryKey<out Registry<out E>>): Optional<Registry<E>> { + val lookup = wrapperLookup.getOptionalWrapper(key).getOrNull() ?: return Optional.empty() + val registry = makeRegistry(lookup, key as RegistryKey<out Registry<E>>) + return Optional.of(registry) + } + + fun <T> entry(reg: RegistryKey<out Registry<T>>): DynamicRegistryManager.Entry<T> { + return DynamicRegistryManager.Entry(reg, getOptional(reg).get()) + } + + override fun streamAllRegistries(): Stream<DynamicRegistryManager.Entry<*>> { + return wrapperLookup.streamAllRegistryKeys() + .map { entry(it as RegistryKey<out Registry<Any>>) } + } + } +} + +class FakeWorld( + registries: DynamicRegistryManager.Immutable = createDynamicRegistry(), +) : World( + Properties, + RegistryKey.of(RegistryKeys.WORLD, Identifier.of("firmament", "fakeworld")), + registries, + registries[RegistryKeys.DIMENSION_TYPE].entryOf( + RegistryKey.of( + RegistryKeys.DIMENSION_TYPE, + Identifier.of("minecraft", "overworld") + ) + ), + { DummyProfiler.INSTANCE }, + true, + false, + 0, 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 getGameRules(): GameRules { + return GameRules() + } + + 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.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS) + } + + 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.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS) + ) + } + + 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 asString(): String { + return "FakeWorld" + } + + override fun getEntityById(id: Int): Entity? { + return null + } + + 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 RecipeManager(registryManager) + } + + 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 + } +} diff --git a/src/main/kotlin/gui/entity/GuiPlayer.kt b/src/main/kotlin/gui/entity/GuiPlayer.kt new file mode 100644 index 0000000..d00b44d --- /dev/null +++ b/src/main/kotlin/gui/entity/GuiPlayer.kt @@ -0,0 +1,54 @@ + +package moe.nea.firmament.gui.entity + +import com.mojang.authlib.GameProfile +import java.util.* +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.world.World + +/** + * @see moe.nea.firmament.init.EarlyRiser + */ +fun makeGuiPlayer(world: FakeWorld): GuiPlayer { + val constructor = GuiPlayer::class.java.getDeclaredConstructor( + World::class.java, + BlockPos::class.java, + Float::class.javaPrimitiveType, + GameProfile::class.java + ) + return constructor.newInstance(world, BlockPos.ORIGIN, 0F, GameProfile(UUID.randomUUID(), "Linnea")) +} + +class GuiPlayer(world: ClientWorld?, profile: GameProfile?) : AbstractClientPlayerEntity(world, profile) { + override fun isSpectator(): Boolean { + return false + } + + override fun isCreative(): Boolean { + return false + } + + override fun shouldRenderName(): Boolean { + return false + } + + var skinTexture: Identifier = DefaultSkinHelper.getSkinTextures(this.getUuid()).texture + var capeTexture: Identifier? = null + var model: Model = Model.WIDE + override fun getSkinTextures(): SkinTextures { + return SkinTextures( + skinTexture, + null, + capeTexture, + null, + model, + true + ) + } +} diff --git a/src/main/kotlin/gui/entity/ModifyAge.kt b/src/main/kotlin/gui/entity/ModifyAge.kt new file mode 100644 index 0000000..a65c368 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyAge.kt @@ -0,0 +1,25 @@ + +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 + +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) { + entity.isBaby = isBaby + } else if (entity is ArmorStandEntity) { + entity.isSmall = isBaby + } else { + error("Cannot set age for $entity") + } + return entity + } + +} diff --git a/src/main/kotlin/gui/entity/ModifyCharged.kt b/src/main/kotlin/gui/entity/ModifyCharged.kt new file mode 100644 index 0000000..d22f6e3 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyCharged.kt @@ -0,0 +1,14 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.mob.CreeperEntity + +object ModifyCharged : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is CreeperEntity) + entity.dataTracker.set(CreeperEntity.CHARGED, true) + return entity + } +} diff --git a/src/main/kotlin/gui/entity/ModifyEquipment.kt b/src/main/kotlin/gui/entity/ModifyEquipment.kt new file mode 100644 index 0000000..73e450e --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyEquipment.kt @@ -0,0 +1,55 @@ + +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.ArmorItem +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import moe.nea.firmament.rei.SBItemStack +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.item.setEncodedSkullOwner +import moe.nea.firmament.util.item.zeroUUID + +object ModifyEquipment : EntityModifier { + val names = mapOf( + "hand" to EquipmentSlot.MAINHAND, + "helmet" to EquipmentSlot.HEAD, + "chestplate" to EquipmentSlot.CHEST, + "leggings" to EquipmentSlot.LEGS, + "feet" to EquipmentSlot.FEET, + ) + + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + names.forEach { (key, slot) -> + info[key]?.let { + entity.equipStack(slot, createItem(it.asString)) + } + } + return entity + } + + 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) } + "LEATHER_LEGGINGS" -> coloredLeatherArmor(Items.LEATHER_LEGGINGS, data) + "LEATHER_BOOTS" -> coloredLeatherArmor(Items.LEATHER_BOOTS, data) + "LEATHER_HELMET" -> coloredLeatherArmor(Items.LEATHER_HELMET, data) + "LEATHER_CHESTPLATE" -> coloredLeatherArmor(Items.LEATHER_CHESTPLATE, data) + else -> error("Unknown leather piece: $type") + } + } + + private fun coloredLeatherArmor(leatherArmor: Item, data: String): ItemStack { + val stack = ItemStack(leatherArmor) + stack.set(DataComponentTypes.DYED_COLOR, DyedColorComponent(data.toInt(16), false)) + return stack + } +} diff --git a/src/main/kotlin/gui/entity/ModifyHorse.kt b/src/main/kotlin/gui/entity/ModifyHorse.kt new file mode 100644 index 0000000..8ac011b --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyHorse.kt @@ -0,0 +1,61 @@ + +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.passive.AbstractHorseEntity +import net.minecraft.item.ItemStack +import net.minecraft.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)!! + "zombie" -> EntityType.ZOMBIE_HORSE.create(fakeWorld)!! + "mule" -> EntityType.MULE.create(fakeWorld)!! + "donkey" -> EntityType.DONKEY.create(fakeWorld)!! + "horse" -> EntityType.HORSE.create(fakeWorld)!! + 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 AbstractHorseEntity.setHorseArmor(itemStack: ItemStack) { + items.setStack(1, itemStack) +} diff --git a/src/main/kotlin/gui/entity/ModifyInvisible.kt b/src/main/kotlin/gui/entity/ModifyInvisible.kt new file mode 100644 index 0000000..8d36991 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyInvisible.kt @@ -0,0 +1,13 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity + +object ModifyInvisible : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + entity.isInvisible = info.get("invisible")?.asBoolean ?: true + return entity + } + +} diff --git a/src/main/kotlin/gui/entity/ModifyName.kt b/src/main/kotlin/gui/entity/ModifyName.kt new file mode 100644 index 0000000..a03da96 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyName.kt @@ -0,0 +1,14 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.text.Text + +object ModifyName : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + entity.customName = Text.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 new file mode 100644 index 0000000..28f0070 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyPlayerSkin.kt @@ -0,0 +1,47 @@ + +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 + +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 + } + +} diff --git a/src/main/kotlin/gui/entity/ModifyRiding.kt b/src/main/kotlin/gui/entity/ModifyRiding.kt new file mode 100644 index 0000000..5c4c78d --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyRiding.kt @@ -0,0 +1,15 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.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) + return entity + } + +} diff --git a/src/main/kotlin/gui/entity/ModifyWither.kt b/src/main/kotlin/gui/entity/ModifyWither.kt new file mode 100644 index 0000000..6083d88 --- /dev/null +++ b/src/main/kotlin/gui/entity/ModifyWither.kt @@ -0,0 +1,20 @@ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.boss.WitherEntity + +object ModifyWither : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is WitherEntity) + info["tiny"]?.let { + entity.setInvulTimer(if (it.asBoolean) 800 else 0) + } + info["armored"]?.let { + entity.health = if (it.asBoolean) 1F else entity.maxHealth + } + return entity + } + +} diff --git a/src/main/kotlin/gui/hud/MoulConfigHud.kt b/src/main/kotlin/gui/hud/MoulConfigHud.kt new file mode 100644 index 0000000..e77d9af --- /dev/null +++ b/src/main/kotlin/gui/hud/MoulConfigHud.kt @@ -0,0 +1,66 @@ + +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 moe.nea.firmament.events.FinalizeResourceManagerEvent +import moe.nea.firmament.events.HudRenderEvent +import moe.nea.firmament.gui.config.HudMeta +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MoulConfigUtils + +abstract class MoulConfigHud( + val name: String, + val hudMeta: HudMeta, +) { + companion object { + private val componentWrapper by lazy { + object : GuiComponentWrapper(GuiContext(TextComponent("§cERROR"))) { + init { + this.client = MC.instance + } + } + } + } + + private var fragment: GuiContext? = null + + fun forceInit() { + } + + open fun shouldRender(): Boolean { + return true + } + + init { + require(name.matches("^[a-z_/]+$".toRegex())) + HudRenderEvent.subscribe { + 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 { + MC.resourceManager.registerReloader(object : SynchronousResourceReloader { + override fun reload(manager: ResourceManager?) { + fragment = null + } + }) + } + } + + fun loadFragment() { + fragment = MoulConfigUtils.loadGui(name, this) + } + +} |