Compare commits

...

26 commits

Author SHA1 Message Date
dc732cc39f
feat: add ESC resets mode setting 2024-06-13 18:52:04 +02:00
d59cf8f9f0
feat: add default mode setting 2024-06-13 18:39:27 +02:00
5252d9abdf
feat: changed server side state log to debug 2024-06-13 18:20:18 +02:00
42737446b2
feat: added player fly speed controls
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-17 01:10:54 +02:00
6a89e5683f
refactor: combined NoClip and ReplaceMode mixins into one mixin each
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-16 16:35:44 +02:00
4c8404d306
refactor: combined Bulldozer and YouInjectButton mixin into one mixin each
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-16 03:52:34 +02:00
b4f46ee703
feat: added option and config for player fly speed
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-16 03:31:41 +02:00
19f4b3649a
feat: created custom slider widget
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-16 03:29:57 +02:00
7325798098
feat: improved loading and saving logic, made Json loader ignore unknown keys
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-11 03:06:01 +02:00
302f486f1c chore/add-ci (#3)
Added ci for dev and release builds.

Reviewed-on: https://codeberg.org/moonleay/Gimbal/pulls/3
Co-authored-by: moonleay <contact@moonleay.net>
Co-committed-by: moonleay <contact@moonleay.net>
2024-05-10 15:17:26 +00:00
68e9eeceb6
chore: bumped protocol version, because server side component changed
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-07 01:14:11 +02:00
moonleay
60e638a3d8 Merge pull request 'fix: Dynamic Property Copying in Replace Mode' (#2) from Cookieso/Gimbal:master into master
Reviewed-on: https://codeberg.org/moonleay/Gimbal/pulls/2
2024-05-06 23:11:57 +00:00
31bab51603 fix: Made replace mode more dynamic to copy properties for all blocks 2024-05-06 21:43:57 +02:00
6002af93a5
feat: added settings and a config system
refactor: Reworked translation key system

Signed-off-by: moonleay <contact@moonleay.net>
2024-05-06 02:52:28 +02:00
e7355f9781
chore: removed unused vars
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-05 20:04:44 +02:00
f10897bf5d
Merge remote-tracking branch 'origin/master' 2024-05-05 20:04:23 +02:00
d26f96ee04
feat: finished HUD pos conf system
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-05 20:03:59 +02:00
89dee6841a
feat: finished HUD pos conf system
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-05 20:03:29 +02:00
5c77165d17
feat: added config, added config ui, started working on HUD configurator
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-05 05:11:33 +02:00
13cbbfa72f
chore: replaced println() with proper logging util
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-04 02:48:56 +02:00
7cf65cffd2
chore: updated overlooked mentions of old name
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-04 02:39:54 +02:00
b5d65623aa
chore!: bumped version to 0.3.0, bumped protocol version to 2
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-04 02:36:41 +02:00
dca01b275f
feat!: reworked replace mode to work server side, added propper stair replacement support
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-04 02:35:49 +02:00
c321746b08
feat: bump version, set correct protocol version
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-01 19:56:31 +02:00
8bcbcc021b
fix: fixed not being able to join worlds in multiplayer and singleplayer
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-01 19:55:53 +02:00
8b7e576d3d
feat: added permission checks to server, reworked login process, upgraded gradle, upgraded loom
Signed-off-by: moonleay <contact@moonleay.net>
2024-05-01 18:42:50 +02:00
51 changed files with 1934 additions and 362 deletions

View file

@ -0,0 +1,42 @@
on:
push:
branches-ignore:
- refs/tags/
jobs:
dev:
runs-on: nodebullshit
steps:
- uses: actions/checkout@v3
name: Checkout
- name: Set short git commit reference
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- uses: https://github.com/actions/setup-java@v4
name: Setup Java
with:
distribution: temurin
java-version: 17
- uses: https://github.com/gradle/actions/setup-gradle@v3
name: Setup Gradle
env:
GIT_SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
- run: ./gradlew runDatagen
name: Generate assets
env:
GIT_SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
- run: ./gradlew build
name: Build project
env:
GIT_SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
- run: ./gradlew publish
name: Upload
env:
GIT_SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
PACKAGE_REPO_KEY: ${{ secrets.PACKAGE_REPO_KEY }}

View file

@ -0,0 +1,54 @@
on:
release:
types:
- released
- prereleased
- created
- published
jobs:
release:
runs-on: nodebullshit
steps:
- uses: actions/checkout@v3
name: Checkout
- name: Fetch the full git repository
run: git fetch --prune --unshallow
- name: Set short git commit reference
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- uses: https://github.com/actions/setup-java@v4
name: Setup Java
with:
distribution: temurin
java-version: 17
- uses: https://github.com/gradle/actions/setup-gradle@v3
name: Setup Gradle
env:
GIT_SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
- run: ./gradlew runDatagen
name: Generate assets
env:
GIT_SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
- run: ./gradlew build
name: Build project
env:
GIT_SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
- run: ./gradlew publish
name: Upload
env:
GIT_SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
PACKAGE_REPO_KEY: ${{ secrets.PACKAGE_REPO_KEY }}
- run: ./gradlew modrinth
name: Publish to Modrinth
env:
GIT_SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}

View file

@ -27,13 +27,23 @@ plugins {
`maven-publish`
eclipse
id("org.jetbrains.gradle.plugin.idea-ext")
id("com.modrinth.minotaur") version "2.+"
}
val mavenVersion = System.getenv("CI_COMMIT_TAG") ?: System.getenv("CI_COMMIT_SHORT_SHA")?.let { "$it-dev" }
?: "0.1.1-dev" //""0.0.0-SNAPSHOT"
val ver = if ((System.getenv("GITHUB_REF") ?: "local").startsWith("refs/tags/"))
System.getenv("GITHUB_REF_NAME") ?: "err" else System.getenv("GIT_SHA_SHORT") ?: "0.0.0"
val mavenVersion = ver
val modId: String by project
val modName: String by project
/*
* Gimbal version stuff
* */
val gimbalProtocolVersion = 3
val mavenGroup: String by project
val mavenArtifact: String by project
@ -50,6 +60,9 @@ group = mavenGroup
project.base.archivesName.set(mavenArtifact)
repositories {
maven { // Dependency api
url = uri("https://oss.sonatype.org/content/repositories/snapshots")
}
}
fabricApi {
@ -66,6 +79,10 @@ dependencies {
modImplementation("net.fabricmc:fabric-language-kotlin:$fabricKotlinVersion")
modImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
modApi("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
{
exclude(group = "net.fabricmc")
}
}
val targetJavaVersion = 17
@ -76,6 +93,7 @@ val templateProps = mapOf(
"modVersion" to project.version as String,
"modId" to modId,
"modName" to modName,
"protocolVersion" to gimbalProtocolVersion,
"minecraftVersion" to minecraftVersion,
"fabricLoaderVersion" to fabricLoaderVersion,
"fabricKotlinVersion" to fabricKotlinVersion,
@ -169,17 +187,18 @@ publishing {
}
repositories {
if (System.getenv("CI_JOB_TOKEN") != null) {
if (System.getenv("CI") != null) {
maven {
name = "GitLab"
val projectId = System.getenv("CI_PROJECT_ID")
val apiV4 = System.getenv("CI_API_V4_URL")
url = uri("$apiV4/projects/$projectId/packages/maven")
name = "Codeberg"
val repoOwner = System.getenv("GITHUB_REPOSITORY_OWNER")
val serverUrl = System.getenv("GITHUB_SERVER_URL")
val accessToken = System.getenv("PACKAGE_REPO_KEY")
url = uri("$serverUrl/api/packages/$repoOwner/maven")
authentication {
create("token", HttpHeaderAuthentication::class.java) {
credentials(HttpHeaderCredentials::class.java) {
name = "Job-Token"
value = System.getenv("CI_JOB_TOKEN")
create("header", HttpHeaderAuthentication::class.java) {
credentials(HttpHeaderCredentials::class) {
name = "Authorization"
value = "token $accessToken"
}
}
}
@ -188,6 +207,33 @@ publishing {
}
}
// build.gradle.kts
modrinth {
token.set(System.getenv("MODRINTH_TOKEN")) // Remember to have the MODRINTH_TOKEN environment variable set or else this will fail - just make sure it stays private!
projectId.set(modId) // This can be the project ID or the slug. Either will work!
versionNumber.set(mavenVersion) // You don't need to set this manually. Will fail if Modrinth has this version already
versionName.set("$modName $mavenVersion for $minecraftVersion")
versionType.set("alpha") // This is the default -- can also be `beta` or `alpha`
uploadFile.set(tasks.remapJar) // With Loom, this MUST be set to `remapJar` instead of `jar`!
gameVersions.addAll(listOf(project.ext["minecraft.version"] as String)) // Must be an array, even with only one version
loaders.add("fabric") // Must also be an array - no need to specify this if you're using Loom or ForgeGradle
changelog.set(
"Changelog v$mavenVersion\n\nCheckout the changelog [on codeberg](${System.getenv("GITHUB_SERVER_URL") ?: ""}/${
System.getenv(
"GITHUB_REPOSITORY"
) ?: ""
}/releases/tag/${System.getenv("GITHUB_REF_NAME") ?: ""})"
)
dependencies { // A special DSL for creating dependencies
// scope.type
// The scope can be `required`, `optional`, `incompatible`, or `embedded`
// The type can either be `project` or `version`
required.project("fabric-api") // Creates a new required dependency on Fabric API
required.project("fabric-language-kotlin")
}
}
rootProject.idea.project {
this as ExtensionAware
configure<ProjectSettings> {

View file

@ -18,7 +18,7 @@
#Generated by fabric-mod-template
#Sun Apr 21 19:29:15 CEST 2024
fabric.loom.version=1.4.6
fabric.loom.version=1.6-SNAPSHOT
kotlin.version=1.9.22
fabric.api.version=0.77.0+1.19.2
yarn.version=1.19.2+build.28

View file

@ -18,7 +18,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View file

@ -19,6 +19,7 @@
package net.moonleay.gimbal
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents
import net.moonleay.gimbal.build.BuildConstants
import net.moonleay.gimbal.networking.GimbalServer
import org.apache.logging.log4j.LogManager
@ -31,6 +32,11 @@ internal object Main : ModInitializer {
LOGGER.info("Registering packets...")
GimbalServer.registerPacketHandler()
LOGGER.info("Packets have been registered.")
LOGGER.info("Registering events...")
ServerPlayConnectionEvents.INIT.register(ServerPlayConnectionEvents.Init { handler, server ->
GimbalServer.notifyPlayerOfPolicy(handler.player)
})
LOGGER.info("Events have been registered.")
LOGGER.info("Gimbal has been initialized on the common side.")
LOGGER.info("${BuildConstants.modName} (${BuildConstants.modId}) v.${BuildConstants.modVersion} by moonleay")
}

View file

@ -20,7 +20,11 @@ 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
import net.moonleay.gimbal.networking.GimbalClient
@ -28,15 +32,23 @@ import org.apache.logging.log4j.LogManager
internal object ClientMain : ClientModInitializer {
private val LOGGER = LogManager.getLogger(BuildConstants.modName)
lateinit var CONFIG: ClientConfigHolder
override fun onInitializeClient() {
LOGGER.info("Initializing Gimbal on the client side...")
KeybindingRegistrar.registerKeybindings()
registerEvents()
this.registerEvents()
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("Applying Config to client.")
ClientEditor.applyConfig(CONFIG.config)
LOGGER.info("Config has been applied.")
LOGGER.info("Gimbal has been initialized on the client side.")
}
@ -45,6 +57,10 @@ internal object ClientMain : ClientModInitializer {
ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { client ->
KeybindingManager.onClientTick(client)
})
ClientLoginConnectionEvents.INIT.register(ClientLoginConnectionEvents.Init { _, _ ->
ClientEditor.onConnectedToNewWorld() // Client is connecting to a new server, reset the editor
})
LOGGER.info("Client events have been registered.")
}

View file

@ -0,0 +1,79 @@
/*
* 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 net.moonleay.gimbal.client.editor.ClientEditor
import java.nio.file.Path
import kotlin.io.path.*
class ClientConfigHolder(private val path: Path) {
var config: GimbalClientConfig
private set
init {
this.config = load()
}
fun updateConfig(new: GimbalClientConfig) {
this.config = new
this.save(config)
ClientEditor.applyConfig(config)
}
@OptIn(ExperimentalSerializationApi::class)
fun load(): GimbalClientConfig {
if (!path.isReadable())
save(GimbalClientConfig())
val iStream = path.inputStream()
val gimbalClientConfig: GimbalClientConfig = iStream.use {
Json {
ignoreUnknownKeys = true
}.decodeFromStream<GimbalClientConfig>(it)
}
iStream.close()
return gimbalClientConfig
}
@OptIn(ExperimentalSerializationApi::class)
fun save(conf: GimbalClientConfig) {
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.")
}
val oStream = path.outputStream()
oStream.use {
Json {
ignoreUnknownKeys = true
}.encodeToStream<GimbalClientConfig>(conf, it)
}
oStream.close()
}
}

View file

@ -0,0 +1,32 @@
/*
* 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
import net.moonleay.gimbal.client.config.enums.ToastSettings
import net.moonleay.gimbal.editor.state.mode.Mode
@Serializable
data class GimbalClientConfig(
val guiSettings: GimbalGuiSettings = GimbalGuiSettings(),
val toastSettings: ToastSettings = ToastSettings.ALL,
val playerFlySpeed: Int = 100,
val shouldEscResetMode: Boolean = true,
val defaultMode: Mode = Mode.NORMAL
)

View file

@ -0,0 +1,33 @@
/*
* 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
import net.moonleay.gimbal.client.config.enums.HorizontalAnchor
import net.moonleay.gimbal.client.config.enums.HudOptions
import net.moonleay.gimbal.client.config.enums.VerticalAnchor
@Serializable
data class GimbalGuiSettings(
val showHud: Boolean = true,
val offset: ScaledRes = ScaledRes(4.0, 4.0),
val horizontalAnchor: HorizontalAnchor = HorizontalAnchor.LEFT,
val verticalAnchor: VerticalAnchor = VerticalAnchor.TOP,
val hudOptions: HudOptions = HudOptions.ALL,
)

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, // offsetX
val scaledY: Double, // offsetY
)

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.enums
enum class Centerpoint {
TOP_LEFT,
TOP_RIGHT,
BOTTOM_RIGHT,
BOTTOM_LEFT,
CENTER
}

View file

@ -16,21 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.mixin;
package net.moonleay.gimbal.client.config.enums
import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
import net.moonleay.gimbal.client.editor.ClientEditor;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import kotlinx.serialization.Serializable
@Mixin(DownloadingTerrainScreen.class)
public class GimbalPolicyCheckMixin {
@Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/DownloadingTerrainScreen;close()V", ordinal = -1))
private void func(CallbackInfo ci) {
ClientEditor.INSTANCE.onConnectedToNewWorld();
}
@Serializable
enum class HorizontalAnchor {
LEFT,
CENTER,
RIGHT,
}

View file

@ -0,0 +1,31 @@
/*
* 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.enums
import kotlinx.serialization.Serializable
import net.minecraft.text.Text
import net.moonleay.gimbal.constants.TranslationKeys
@Serializable
enum class HudOptions(val translatableText: Text) {
ALL(Text.translatable(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_ALL)),
NUMBER(Text.translatable(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_NUMBER)),
INITIAL(Text.translatable(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_INITIALS)),
ONLY_MODE(Text.translatable(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_MODE_ONLY)),
}

View file

@ -0,0 +1,32 @@
/*
* 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.enums
import kotlinx.serialization.Serializable
import net.minecraft.text.Text
import net.moonleay.gimbal.client.util.ToastType
import net.moonleay.gimbal.constants.TranslationKeys
@Serializable
enum class ToastSettings(val translatableText: Text, val allowedTypes: List<ToastType>) {
ALL(Text.translatable(TranslationKeys.Gui.Config.Toasts.SHOW_ALL), listOf(ToastType.SYSTEM, ToastType.TOGGLE)),
ONLY_TOGGLE(Text.translatable(TranslationKeys.Gui.Config.Toasts.SHOW_TOGGLE), listOf(ToastType.TOGGLE)),
ONLY_SYSTEM(Text.translatable(TranslationKeys.Gui.Config.Toasts.SHOW_SYSTEM), listOf(ToastType.SYSTEM)),
NONE(Text.translatable(TranslationKeys.Gui.Config.Toasts.SHOW_NONE), listOf())
}

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.enums
import kotlinx.serialization.Serializable
@Serializable
enum class VerticalAnchor {
TOP,
CENTER,
BOTTOM,
}

View file

@ -20,37 +20,48 @@ package net.moonleay.gimbal.client.editor
import net.minecraft.client.MinecraftClient
import net.minecraft.text.Text
import net.moonleay.gimbal.build.BuildConstants
import net.moonleay.gimbal.client.ClientMain
import net.moonleay.gimbal.client.config.GimbalClientConfig
import net.moonleay.gimbal.client.config.enums.HudOptions
import net.moonleay.gimbal.client.util.ChatUtil
import net.moonleay.gimbal.client.util.ToastType
import net.moonleay.gimbal.editor.ServerEditorManager
import net.moonleay.gimbal.editor.state.EditorState
import net.moonleay.gimbal.editor.state.GimbalPolicyType
import net.moonleay.gimbal.editor.state.GimbalServerState
import net.moonleay.gimbal.editor.state.mode.Capability
import net.moonleay.gimbal.editor.state.mode.Mode
import net.moonleay.gimbal.editor.state.mode.ModeModifier
import net.moonleay.gimbal.editor.util.EditorUtil
import net.moonleay.gimbal.editor.util.GimbalPolicy
import net.moonleay.gimbal.networking.GimbalClient
import org.apache.logging.log4j.LogManager
object ClientEditor {
private var POLICY = GimbalPolicyType.NOT_PRESENT
private var CURRENT_MODE = Mode.NORMAL
private lateinit var CURRENT_MODE: Mode
private var TEMP_DISABLED_MODE = Mode.UNKNOWN
private val CURRENT_MODE_MODIFIER = mutableListOf<ModeModifier>()
private val TEMP_DISABLED_MODIFIERS = mutableListOf<ModeModifier>()
private val DISABLED_MODIFIERS_STORAGE = mutableListOf<ModeModifier>()
private val LOGGER = LogManager.getLogger(BuildConstants.modName)
fun applyConfig(config: GimbalClientConfig, isBoot: Boolean = true) {
if (isBoot) {
this.CURRENT_MODE = config.defaultMode
return
}
if (!config.shouldEscResetMode) return
this.CURRENT_MODE = config.defaultMode
onUpdated()
}
fun onConnectedToNewWorld() {
POLICY = GimbalPolicyType.NOT_PRESENT
GimbalClient.checkIfServerHasGimble(
EditorState(
CURRENT_MODE,
CURRENT_MODE_MODIFIER
)
)
if (TEMP_DISABLED_MODE == Mode.UNKNOWN) {
TEMP_DISABLED_MODE =
CURRENT_MODE
@ -62,9 +73,9 @@ object ClientEditor {
CURRENT_MODE_MODIFIER.clear()
}
fun onAllowedCheck(data: GimbalPolicy) {
POLICY = data.policy
if (data.policy == GimbalPolicyType.ALLOWED) {
fun onPolicyReceived(data: GimbalServerState, shouldUpdateServer: Boolean = true) {
POLICY = data.policyType
if (data.policyType == GimbalPolicyType.ALLOWED) {
if (TEMP_DISABLED_MODE != Mode.UNKNOWN) {
CURRENT_MODE =
TEMP_DISABLED_MODE
@ -74,7 +85,19 @@ object ClientEditor {
DISABLED_MODIFIERS_STORAGE
)
DISABLED_MODIFIERS_STORAGE.clear()
onUpdated()
onUpdated(shouldUpdateServer)
if (!shouldUpdateServer) {
val player = MinecraftClient.getInstance().player
if (player == null) {
LOGGER.warn("Player is null!") //FIXME: Fix player is null at world join!
return
}
ServerEditorManager.updateEditorState(
MinecraftClient.getInstance().player!!.uuid,
this.getClientState()
)
}
}
}
@ -92,18 +115,15 @@ object ClientEditor {
}
fun isInNonDefaultMode(): Boolean {
return CURRENT_MODE != Mode.NORMAL
return CURRENT_MODE != ClientMain.CONFIG.config.defaultMode
}
/*
* Send an updated player state to the server
* */
private fun updateServerState() {
val state = EditorState(
CURRENT_MODE,
CURRENT_MODE_MODIFIER
)
GimbalClient.sendEditorState(state)
val state = this.getClientState()
GimbalClient.sendStatePacket(state)
ServerEditorManager.updateEditorState(MinecraftClient.getInstance().player!!.uuid, state)
}
@ -113,7 +133,13 @@ object ClientEditor {
* */
fun setMode(mode: Mode) {
if (!isAllowed()) {
ChatUtil.showToastToSelf("Gimble is disabled", "You cannot change modes", MinecraftClient.getInstance())
ChatUtil.showToastToSelf(
"Gimbal is disabled",
"You cannot change modes",
ToastType.SYSTEM,
ClientMain.CONFIG.config,
MinecraftClient.getInstance()
)
return
}
if (!MinecraftClient.getInstance().player!!.isCreative)
@ -128,39 +154,60 @@ object ClientEditor {
* */
fun toggleModifier(mod: ModeModifier) {
if (!isAllowed()) {
ChatUtil.showToastToSelf("Gimble is disabled", "You cannot change modifiers", MinecraftClient.getInstance())
ChatUtil.showToastToSelf(
"Gimbal is disabled",
"You cannot change modifiers",
ToastType.SYSTEM,
ClientMain.CONFIG.config,
MinecraftClient.getInstance()
)
return
}
if (!MinecraftClient.getInstance().player!!.isCreative)
return
if (CURRENT_MODE.incompatibleModifiers.contains(mod)){
if (TEMP_DISABLED_MODIFIERS.contains(mod))
if (TEMP_DISABLED_MODIFIERS.contains(mod)) {
TEMP_DISABLED_MODIFIERS.remove(mod)
else
} else {
TEMP_DISABLED_MODIFIERS.add(mod)
}
}
else {
if (CURRENT_MODE_MODIFIER.contains(mod))
if (CURRENT_MODE_MODIFIER.contains(mod)) {
CURRENT_MODE_MODIFIER.remove(mod)
else
ChatUtil.showToastToSelf(
"Disabled ${mod.displayName}",
"[${this.getDisplayNameListAsString(CURRENT_MODE_MODIFIER)}]",
ToastType.TOGGLE, ClientMain.CONFIG.config,
MinecraftClient.getInstance()
)
} else {
CURRENT_MODE_MODIFIER.add(mod)
ChatUtil.showToastToSelf(
"Enabled ${mod.displayName}",
"[${this.getDisplayNameListAsString(CURRENT_MODE_MODIFIER)}]",
ToastType.TOGGLE, ClientMain.CONFIG.config,
MinecraftClient.getInstance()
)
}
}
onUpdated()
}
private fun onUpdated() {
private fun onUpdated(shouldUpdateServer: Boolean = true) {
CURRENT_MODE_MODIFIER.sortBy {
it.displayName
}
checkForIncompatibleModeModifiers()
checkForIncompatibleModeModifiers(shouldUpdateServer)
}
/**
* This runs on Mode updated
*/
private fun checkForIncompatibleModeModifiers() {
private fun checkForIncompatibleModeModifiers(shouldUpdateServer: Boolean = true) {
if (TEMP_DISABLED_MODIFIERS.size > 0) {
CURRENT_MODE_MODIFIER.addAll(
TEMP_DISABLED_MODIFIERS
@ -185,19 +232,23 @@ object ClientEditor {
}
// Update State
updateServerState()
if (shouldUpdateServer)
updateServerState()
if(TEMP_DISABLED_MODIFIERS.isNotEmpty()) {
ChatUtil.showToastToSelf(
"${CURRENT_MODE.displayName} Mode disabled",
getListAsString(TEMP_DISABLED_MODIFIERS), MinecraftClient.getInstance()
getDisplayNameListAsString(TEMP_DISABLED_MODIFIERS),
ToastType.SYSTEM,
ClientMain.CONFIG.config,
MinecraftClient.getInstance()
)
}
}
private fun getListAsString(list: List<ModeModifier>): String{
private fun getDisplayNameListAsString(list: List<ModeModifier>): String {
if(list.isEmpty())
return "Empty list"
return ""
val sb = StringBuilder()
for (mod in list) {
sb.append(mod.displayName)
@ -206,28 +257,60 @@ object ClientEditor {
return sb.toString().dropLast(2)
}
private fun getShortNameListAsString(list: List<ModeModifier>): String {
if (list.isEmpty())
return ""
val sb = StringBuilder()
for (mod in list) {
sb.append(mod.shortName)
sb.append(", ")
}
return sb.toString().dropLast(2)
}
/*
* Get the display text to display in the HUD
* */
fun getModeDisplayText(): Text {
fun getModeDisplayText(config: GimbalClientConfig): Text {
val displayText = StringBuilder(CURRENT_MODE.displayName)
if (CURRENT_MODE_MODIFIER.isNotEmpty() && isAllowed() && MinecraftClient.getInstance().player?.isCreative == true) {
displayText.append(" [")
for (i in CURRENT_MODE_MODIFIER.indices) {
displayText.append(CURRENT_MODE_MODIFIER[i].displayName)
if (i != CURRENT_MODE_MODIFIER.size - 1) {
displayText.append(", ")
if (isAllowed() && MinecraftClient.getInstance().player?.isCreative == true) {
when (config.guiSettings.hudOptions) {
HudOptions.ALL -> {
if (CURRENT_MODE_MODIFIER.size > 0) {
displayText.append(" [")
displayText.append(this.getDisplayNameListAsString(CURRENT_MODE_MODIFIER))
displayText.append("]")
}
return Text.of(displayText.toString())
}
HudOptions.NUMBER -> {
displayText.append(" (")
displayText.append(CURRENT_MODE_MODIFIER.size)
displayText.append(")")
return Text.of(displayText.toString())
}
HudOptions.INITIAL -> {
if (CURRENT_MODE_MODIFIER.size > 0) {
displayText.append(" [")
displayText.append(this.getShortNameListAsString(CURRENT_MODE_MODIFIER))
displayText.append("]")
}
return Text.of(displayText.toString())
}
HudOptions.ONLY_MODE -> return Text.of(displayText.toString())
}
displayText.append("]")
} else if (!isAllowed() || !MinecraftClient.getInstance().player?.isCreative!!) {
displayText.clear()
displayText.append("[GIMBLE DISABLED]")
}
return Text.of(displayText.toString())
return Text.of("[GIMBAL DISABLED]")
}
fun getCurrentColor(): Int {
return CURRENT_MODE.color
}
fun getClientState(): EditorState {
return EditorState(CURRENT_MODE, CURRENT_MODE_MODIFIER)
}
}

View file

@ -30,6 +30,7 @@ import net.moonleay.gimbal.client.keybindings.impl.editormodemodifier.ToggleNoUp
import net.moonleay.gimbal.client.keybindings.impl.gamemode.CreativeModeShortcut
import net.moonleay.gimbal.client.keybindings.impl.gamemode.SpectatorModeShortcut
import net.moonleay.gimbal.client.keybindings.impl.gamemode.SurvivalModeShortcut
import net.moonleay.gimbal.constants.TranslationKeys
import org.apache.logging.log4j.LogManager
import org.lwjgl.glfw.GLFW
@ -47,28 +48,28 @@ object KeybindingRegistrar {
private fun registerSetEditorModeModifierKeybindings() {
val toggleBulldozerModifierShortcut = KeyBinding(
"gimbal.key.editor.modifier.bulldozer",
TranslationKeys.Keybindings.Binding.Editor.Modifier.BULLDOZER,
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_UNKNOWN,
"gimbal.category.editormodifier"
TranslationKeys.Keybindings.Category.Editor.MODIFIER
)
val toggleForcePlaceModifierShortcut = KeyBinding(
"gimbal.key.editor.modifier.forceplace",
TranslationKeys.Keybindings.Binding.Editor.Modifier.FORCE_PLACE,
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_UNKNOWN,
"gimbal.category.editormodifier"
TranslationKeys.Keybindings.Category.Editor.MODIFIER
)
val toggleNoUpdatesModifierShortcut = KeyBinding(
"gimbal.key.editor.modifier.noupdates",
TranslationKeys.Keybindings.Binding.Editor.Modifier.NO_UPDATES,
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_UNKNOWN,
"gimbal.category.editormodifier"
TranslationKeys.Keybindings.Category.Editor.MODIFIER
)
val toggleNoClipModifierShortcut = KeyBinding(
"gimbal.key.editor.modifier.noclip",
TranslationKeys.Keybindings.Binding.Editor.Modifier.NO_CLIP,
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_UNKNOWN,
"gimbal.category.editormodifier"
TranslationKeys.Keybindings.Category.Editor.MODIFIER
)
KeybindingManager.registerShortcut(ToggleBulldozerModifierShortcut(toggleBulldozerModifierShortcut))
@ -79,16 +80,16 @@ object KeybindingRegistrar {
private fun registerSetEditorModeKeybindings() {
val insertKeyBinding = KeyBinding(
"gimbal.key.editor.mode.insert",
TranslationKeys.Keybindings.Binding.Editor.Mode.INSERT,
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_I,
"gimbal.category.editormode"
TranslationKeys.Keybindings.Category.Editor.MODE
)
val replaceKeyBinding = KeyBinding(
"gimbal.key.editor.mode.replace",
TranslationKeys.Keybindings.Binding.Editor.Mode.REPLACE,
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_R,
"gimbal.category.editormode"
TranslationKeys.Keybindings.Category.Editor.MODE
)
// val visualKeyBinding = KeyBinding(
// "gimbal.key.editor.mode.visual",
@ -103,22 +104,22 @@ object KeybindingRegistrar {
private fun registerSetGameModeKeybindings() {
val survivalKeyBinding = KeyBinding(
"gimbal.key.game.mode.survival",
TranslationKeys.Keybindings.Binding.Game.Mode.SURVIVAL,
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_UNKNOWN,
"gimbal.category.gamemode"
TranslationKeys.Keybindings.Category.Game.GAMEMODE
)
val creativeKeyBinding = KeyBinding(
"gimbal.key.game.mode.creative",
TranslationKeys.Keybindings.Binding.Game.Mode.CREATIVE,
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_UNKNOWN,
"gimbal.category.gamemode"
TranslationKeys.Keybindings.Category.Game.GAMEMODE
)
val spectatorKeyBinding = KeyBinding(
"gimbal.key.game.mode.spectator",
TranslationKeys.Keybindings.Binding.Game.Mode.SPECTATOR,
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_UNKNOWN,
"gimbal.category.gamemode"
TranslationKeys.Keybindings.Category.Game.GAMEMODE
)
KeybindingManager.registerShortcut(SurvivalModeShortcut(survivalKeyBinding))
KeybindingManager.registerShortcut(CreativeModeShortcut(creativeKeyBinding))

View file

@ -0,0 +1,151 @@
/*
* 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.ClientConfigHolder
import net.moonleay.gimbal.client.config.GimbalClientConfig
import net.moonleay.gimbal.client.config.GimbalGuiSettings
import net.moonleay.gimbal.client.config.ScaledRes
import net.moonleay.gimbal.client.util.screen.ScreenUtil
import net.moonleay.gimbal.constants.TranslationKeys
import org.apache.logging.log4j.LogManager
class GimbalEditHudGui(private val parent: Screen, private val cfg: ClientConfigHolder) :
Screen(Text.translatable(TranslationKeys.Gui.Config.Hud.EDIT_HUD)) {
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.offset.scaledX
this.tempYOffset = cfg.config.guiSettings.offset.scaledY
}
override fun init() {
super.init()
this.addDrawableChild(ButtonWidget(
this.width / 2 + 10, this.height - 27, 90, 20, Text.translatable(TranslationKeys.Gui.Config.Hud.RESET_HUD)
) { _: 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 anchor = ScreenUtil.getAnchor(this.tempXOffset, this.tempYOffset)
val newConf = GimbalClientConfig(
guiSettings = GimbalGuiSettings(
showHud = oldConf.guiSettings.showHud,
offset = ScaledRes(
scaledX = this.tempXOffset,
scaledY = this.tempYOffset,
),
horizontalAnchor = anchor.first,
verticalAnchor = anchor.second,
hudOptions = oldConf.guiSettings.hudOptions
),
toastSettings = oldConf.toastSettings,
playerFlySpeed = oldConf.playerFlySpeed,
shouldEscResetMode = oldConf.shouldEscResetMode,
defaultMode = oldConf.defaultMode
)
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 wantedX = ScreenUtil.getScaled(this.client!!.window.width, mouse.x.toFloat())
val wantedY = ScreenUtil.getScaled(this.client!!.window.height, mouse.y.toFloat())
if (!ScreenUtil.isPositionOutOfBounds(wantedX, wantedY)) {
this.tempXOffset = wantedX
this.tempYOffset = wantedY
}
}
}
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)
val scaleFactor = this.client!!.window.calculateScaleFactor(
this.client!!.options.guiScale.value,
this.client!!.forcesUnicodeFont()
)
val anchor = ScreenUtil.getAnchor(this.tempXOffset, this.tempYOffset)
this.textRenderer.drawWithShadow(
matrices,
Text.translatable(TranslationKeys.Gui.Config.Hud.EXAMPLE_HUD_TEXT),
ScreenUtil.getXForAnchor(
ScreenUtil.getReal(this.client!!.window.width, this.tempXOffset) / scaleFactor,
anchor.first,
this.textRenderer.getWidth(Text.translatable(TranslationKeys.Gui.Config.Hud.EXAMPLE_HUD_TEXT))
),
ScreenUtil.getYForAnchor(
ScreenUtil.getReal(this.client!!.window.height, this.tempYOffset) / scaleFactor,
anchor.second,
this.textRenderer.fontHeight
),
0xFFFFFF
)
super.render(matrices, mouseX, mouseY, delta)
}
}

View file

@ -0,0 +1,290 @@
/*
* 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
import net.moonleay.gimbal.client.config.enums.HudOptions
import net.moonleay.gimbal.client.config.enums.ToastSettings
import net.moonleay.gimbal.client.screen.widgets.GimbalSliderWidget
import net.moonleay.gimbal.client.util.NumberUtil
import net.moonleay.gimbal.constants.TranslationKeys
import net.moonleay.gimbal.editor.state.mode.Mode
class GimbalSettingsGui(private val parent: Screen, private val cfg: ClientConfigHolder) :
Screen(Text.translatable(TranslationKeys.Gui.Config.SCREEN_NAME)) {
private var hudOptions: HudOptions
private var toastSettings: ToastSettings
private var playerFlySpeed: Int
private var defaultMode: Mode
init {
this.hudOptions = cfg.config.guiSettings.hudOptions
this.toastSettings = cfg.config.toastSettings
this.playerFlySpeed = cfg.config.playerFlySpeed
this.defaultMode = cfg.config.defaultMode
}
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(TranslationKeys.Gui.Config.Hud.SHOW_HUD)
) { _: CyclingButtonWidget<Boolean?>?, isEnabled: Boolean? ->
val oldGui = cfg.config.guiSettings
val newGui = GimbalGuiSettings(
showHud = isEnabled ?: true,
offset = oldGui.offset,
horizontalAnchor = oldGui.horizontalAnchor,
verticalAnchor = oldGui.verticalAnchor,
hudOptions = oldGui.hudOptions
)
val newConf = GimbalClientConfig(
guiSettings = newGui,
toastSettings = cfg.config.toastSettings,
playerFlySpeed = cfg.config.playerFlySpeed,
shouldEscResetMode = cfg.config.shouldEscResetMode,
defaultMode = cfg.config.defaultMode
)
cfg.updateConfig(newConf)
})
this.addDrawableChild(ButtonWidget(
this.width / 2 - 155 + 160,
this.height / 6 + 24 * 0,
150,
20,
Text.translatable(TranslationKeys.Gui.Config.Hud.EDIT_HUD)
) { _: ButtonWidget? ->
this.client!!.setScreen(GimbalEditHudGui(this, cfg))
})
this.addDrawableChild<CyclingButtonWidget<HudOptions>>(
CyclingButtonWidget.builder<HudOptions> { value: HudOptions? ->
when (value) {
HudOptions.ALL -> {
return@builder HudOptions.ALL.translatableText
}
HudOptions.NUMBER -> {
return@builder HudOptions.NUMBER.translatableText
}
HudOptions.INITIAL -> {
return@builder HudOptions.INITIAL.translatableText
}
HudOptions.ONLY_MODE -> {
return@builder HudOptions.ONLY_MODE.translatableText
}
else -> return@builder Text.translatable(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_ALL)
}
}
.values(HudOptions.entries)
.initially(this.hudOptions)
.build(
this.width / 2 - 155,
this.height / 6 + 24 * 1,
150,
20,
Text.translatable(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_MODIFIERS)
) { _: CyclingButtonWidget<HudOptions>?, hudDO: HudOptions ->
this.hudOptions = hudDO
val oldConfig = cfg.config
val oldGuiConfig = oldConfig.guiSettings
val newConfig = GimbalClientConfig(
guiSettings = GimbalGuiSettings(
horizontalAnchor = oldGuiConfig.horizontalAnchor,
verticalAnchor = oldGuiConfig.verticalAnchor,
showHud = oldGuiConfig.showHud,
offset = oldGuiConfig.offset,
hudOptions = this.hudOptions
),
toastSettings = oldConfig.toastSettings,
playerFlySpeed = oldConfig.playerFlySpeed,
shouldEscResetMode = oldConfig.shouldEscResetMode,
defaultMode = oldConfig.defaultMode
)
cfg.updateConfig(newConfig)
}
)
this.addDrawableChild<CyclingButtonWidget<ToastSettings>>(
CyclingButtonWidget.builder<ToastSettings> { value: ToastSettings? ->
when (value) {
ToastSettings.ALL -> {
return@builder ToastSettings.ALL.translatableText
}
ToastSettings.ONLY_TOGGLE -> {
return@builder ToastSettings.ONLY_TOGGLE.translatableText
}
ToastSettings.ONLY_SYSTEM -> {
return@builder ToastSettings.ONLY_SYSTEM.translatableText
}
ToastSettings.NONE -> {
return@builder ToastSettings.NONE.translatableText
}
else -> return@builder Text.translatable(TranslationKeys.Gui.Config.Toasts.SHOW_ALL)
}
}
.values(ToastSettings.entries)
.initially(this.toastSettings)
.build(
this.width / 2 - 155 + 160,
this.height / 6 + 24 * 1,
150,
20,
Text.translatable(TranslationKeys.Gui.Config.Toasts.SHOW_TOASTS)
) { _: CyclingButtonWidget<ToastSettings>?, toastStng: ToastSettings ->
this.toastSettings = toastStng
val oldConfig = cfg.config
val newConfig = GimbalClientConfig(
guiSettings = oldConfig.guiSettings,
toastSettings = this.toastSettings,
playerFlySpeed = oldConfig.playerFlySpeed,
shouldEscResetMode = oldConfig.shouldEscResetMode,
defaultMode = oldConfig.defaultMode
)
cfg.updateConfig(newConfig)
}
)
this.addDrawableChild(GimbalSliderWidget(
this.width / 2 - 155,
this.height / 6 + 24 * 2,
150,
20,
Text.translatable(TranslationKeys.Gui.Config.PLAYER_FLY_SPEED),
NumberUtil.linearInterpolate(this.playerFlySpeed.toDouble(), 100.0, 1000.0, 0.0, 1.0),
100.0,
1000.0,
) { value ->
this.playerFlySpeed = NumberUtil.linearInterpolate(value, 0.0, 1.0, 100.0, 1000.0).toInt()
if (client!!.player != null && client!!.player!!.isCreative) {
client!!.player!!.abilities.flySpeed = (this.playerFlySpeed / 100) * 0.05f
}
val oldConfig = cfg.config
val newConfig = GimbalClientConfig(
guiSettings = oldConfig.guiSettings,
toastSettings = this.toastSettings,
playerFlySpeed = this.playerFlySpeed,
shouldEscResetMode = oldConfig.shouldEscResetMode,
defaultMode = oldConfig.defaultMode
)
cfg.updateConfig(newConfig)
})
this.addDrawableChild(CyclingButtonWidget.onOffBuilder()
.initially(cfg.config.shouldEscResetMode)
.build(
this.width / 2 - 155,
this.height / 6 + 24 * 3,
150,
20,
Text.translatable(TranslationKeys.Gui.Config.SHOULD_ESC_RESET_MODE)
) { _: CyclingButtonWidget<Boolean?>?, isEnabled: Boolean? ->
val newConf = GimbalClientConfig(
guiSettings = cfg.config.guiSettings,
toastSettings = cfg.config.toastSettings,
playerFlySpeed = cfg.config.playerFlySpeed,
shouldEscResetMode = isEnabled?: true,
defaultMode = cfg.config.defaultMode
)
cfg.updateConfig(newConf)
})
this.addDrawableChild<CyclingButtonWidget<Mode>>(
CyclingButtonWidget.builder<Mode> { value: Mode? ->
if (value == null) {
return@builder Text.literal(Mode.UNKNOWN.displayName)
}
return@builder Text.literal(value.displayName)
}
.values(Mode.entries)
.initially(this.defaultMode)
.build(
this.width / 2 - 155 + 160,
this.height / 6 + 24 * 3,
150,
20,
Text.translatable(TranslationKeys.Gui.Config.DEFAULT_MODE)
) { b: CyclingButtonWidget<Mode>?, requestedMode: Mode ->
if (requestedMode.hidden){
b!!.value = Mode.NORMAL
this.defaultMode = Mode.NORMAL
}
else {
this.defaultMode = requestedMode
}
val oldConfig = cfg.config
val newConfig = GimbalClientConfig(
guiSettings = oldConfig.guiSettings,
toastSettings = oldConfig.toastSettings,
playerFlySpeed = oldConfig.playerFlySpeed,
shouldEscResetMode = oldConfig.shouldEscResetMode,
defaultMode = this.defaultMode
)
cfg.updateConfig(newConfig)
}
)
// 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,50 @@
/*
* 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.widgets
import net.minecraft.client.gui.widget.SliderWidget
import net.minecraft.text.Text
import net.moonleay.gimbal.client.util.NumberUtil
class GimbalSliderWidget(
x: Int,
y: Int,
width: Int,
height: Int,
private val text: Text,
value: Double,
private val lowerEnd: Double,
private val upperEnd: Double,
private val setValue: (Double) -> Unit,
) :
SliderWidget(x, y, width, height, text, value) {
init {
this.message = this.text.copy()
.append(": " + (NumberUtil.linearInterpolate(this.value, 0.0, 1.0, this.lowerEnd, this.upperEnd).toInt()))
}
override fun updateMessage() {
this.message = this.text.copy()
.append(": " + (NumberUtil.linearInterpolate(this.value, 0.0, 1.0, this.lowerEnd, this.upperEnd).toInt()))
}
override fun applyValue() {
this.setValue(this.value)
}
}

View file

@ -21,6 +21,7 @@ package net.moonleay.gimbal.client.util
import net.minecraft.client.MinecraftClient
import net.minecraft.client.toast.SystemToast
import net.minecraft.text.Text
import net.moonleay.gimbal.client.config.GimbalClientConfig
object ChatUtil {
/**
@ -37,7 +38,16 @@ object ChatUtil {
client.inGameHud.chatHud.addMessage(Text.of(message))
}
fun showToastToSelf(title: String, description: String, client: MinecraftClient) {
fun showToastToSelf(
title: String,
description: String,
type: ToastType,
config: GimbalClientConfig,
client: MinecraftClient,
) {
if (!config.toastSettings.allowedTypes.contains(type))
return
val toast = SystemToast(SystemToast.Type.PERIODIC_NOTIFICATION, Text.of(title), Text.of(description))
client.toastManager.add(toast)
}

View file

@ -0,0 +1,44 @@
/*
* 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
object NumberUtil {
/**
* Interpolate a number between two numbers in a linear way
*/
fun linearInterpolate(number: Double, min: Double, max: Double, lowerEnd: Double, upperEnd: Double): Double {
val subtractFromNr = (0 - min) * -1
val minMaxDiff = max - min
val onePercent = minMaxDiff / 100.0
val percentOfNumber = (number - subtractFromNr) / onePercent
val toAddToLowerEnd = (0 - lowerEnd) * -1
val upperLowerDiff = upperEnd - lowerEnd
val onePercentOfEnd = upperLowerDiff / 100.0
return (percentOfNumber * onePercentOfEnd) + toAddToLowerEnd
}
/**
* Interpolate a number between two numbers in a logarithmic way
*/
fun logarithmicInterpolate(number: Double, min: Double, max: Double, lowerEnd: Double, upperEnd: Double): Double {
TODO("Not impl.")
}
}

View file

@ -0,0 +1,24 @@
/*
* 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
enum class ToastType {
TOGGLE,
SYSTEM,
}

View file

@ -0,0 +1,71 @@
/*
* 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.enums.HorizontalAnchor
import net.moonleay.gimbal.client.config.enums.VerticalAnchor
object ScreenUtil {
fun getAnchor(x: Double, y: Double): Pair<HorizontalAnchor, VerticalAnchor> {
return when (x) {
in 0.0..<34.0 -> HorizontalAnchor.LEFT
in 34.0..<67.0 -> HorizontalAnchor.CENTER
else -> HorizontalAnchor.RIGHT
} to when (y) {
in 0.0..<34.0 -> VerticalAnchor.TOP
in 34.0..<67.0 -> VerticalAnchor.CENTER
else -> VerticalAnchor.BOTTOM
}
}
fun getXForAnchor(x: Float, anchor: HorizontalAnchor, textWidth: Int): Float {
return when (anchor) {
HorizontalAnchor.LEFT -> x
HorizontalAnchor.CENTER -> x - (textWidth / 2)
HorizontalAnchor.RIGHT -> x - textWidth
}
}
fun getYForAnchor(y: Float, anchor: VerticalAnchor, textHeight: Int): Float {
return when (anchor) {
VerticalAnchor.TOP -> y
VerticalAnchor.CENTER -> y - (textHeight / 2)
VerticalAnchor.BOTTOM -> y - textHeight
}
}
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

@ -16,14 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.gimbal.networking
package net.moonleay.gimbal.constants
import net.minecraft.util.Identifier
import net.moonleay.gimbal.build.BuildConstants
object PacketIDs {
val UPDATE_EDITOR_STATE_ID = Identifier(BuildConstants.modId, "update_editor_state")
val GIMBLE_PRERENCE_CHECK_ID = Identifier(BuildConstants.modId, "gimble_preference_check")
val TRANSFER_GIMBLE_POLICY_ID = Identifier(BuildConstants.modId, "gimble_is_present")
val GIMBAL_POLICY_PACKET_ID = Identifier(BuildConstants.modId, "gimbal_policy")
}

View file

@ -0,0 +1,23 @@
/*
* 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.constants
object PermissionIDs {
const val GIMBAL_USAGE_PERMISSION = "gimbal.use"
}

View file

@ -0,0 +1,127 @@
/*
* 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.constants
import net.moonleay.gimbal.build.BuildConstants
object TranslationKeys {
object Gui {
const val BASE = "gui.${BuildConstants.modId}."
object Config {
const val BASE = "${TranslationKeys.Gui.BASE}conf."
const val SCREEN_NAME = "${BASE}settings"
const val GENERIC_ENABLED = "${BASE}enabled"
const val GENERIC_DISABLED = "${BASE}disabled"
const val PLAYER_FLY_SPEED = "${BASE}flySpeed"
const val SHOULD_ESC_RESET_MODE = "${BASE}shouldESCResetMode"
const val DEFAULT_MODE = "${BASE}defaultMode"
object Hud {
const val BASE = "${TranslationKeys.Gui.Config.BASE}hud."
const val SHOW_HUD = "${BASE}show"
const val EDIT_HUD = "${BASE}edit"
const val RESET_HUD = "${BASE}reset"
const val EXAMPLE_HUD_TEXT = "${BASE}example"
object Modifiers {
const val BASE = "${TranslationKeys.Gui.Config.Hud.BASE}modifiers."
const val SHOW_MODIFIERS = "${BASE}show"
const val SHOW_ALL = "${BASE}all"
const val SHOW_NUMBER = "${BASE}number"
const val SHOW_INITIALS = "${BASE}initials"
const val SHOW_MODE_ONLY = "${BASE}modeOnly"
}
}
object Toasts {
const val BASE = "${TranslationKeys.Gui.Config.BASE}toasts."
const val SHOW_TOASTS = "${BASE}show"
const val SHOW_ALL = "${BASE}all"
const val SHOW_TOGGLE = "${BASE}toggle"
const val SHOW_SYSTEM = "${BASE}system"
const val SHOW_NONE = "${BASE}none"
}
}
}
object Keybindings {
// No base here, Categories and Bindings have separate BASEs
object Category {
const val BASE = "category.${BuildConstants.modId}."
object Editor {
const val BASE = "${TranslationKeys.Keybindings.Category.BASE}editor."
const val MODE = "${BASE}mode"
const val MODIFIER = "${BASE}modifier"
}
object Game {
const val BASE = "${TranslationKeys.Keybindings.Category.BASE}game."
const val GAMEMODE = "${BASE}mode"
}
}
object Binding {
const val BASE = "key.${BuildConstants.modId}."
object Editor {
const val BASE = "${TranslationKeys.Keybindings.Binding.BASE}editor."
object Mode {
const val BASE = "${TranslationKeys.Keybindings.Binding.Editor.BASE}mode."
const val INSERT = "${BASE}insert"
const val REPLACE = "${BASE}replace"
const val VISUAL = "${BASE}visual"
}
object Modifier {
const val BASE = "${TranslationKeys.Keybindings.Binding.Editor.BASE}modifier."
const val BULLDOZER = "${BASE}autoClicker"
const val FORCE_PLACE = "${BASE}forcePlace"
const val NO_UPDATES = "${BASE}noUpdates"
const val NO_CLIP = "${BASE}noClip"
}
}
object Game {
const val BASE = "${TranslationKeys.Keybindings.Binding.BASE}game."
object Mode {
const val BASE = "${TranslationKeys.Keybindings.Binding.Game.BASE}mode."
const val SURVIVAL = "${BASE}survival"
const val CREATIVE = "${BASE}creative"
const val SPECTATOR = "${BASE}spectator"
}
}
}
}
}

View file

@ -28,6 +28,6 @@ internal class DataGenerator : DataGeneratorEntrypoint {
override fun onInitializeDataGenerator(fabricDataGenerator: FabricDataGenerator) {
LOGGER.info("Starting Data Generation")
fabricDataGenerator.addProvider(En_us_GimbalLanguageProvider(fabricDataGenerator))
fabricDataGenerator.addProvider(EnUsLanguageProvider(fabricDataGenerator))
}
}

View file

@ -0,0 +1,96 @@
/*
* 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.datagen
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator
import net.fabricmc.fabric.api.datagen.v1.provider.FabricLanguageProvider
import net.moonleay.gimbal.constants.TranslationKeys
class EnUsLanguageProvider(dataGenerator: FabricDataGenerator?) :
FabricLanguageProvider(dataGenerator, "en_us") {
override fun generateTranslations(translationBuilder: TranslationBuilder?) {
if (translationBuilder == null) return
/// Screens
// Gimbal settings screen
translationBuilder.add(TranslationKeys.Gui.Config.SCREEN_NAME, "Gimbal Settings")
// Hud
translationBuilder.add(TranslationKeys.Gui.Config.Hud.SHOW_HUD, "Show HUD")
//Gimbal HUD pos edit
translationBuilder.add(TranslationKeys.Gui.Config.Hud.EDIT_HUD, "Edit HUD Layout...")
translationBuilder.add(TranslationKeys.Gui.Config.Hud.RESET_HUD, "Reset Layout")
translationBuilder.add(TranslationKeys.Gui.Config.Hud.EXAMPLE_HUD_TEXT, "EXAMPLE [No Clip, Force]")
//Gimbal HUD Content
translationBuilder.add(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_MODIFIERS, "Show Modifiers")
translationBuilder.add(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_ALL, "All")
translationBuilder.add(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_NUMBER, "Number")
translationBuilder.add(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_INITIALS, "Short")
translationBuilder.add(TranslationKeys.Gui.Config.Hud.Modifiers.SHOW_MODE_ONLY, "None")
//Gimbal Toasts
translationBuilder.add(TranslationKeys.Gui.Config.Toasts.SHOW_TOASTS, "Show Toasts")
translationBuilder.add(TranslationKeys.Gui.Config.Toasts.SHOW_ALL, "All")
translationBuilder.add(TranslationKeys.Gui.Config.Toasts.SHOW_TOGGLE, "Only Toggle")
translationBuilder.add(TranslationKeys.Gui.Config.Toasts.SHOW_SYSTEM, "Only System")
translationBuilder.add(TranslationKeys.Gui.Config.Toasts.SHOW_NONE, "None")
// Gimbal Generic Settings
translationBuilder.add(TranslationKeys.Gui.Config.PLAYER_FLY_SPEED, "Fly Speed")
translationBuilder.add(TranslationKeys.Gui.Config.SHOULD_ESC_RESET_MODE, "ESC Resets Mode")
translationBuilder.add(TranslationKeys.Gui.Config.DEFAULT_MODE, "Default Mode")
// Gimbal Generic Gui
translationBuilder.add(TranslationKeys.Gui.Config.GENERIC_ENABLED, "Enabled")
translationBuilder.add(TranslationKeys.Gui.Config.GENERIC_DISABLED, "Disabled")
/// Options
// Editor modes
translationBuilder.add(TranslationKeys.Keybindings.Category.Editor.MODE, "Editor Modes")
translationBuilder.add(TranslationKeys.Keybindings.Binding.Editor.Mode.INSERT, "Enter Insert Mode")
translationBuilder.add(TranslationKeys.Keybindings.Binding.Editor.Mode.REPLACE, "Enter Replace Mode")
translationBuilder.add(TranslationKeys.Keybindings.Binding.Editor.Mode.VISUAL, "Enter Visual Mode")
// Editor mode modifiers
translationBuilder.add(TranslationKeys.Keybindings.Category.Editor.MODIFIER, "Editor Mode Modifiers")
translationBuilder.add(
TranslationKeys.Keybindings.Binding.Editor.Modifier.BULLDOZER,
"Toggle Bulldozer Modifier"
)
translationBuilder.add(
TranslationKeys.Keybindings.Binding.Editor.Modifier.FORCE_PLACE,
"Toggle Force Place Modifier"
)
translationBuilder.add(
TranslationKeys.Keybindings.Binding.Editor.Modifier.NO_UPDATES,
"Toggle No Updates Modifier"
)
translationBuilder.add(TranslationKeys.Keybindings.Binding.Editor.Modifier.NO_CLIP, "Toggle No Clip Modifier")
// Game mode
translationBuilder.add(TranslationKeys.Keybindings.Category.Game.GAMEMODE, "Game Mode")
translationBuilder.add(TranslationKeys.Keybindings.Binding.Game.Mode.SURVIVAL, "Enable Survival Mode")
translationBuilder.add(TranslationKeys.Keybindings.Binding.Game.Mode.CREATIVE, "Enable Creative Mode")
translationBuilder.add(TranslationKeys.Keybindings.Binding.Game.Mode.SPECTATOR, "Enable Spectator Mode")
}
}

View file

@ -1,50 +0,0 @@
/*
* 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.datagen
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator
import net.fabricmc.fabric.api.datagen.v1.provider.FabricLanguageProvider
class En_us_GimbalLanguageProvider(dataGenerator: FabricDataGenerator?) :
FabricLanguageProvider(dataGenerator, "en_us") {
override fun generateTranslations(translationBuilder: TranslationBuilder?) {
if (translationBuilder == null) return
// 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")
translationBuilder.add("gimbal.key.editor.modifier.forceplace", "Toggle Force Place Modifier")
translationBuilder.add("gimbal.key.editor.modifier.noupdates", "Toggle No Updates Modifier")
translationBuilder.add("gimbal.key.editor.modifier.noclip", "Toggle No Clip Modifier")
// Game mode
translationBuilder.add("gimbal.category.gamemode", "Game Mode")
translationBuilder.add("gimbal.key.game.mode.survival", "Enable Survival Mode")
translationBuilder.add("gimbal.key.game.mode.creative", "Enable Creative Mode")
translationBuilder.add("gimbal.key.game.mode.spectator", "Enable Spectator Mode")
}
}

View file

@ -32,7 +32,7 @@ object ServerEditorManager {
fun updateEditorState(playerUUID: UUID, editorState: EditorState) {
STATEMAP[playerUUID] = editorState
LOGGER.info("$playerUUID: ${editorState.editorMode} with ${editorState.editorModifier}")
LOGGER.debug("{}: {} with {}", playerUUID, editorState.editorMode, editorState.editorModifier)
}

View file

@ -18,8 +18,11 @@
package net.moonleay.gimbal.editor.state
import kotlinx.serialization.Serializable
@Serializable
enum class GimbalPolicyType {
ALLOWED,
DENIED,
NOT_PRESENT
NOT_PRESENT,
}

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.editor.state
import kotlinx.serialization.Serializable
@Serializable
data class GimbalServerState(
val protocolVersion: Int,
val policyType: GimbalPolicyType,
)

View file

@ -18,10 +18,11 @@
package net.moonleay.gimbal.editor.state.mode
enum class Mode(val displayName: String, val color: Int, val incompatibleModifiers: List<ModeModifier>){
UNKNOWN("UNKNOWN", 0x000000, listOf()), // Unknown mode. This mode cannot be entered
NORMAL("NORMAL", 0x90a959, listOf(ModeModifier.NO_UPDATES, ModeModifier.BULLDOZER, ModeModifier.FORCE_PLACE)), // Do nothing
INSERT("INSERT", 0xf4bf75, listOf()), // Place and break blocks
REPLACE("REPLACE", 0xac4242, listOf(ModeModifier.NO_UPDATES)), // Replace blocks
VISUAL("VISUAL", 0x6a9fb5, listOf(ModeModifier.BULLDOZER)), // Do fancy stuff with WE
enum class Mode(val displayName: String, val color: Int, val hidden: Boolean, val incompatibleModifiers: List<ModeModifier>){
UNKNOWN("UNKNOWN", 0x000000, true, listOf()), // Unknown mode. This mode cannot be entered
NORMAL("NORMAL", 0x90a959, false, listOf(ModeModifier.NO_UPDATES, ModeModifier.BULLDOZER, ModeModifier.FORCE_PLACE)), // Do nothing
INSERT("INSERT", 0xf4bf75, false, listOf()), // Place and break blocks
REPLACE("REPLACE", 0xac4242, false, listOf(ModeModifier.NO_UPDATES)), // Replace blocks
// Add hidden modes after this comment
VISUAL("VISUAL", 0x6a9fb5, true, listOf(ModeModifier.BULLDOZER)), // Do fancy stuff with WE
}

View file

@ -18,10 +18,10 @@
package net.moonleay.gimbal.editor.state.mode
enum class ModeModifier(val displayName: String) {
enum class ModeModifier(val displayName: String, val shortName: String) {
// NONE("None"), // No Modifiers - default behavior
NO_UPDATES("NO UPDATES"), // Do not update blocks when placing
BULLDOZER("BULLDOZER"), // Break blocks fast
FORCE_PLACE("FORCE"), // Ignore block placement restrictions
NO_CLIP("NO CLIP"), // Do not collide with blocks
NO_UPDATES("NO UPDATES", "NU"), // Do not update blocks when placing
BULLDOZER("BULLDOZER", "BLR"), // Break blocks fast
FORCE_PLACE("FORCE", "FP"), // Ignore block placement restrictions
NO_CLIP("NO CLIP", "NC"), // Do not collide with blocks
}

View file

@ -20,9 +20,13 @@ package net.moonleay.gimbal.mixin;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.moonleay.gimbal.client.editor.ClientEditor;
import net.moonleay.gimbal.editor.state.mode.Capability;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@ -32,30 +36,49 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Objects;
@Mixin(MinecraftClient.class)
public abstract class BulldozerMixin {
@Shadow protected int attackCooldown;
@Mixin(MinecraftClient.class)
public static abstract class MinecraftClientMixin {
@Shadow
@Nullable
public ClientPlayerEntity player;
@Shadow
protected int attackCooldown;
@Shadow
@Nullable
public ClientPlayerEntity player;
@Inject(method = "doAttack", at = @At(value = "HEAD"))
private void func(CallbackInfoReturnable<Boolean> cir) {
if (!ClientEditor.INSTANCE.shouldClient(Capability.BULLDOZER) || !Objects.requireNonNull(this.player).isCreative()) {
return;
@Inject(method = "doAttack", at = @At(value = "HEAD"))
private void func(CallbackInfoReturnable<Boolean> cir) {
if (!ClientEditor.INSTANCE.shouldClient(Capability.BULLDOZER) || !Objects.requireNonNull(this.player).isCreative()) {
return;
}
this.attackCooldown = 0;
}
@Inject(method = "handleBlockBreaking", at = @At(value = "HEAD"))
private void func2(boolean breaking, CallbackInfo ci) {
if (!ClientEditor.INSTANCE.shouldClient(Capability.BULLDOZER) || !breaking || !Objects.requireNonNull(this.player).isCreative()) {
return;
}
this.attackCooldown = 0;
}
this.attackCooldown = 0;
}
@Inject(method = "handleBlockBreaking", at = @At(value = "HEAD"))
private void func2(boolean breaking, CallbackInfo ci) {
if (!ClientEditor.INSTANCE.shouldClient(Capability.BULLDOZER) || !breaking || !Objects.requireNonNull(this.player).isCreative()) {
return;
@Mixin(ClientPlayerInteractionManager.class)
public static abstract class ClientPlayerInteractionManagerMixin {
@Shadow
private int blockBreakingCooldown;
@Shadow
@Final
private MinecraftClient client;
@Inject(method = "updateBlockBreakingProgress", at = @At("HEAD"))
private void func(BlockPos pos, Direction direction, CallbackInfoReturnable<Boolean> cir) {
if (!ClientEditor.INSTANCE.shouldClient(Capability.BULLDOZER) || !Objects.requireNonNull(this.client.player).isCreative()) {
return;
}
this.blockBreakingCooldown = 0;
}
this.attackCooldown = 0;
}
}

View file

@ -22,7 +22,11 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.hud.InGameHud;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import net.moonleay.gimbal.client.ClientMain;
import net.moonleay.gimbal.client.config.GimbalGuiSettings;
import net.moonleay.gimbal.client.editor.ClientEditor;
import net.moonleay.gimbal.client.util.screen.ScreenUtil;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@ -31,16 +35,40 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(InGameHud.class)
public class HudMixin {
public abstract class HudMixin {
@Shadow @Final private MinecraftClient client;
@Inject(method = "renderStatusEffectOverlay", at = @At("HEAD"))
private void render(MatrixStack matrices, CallbackInfo ci) {
TextRenderer tr = this.client.textRenderer;
tr.drawWithShadow(matrices,
ClientEditor.INSTANCE.getModeDisplayText(),
4, 4,
ClientEditor.INSTANCE.getCurrentColor());
GimbalGuiSettings conf = ClientMain.CONFIG.getConfig().getGuiSettings();
if (!conf.getShowHud())
return;
int scaleFactor = this.client.getWindow()
.calculateScaleFactor(
this.client.options.getGuiScale().getValue(),
this.client.forcesUnicodeFont()
);
Text displayText = ClientEditor.INSTANCE.getModeDisplayText(ClientMain.CONFIG.getConfig());
tr.drawWithShadow(
matrices,
displayText,
ScreenUtil.INSTANCE.getXForAnchor(
ScreenUtil.INSTANCE.getReal(
this.client.getWindow().getWidth(),
conf.getOffset().getScaledX() / scaleFactor),
conf.getHorizontalAnchor(),
this.client.textRenderer.getWidth(displayText)
),
ScreenUtil.INSTANCE.getYForAnchor(
ScreenUtil.INSTANCE.getReal(
this.client.getWindow().getHeight(),
conf.getOffset().getScaledY() / scaleFactor),
conf.getVerticalAnchor(),
this.client.textRenderer.fontHeight
),
ClientEditor.INSTANCE.getCurrentColor()
);
}
}

View file

@ -1,41 +0,0 @@
/*
* 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.MinecraftClient;
import net.minecraft.client.render.Camera;
import net.moonleay.gimbal.client.editor.ClientEditor;
import net.moonleay.gimbal.editor.state.mode.Capability;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Camera.class)
public class NoClipCameraFixMixin {
@Inject(method = "clipToSpace", at = @At("HEAD"), cancellable = true)
private void fixCameraInNoClip(double desiredCameraDistance, CallbackInfoReturnable<Double> cir) {
if (!ClientEditor.INSTANCE.shouldClient(Capability.NO_CLIP) || (!(MinecraftClient.getInstance().player != null && MinecraftClient.getInstance().player.isCreative()))) {
return;
}
cir.setReturnValue(desiredCameraDistance);
cir.cancel();
}
}

View file

@ -19,12 +19,15 @@
package net.moonleay.gimbal.mixin;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.Camera;
import net.minecraft.entity.EntityPose;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerAbilities;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.world.World;
import net.moonleay.gimbal.client.editor.ClientEditor;
import net.moonleay.gimbal.editor.ServerEditorManager;
import net.moonleay.gimbal.editor.state.mode.Capability;
import org.spongepowered.asm.mixin.Mixin;
@ -32,51 +35,72 @@ import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.UUID;
@Mixin(PlayerEntity.class)
public abstract class NoClipMixin extends LivingEntity {
@Shadow public abstract GameProfile getGameProfile();
public abstract class NoClipMixin {
@Shadow public abstract PlayerAbilities getAbilities();
@Shadow
public abstract boolean isCreative();
protected NoClipMixin(EntityType<? extends LivingEntity> entityType, World world) {
super(entityType, world);
} // Server side
@Inject(method = "tick", at = @At(value = "FIELD",
target = "Lnet/minecraft/entity/player/PlayerEntity;noClip:Z", shift = At.Shift.AFTER)
) private void enoClip(CallbackInfo ci) {
if (!this.isCreative())
return;
UUID uuid = this.getGameProfile().getId();
if (!ServerEditorManager.INSTANCE.shouldPlayer(uuid, Capability.NO_CLIP)) {
return; // NoClip is not enabled
@Mixin(PlayerEntity.class) // Serverside, allows clipping
public static abstract class PlayerEntityMixin extends LivingEntity {
protected PlayerEntityMixin(EntityType<? extends LivingEntity> entityType, World world) {
super(entityType, world);
}
if (!this.getAbilities().flying) {
return;
@Shadow
public abstract GameProfile getGameProfile();
@Shadow
public abstract PlayerAbilities getAbilities();
@Shadow
public abstract boolean isCreative();
@Inject(method = "tick", at = @At(value = "FIELD",
target = "Lnet/minecraft/entity/player/PlayerEntity;noClip:Z", shift = At.Shift.AFTER)
)
private void enoClip(CallbackInfo ci) {
if (!this.isCreative())
return;
UUID uuid = this.getGameProfile().getId();
if (!ServerEditorManager.INSTANCE.shouldPlayer(uuid, Capability.NO_CLIP)) {
return; // NoClip is not enabled
}
if (!this.getAbilities().flying) {
return;
}
// Enable NoClip
this.noClip = true;
}
@Inject(method = "updatePose", at = @At("HEAD"))
private void onUpdatePose(CallbackInfo ci) {
UUID uuid = this.getGameProfile().getId();
if (!ServerEditorManager.INSTANCE.shouldPlayer(uuid, Capability.NO_CLIP))
return; // NoClip is not enabled
if (!this.getAbilities().flying)
return;
// Force standing pose in NoClip mode
this.setPose(EntityPose.STANDING);
}
// Enable NoClip
this.noClip = true;
}
@Inject(method = "updatePose", at = @At("HEAD"))
private void onUpdatePose(CallbackInfo ci) {
UUID uuid = this.getGameProfile().getId();
if (!ServerEditorManager.INSTANCE.shouldPlayer(uuid, Capability.NO_CLIP))
return; // NoClip is not enabled
if (!this.getAbilities().flying)
return;
@Mixin(Camera.class) // Clientside, fixes camera
public static abstract class CameraMixin {
// Force standing pose in NoClip mode
this.setPose(EntityPose.STANDING);
@Inject(method = "clipToSpace", at = @At("HEAD"), cancellable = true)
private void fixCameraInNoClip(double desiredCameraDistance, CallbackInfoReturnable<Double> cir) {
if (!ClientEditor.INSTANCE.shouldClient(Capability.NO_CLIP) || (!(MinecraftClient.getInstance().player != null && MinecraftClient.getInstance().player.isCreative()))) {
return;
}
cir.setReturnValue(desiredCameraDistance);
cir.cancel();
}
}
}

View file

@ -21,9 +21,9 @@ package net.moonleay.gimbal.mixin;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.option.GameOptions;
import net.moonleay.gimbal.client.ClientMain;
import net.moonleay.gimbal.client.editor.ClientEditor;
import net.moonleay.gimbal.editor.state.mode.Capability;
import net.moonleay.gimbal.editor.state.mode.Mode;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@ -43,12 +43,12 @@ public class NormalModeMixin {
public ClientPlayerEntity player;
@Inject(method = "openPauseMenu", at = @At("HEAD"), cancellable = true)
private void setNormalMode(boolean pause, CallbackInfo ci) {
if (ClientEditor.INSTANCE.isInNonDefaultMode() && ClientEditor.INSTANCE.isAllowed()) {
private void setDefaultMode(boolean pause, CallbackInfo ci) {
if (ClientMain.CONFIG.getConfig().getShouldEscResetMode() && ClientEditor.INSTANCE.isInNonDefaultMode() && ClientEditor.INSTANCE.isAllowed()) {
assert this.player != null;
if (this.player.isCreative()) {
// Set the editor mode to normal
ClientEditor.INSTANCE.setMode(Mode.NORMAL);
ClientEditor.INSTANCE.setMode(ClientMain.CONFIG.getConfig().getDefaultMode());
ci.cancel();
}
}

View file

@ -19,34 +19,27 @@
package net.moonleay.gimbal.mixin;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.moonleay.gimbal.client.editor.ClientEditor;
import net.moonleay.gimbal.editor.state.mode.Capability;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.packet.s2c.play.PlayerAbilitiesS2CPacket;
import net.moonleay.gimbal.client.ClientMain;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Objects;
@Mixin(ClientPlayerInteractionManager.class)
public class BulldozerMixin2 {
@Shadow private int blockBreakingCooldown;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class PlayerFlySpeedMixin {
@Shadow
@Final
private MinecraftClient client;
@Inject(method = "updateBlockBreakingProgress", at = @At("HEAD"))
private void func(BlockPos pos, Direction direction, CallbackInfoReturnable<Boolean> cir) {
if (!ClientEditor.INSTANCE.shouldClient(Capability.BULLDOZER) || !Objects.requireNonNull(this.client.player).isCreative()) {
return;
}
this.blockBreakingCooldown = 0;
@Inject(method = "onPlayerAbilities", at = @At(value = "RETURN"))
private void func(PlayerAbilitiesS2CPacket packet, CallbackInfo ci) {
this.client.player.getAbilities().setFlySpeed((ClientMain.CONFIG.getConfig().getPlayerFlySpeed() / 100) * 0.05f);
//client!!.player!!.abilities.flySpeed = (this.playerFlySpeed / 100) * 0.05f
}
}

View file

@ -19,69 +19,86 @@
package net.moonleay.gimbal.mixin;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.client.particle.ParticleManager;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.*;
import net.minecraft.state.property.Property;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.moonleay.gimbal.client.editor.ClientEditor;
import net.minecraft.world.World;
import net.moonleay.gimbal.editor.ServerEditorManager;
import net.moonleay.gimbal.editor.state.mode.Capability;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftClient.class)
import java.util.UUID;
public abstract class ReplaceModeMixin {
@Mixin(ItemPlacementContext.class)
public static abstract class ItemPlacementContextMixin extends ItemUsageContext {
@Shadow
protected boolean canReplaceExisting;
@Shadow protected abstract void handleBlockBreaking(boolean breaking);
@Mutable
@Shadow
@Final
private BlockPos placementPos;
@Shadow @Nullable public ClientPlayerInteractionManager interactionManager;
public ItemPlacementContextMixin(PlayerEntity player, Hand hand, BlockHitResult hit) {
super(player, hand, hit);
}
@Shadow @Final public ParticleManager particleManager;
@Inject(method = "<init>(Lnet/minecraft/world/World;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/util/Hand;Lnet/minecraft/item/ItemStack;Lnet/minecraft/util/hit/BlockHitResult;)V", at = @At(value = "RETURN"))
private void func(World world, PlayerEntity playerEntity, Hand hand, ItemStack itemStack, BlockHitResult blockHitResult, CallbackInfo ci) {
if (playerEntity == null)
return;
UUID id = playerEntity.getGameProfile().getId();
if (!ServerEditorManager.INSTANCE.shouldPlayer(id, Capability.REPLACE))
return;
@Shadow @Nullable public ClientPlayerEntity player;
this.canReplaceExisting = true;
this.placementPos = blockHitResult.getBlockPos();
}
}
@Shadow @Nullable public HitResult crosshairTarget;
@Mixin(BlockItem.class)
public static abstract class BlockItemMixin extends Item {
@Inject(method = "doItemUse", at = @At("HEAD"))
private void replaceBlock(CallbackInfo ci) {
assert this.player != null;
if (!this.player.isCreative())
return;
// Check if should run
if (!ClientEditor.INSTANCE.shouldClient(Capability.REPLACE))
return; // Mode is not REPLACE, ignore
MinecraftClient client = MinecraftClient.getInstance();
assert this.interactionManager != null;
if (!this.interactionManager.getCurrentGameMode().isCreative())
return;
if (!(this.crosshairTarget instanceof BlockHitResult blockHitResult))
return;
if (blockHitResult == null)
return;
public BlockItemMixin(Settings settings) {
super(settings);
}
// Gather data
BlockPos pos = blockHitResult.getBlockPos();
Direction direction = blockHitResult.getSide();
BlockState blockState = client.world.getBlockState(pos);
@Shadow
protected abstract boolean place(ItemPlacementContext context, BlockState state);
// Start sending shit
client.getTutorialManager().onBlockBreaking(client.world, pos, blockState, 1.0F);
this.interactionManager.sendSequencedPacket(client.world, sequence -> {
this.interactionManager.breakBlock(pos);
return new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, pos, direction, sequence);
});
this.player.swingHand(Hand.MAIN_HAND);
@Redirect(method = "place(Lnet/minecraft/item/ItemPlacementContext;)Lnet/minecraft/util/ActionResult;", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/BlockItem;place(Lnet/minecraft/item/ItemPlacementContext;Lnet/minecraft/block/BlockState;)Z"))
private boolean func(BlockItem instance, ItemPlacementContext context, BlockState state) {
if (context.getPlayer() == null)
return this.place(context, state);
UUID id = context.getPlayer().getGameProfile().getId();
if (!ServerEditorManager.INSTANCE.shouldPlayer(id, Capability.REPLACE))
return this.place(context, state);
BlockState oldState = context.getWorld().getBlockState(context.getBlockPos());
return this.place(context, getTargetBlockState(oldState, instance.getBlock().getDefaultState()));
}
@Unique
public BlockState getTargetBlockState(BlockState oldState, BlockState newBlock) {
var oldManager = oldState.getBlock().getStateManager();
var newManager = newBlock.getBlock().getStateManager();
for (var prop : oldManager.getProperties()) {
var matchingProp = newManager.getProperty(prop.getName());
if (matchingProp != null) {
//noinspection rawtypes,unchecked
newBlock = newBlock.with((Property) matchingProp, oldState.get(matchingProp));
}
}
return newBlock;
}
}
}

View file

@ -0,0 +1,104 @@
/*
* 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.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 net.moonleay.gimbal.constants.TranslationKeys;
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;
// What is my purpose?
public class YouInjectButtonMixin {
// Go my god.
@Mixin(TitleScreen.class)
public static abstract class TitleScreenMixin extends Screen {
@Unique
private static final Identifier GIMBAL_TEXTURE = new Identifier(BuildConstants.modId, "textures/gimbal_options_texture_button.png");
protected TitleScreenMixin(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(TranslationKeys.Gui.Config.SCREEN_NAME)
)
);
}
}
@Mixin(GameMenuScreen.class)
public static abstract class GameMenuScreenMixin extends Screen {
@Unique
private static final Identifier GIMBAL_TEXTURE = new Identifier(BuildConstants.modId, "textures/gimbal_options_texture_button.png");
protected GameMenuScreenMixin(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(TranslationKeys.Gui.Config.SCREEN_NAME)
)
);
}
}
}

View file

@ -25,38 +25,46 @@ import kotlinx.serialization.encodeToByteArray
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.minecraft.network.PacketByteBuf
import net.moonleay.gimbal.build.BuildConstants
import net.moonleay.gimbal.client.editor.ClientEditor
import net.moonleay.gimbal.constants.PacketIDs
import net.moonleay.gimbal.editor.state.EditorState
import net.moonleay.gimbal.editor.util.GimbalPolicy
import net.moonleay.gimbal.editor.state.GimbalPolicyType
import net.moonleay.gimbal.editor.state.GimbalServerState
object GimbalClient {
fun registerPacketHandlers() {
ClientPlayNetworking.registerGlobalReceiver(PacketIDs.TRANSFER_GIMBLE_POLICY_ID) { _, _, buf, _ ->
onAllowedCheck(buf)
ClientPlayNetworking.registerGlobalReceiver(PacketIDs.GIMBAL_POLICY_PACKET_ID) { _, _, buf, handler ->
val state = this.getServerState(buf)
val isUsable = this.isGimbalUsable(state)
if (isUsable) {
val buf = PacketByteBufs.create()
buf.writeByteArray(Cbor.encodeToByteArray(ClientEditor.getClientState()))
handler.sendPacket(PacketIDs.UPDATE_EDITOR_STATE_ID, buf)
}
ClientEditor.onPolicyReceived(state, false)
}
}
@OptIn(ExperimentalSerializationApi::class)
private fun onAllowedCheck(buf: PacketByteBuf) {
val policy = Cbor.decodeFromByteArray<GimbalPolicy>(buf.readByteArray())
ClientEditor.onAllowedCheck(policy) // Update the client's policy
private fun getServerState(buf: PacketByteBuf): GimbalServerState {
val serverState = Cbor.decodeFromByteArray<GimbalServerState>(buf.readByteArray())
return serverState
}
/**
* Sends the given [EditorState] to the server.
*/
@OptIn(ExperimentalSerializationApi::class)
fun sendEditorState(state: EditorState) {
val buf = PacketByteBufs.create()
buf.writeByteArray(Cbor.encodeToByteArray(state))
ClientPlayNetworking.send(PacketIDs.UPDATE_EDITOR_STATE_ID, buf)
private fun isGimbalUsable(state: GimbalServerState): Boolean {
return state.policyType == GimbalPolicyType.ALLOWED && state.protocolVersion.toString() == BuildConstants.protocolVersion
}
@OptIn(ExperimentalSerializationApi::class)
fun checkIfServerHasGimble(state: EditorState) {
private fun createStatePacket(state: EditorState): PacketByteBuf {
val buf = PacketByteBufs.create()
buf.writeByteArray(Cbor.encodeToByteArray(state))
ClientPlayNetworking.send(PacketIDs.GIMBLE_PRERENCE_CHECK_ID, buf)
return buf
}
fun sendStatePacket(state: EditorState) {
ClientPlayNetworking.send(PacketIDs.UPDATE_EDITOR_STATE_ID, createStatePacket(state))
}
}

View file

@ -18,31 +18,48 @@
package net.moonleay.gimbal.networking
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import me.lucko.fabric.api.permissions.v0.Permissions
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.minecraft.network.PacketByteBuf
import net.minecraft.server.network.ServerPlayerEntity
import net.moonleay.gimbal.build.BuildConstants
import net.moonleay.gimbal.constants.PacketIDs
import net.moonleay.gimbal.constants.PermissionIDs
import net.moonleay.gimbal.editor.ServerEditorManager
import net.moonleay.gimbal.editor.state.EditorState
import net.moonleay.gimbal.editor.state.GimbalPolicyType
import net.moonleay.gimbal.editor.util.GimbalPolicy
import net.moonleay.gimbal.editor.state.GimbalServerState
import org.apache.logging.log4j.LogManager
object GimbalServer {
private val LOGGER = LogManager.getLogger(BuildConstants.modName)
fun registerPacketHandler() {
ServerPlayNetworking
.registerGlobalReceiver(PacketIDs.UPDATE_EDITOR_STATE_ID)
{ server, player, handler, buf, responseSender ->
{ _, player, _, buf, _ ->
handleStateUpdate(player, buf)
}
ServerPlayNetworking
.registerGlobalReceiver(PacketIDs.GIMBLE_PRERENCE_CHECK_ID)
{ server, player, handler, buf, responseSender ->
handlePresenceCheck(player, buf)
}
}
@OptIn(ExperimentalSerializationApi::class)
fun notifyPlayerOfPolicy(player: ServerPlayerEntity) {
LOGGER.info("Sending policy to " + player.name.string)
val buf = PacketByteBufs.create()
buf.writeByteArray(
Cbor.encodeToByteArray(
GimbalServerState(
BuildConstants.protocolVersion.toInt(),
if (this.isPlayerAllowedToUseGimbal(player)) GimbalPolicyType.ALLOWED else GimbalPolicyType.DENIED
)
)
)
ServerPlayNetworking.send(player, PacketIDs.GIMBAL_POLICY_PACKET_ID, buf)
}
private fun handleStateUpdate(player: ServerPlayerEntity, buf: PacketByteBuf){
@ -51,12 +68,7 @@ object GimbalServer {
// player.sendMessage(Text.of("Mode: ${state.editorMode} with ${state.editorModifier}"))
}
private fun handlePresenceCheck(player: ServerPlayerEntity, buf: PacketByteBuf) {
val state = Cbor.decodeFromByteArray<EditorState>(buf.readByteArray())
ServerEditorManager.updateEditorState(player.uuid, state)
val buffer = PacketByteBufs.create()
buffer.writeByteArray(Cbor.encodeToByteArray(GimbalPolicy(GimbalPolicyType.ALLOWED)))
ServerPlayNetworking.send(player, PacketIDs.TRANSFER_GIMBLE_POLICY_ID, buffer)
private fun isPlayerAllowedToUseGimbal(player: ServerPlayerEntity): Boolean {
return player.hasPermissionLevel(2) || Permissions.check(player, PermissionIDs.GIMBAL_USAGE_PERMISSION)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -7,6 +7,9 @@
"authors": [
"moonleay"
],
"contributors": [
"Cookieso"
],
"icon": "assets/${modId}/logo.png",
"contact": {
"email": "contact@moonleay.net",

View file

@ -2,3 +2,4 @@ accessWidener v2 named
accessible method net/minecraft/client/MinecraftClient handleBlockBreaking (Z)V
accessible method net/minecraft/client/network/ClientPlayerInteractionManager sendSequencedPacket (Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/client/network/SequencedPacketCreator;)V
accessible field net/minecraft/state/State owner Ljava/lang/Object;

View file

@ -6,16 +6,19 @@
"mixins": [
"ForcePlaceMixin",
"NoBlockUpdatesMixin",
"NoClipMixin"
"NoClipMixin$PlayerEntityMixin",
"ReplaceModeMixin$BlockItemMixin",
"ReplaceModeMixin$ItemPlacementContextMixin"
],
"client": [
"BulldozerMixin",
"BulldozerMixin2",
"GimbalPolicyCheckMixin",
"BulldozerMixin$ClientPlayerInteractionManagerMixin",
"BulldozerMixin$MinecraftClientMixin",
"HudMixin",
"NoClipCameraFixMixin",
"NoClipMixin$CameraMixin",
"NormalModeMixin",
"ReplaceModeMixin"
"PlayerFlySpeedMixin",
"YouInjectButtonMixin$GameMenuScreenMixin",
"YouInjectButtonMixin$TitleScreenMixin"
],
"injectors": {
"defaultRequire": 1

View file

@ -22,4 +22,6 @@ internal object BuildConstants {
const val modId = "${modId}"
const val modName = "${modName}"
const val modVersion = "${modVersion}"
const val protocolVersion = "${protocolVersion}"
}