Refactor source layout

Introduce compat source sets and move all kotlin sources to the main directory

[no changelog]
This commit is contained in:
Linnea Gräf
2024-08-28 19:04:24 +02:00
parent a690630816
commit d2f240ff0c
251 changed files with 295 additions and 38 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
package moe.nea.firmament.gui.config
abstract class ManagedConfigElement {
abstract val name: String
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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