feat: added config, added config ui, started working on HUD configurator

Signed-off-by: moonleay <contact@moonleay.net>
This commit is contained in:
moonleay 2024-05-05 05:11:33 +02:00
parent 13cbbfa72f
commit 5c77165d17
Signed by: moonleay
GPG key ID: 82667543CCD715FB
15 changed files with 643 additions and 2 deletions

View file

@ -21,7 +21,9 @@ package net.moonleay.gimbal.client
import net.fabricmc.api.ClientModInitializer
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents
import net.fabricmc.loader.api.FabricLoader
import net.moonleay.gimbal.build.BuildConstants
import net.moonleay.gimbal.client.config.ClientConfigHolder
import net.moonleay.gimbal.client.editor.ClientEditor
import net.moonleay.gimbal.client.keybindings.KeybindingManager
import net.moonleay.gimbal.client.keybindings.KeybindingRegistrar
@ -30,6 +32,7 @@ import org.apache.logging.log4j.LogManager
internal object ClientMain : ClientModInitializer {
private val LOGGER = LogManager.getLogger(BuildConstants.modName)
lateinit var CONFIG: ClientConfigHolder
override fun onInitializeClient() {
@ -39,6 +42,10 @@ internal object ClientMain : ClientModInitializer {
LOGGER.info("Registering packets...")
GimbalClient.registerPacketHandlers()
LOGGER.info("Packets have been registered.")
LOGGER.info("Loading client config.")
val gimbalConfigPath = FabricLoader.getInstance().configDir.resolve(BuildConstants.modId + "/client.json")
CONFIG = ClientConfigHolder(gimbalConfigPath)
LOGGER.info("Config has been loaded.")
LOGGER.info("Gimbal has been initialized on the client side.")
}

View file

@ -0,0 +1,30 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.client.config
import kotlinx.serialization.Serializable
@Serializable
enum class AnchorPoint {
TOP_LEFT,
TOP_RIGHT,
BOTTOM_RIGHT,
BOTTOM_LEFT,
CENTER
}

View file

@ -0,0 +1,27 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.client.config
enum class Centerpoint {
TOP_LEFT,
TOP_RIGHT,
BOTTOM_RIGHT,
BOTTOM_LEFT,
CENTER
}

View file

@ -0,0 +1,70 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.client.config
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import java.nio.file.Path
import kotlin.io.path.*
class ClientConfigHolder(private val path: Path) {
var config: GimbalClientConfig
private set
init {
config = load()
}
fun updateConfig(new: GimbalClientConfig) {
config = new
save(config)
}
@OptIn(ExperimentalSerializationApi::class)
fun load(): GimbalClientConfig {
if (!path.isReadable())
save(GimbalClientConfig())
return path.inputStream().use {
Json.decodeFromStream(it)
}
}
@OptIn(ExperimentalSerializationApi::class)
fun save(conf: GimbalClientConfig) {
if (!path.isWritable()) {
if (!path.parent.exists()) {
path.parent.createDirectory()
}
if (!path.parent.isWritable()) {
throw Exception("Parent of path ${path.parent.pathString} is not writable.")
}
if (!path.exists())
path.createFile()
if (!path.isWritable()) {
throw Exception("Path (${path.pathString}) exists, but is not writable.")
}
}
path.outputStream().use {
Json.encodeToStream(conf, it)
}
}
}

View file

@ -0,0 +1,27 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.client.config
import kotlinx.serialization.Serializable
@Serializable
data class GimbalClientConfig(
val guiSettings: GimbalGuiSettings = GimbalGuiSettings(),
val showToasts: Boolean = true,
)

View file

@ -0,0 +1,28 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.client.config
import kotlinx.serialization.Serializable
@Serializable
data class GimbalGuiSettings(
val showHud: Boolean = true,
val scaledRes: ScaledRes = ScaledRes(4.0, 4.0),
val anchorPoint: AnchorPoint = AnchorPoint.TOP_LEFT,
)

View file

@ -0,0 +1,27 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.client.config
import kotlinx.serialization.Serializable
@Serializable
data class ScaledRes(
val scaledX: Double,
val scaledY: Double,
)

View file

@ -0,0 +1,135 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.client.screen
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.screen.ScreenTexts
import net.minecraft.text.Text
import net.moonleay.gimbal.build.BuildConstants
import net.moonleay.gimbal.client.config.*
import net.moonleay.gimbal.client.util.screen.ScreenUtil
import org.apache.logging.log4j.LogManager
class GimbalEditHudGui(private val parent: Screen, private val cfg: ClientConfigHolder) :
Screen(Text.translatable("gimbal.gui.edithud")) {
private val LOGGER = LogManager.getLogger(BuildConstants.modName)
private var tempXOffset = 4.0
private var tempYOffset = 4.0
private var shouldFollow = false
init {
this.tempXOffset = cfg.config.guiSettings.scaledRes.scaledX
this.tempYOffset = cfg.config.guiSettings.scaledRes.scaledY
}
override fun init() {
super.init()
this.addDrawableChild(ButtonWidget(
this.width / 2 + 10, this.height - 27, 90, 20, Text.translatable("gimbal.gui.resethud")
) { _: ButtonWidget? ->
tempXOffset = 4.0
tempYOffset = 4.0
shouldFollow = false
})
this.addDrawableChild(ButtonWidget(
this.width / 2 - 100, this.height - 27, 90, 20, ScreenTexts.DONE
) { _: ButtonWidget? ->
this.save()
this.client!!.setScreen(
this.parent
)
})
}
private fun save() {
val oldConf = cfg.config
if (ScreenUtil.isPositionOutOfBounds(this.tempXOffset, this.tempYOffset)) {
LOGGER.info("Text is not in bounds. This will not be saved in order to keep the user from making the hud inaccessible.")
return
}
LOGGER.info("Saving Pos...")
val newConf = GimbalClientConfig(
showToasts = oldConf.showToasts,
guiSettings = GimbalGuiSettings(
showHud = oldConf.guiSettings.showHud,
scaledRes = ScaledRes(
scaledX = this.tempXOffset,
scaledY = this.tempYOffset,
)
)
)
cfg.updateConfig(newConf)
}
override fun close() {
this.save()
this.client!!.setScreen(this.parent)
}
override fun tick() {
super.tick()
if (this.shouldFollow) {
val mouse = this.client?.mouse!!
val scaleFactor = this.client!!.window.calculateScaleFactor(
this.client!!.options.guiScale.value,
this.client!!.forcesUnicodeFont()
)
this.tempXOffset = ScreenUtil.getScaled(this.width, mouse.x.toFloat() / scaleFactor)
this.tempYOffset = ScreenUtil.getScaled(this.height, mouse.y.toFloat() / scaleFactor)
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
this.shouldFollow = true
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
this.shouldFollow = false
return super.mouseReleased(mouseX, mouseY, button)
}
override fun render(matrices: MatrixStack?, mouseX: Int, mouseY: Int, delta: Float) {
this.renderBackground(matrices)
drawCenteredText(matrices, this.textRenderer, this.title, this.width / 2, 15, 16777215)
this.textRenderer.drawWithShadow(
matrices,
Text.translatable("gimbal.gui.examplehud"),
ScreenUtil.getXForRender(
ScreenUtil.getReal(this.width, this.tempXOffset),
AnchorPoint.CENTER,
this.textRenderer.getWidth(Text.translatable("gimbal.gui.examplehud"))
),
ScreenUtil.getYForRender(
ScreenUtil.getReal(this.height, this.tempYOffset),
AnchorPoint.CENTER,
this.textRenderer.fontHeight
),
0xFFFFFF
) // TODO: improve placement when changing the scaling
// LOGGER.info("width: ${this.width}, height: ${this.height}, posX: ${ScreenUtil.getReal(this.width, this.tempXOffset)}, posY: ${ScreenUtil.getReal(this.height, this.tempYOffset)}, scaleX: ${this.tempXOffset}, scaleY: ${this.tempYOffset}")
super.render(matrices, mouseX, mouseY, delta)
}
}

View file

@ -0,0 +1,84 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.client.screen
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.gui.widget.CyclingButtonWidget
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.screen.ScreenTexts
import net.minecraft.text.Text
import net.moonleay.gimbal.client.config.ClientConfigHolder
import net.moonleay.gimbal.client.config.GimbalClientConfig
import net.moonleay.gimbal.client.config.GimbalGuiSettings
class GimbalSettingsGui(private val parent: Screen, private val cfg: ClientConfigHolder) :
Screen(Text.translatable("gimbal.gui.settings")) {
override fun init() {
this.addDrawableChild(CyclingButtonWidget.onOffBuilder(
// Text.translatable("gimbal.gui.enabled"),
// Text.translatable("gimbal.gui.disabled")
)
.initially(cfg.config.guiSettings.showHud)
.build(
this.width / 2 - 155,
this.height / 6 + 24 * 0,
150,
20,
Text.translatable("gimbal.gui.hud")
) { _: CyclingButtonWidget<Boolean?>?, isEnabled: Boolean? ->
val oldGui = cfg.config.guiSettings
val newGui = GimbalGuiSettings(
showHud = isEnabled ?: true,
scaledRes = oldGui.scaledRes,
anchorPoint = oldGui.anchorPoint
)
val newConf = GimbalClientConfig(
guiSettings = newGui,
showToasts = cfg.config.showToasts
)
cfg.updateConfig(newConf)
})
this.addDrawableChild(ButtonWidget(
this.width / 2 - 155 + 160,
this.height / 6 + 24 * 0,
150,
20,
Text.translatable("gimbal.gui.edithud")
) { _: ButtonWidget? ->
this.client!!.setScreen(GimbalEditHudGui(this, cfg))
}
)
// Done button
this.addDrawableChild(ButtonWidget(
this.width / 2 - 100, this.height / 6 + 168, 200, 20, ScreenTexts.DONE
) { _: ButtonWidget? ->
this.client!!.setScreen(
this.parent
)
})
}
override fun render(matrices: MatrixStack?, mouseX: Int, mouseY: Int, delta: Float) {
this.renderBackground(matrices)
drawCenteredText(matrices, this.textRenderer, this.title, this.width / 2, 15, 16777215)
super.render(matrices, mouseX, mouseY, delta)
}
}

View file

@ -0,0 +1,61 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.client.util.screen
import net.moonleay.gimbal.client.config.AnchorPoint
object ScreenUtil {
fun getXForRender(x: Float, anchor: AnchorPoint, textWidth: Int): Float {
return when (anchor) {
AnchorPoint.TOP_LEFT -> x
AnchorPoint.BOTTOM_LEFT -> x
AnchorPoint.TOP_RIGHT -> x - textWidth
AnchorPoint.BOTTOM_RIGHT -> x - textWidth
AnchorPoint.CENTER -> x - textWidth / 2
}
}
fun getYForRender(y: Float, anchor: AnchorPoint, textHeight: Int): Float {
return when (anchor) {
AnchorPoint.TOP_LEFT -> y
AnchorPoint.TOP_RIGHT -> y
AnchorPoint.BOTTOM_RIGHT -> y - textHeight
AnchorPoint.BOTTOM_LEFT -> y - textHeight
AnchorPoint.CENTER -> y - textHeight / 2
}
}
fun getScaled(length: Int, real: Float): Double {
val scale = length / 100.0
return real / scale
}
fun getReal(length: Int, scaled: Double): Float {
val scale = length / 100.0
return (scaled * scale).toFloat()
}
fun isPositionOutOfBounds(x: Double, y: Double): Boolean {
val tooBig = 100 < x || 100 < y
val tooSmall = 0 > x || 0 > y
return tooBig || tooSmall
}
}

View file

@ -26,13 +26,25 @@ class En_us_GimbalLanguageProvider(dataGenerator: FabricDataGenerator?) :
override fun generateTranslations(translationBuilder: TranslationBuilder?) {
if (translationBuilder == null) return
/// Screens
// Gimbal settings screen
translationBuilder.add("gimbal.gui.settings", "Gimbal Settings")
translationBuilder.add("gimbal.gui.hud", "Show HUD")
translationBuilder.add("gimbal.gui.edithud", "Edit HUD Layout...")
translationBuilder.add("gimbal.gui.resethud", "Reset Layout")
translationBuilder.add("gimbal.gui.examplehud", "EXAMPLE [No Clip, Force]")
translationBuilder.add("gimbal.gui.enabled", "Enabled")
translationBuilder.add("gimbal.gui.disabled", "Disabled")
/// Options
// Editor modes
translationBuilder.add("gimbal.category.editormode", "Editor Modes")
translationBuilder.add("gimbal.key.editor.mode.insert", "Enter Insert Mode")
translationBuilder.add("gimbal.key.editor.mode.replace", "Enter Replace Mode")
translationBuilder.add("gimbal.key.editor.mode.visual", "Enter Visual Mode")
// Editor mode modifiers
translationBuilder.add("gimbal.category.editormodifier", "Editor Mode Modifiers")
translationBuilder.add("gimbal.key.editor.modifier.bulldozer", "Toggle Bulldozer Modifier")

View file

@ -0,0 +1,66 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.mixin;
import net.minecraft.client.gui.screen.GameMenuScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.TexturedButtonWidget;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.moonleay.gimbal.build.BuildConstants;
import net.moonleay.gimbal.client.ClientMain;
import net.moonleay.gimbal.client.screen.GimbalSettingsGui;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(GameMenuScreen.class)
// What is my purpose?
public class YouInjectButtonMixin extends Screen {
// Go my god.
@Unique
private static final Identifier GIMBAL_TEXTURE = new Identifier(BuildConstants.modId, "textures/gimbal_options_texture_button.png");
protected YouInjectButtonMixin(Text title) {
super(title);
}
@Inject(method = "initWidgets", at = @At(value = "RETURN"))
private void func(CallbackInfo ci) {
this.addDrawableChild(
new TexturedButtonWidget(
this.width / 2 - 124,
this.height / 4 + 96 + -16,
20,
20,
0,
0,
20,
GIMBAL_TEXTURE,
20,
40,
button -> this.client.setScreen(new GimbalSettingsGui(this, ClientMain.CONFIG)),
Text.translatable("gimbal.gui.settings")
)
);
}
}

View file

@ -0,0 +1,65 @@
/*
* Gimbal
* Copyright (C) 2024 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.mixin;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.TitleScreen;
import net.minecraft.client.gui.widget.TexturedButtonWidget;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.moonleay.gimbal.build.BuildConstants;
import net.moonleay.gimbal.client.ClientMain;
import net.moonleay.gimbal.client.screen.GimbalSettingsGui;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(TitleScreen.class)
public class YouInjectButtonMixin2 extends Screen {
@Unique
private static final Identifier GIMBAL_TEXTURE = new Identifier(BuildConstants.modId, "textures/gimbal_options_texture_button.png");
protected YouInjectButtonMixin2(Text title) {
super(title);
}
@Inject(method = "init", at = @At(value = "RETURN"))
private void func(CallbackInfo ci) {
int l = this.height / 4 + 48;
this.addDrawableChild(
new TexturedButtonWidget(
this.width / 2 - 124 - 24,
l + 72 + 12,
20,
20,
0,
0,
20,
GIMBAL_TEXTURE,
20,
40,
button -> this.client.setScreen(new GimbalSettingsGui(this, ClientMain.CONFIG)),
Text.translatable("gimbal.gui.settings")
)
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -15,7 +15,9 @@
"BulldozerMixin2",
"HudMixin",
"NoClipCameraFixMixin",
"NormalModeMixin"
"NormalModeMixin",
"YouInjectButtonMixin",
"YouInjectButtonMixin2"
],
"injectors": {
"defaultRequire": 1