From eb77064416a886b44f650ce39bac9bf6eb6282d3 Mon Sep 17 00:00:00 2001 From: enjarai Date: Tue, 2 Jan 2024 09:14:41 +0100 Subject: [PATCH] Just going to throw this on a branch so i have easy access to it --- build.gradle | 253 ++++------ common/build.gradle | 49 ++ .../doabarrelroll/DoABarrelRollClient.java | 126 +++++ .../enjarai/doabarrelroll/ModKeybindings.java | 166 ++++++ .../nl/enjarai/doabarrelroll/ModMath.java | 37 ++ .../enjarai/doabarrelroll/api/RollCamera.java | 5 + .../enjarai/doabarrelroll/api/RollMouse.java | 10 + .../doabarrelroll/api/event/ClientEvents.java | 18 + .../api/event/StarFox64Events.java | 18 + .../doabarrelroll/api/key/InputContext.java | 27 + .../enjarai/doabarrelroll/compat/Compat.java | 27 + .../compat/CompatMixinPlugin.java | 52 ++ .../yacl/ExpressionParserController.java | 38 ++ .../compat/yacl/YACLImplementation.java | 384 ++++++++++++++ .../config/ActivationBehaviour.java | 26 + .../doabarrelroll/config/MigrationValue.java | 18 + .../doabarrelroll/config/ModConfig.java | 474 ++++++++++++++++++ .../doabarrelroll/config/ModConfigScreen.java | 38 ++ .../ExpressionParserTypeAdapter.java | 18 + .../fabric/DoABarrelRollFabricClient.java | 37 ++ .../fabric/net/HandshakeClientFabric.java | 28 ++ .../net/ServerConfigUpdateClientFabric.java | 13 + .../flight/RotationModifiers.java | 141 ++++++ .../impl/key/InputContextImpl.java | 89 ++++ .../doabarrelroll/math/SyntaxHighlighter.java | 220 ++++++++ .../mixin/client/ClientPlayerEntityMixin.java | 35 ++ .../mixin/client/InGameHudMixin.java | 55 ++ .../mixin/client/LivingEntityMixin.java | 67 +++ .../mixin/client/PlayerEntityMixin.java | 62 +++ .../client/key/KeyBindingEntryMixin.java | 40 ++ .../mixin/client/key/KeyBindingMixin.java | 109 ++++ .../mixin/client/roll/CameraMixin.java | 135 +++++ .../mixin/client/roll/DebugHudMixin.java | 43 ++ .../mixin/client/roll/GameRendererMixin.java | 30 ++ .../mixin/client/roll/MouseMixin.java | 111 ++++ .../roll/PlayerEntityRendererMixin.java | 48 ++ .../AbstractClientPlayerEntityMixin.java | 9 + .../roll/entity/ClientPlayerEntityMixin.java | 130 +++++ .../doabarrelroll/net/HandshakeClient.java | 106 ++++ .../doabarrelroll/net/RollSyncClient.java | 44 ++ .../net/ServerConfigUpdateClient.java | 59 +++ .../render/HorizonLineWidget.java | 40 ++ .../render/MomentumCrosshairWidget.java | 35 ++ .../doabarrelroll/render/RenderHelper.java | 26 + .../doabarrelroll/util/MixinHooks.java | 10 + .../doabarrelroll/util/StarFoxUtil.java | 103 ++++ .../enjarai/doabarrelroll/util/ToastUtil.java | 16 + .../nl/enjarai/doabarrelroll/util/Value.java | 19 + .../util/key/ContextualKeyBinding.java | 11 + .../do-a-barrel-roll.client.mixins.json | 23 + ...rel-roll.compat.cameraoverhaul.mixins.json | 15 + ...-barrel-roll.compat.controlify.mixins.json | 15 + .../enjarai/doabarrelroll/DoABarrelRoll.java | 45 ++ .../enjarai/doabarrelroll/api/RollEntity.java | 19 + .../doabarrelroll/api/event/Event.java | 18 + .../doabarrelroll/api/event/RollContext.java | 27 + .../doabarrelroll/api/event/RollEvents.java | 50 ++ .../doabarrelroll/api/event/RollGroup.java | 59 +++ .../doabarrelroll/api/event/ServerEvents.java | 19 + .../doabarrelroll/api/event/ThrustEvents.java | 22 + .../doabarrelroll/api/event/TriState.java | 7 + .../api/rotation/RotationInstant.java | 38 ++ .../doabarrelroll/config/KineticDamage.java | 19 + .../config/LimitedModConfigServer.java | 22 + .../doabarrelroll/config/ModConfigServer.java | 51 ++ .../config/MutableConfigServer.java | 25 + .../doabarrelroll/config/Sensitivity.java | 16 + .../fabric/DoABarrelRollFabric.java | 38 ++ .../fabric/net/HandshakeServerFabric.java | 48 ++ .../fabric/net/ServerConfigUpdaterFabric.java | 16 + .../doabarrelroll/impl/event/EventImpl.java | 81 +++ .../impl/event/RollContextImpl.java | 45 ++ .../impl/event/RollGroupImpl.java | 48 ++ .../impl/rotation/RotationInstantImpl.java | 44 ++ .../doabarrelroll/math/Expression.java | 7 + .../doabarrelroll/math/ExpressionParser.java | 184 +++++++ .../doabarrelroll/math/MagicNumbers.java | 6 + .../nl/enjarai/doabarrelroll/math/Parser.java | 51 ++ .../mixin/CommandManagerMixin.java | 22 + .../mixin/LivingEntityMixin.java | 35 ++ .../mixin/roll/EntityTrackerEntryMixin.java | 40 ++ .../mixin/roll/entity/EntityMixin.java | 50 ++ .../mixin/roll/entity/LivingEntityMixin.java | 17 + .../mixin/roll/entity/PlayerEntityMixin.java | 66 +++ .../doabarrelroll/net/HandshakeServer.java | 192 +++++++ .../doabarrelroll/net/RollSyncServer.java | 40 ++ .../doabarrelroll/net/ServerConfigHolder.java | 156 ++++++ .../doabarrelroll/net/SyncableConfig.java | 12 + .../doabarrelroll/net/ValidatableConfig.java | 5 + .../doabarrelroll/platform/Services.java | 28 ++ .../platform/services/PlatformHelper.java | 21 + .../doabarrelroll/util/DelayedRunnable.java | 22 + .../resources/do_a_barrel_roll.accesswidener | 3 + .../resources/do_a_barrel_roll.mixins.json | 23 + common/src/main/resources/pack.mcmeta | 6 + fabric/build.gradle | 90 ++++ .../cameraoverhaul/CameraOverhaulPlugin.java | 12 + .../mixin/CameraSystemMixin.java | 69 +++ .../compat/controlify/ControlifyCompat.java | 124 +++++ .../compat/controlify/ControlifyPlugin.java | 12 + .../mixin/InGameInputHandlerMixin.java | 28 ++ .../compat/modmenu/ModMenuIntegration.java | 21 + .../platform/FabricPlatformHelper.java | 10 + ...arrelroll.platform.services.PlatformHelper | 1 + .../do_a_barrel_roll.fabric.mixins.json | 16 + fabric/src/main/resources/fabric.mod.json | 73 +++ forge/build.gradle | 92 ++++ forge/gradle.properties | 2 + .../platform/ForgePlatformHelper.java | 10 + forge/src/main/resources/META-INF/mods.toml | 72 +++ ...arrelroll.platform.services.PlatformHelper | 1 + .../do_a_barrel_roll.forge.mixins.json | 16 + gradle.properties | 53 +- settings.gradle | 25 +- 114 files changed, 6011 insertions(+), 167 deletions(-) create mode 100644 common/build.gradle create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/DoABarrelRollClient.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/ModKeybindings.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/ModMath.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/api/RollCamera.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/api/RollMouse.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/api/event/ClientEvents.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/api/event/StarFox64Events.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/api/key/InputContext.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/compat/Compat.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/compat/CompatMixinPlugin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/compat/yacl/ExpressionParserController.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/compat/yacl/YACLImplementation.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/config/ActivationBehaviour.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/config/MigrationValue.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/config/ModConfig.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/config/ModConfigScreen.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/config/serialization/ExpressionParserTypeAdapter.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/fabric/DoABarrelRollFabricClient.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/fabric/net/HandshakeClientFabric.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/fabric/net/ServerConfigUpdateClientFabric.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/flight/RotationModifiers.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/impl/key/InputContextImpl.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/math/SyntaxHighlighter.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/ClientPlayerEntityMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/InGameHudMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/LivingEntityMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/PlayerEntityMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/key/KeyBindingEntryMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/key/KeyBindingMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/CameraMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/DebugHudMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/GameRendererMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/MouseMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/PlayerEntityRendererMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/entity/AbstractClientPlayerEntityMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/entity/ClientPlayerEntityMixin.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/net/HandshakeClient.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/net/RollSyncClient.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/net/ServerConfigUpdateClient.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/render/HorizonLineWidget.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/render/MomentumCrosshairWidget.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/render/RenderHelper.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/util/MixinHooks.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/util/StarFoxUtil.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/util/ToastUtil.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/util/Value.java create mode 100644 common/src/client/java/nl/enjarai/doabarrelroll/util/key/ContextualKeyBinding.java create mode 100644 common/src/client/resources/do-a-barrel-roll.client.mixins.json create mode 100644 common/src/client/resources/do-a-barrel-roll.compat.cameraoverhaul.mixins.json create mode 100644 common/src/client/resources/do-a-barrel-roll.compat.controlify.mixins.json create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/DoABarrelRoll.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/api/RollEntity.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/api/event/Event.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollContext.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollEvents.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollGroup.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/api/event/ServerEvents.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/api/event/ThrustEvents.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/api/event/TriState.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/api/rotation/RotationInstant.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/config/KineticDamage.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/config/LimitedModConfigServer.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/config/ModConfigServer.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/config/MutableConfigServer.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/config/Sensitivity.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/fabric/DoABarrelRollFabric.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/fabric/net/HandshakeServerFabric.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/fabric/net/ServerConfigUpdaterFabric.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/impl/event/EventImpl.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/impl/event/RollContextImpl.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/impl/event/RollGroupImpl.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/impl/rotation/RotationInstantImpl.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/math/Expression.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/math/ExpressionParser.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/math/MagicNumbers.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/math/Parser.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/mixin/CommandManagerMixin.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/mixin/LivingEntityMixin.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/EntityTrackerEntryMixin.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/EntityMixin.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/LivingEntityMixin.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/PlayerEntityMixin.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/net/HandshakeServer.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/net/RollSyncServer.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/net/ServerConfigHolder.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/net/SyncableConfig.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/net/ValidatableConfig.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/platform/Services.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/platform/services/PlatformHelper.java create mode 100644 common/src/main/java/nl/enjarai/doabarrelroll/util/DelayedRunnable.java create mode 100644 common/src/main/resources/do_a_barrel_roll.accesswidener create mode 100644 common/src/main/resources/do_a_barrel_roll.mixins.json create mode 100644 common/src/main/resources/pack.mcmeta create mode 100644 fabric/build.gradle create mode 100644 fabric/src/main/java/nl/enjarai/doabarrelroll/compat/cameraoverhaul/CameraOverhaulPlugin.java create mode 100644 fabric/src/main/java/nl/enjarai/doabarrelroll/compat/cameraoverhaul/mixin/CameraSystemMixin.java create mode 100644 fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/ControlifyCompat.java create mode 100644 fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/ControlifyPlugin.java create mode 100644 fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/mixin/InGameInputHandlerMixin.java create mode 100644 fabric/src/main/java/nl/enjarai/doabarrelroll/compat/modmenu/ModMenuIntegration.java create mode 100644 fabric/src/main/java/nl/enjarai/doabarrelroll/platform/FabricPlatformHelper.java create mode 100644 fabric/src/main/resources/META-INF/services/nl.enjarai.doabarrelroll.platform.services.PlatformHelper create mode 100644 fabric/src/main/resources/do_a_barrel_roll.fabric.mixins.json create mode 100644 fabric/src/main/resources/fabric.mod.json create mode 100644 forge/build.gradle create mode 100644 forge/gradle.properties create mode 100644 forge/src/main/java/nl/enjarai/doabarrelroll/platform/ForgePlatformHelper.java create mode 100644 forge/src/main/resources/META-INF/mods.toml create mode 100644 forge/src/main/resources/META-INF/services/nl.enjarai.doabarrelroll.platform.services.PlatformHelper create mode 100644 forge/src/main/resources/do_a_barrel_roll.forge.mixins.json diff --git a/build.gradle b/build.gradle index 587fe266..6330b6d8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,181 +1,130 @@ plugins { - id 'fabric-loom' version '1.4-SNAPSHOT' -// id 'io.github.juuxel.loom-vineflower' version '1.11.0' - id 'maven-publish' - id 'me.fallenbreath.yamlang' version '1.2.1' + id 'dev.architectury.loom' version '1.2-SNAPSHOT' apply(false) + id 'me.fallenbreath.yamlang' version '1.2.1' apply(false) } -version = project.mod_version -group = project.maven_group +version = "${mod_version}+${minecraft_version}"; -loom { - accessWidenerPath = file("src/main/resources/do-a-barrel-roll.accesswidener") +subprojects { + apply plugin: 'java' + apply plugin: 'me.fallenbreath.yamlang' - splitEnvironmentSourceSets() - - mods { - "do_a_barrel_roll" { - sourceSet sourceSets.main - sourceSet sourceSets.client - } - } -} - -repositories { - // Add repositories to retrieve artifacts from in here. - // You should only use this when depending on other mods because - // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. - // See https://docs.gradle.org/current/userguide/declaring_repositories.html - // for more information about repositories. - - // Personal maven for cicada and backup mirrors of some dependencies. - maven { url = "https://maven.enjarai.dev/mirrors" } - maven { url = "https://maven.enjarai.dev/releases" } - - maven { - url = "https://www.cursemaven.com" - allowInsecureProtocol = true - } - - // Mod Menu. - maven { url 'https://maven.terraformersmc.com' } + repositories { + // Personal maven for cicada and backup mirrors of some dependencies. + maven { url "https://maven.enjarai.dev/mirrors" } + maven { url "https://maven.enjarai.dev/releases" } - // MidnightControls. - maven { url = "https://aperlambda.github.io/maven" } - maven { - name = "Modrinth" - url = "https://api.modrinth.com/maven" - content { - includeGroup "maven.modrinth" - } - } + // Mod Menu. + maven { url 'https://maven.terraformersmc.com' } - // Cloth Config. - maven { url "https://maven.shedaniel.me/" } - maven { url "https://maven.terraformersmc.com/releases/" } + // Cloth Config. + maven { url "https://maven.shedaniel.me/" } + maven { url "https://maven.terraformersmc.com/releases/" } - // Jitpack for Mixin Extras. - maven { - url 'https://jitpack.io' - content { - includeGroup 'com.github.llamalad7' + // Jitpack for Mixin Extras. + maven { + url 'https://jitpack.io' + content { + includeGroup 'com.github.llamalad7' + } } - } - - // YACL/Controlify - maven { url = "https://maven.isxander.dev/releases" } - maven { url = "https://maven.quiltmc.org/repository/release/" } - - // Mod Menu - maven { url "https://maven.terraformersmc.com/releases" } - - // Permissions API - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } -} -dependencies { - // To change the versions see the gradle.properties file - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings "net.fabricmc:yarn:${project.yarn_version}:v2" - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + // YACL/Controlify + maven { url "https://maven.isxander.dev/releases" } + maven { url "https://maven.quiltmc.org/repository/release/" } - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + // Mod Menu + maven { url "https://maven.terraformersmc.com/releases" } - include modImplementation("nl.enjarai:cicada-lib:${project.cicada_version}") { - exclude group: "net.fabricmc.fabric-api" + // Permissions API + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } } - modImplementation("dev.isxander.yacl:yet-another-config-lib-fabric:${project.yacl_version}") { - exclude group: "net.fabricmc" + java { + toolchain.languageVersion = JavaLanguageVersion.of(17) + withSourcesJar() + withJavadocJar() } - modCompileOnly("dev.isxander:controlify:${project.controlify_version}") { - exclude group: "net.fabricmc" - exclude group: "maven.modrinth" - exclude group: "com.github.CaffeineMC" + jar { + from(rootProject.file("LICENSE")) { + rename { "${it}_${mod_name}" } + } + manifest { + attributes([ + 'Specification-Title' : mod_name, + 'Specification-Vendor' : mod_author, + 'Specification-Version' : project.jar.archiveVersion, + 'Implementation-Title' : project.name, + 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_author, + 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'Timestamp' : System.currentTimeMillis(), + 'Built-On-Java' : "${System.getProperty('java.vm.version')} (${System.getProperty('java.vm.vendor')})", + 'Built-On-Minecraft' : minecraft_version + ]) + } } - // Mod Menu integration. - modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}" - - // Mixin Extras for extra compatibility. - implementation("com.github.llamalad7.mixinextras:mixinextras-fabric:${project.mixin_extras_version}") - annotationProcessor("com.github.llamalad7.mixinextras:mixinextras-fabric:${project.mixin_extras_version}") - clientAnnotationProcessor("com.github.llamalad7.mixinextras:mixinextras-fabric:${project.mixin_extras_version}") - - include(implementation("com.github.bawnorton.mixinsquared:mixinsquared-fabric:${project.mixin_squared_version}")) - annotationProcessor("com.github.bawnorton.mixinsquared:mixinsquared-fabric:${project.mixin_squared_version}") - clientAnnotationProcessor("com.github.bawnorton.mixinsquared:mixinsquared-fabric:${project.mixin_squared_version}") - - include(modImplementation("me.lucko:fabric-permissions-api:${project.permissions_api_version}")) -} - -base { - archivesName = project.archives_base_name -} - -version = "${rootProject.mod_version}+${rootProject.minecraft_version}-fabric" - -processResources { - inputs.property "version", "${rootProject.mod_version}+${rootProject.minecraft_version}" - - filesMatching("fabric.mod.json") { - expand "version": "${rootProject.mod_version}+${rootProject.minecraft_version}" + sourcesJar { + from(rootProject.file("LICENSE")) { + rename { "${it}_${mod_name}" } + } } -} - -tasks.withType(JavaCompile).configureEach { - // Minecraft 1.18 (1.18-pre2) upwards uses Java 17. - it.options.release = 17 -} - -java { - // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task - // if it is present. - // If you remove this line, sources will not be generated. - withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - -jar { - from("LICENSE") { - rename { "${it}_${base.archivesName.get()}"} + tasks.withType(JavaCompile).configureEach { + it.options.encoding = 'UTF-8' + it.options.getRelease().set(17) } -} -tasks.withType(JavaCompile) { - options.encoding = "UTF-8" - options.release = 17 -} + plugins.withId('dev.architectury.loom') { + loom { + accessWidenerPath = project(":common").file("src/main/resources/${mod_id}.accesswidener") + } + } -// configure the maven publication -publishing { - repositories { - maven { - name = "enjaraiMaven" - url = "https://maven.enjarai.nl/releases" - credentials(PasswordCredentials) - authentication { - basic(BasicAuthentication) - } + processResources { + var replaceProperties = [ + minecraft_version : minecraft_version, + + forge_minecraft_version_range : forge_minecraft_version_range, + forge_version : forge_version, + forge_version_range : forge_version_range, + loader_version_range : loader_version_range, + + fabric_minecraft_version_range: fabric_minecraft_version_range, + + fabric_version : fabric_version, + fabric_loader_version : fabric_loader_version, + forge_fabric_version : forge_fabric_version, + forge_fabric_loader_version : forge_fabric_loader_version, + + mod_id : mod_id, + mod_name : mod_name, + mod_version : version, // findProperty('mod_version') ?: project.jar.archiveVersion, + mod_license : mod_license, + mod_author : mod_author, + mod_credits : mod_credits, + mod_description : mod_description, + mod_url : mod_url, + mod_sources_url : mod_sources_url, + mod_issue_tracker_url : mod_issue_tracker_url, + ] + inputs.properties replaceProperties + filesMatching(['pack.mcmeta', 'fabric.mod.json', 'META-INF/mods.toml', '*.mixins.json']) { + expand replaceProperties } } - publications { - mavenJava(MavenPublication) { - groupId = project.maven_group - artifactId = archivesBaseName - version = project.version - - from components.java - } + // Disables Gradle's custom module metadata from being published to maven. The + // metadata includes mapped dependencies which are not reasonably consumable by + // other mod developers. + tasks.withType(GenerateModuleMetadata).configureEach { + enabled = false } -} -yamlang { - targetSourceSets = [sourceSets.main] - inputDir = 'assets/do_a_barrel_roll/lang' + yamlang { + targetSourceSets = [sourceSets.main] + inputDir = "assets/${mod_id}/lang" + } } diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 00000000..5b12d692 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,49 @@ +plugins { + id 'java' + id 'idea' + id 'maven-publish' + id 'dev.architectury.loom' +} + +base { + archivesName = "${mod_name}-common-${minecraft_version}" +} + +loom { + splitEnvironmentSourceSets() + + mods { + "do_a_barrel_roll" { + sourceSet sourceSets.main + sourceSet sourceSets.client + } + } +} + +dependencies { + minecraft "com.mojang:minecraft:${minecraft_version}" + mappings "net.fabricmc:yarn:${yarn_version}:v2" + + modCompileOnly "net.fabricmc.fabric-api:fabric-api:${fabric_version}" + modCompileOnly "net.fabricmc:fabric-loader:${fabric_loader_version}" + + modImplementation("dev.isxander.yacl:yet-another-config-lib-common:${project.yacl_version}") { + exclude group: "net.fabricmc" + } + + implementation(annotationProcessor("com.github.bawnorton.mixinsquared:mixinsquared-fabric:${project.mixin_squared_version}")) +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId base.archivesName.get() + from components.java + } + } + repositories { + maven { + url "file://" + System.getenv("local_maven") + } + } +} \ No newline at end of file diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/DoABarrelRollClient.java b/common/src/client/java/nl/enjarai/doabarrelroll/DoABarrelRollClient.java new file mode 100644 index 00000000..b97c7c25 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/DoABarrelRollClient.java @@ -0,0 +1,126 @@ +package nl.enjarai.doabarrelroll; + +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.util.SmoothUtil; +import nl.enjarai.doabarrelroll.api.RollEntity; +import nl.enjarai.doabarrelroll.api.RollMouse; +import nl.enjarai.doabarrelroll.api.event.ClientEvents; +import nl.enjarai.doabarrelroll.api.event.RollEvents; +import nl.enjarai.doabarrelroll.api.event.RollGroup; +import nl.enjarai.doabarrelroll.config.ActivationBehaviour; +import nl.enjarai.doabarrelroll.config.LimitedModConfigServer; +import nl.enjarai.doabarrelroll.config.ModConfig; +import nl.enjarai.doabarrelroll.config.ModConfigServer; +import nl.enjarai.doabarrelroll.flight.RotationModifiers; +import nl.enjarai.doabarrelroll.net.HandshakeClient; +import nl.enjarai.doabarrelroll.render.HorizonLineWidget; +import nl.enjarai.doabarrelroll.render.MomentumCrosshairWidget; +import nl.enjarai.doabarrelroll.util.MixinHooks; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector2d; + +import java.lang.reflect.Method; + +public class DoABarrelRollClient { + public static final HandshakeClient HANDSHAKE_CLIENT = new HandshakeClient<>( + ModConfigServer.CODEC, + LimitedModConfigServer.getCodec(), + ClientEvents::updateServerConfig + ); + public static final SmoothUtil PITCH_SMOOTHER = new SmoothUtil(); + public static final SmoothUtil YAW_SMOOTHER = new SmoothUtil(); + public static final SmoothUtil ROLL_SMOOTHER = new SmoothUtil(); + public static final RollGroup FALL_FLYING_GROUP = RollGroup.of(DoABarrelRoll.id("fall_flying")); + public static double throttle = 0; + + public static void init() { + FALL_FLYING_GROUP.trueIf(DoABarrelRollClient::isFallFlying); + + // Keyboard modifiers + RollEvents.EARLY_CAMERA_MODIFIERS.register(context -> context + .useModifier(RotationModifiers::manageThrottle, ModConfig.INSTANCE::getEnableThrust) + .useModifier(RotationModifiers.buttonControls(1800)), + 2000, FALL_FLYING_GROUP); + + // Mouse modifiers, including swapping axes + RollEvents.EARLY_CAMERA_MODIFIERS.register(context -> context + .useModifier(ModConfig.INSTANCE::configureRotation), + 1000, FALL_FLYING_GROUP); + + // Generic movement modifiers, banking and such + RollEvents.LATE_CAMERA_MODIFIERS.register(context -> context + .useModifier(RotationModifiers::applyControlSurfaceEfficacy, ModConfig.INSTANCE::getSimulateControlSurfaceEfficacy) + .useModifier(RotationModifiers.smoothing( + PITCH_SMOOTHER, YAW_SMOOTHER, ROLL_SMOOTHER, + ModConfig.INSTANCE.getSmoothing() + )) + .useModifier(RotationModifiers::banking, ModConfig.INSTANCE::getEnableBanking), + 1000, FALL_FLYING_GROUP); + + ClientTickEvents.END_CLIENT_TICK.register((client) -> { + if (!isFallFlying()) { + clearValues(); + } + }); + + ClientEvents.SERVER_CONFIG_UPDATE.register(ModConfig.INSTANCE::notifyPlayerOfServerConfig); + } + + public static void onRenderCrosshair(DrawContext context, float tickDelta, int scaledWidth, int scaledHeight) { + if (!isFallFlying()) return; + + var matrices = context.getMatrices(); + var entity = MinecraftClient.getInstance().getCameraEntity(); + var rollEntity = ((RollEntity) entity); + if (entity != null) { + if (ModConfig.INSTANCE.getShowHorizon()) { + HorizonLineWidget.render(matrices, scaledWidth, scaledHeight, + rollEntity.doABarrelRoll$getRoll(tickDelta), entity.getPitch(tickDelta)); + } + + if (ModConfig.INSTANCE.getMomentumBasedMouse() && ModConfig.INSTANCE.getShowMomentumWidget()) { + var rollMouse = (RollMouse) MinecraftClient.getInstance().mouse; + + MomentumCrosshairWidget.render(matrices, scaledWidth, scaledHeight, new Vector2d(rollMouse.doABarrelRoll$getMouseTurnVec())); + } + } + } + + private static void clearValues() { + PITCH_SMOOTHER.clear(); + YAW_SMOOTHER.clear(); + ROLL_SMOOTHER.clear(); + throttle = 0; + } + + public static boolean isFallFlying() { + if (!HANDSHAKE_CLIENT.getConfig().map(LimitedModConfigServer::forceEnabled).orElse(false)) { + var hybrid = ModConfig.INSTANCE.getActivationBehaviour() == ActivationBehaviour.HYBRID || + ModConfig.INSTANCE.getActivationBehaviour() == ActivationBehaviour.HYBRID_TOGGLE; + if (hybrid && !MixinHooks.thirdJump) { + return false; + } + + if (!ModConfig.INSTANCE.getModEnabled()) { + return false; + } + } + + var player = MinecraftClient.getInstance().player; + if (player == null) { + return false; + } + if (ModConfig.INSTANCE.getDisableWhenSubmerged() && player.isSubmergedInWater()) { + return false; + } + return player.isFallFlying(); + } + + public static boolean isConnectedToRealms() { + return false; // We are not connected to realms. + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/ModKeybindings.java b/common/src/client/java/nl/enjarai/doabarrelroll/ModKeybindings.java new file mode 100644 index 00000000..d9d8c025 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/ModKeybindings.java @@ -0,0 +1,166 @@ +package nl.enjarai.doabarrelroll; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.text.Text; +import nl.enjarai.doabarrelroll.api.key.InputContext; +import nl.enjarai.doabarrelroll.config.LimitedModConfigServer; +import nl.enjarai.doabarrelroll.config.ModConfig; +import nl.enjarai.doabarrelroll.config.ModConfigScreen; +import org.lwjgl.glfw.GLFW; + +import java.util.List; + +public class ModKeybindings { + + public static final KeyBinding TOGGLE_ENABLED = new KeyBinding( + "key.do_a_barrel_roll.toggle_enabled", + InputUtil.Type.KEYSYM, + GLFW.GLFW_KEY_I, + "category.do_a_barrel_roll.do_a_barrel_roll" + ); + public static final KeyBinding TOGGLE_THRUST = new KeyBinding( + "key.do_a_barrel_roll.toggle_thrust", + InputUtil.Type.KEYSYM, + InputUtil.UNKNOWN_KEY.getCode(), + "category.do_a_barrel_roll.do_a_barrel_roll" + ); + public static final KeyBinding OPEN_CONFIG = new KeyBinding( + "key.do_a_barrel_roll.open_config", + InputUtil.Type.KEYSYM, + InputUtil.UNKNOWN_KEY.getCode(), + "category.do_a_barrel_roll.do_a_barrel_roll" + ); + + public static final KeyBinding PITCH_UP = new KeyBinding( + "key.do_a_barrel_roll.pitch_up", + InputUtil.Type.KEYSYM, + InputUtil.UNKNOWN_KEY.getCode(), + "category.do_a_barrel_roll.do_a_barrel_roll.movement" + ); + public static final KeyBinding PITCH_DOWN = new KeyBinding( + "key.do_a_barrel_roll.pitch_down", + InputUtil.Type.KEYSYM, + InputUtil.UNKNOWN_KEY.getCode(), + "category.do_a_barrel_roll.do_a_barrel_roll.movement" + ); + public static final KeyBinding YAW_LEFT = new KeyBinding( + "key.do_a_barrel_roll.yaw_left", + InputUtil.Type.KEYSYM, + GLFW.GLFW_KEY_A, + "category.do_a_barrel_roll.do_a_barrel_roll.movement" + ); + public static final KeyBinding YAW_RIGHT = new KeyBinding( + "key.do_a_barrel_roll.yaw_right", + InputUtil.Type.KEYSYM, + GLFW.GLFW_KEY_D, + "category.do_a_barrel_roll.do_a_barrel_roll.movement" + ); + public static final KeyBinding ROLL_LEFT = new KeyBinding( + "key.do_a_barrel_roll.roll_left", + InputUtil.Type.KEYSYM, + InputUtil.UNKNOWN_KEY.getCode(), + "category.do_a_barrel_roll.do_a_barrel_roll.movement" + ); + public static final KeyBinding ROLL_RIGHT = new KeyBinding( + "key.do_a_barrel_roll.roll_right", + InputUtil.Type.KEYSYM, + InputUtil.UNKNOWN_KEY.getCode(), + "category.do_a_barrel_roll.do_a_barrel_roll.movement" + ); + public static final KeyBinding THRUST_FORWARD = new KeyBinding( + "key.do_a_barrel_roll.thrust_forward", + InputUtil.Type.KEYSYM, + GLFW.GLFW_KEY_W, + "category.do_a_barrel_roll.do_a_barrel_roll.movement" + ); + public static final KeyBinding THRUST_BACKWARD = new KeyBinding( + "key.do_a_barrel_roll.thrust_backward", + InputUtil.Type.KEYSYM, + InputUtil.UNKNOWN_KEY.getCode(), + "category.do_a_barrel_roll.do_a_barrel_roll.movement" + ); + + public static final List FABRIC = List.of( + TOGGLE_ENABLED, + TOGGLE_THRUST, + OPEN_CONFIG, + PITCH_UP, + PITCH_DOWN, + YAW_LEFT, + YAW_RIGHT, + ROLL_LEFT, + ROLL_RIGHT, + THRUST_FORWARD, + THRUST_BACKWARD + ); + + public static final InputContext CONTEXT = InputContext.of( + DoABarrelRoll.id("fall_flying"), + DoABarrelRollClient.FALL_FLYING_GROUP + ); + + static { + CONTEXT.addKeyBinding(PITCH_UP); + CONTEXT.addKeyBinding(PITCH_DOWN); + CONTEXT.addKeyBinding(YAW_LEFT); + CONTEXT.addKeyBinding(YAW_RIGHT); + CONTEXT.addKeyBinding(ROLL_LEFT); + CONTEXT.addKeyBinding(ROLL_RIGHT); + CONTEXT.addKeyBinding(THRUST_FORWARD); + CONTEXT.addKeyBinding(THRUST_BACKWARD); + } + + public static void clientTick(MinecraftClient client) { + while (TOGGLE_ENABLED.wasPressed()) { + if (!DoABarrelRollClient.HANDSHAKE_CLIENT.getConfig().map(LimitedModConfigServer::forceEnabled).orElse(false)) { + ModConfig.INSTANCE.setModEnabled(!ModConfig.INSTANCE.getModEnabled()); + ModConfig.INSTANCE.save(); + + if (client.player != null) { + client.player.sendMessage( + Text.translatable( + "key.do_a_barrel_roll." + + (ModConfig.INSTANCE.getModEnabled() ? "toggle_enabled.enable" : "toggle_enabled.disable") + ), + true + ); + } + } else { + if (client.player != null) { + client.player.sendMessage( + Text.translatable("key.do_a_barrel_roll.toggle_enabled.disallowed"), + true + ); + } + } + } + while (TOGGLE_THRUST.wasPressed()) { + if (DoABarrelRollClient.HANDSHAKE_CLIENT.getConfig().map(LimitedModConfigServer::allowThrusting).orElse(false)) { + ModConfig.INSTANCE.setEnableThrust(!ModConfig.INSTANCE.getEnableThrust()); + ModConfig.INSTANCE.save(); + + if (client.player != null) { + client.player.sendMessage( + Text.translatable( + "key.do_a_barrel_roll." + + (ModConfig.INSTANCE.getEnableThrust() ? "toggle_thrust.enable" : "toggle_thrust.disable") + ), + true + ); + } + } else { + if (client.player != null) { + client.player.sendMessage( + Text.translatable("key.do_a_barrel_roll.toggle_thrust.disallowed"), + true + ); + } + } + } + while (OPEN_CONFIG.wasPressed()) { + client.setScreen(ModConfigScreen.create(client.currentScreen)); + } + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/ModMath.java b/common/src/client/java/nl/enjarai/doabarrelroll/ModMath.java new file mode 100644 index 00000000..71b40f52 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/ModMath.java @@ -0,0 +1,37 @@ +package nl.enjarai.doabarrelroll; + +import java.util.function.BiConsumer; + +public class ModMath { + + public static void forBresenhamLine(int x0, int y0, int x1, int y1, BiConsumer consumer) { + + int dx = Math.abs(x1 - x0); + int dy = Math.abs(y1 - y0); + + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; + + int err = dx - dy; + int e2; + + while (true) { + + consumer.accept(x0, y0); + + if (x0 == x1 && y0 == y1) break; + + e2 = 2 * err; + + if (e2 > -dy) { + err = err - dy; + x0 = x0 + sx; + } + + if (e2 < dx) { + err = err + dx; + y0 = y0 + sy; + } + } + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/api/RollCamera.java b/common/src/client/java/nl/enjarai/doabarrelroll/api/RollCamera.java new file mode 100644 index 00000000..64529ed6 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/api/RollCamera.java @@ -0,0 +1,5 @@ +package nl.enjarai.doabarrelroll.api; + +public interface RollCamera { + float doABarrelRoll$getRoll(); +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/api/RollMouse.java b/common/src/client/java/nl/enjarai/doabarrelroll/api/RollMouse.java new file mode 100644 index 00000000..72453b58 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/api/RollMouse.java @@ -0,0 +1,10 @@ +package nl.enjarai.doabarrelroll.api; + +import net.minecraft.client.network.ClientPlayerEntity; +import org.joml.Vector2d; + +public interface RollMouse { + boolean doABarrelRoll$updateMouse(ClientPlayerEntity player, double cursorDeltaX, double cursorDeltaY, double mouseDelta); + + Vector2d doABarrelRoll$getMouseTurnVec(); +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/api/event/ClientEvents.java b/common/src/client/java/nl/enjarai/doabarrelroll/api/event/ClientEvents.java new file mode 100644 index 00000000..031079d6 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/api/event/ClientEvents.java @@ -0,0 +1,18 @@ +package nl.enjarai.doabarrelroll.api.event; + +import nl.enjarai.doabarrelroll.config.LimitedModConfigServer; +import nl.enjarai.doabarrelroll.impl.event.EventImpl; + +public interface ClientEvents { + Event SERVER_CONFIG_UPDATE = new EventImpl<>(); + + interface ServerConfigUpdateEvent { + void updateServerConfig(LimitedModConfigServer config); + } + + static void updateServerConfig(LimitedModConfigServer config) { + for (var listener : SERVER_CONFIG_UPDATE.getListeners()) { + listener.updateServerConfig(config); + } + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/api/event/StarFox64Events.java b/common/src/client/java/nl/enjarai/doabarrelroll/api/event/StarFox64Events.java new file mode 100644 index 00000000..d35cd0c2 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/api/event/StarFox64Events.java @@ -0,0 +1,18 @@ +package nl.enjarai.doabarrelroll.api.event; + +import net.minecraft.client.network.ClientPlayerEntity; +import nl.enjarai.doabarrelroll.impl.event.EventImpl; + +public interface StarFox64Events { + Event DOES_A_BARREL_ROLL = new EventImpl<>(); + + static void doesABarrelRoll(ClientPlayerEntity player) { + for (var listener : DOES_A_BARREL_ROLL.getListeners()) { + listener.onBarrelRoll(player); + } + } + + interface DoesABarrelRollEvent { + void onBarrelRoll(ClientPlayerEntity player); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/api/key/InputContext.java b/common/src/client/java/nl/enjarai/doabarrelroll/api/key/InputContext.java new file mode 100644 index 00000000..4c89099b --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/api/key/InputContext.java @@ -0,0 +1,27 @@ +package nl.enjarai.doabarrelroll.api.key; + +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.util.Identifier; +import nl.enjarai.doabarrelroll.impl.key.InputContextImpl; + +import java.util.List; +import java.util.function.Supplier; + +public interface InputContext { + static InputContext of(Identifier id, Supplier activeCondition) { + return new InputContextImpl(id, activeCondition); + } + + Identifier getId(); + + boolean isActive(); + + void addKeyBinding(KeyBinding keyBinding); + + List getKeyBindings(); + + KeyBinding getKeyBinding(InputUtil.Key key); + + void updateKeysByCode(); +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/compat/Compat.java b/common/src/client/java/nl/enjarai/doabarrelroll/compat/Compat.java new file mode 100644 index 00000000..9299eba1 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/compat/Compat.java @@ -0,0 +1,27 @@ +package nl.enjarai.doabarrelroll.compat; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.Version; +import net.fabricmc.loader.api.VersionParsingException; + +public class Compat { + public static final Version YACL_MIN_VERSION; + + static { + try { + YACL_MIN_VERSION = Version.parse("3.1.0"); + } catch (VersionParsingException e) { + throw new RuntimeException(e); + } + } + + public static boolean isYACLLoaded() { + return FabricLoader.getInstance().isModLoaded("yet_another_config_lib_v3"); + } + + public static boolean isYACLUpToDate() { + return FabricLoader.getInstance().getModContainer("yet_another_config_lib_v3") + .filter(modContainer -> modContainer.getMetadata().getVersion().compareTo(YACL_MIN_VERSION) >= 0) + .isPresent(); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/compat/CompatMixinPlugin.java b/common/src/client/java/nl/enjarai/doabarrelroll/compat/CompatMixinPlugin.java new file mode 100644 index 00000000..95e2580a --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/compat/CompatMixinPlugin.java @@ -0,0 +1,52 @@ +package nl.enjarai.doabarrelroll.compat; + +import net.fabricmc.loader.api.FabricLoader; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Set; + +/** + * Mixin config plugin for compat, only applies mixins if specific mods are present. + */ +public interface CompatMixinPlugin extends IMixinConfigPlugin { + Set getRequiredMods(); + + @Override + default void onLoad(String mixinPackage) { + + } + + @Override + default String getRefMapperConfig() { + return null; + } + + @Override + default boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + return getRequiredMods().stream() + .allMatch((modId) -> FabricLoader.getInstance().isModLoaded(modId)); + } + + @Override + default void acceptTargets(Set myTargets, Set otherTargets) { + + } + + @Override + default List getMixins() { + return null; + } + + @Override + default void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } + + @Override + default void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/compat/yacl/ExpressionParserController.java b/common/src/client/java/nl/enjarai/doabarrelroll/compat/yacl/ExpressionParserController.java new file mode 100644 index 00000000..6aead264 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/compat/yacl/ExpressionParserController.java @@ -0,0 +1,38 @@ +package nl.enjarai.doabarrelroll.compat.yacl; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.utils.Dimension; +import dev.isxander.yacl3.gui.AbstractWidget; +import dev.isxander.yacl3.gui.YACLScreen; +import dev.isxander.yacl3.gui.controllers.string.IStringController; +import dev.isxander.yacl3.gui.controllers.string.StringControllerElement; +import net.minecraft.text.Text; +import nl.enjarai.doabarrelroll.math.ExpressionParser; +import nl.enjarai.doabarrelroll.math.SyntaxHighlighter; + +public record ExpressionParserController(Option option) implements IStringController { + @Override + public String getString() { + return option.pendingValue().getString(); + } + + @Override + public void setFromString(String value) { + option.requestSet(new ExpressionParser(value)); + } + + @Override + public Text formatValue() { + return SyntaxHighlighter.highlightText(getString()); + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension widgetDimension) { + return new StringControllerElement(this, screen, widgetDimension, true) { + @Override + protected int getValueColor() { + return option.pendingValue().hasError() ? 0xFF5555 : super.getValueColor(); + } + }; + } +} \ No newline at end of file diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/compat/yacl/YACLImplementation.java b/common/src/client/java/nl/enjarai/doabarrelroll/compat/yacl/YACLImplementation.java new file mode 100644 index 00000000..9476be74 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/compat/yacl/YACLImplementation.java @@ -0,0 +1,384 @@ +package nl.enjarai.doabarrelroll.compat.yacl; + +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.api.controller.*; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ConfirmScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Util; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.DoABarrelRollClient; +import nl.enjarai.doabarrelroll.ModKeybindings; +import nl.enjarai.doabarrelroll.api.event.ClientEvents; +import nl.enjarai.doabarrelroll.config.*; +import nl.enjarai.doabarrelroll.math.ExpressionParser; +import nl.enjarai.doabarrelroll.net.ServerConfigUpdateClient; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.function.Consumer; + +public class YACLImplementation { + public static Screen generateConfigScreen(Screen parent) { + var inWorld = MinecraftClient.getInstance().world != null; + ClientPlayerEntity player; + var onRealms = DoABarrelRollClient.isConnectedToRealms() && + (player = MinecraftClient.getInstance().player) != null && player.hasPermissionLevel(2); + var serverConfig = DoABarrelRollClient.HANDSHAKE_CLIENT.getConfig(); + + var thrustingAllowed = new Dependable(serverConfig.map(LimitedModConfigServer::allowThrusting).orElse(!inWorld || onRealms)); + var allowDisabled = new Dependable(!serverConfig.map(LimitedModConfigServer::forceEnabled).orElse(false)); + + var builder = YetAnotherConfigLib.createBuilder() + .title(getText("title")) + .category(ConfigCategory.createBuilder() + .name(getText("general")) + .option(allowDisabled.add(getBooleanOption("general", "mod_enabled", false, false) + .description(OptionDescription.createBuilder() + .text(Text.translatable("config.do_a_barrel_roll.general.mod_enabled.description", + KeyBindingHelper.getBoundKeyOf(ModKeybindings.TOGGLE_ENABLED).getLocalizedText())) + .build()) + .binding(true, () -> ModConfig.INSTANCE.getModEnabled(), value -> ModConfig.INSTANCE.setModEnabled(value)))) + .group(OptionGroup.createBuilder() + .name(getText("controls")) + .option(getBooleanOption("controls", "switch_roll_and_yaw", true, false) + .binding(false, () -> ModConfig.INSTANCE.getSwitchRollAndYaw(), value -> ModConfig.INSTANCE.setSwitchRollAndYaw(value)) + .build()) + .option(getBooleanOption("controls", "invert_pitch", true, false) + .binding(false, () -> ModConfig.INSTANCE.getInvertPitch(), value -> ModConfig.INSTANCE.setInvertPitch(value)) + .build()) + .option(getBooleanOption("controls", "momentum_based_mouse", true, false) + .binding(false, () -> ModConfig.INSTANCE.getMomentumBasedMouse(), value -> ModConfig.INSTANCE.setMomentumBasedMouse(value)) + .build()) + .option(getOption(Double.class, "controls", "momentum_mouse_deadzone", true, false) + .controller(option -> getDoubleSlider(option, 0.0, 1.0, 0.01)) + .binding(0.2, () -> ModConfig.INSTANCE.getMomentumMouseDeadzone(), value -> ModConfig.INSTANCE.setMomentumMouseDeadzone(value)) + .build()) + .option(getOption(ActivationBehaviour.class, "controls", "activation_behaviour", false, false) + .description(behaviour -> OptionDescription.createBuilder() + .text(getText("controls", "activation_behaviour.description") + .append(getText("controls", "activation_behaviour.description." + behaviour.name().toLowerCase()))) + .build()) + .controller(option1 -> EnumControllerBuilder.create(option1) + .enumClass(ActivationBehaviour.class)) + .binding(ActivationBehaviour.VANILLA, () -> ModConfig.INSTANCE.getActivationBehaviour(), value -> ModConfig.INSTANCE.setActivationBehaviour(value)) + .build()) + .option(getBooleanOption("controls", "disable_when_submerged", true, false) + .binding(true, () -> ModConfig.INSTANCE.getDisableWhenSubmerged(), value -> ModConfig.INSTANCE.setDisableWhenSubmerged(value)) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("hud")) + .option(getBooleanOption("hud", "show_horizon", true, true) + .binding(false, () -> ModConfig.INSTANCE.getShowHorizon(), value -> ModConfig.INSTANCE.setShowHorizon(value)) + .build()) + .option(getBooleanOption("controls", "show_momentum_widget", true, true) + .binding(true, () -> ModConfig.INSTANCE.getShowMomentumWidget(), value -> ModConfig.INSTANCE.setShowMomentumWidget(value)) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("banking")) + .option(getBooleanOption("banking", "enable_banking", true, false) + .binding(true, () -> ModConfig.INSTANCE.getEnableBanking(), value -> ModConfig.INSTANCE.setEnableBanking(value)) + .build()) + .option(getOption(Double.class, "banking", "banking_strength", false, false) + .controller(option -> getDoubleSlider(option, 0.0, 100.0, 1.0)) + .binding(20.0, () -> ModConfig.INSTANCE.getBankingStrength(), value -> ModConfig.INSTANCE.setBankingStrength(value)) + .build()) + .option(getBooleanOption("banking", "simulate_control_surface_efficacy", true, false) + .binding(false, () -> ModConfig.INSTANCE.getSimulateControlSurfaceEfficacy(), value -> ModConfig.INSTANCE.setSimulateControlSurfaceEfficacy(value)) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("thrust")) + .collapsed(true) + .option(thrustingAllowed.add(getBooleanOption("thrust", "enable_thrust", true, false) + .binding(false, () -> ModConfig.INSTANCE.getEnableThrustClient(), value -> ModConfig.INSTANCE.setEnableThrust(value)))) + .option(thrustingAllowed.add(getOption(Double.class, "thrust", "max_thrust", true, false) + .controller(option -> getDoubleSlider(option, 0.1, 10.0, 0.1)) + .binding(2.0, () -> ModConfig.INSTANCE.getMaxThrust(), value -> ModConfig.INSTANCE.setMaxThrust(value)))) + .option(thrustingAllowed.add(getOption(Double.class, "thrust", "thrust_acceleration", true, false) + .controller(option -> getDoubleSlider(option, 0.1, 1.0, 0.1)) + .binding(0.1, () -> ModConfig.INSTANCE.getThrustAcceleration(), value -> ModConfig.INSTANCE.setThrustAcceleration(value)))) + .option(thrustingAllowed.add(getBooleanOption("thrust", "thrust_particles", false, false) + .binding(true, () -> ModConfig.INSTANCE.getThrustParticles(), value -> ModConfig.INSTANCE.setThrustParticles(value)))) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("misc")) + .option(getBooleanOption("misc", "enable_easter_eggs", false, false) + .binding(ModConfig.DEFAULT.getEnableEasterEggs(), ModConfig.INSTANCE::getEnableEasterEggs, ModConfig.INSTANCE::setEnableEasterEggs) + .build()) + .build()) + .build()) + .category(ConfigCategory.createBuilder() + .name(getText("sensitivity")) + .group(OptionGroup.createBuilder() + .name(getText("smoothing")) + .option(getOption(Double.class, "smoothing", "smoothing_pitch", false, false) + .controller(option -> getDoubleSlider(option, 0.0, 5.0, 0.1)) + .binding(1.0, () -> ModConfig.INSTANCE.getSmoothingPitch(), value -> ModConfig.INSTANCE.setSmoothingPitch(value)) + .build()) + .option(getOption(Double.class, "smoothing", "smoothing_yaw", false, false) + .controller(option -> getDoubleSlider(option, 0.0, 5.0, 0.1)) + .binding(2.5, () -> ModConfig.INSTANCE.getSmoothingYaw(), value -> ModConfig.INSTANCE.setSmoothingYaw(value)) + .build()) + .option(getOption(Double.class, "smoothing", "smoothing_roll", false, false) + .controller(option -> getDoubleSlider(option, 0.0, 5.0, 0.1)) + .binding(1.0, () -> ModConfig.INSTANCE.getSmoothingRoll(), value -> ModConfig.INSTANCE.setSmoothingRoll(value)) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("desktop")) + .option(getOption(Double.class, "desktop", "pitch", false, false) + .controller(option -> getDoubleSlider(option, 0.1, 10.0, 0.1)) + .binding(1.0, () -> ModConfig.INSTANCE.getDesktopPitch(), value -> ModConfig.INSTANCE.setDesktopPitch(value)) + .build()) + .option(getOption(Double.class, "desktop", "yaw", false, false) + .controller(option -> getDoubleSlider(option, 0.1, 10.0, 0.1)) + .binding(0.4, () -> ModConfig.INSTANCE.getDesktopYaw(), value -> ModConfig.INSTANCE.setDesktopYaw(value)) + .build()) + .option(getOption(Double.class, "desktop", "roll", false, false) + .controller(option -> getDoubleSlider(option, 0.1, 10.0, 0.1)) + .binding(1.0, () -> ModConfig.INSTANCE.getDesktopRoll(), value -> ModConfig.INSTANCE.setDesktopRoll(value)) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("controller")) + .collapsed(!(FabricLoader.getInstance().isModLoaded("controlify") || FabricLoader.getInstance().isModLoaded("midnightcontrols"))) + .description(OptionDescription.createBuilder() + .text(getText("controller.description")) + .build()) + .option(getOption(Double.class, "controller", "pitch", false, false) + .controller(option -> getDoubleSlider(option, 0.1, 10.0, 0.1)) + .binding(1.0, () -> ModConfig.INSTANCE.getControllerPitch(), value -> ModConfig.INSTANCE.setControllerPitch(value)) + .build()) + .option(getOption(Double.class, "controller", "yaw", false, false) + .controller(option -> getDoubleSlider(option, 0.1, 10.0, 0.1)) + .binding(0.4, () -> ModConfig.INSTANCE.getControllerYaw(), value -> ModConfig.INSTANCE.setControllerYaw(value)) + .build()) + .option(getOption(Double.class, "controller", "roll", false, false) + .controller(option -> getDoubleSlider(option, 0.1, 10.0, 0.1)) + .binding(1.0, () -> ModConfig.INSTANCE.getControllerRoll(), value -> ModConfig.INSTANCE.setControllerRoll(value)) + .build()) + .build()) + .build()) + .category(ConfigCategory.createBuilder() + .name(getText("advanced")) + .option(LabelOption.create(getText("advanced", "description"))) + .group(OptionGroup.createBuilder() + .name(getText("banking_math")) + .option(getExpressionOption("banking_math", "banking_x_formula", true, false) + .binding(ModConfig.DEFAULT.getBankingXFormula(), ModConfig.INSTANCE::getBankingXFormula, ModConfig.INSTANCE::setBankingXFormula) + .build()) + .option(getExpressionOption("banking_math", "banking_y_formula", true, false) + .binding(ModConfig.DEFAULT.getBankingYFormula(), ModConfig.INSTANCE::getBankingYFormula, ModConfig.INSTANCE::setBankingYFormula) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("control_surface_efficacy")) + .option(getExpressionOption("control_surface_efficacy", "elevator", true, false) + .binding(ModConfig.DEFAULT.getElevatorEfficacyFormula(), ModConfig.INSTANCE::getElevatorEfficacyFormula, ModConfig.INSTANCE::setElevatorEfficacyFormula) + .build()) + .option(getExpressionOption("control_surface_efficacy", "aileron", true, false) + .binding(ModConfig.DEFAULT.getAileronEfficacyFormula(), ModConfig.INSTANCE::getAileronEfficacyFormula, ModConfig.INSTANCE::setAileronEfficacyFormula) + .build()) + .option(getExpressionOption("control_surface_efficacy", "rudder", true, false) + .binding(ModConfig.DEFAULT.getRudderEfficacyFormula(), ModConfig.INSTANCE::getRudderEfficacyFormula, ModConfig.INSTANCE::setRudderEfficacyFormula) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("documentation")) + .option(LabelOption.create(getText("documentation", "description"))) + .option(ButtonOption.createBuilder() + .name(getText("documentation", "get_help")) + .text(getText("documentation", "get_help.text")) + .action((screen, btn) -> { + var client = MinecraftClient.getInstance(); + client.setScreen(new ConfirmScreen((result) -> { + if (result) { + Util.getOperatingSystem().open(URI.create("https://discord.gg/WcYsDDQtyR")); + } + client.setScreen(screen); + }, getText("documentation", "get_help"), getText("documentation", "get_help.confirm"), ScreenTexts.YES, ScreenTexts.NO)); + }) + .build()) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("variables_documentation")) + .collapsed(true) + .option(LabelOption.create(getText("variables_documentation", "description"))) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("functions_documentation")) + .collapsed(true) + .option(LabelOption.create(getText("functions_documentation", "description"))) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("constants_documentation")) + .collapsed(true) + .option(LabelOption.create(getText("constants_documentation", "description"))) + .build()) + .build()); + + // If we're in a world, use the synced config, otherwise, grab our local one. + var fullServerConfig = inWorld + ? DoABarrelRollClient.HANDSHAKE_CLIENT.getFullConfig() + : Optional.of(DoABarrelRoll.CONFIG_HOLDER.instance); + MutableConfigServer mut; + if (fullServerConfig.isPresent()) { + mut = new MutableConfigServer(fullServerConfig.get()); + builder.category(ConfigCategory.createBuilder() + .name(getText("server")) + .option(LabelOption.create(getText("server", "description"))) + .option(getBooleanOption("server", "force_enabled", true, false) + .binding(ModConfigServer.DEFAULT.forceEnabled(), () -> mut.forceEnabled, value -> mut.forceEnabled = value) + .build()) + .option(getBooleanOption("server", "force_installed", true, false) + .binding(ModConfigServer.DEFAULT.forceInstalled(), () -> mut.forceInstalled, value -> mut.forceInstalled = value) + .build()) + .option(getOption(Integer.class, "server", "installed_timeout", true, false) + .controller(option -> IntegerSliderControllerBuilder.create(option) + .range(0, 100) + .step(1)) + .binding(ModConfigServer.DEFAULT.installedTimeout(), () -> mut.installedTimeout, value -> mut.installedTimeout = value) + .build()) + .group(OptionGroup.createBuilder() + .name(getText("gameplay")) + .option(getBooleanOption("gameplay", "allow_thrusting", true, false) + .binding(ModConfigServer.DEFAULT.allowThrusting(), () -> mut.allowThrusting, value -> mut.allowThrusting = value) + .build()) + .option(getOption(KineticDamage.class, "gameplay", "kinetic_damage", false, false) + .description(behaviour -> OptionDescription.createBuilder() + .text(getText("gameplay", "kinetic_damage.description") + .append(getText("gameplay", "kinetic_damage.description." + behaviour.name().toLowerCase()))) + .build()) + .controller(option -> EnumControllerBuilder.create(option) + .enumClass(KineticDamage.class) + .valueFormatter(kd -> getText("gameplay", "kinetic_damage." + kd.name().toLowerCase(Locale.ROOT)))) + .binding(ModConfigServer.DEFAULT.kineticDamage(), () -> mut.kineticDamage, value -> mut.kineticDamage = value) + .build()) + .build()) + .build()); + } else mut = null; + + Consumer configListener = config -> { + // Update options that have dependent availability. + thrustingAllowed.set(config.allowThrusting()); + allowDisabled.set(!config.forceEnabled()); + }; + + return builder + .save(() -> { + ModConfig.INSTANCE.save(); + + if (mut != null) { + if (MinecraftClient.getInstance().world == null) { + DoABarrelRoll.CONFIG_HOLDER.instance = mut.toImmutable(); + } else { + var original = DoABarrelRollClient.HANDSHAKE_CLIENT.getConfig(); + if (original.isPresent()){ + var imut = mut.toImmutable(); + if (!imut.equals(original.get())) { + ServerConfigUpdateClient.sendUpdate(imut); + configListener.accept(imut); + } + } + } + } + }) + .screenInit(screen -> { + // Add a listener for this screen to update elements when the server config changes. + ClientEvents.ServerConfigUpdateEvent listener = configListener::accept; + ClientEvents.SERVER_CONFIG_UPDATE.register(listener); + ScreenEvents.remove(screen).register(screen1 -> ClientEvents.SERVER_CONFIG_UPDATE.unregister(listener)); + }) + .build() + .generateScreen(parent); + } + + private static Option.Builder getOption(Class clazz, String category, String key, boolean description, boolean image) { + Option.Builder builder = Option.createBuilder() + .name(getText(category, key)); + var descBuilder = OptionDescription.createBuilder(); + if (description) { + descBuilder.text(getText(category, key + ".description")); + } + if (image) { + descBuilder.image(DoABarrelRoll.id("textures/gui/config/images/" + category + "/" + key + ".png"), 480, 275); + } + builder.description(descBuilder.build()); + return builder; + } + + private static Option.Builder getBooleanOption(String category, String key, boolean description, boolean image) { + return getOption(Boolean.class, category, key, description, image) + .controller(TickBoxControllerBuilder::create); + } + + private static Option.Builder getExpressionOption(String category, String key, boolean description, boolean image) { + return getOption(ExpressionParser.class, category, key, false, false) + .description(parser -> { + var descBuilder = OptionDescription.createBuilder(); + var error = Text.literal(parser.hasError() ? parser.getError().getMessage() : "") + .formatted(Formatting.RED); + if (description) { + descBuilder.text(getText(category, key + ".description").append("\n\n").append(error)); + } else { + descBuilder.text(error); + } + if (image) { + descBuilder.image(DoABarrelRoll.id("textures/gui/config/images/" + category + "/" + key + ".png"), 480, 275); + } + return descBuilder.build(); + }) + .customController(ExpressionParserController::new); + } + + private static DoubleSliderControllerBuilder getDoubleSlider(Option option, double min, double max, double step) { + return DoubleSliderControllerBuilder.create(option) + .range(min, max) + .step(step); + } + + private static MutableText getText(String category, String key) { + return Text.translatable("config.do_a_barrel_roll." + category + "." + key); + } + + private static MutableText getText(String key) { + return Text.translatable("config.do_a_barrel_roll." + key); + } + + private static class Dependable { + private final boolean initial; + private final List> options = new ArrayList<>(); + + private Dependable(boolean initial) { + this.initial = initial; + } + + Option add(Option.Builder builder) { + var option = builder + .available(initial) + .build(); + options.add(option); + return option; + } + + void set(boolean value) { + for (var option : options) { + option.setAvailable(value); + } + } + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/config/ActivationBehaviour.java b/common/src/client/java/nl/enjarai/doabarrelroll/config/ActivationBehaviour.java new file mode 100644 index 00000000..c0992b48 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/config/ActivationBehaviour.java @@ -0,0 +1,26 @@ +package nl.enjarai.doabarrelroll.config; + +import net.minecraft.text.Text; +import net.minecraft.util.TranslatableOption; + +public enum ActivationBehaviour implements TranslatableOption { + VANILLA, + TRIPLE_JUMP, + HYBRID, + HYBRID_TOGGLE; + + @Override + public int getId() { + return this.ordinal(); + } + + @Override + public String getTranslationKey() { + return "config.do_a_barrel_roll.controls.activation_behaviour." + this.name().toLowerCase(); + } + + @Override + public Text getText() { + return Text.translatable(getTranslationKey()); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/config/MigrationValue.java b/common/src/client/java/nl/enjarai/doabarrelroll/config/MigrationValue.java new file mode 100644 index 00000000..416e8fba --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/config/MigrationValue.java @@ -0,0 +1,18 @@ +package nl.enjarai.doabarrelroll.config; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; + +public @interface MigrationValue { + class SerializationStrategy implements ExclusionStrategy { + @Override + public boolean shouldSkipField(FieldAttributes f) { + return f.getAnnotation(MigrationValue.class) != null; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/config/ModConfig.java b/common/src/client/java/nl/enjarai/doabarrelroll/config/ModConfig.java new file mode 100644 index 00000000..1f8bad15 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/config/ModConfig.java @@ -0,0 +1,474 @@ +package nl.enjarai.doabarrelroll.config; + + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import nl.enjarai.doabarrelroll.DoABarrelRollClient; +import nl.enjarai.doabarrelroll.api.event.RollContext; +import nl.enjarai.doabarrelroll.api.rotation.RotationInstant; +import nl.enjarai.doabarrelroll.config.serialization.ExpressionParserTypeAdapter; +import nl.enjarai.doabarrelroll.math.ExpressionParser; +import nl.enjarai.doabarrelroll.util.ToastUtil; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + +public class ModConfig { + public static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(ExpressionParser.class, new ExpressionParserTypeAdapter()) + .addSerializationExclusionStrategy(new MigrationValue.SerializationStrategy()) + .setPrettyPrinting() + .create(); + public static final ModConfig DEFAULT = new ModConfig(); + public static final Path CONFIG_FILE = FabricLoader.getInstance() + .getConfigDir().resolve("do_a_barrel_roll-client.json"); + public static ModConfig INSTANCE = loadConfigFile(CONFIG_FILE.toFile()); + + public static void touch() { + // touch the grass + } + + + private void performMigrations() { + if (format_version_do_not_edit <= 1) { // configs below format version 1 use old smoothing values + var enabled = sensitivity.smoothing.smoothing_enabled; + sensitivity.camera_smoothing.pitch = enabled ? 1 / sensitivity.smoothing.smoothing_pitch : 0; + sensitivity.camera_smoothing.yaw = enabled ? 1 / sensitivity.smoothing.smoothing_yaw : 0; + sensitivity.camera_smoothing.roll = enabled ? 1 / sensitivity.smoothing.smoothing_roll : 0; + } + + format_version_do_not_edit = 2; // update format version + } + + + int format_version_do_not_edit = 2; + + General general = new General(); + static class General { + boolean mod_enabled = true; + + Controls controls = new Controls(); + static class Controls { + boolean switch_roll_and_yaw = false; + boolean invert_pitch = false; + boolean momentum_based_mouse = false; + double momentum_mouse_deadzone = 0.2; + boolean show_momentum_widget = true; + ActivationBehaviour activation_behaviour = ActivationBehaviour.VANILLA; + boolean disable_when_submerged = true; + } + + Hud hud = new Hud(); + static class Hud { + boolean show_horizon = false; + } + + Banking banking = new Banking(); + static class Banking { + boolean enable_banking = true; + double banking_strength = 20.0; + boolean simulate_control_surface_efficacy = false; + } + + Thrust thrust = new Thrust(); + static class Thrust { + boolean enable_thrust = false; + double max_thrust = 2.0; + double thrust_acceleration = 0.1; + boolean thrust_particles = true; + } + + Misc misc = new Misc(); + static class Misc { + boolean enable_easter_eggs = true; + } + } + + SensitivityConfig sensitivity = new SensitivityConfig(); + static class SensitivityConfig { + @MigrationValue + Smoothing smoothing = new Smoothing(); + static class Smoothing { + boolean smoothing_enabled = true; + double smoothing_pitch = 1.0; + double smoothing_yaw = 0.4; + double smoothing_roll = 1.0; + } + + Sensitivity camera_smoothing = new Sensitivity(1.0, 2.5, 1.0); + Sensitivity desktop = new Sensitivity(); + Sensitivity controller = new Sensitivity(); + } + + AdvancedConfig advanced = new AdvancedConfig(); + static class AdvancedConfig { + ExpressionParser banking_x_formula = new ExpressionParser("sin($roll * TO_RAD) * cos($pitch * TO_RAD) * 10 * $banking_strength"); + ExpressionParser banking_y_formula = new ExpressionParser("(-1 + cos($roll * TO_RAD)) * cos($pitch * TO_RAD) * 10 * $banking_strength"); + + ExpressionParser elevator_efficacy_formula = new ExpressionParser("$velocity_x * $look_x + $velocity_y * $look_y + $velocity_z * $look_z"); + ExpressionParser aileron_efficacy_formula = new ExpressionParser("$velocity_x * $look_x + $velocity_y * $look_y + $velocity_z * $look_z"); + ExpressionParser rudder_efficacy_formula = new ExpressionParser("$velocity_x * $look_x + $velocity_y * $look_y + $velocity_z * $look_z"); + } + + public boolean getModEnabled() { + return general.mod_enabled; + } + + public boolean getSwitchRollAndYaw() { + return general.controls.switch_roll_and_yaw; + } //= false; + + public boolean getMomentumBasedMouse() { + return general.controls.momentum_based_mouse; + } //= false; + + public double getMomentumMouseDeadzone() { + return general.controls.momentum_mouse_deadzone; + } + + public boolean getShowMomentumWidget() { + return general.controls.show_momentum_widget; + } //= true; + + public boolean getInvertPitch() { + return general.controls.invert_pitch; + } //= false; + + public ActivationBehaviour getActivationBehaviour() { + return general.controls.activation_behaviour; + } //= ActivationBehaviour.VANILLA; + + public boolean getDisableWhenSubmerged() { + return general.controls.disable_when_submerged; + } //= true; + + public boolean getShowHorizon() { + return general.hud.show_horizon; + } + + public boolean getEnableBanking() { + return general.banking.enable_banking; + }// = true; + + public double getBankingStrength() { + return general.banking.banking_strength; + }// = 20; + + public boolean getSimulateControlSurfaceEfficacy() { + return general.banking.simulate_control_surface_efficacy; + }// = false; + + public boolean getEnableThrust() { + if (general.thrust.enable_thrust) { + ClientPlayerEntity player; + if (DoABarrelRollClient.isConnectedToRealms() && + (player = MinecraftClient.getInstance().player) != null && player.hasPermissionLevel(2)) { + return true; + } + + return DoABarrelRollClient.HANDSHAKE_CLIENT.getConfig() + .map(LimitedModConfigServer::allowThrusting) + .orElse(false); + } + + return false; + } + + public boolean getEnableThrustClient() { + return general.thrust.enable_thrust; + } + + public double getMaxThrust() { + return general.thrust.max_thrust; + } + + public double getThrustAcceleration() { + return general.thrust.thrust_acceleration; + } + + public boolean getThrustParticles() { + return general.thrust.thrust_particles; + } + + public boolean getEnableEasterEggs() { + return general.misc.enable_easter_eggs; + } + + public double getSmoothingPitch() { + return sensitivity.camera_smoothing.pitch; + } + + public double getSmoothingYaw() { + return sensitivity.camera_smoothing.yaw; + } + + public double getSmoothingRoll() { + return sensitivity.camera_smoothing.roll; + } + + public Sensitivity getSmoothing() { + return sensitivity.camera_smoothing; + } + + public Sensitivity getDesktopSensitivity() { + return sensitivity.desktop; + } + + public double getDesktopPitch() { + return sensitivity.desktop.pitch; + } + + public double getDesktopYaw() { + return sensitivity.desktop.yaw; + } + + public double getDesktopRoll() { + return sensitivity.desktop.roll; + } + + public Sensitivity getControllerSensitivity() { + return sensitivity.controller; + } + + public double getControllerPitch() { + return sensitivity.controller.pitch; + } + + public double getControllerYaw() { + return sensitivity.controller.yaw; + } + + public double getControllerRoll() { + return sensitivity.controller.roll; + } + + public ExpressionParser getBankingXFormula() { + return advanced.banking_x_formula; + } + + public ExpressionParser getBankingYFormula() { + return advanced.banking_y_formula; + } + + public ExpressionParser getElevatorEfficacyFormula() { + return advanced.elevator_efficacy_formula; + } + + public ExpressionParser getAileronEfficacyFormula() { + return advanced.aileron_efficacy_formula; + } + + public ExpressionParser getRudderEfficacyFormula() { + return advanced.rudder_efficacy_formula; + } + + public void setModEnabled(boolean enabled) { + general.mod_enabled = enabled; + } + + public void setSwitchRollAndYaw(boolean enabled) { + general.controls.switch_roll_and_yaw = enabled; + } + + public void setMomentumBasedMouse(boolean enabled) { + general.controls.momentum_based_mouse = enabled; + } + + public void setMomentumMouseDeadzone(double deadzone) { + general.controls.momentum_mouse_deadzone = deadzone; + } + + public void setShowMomentumWidget(boolean enabled) { + general.controls.show_momentum_widget = enabled; + } + + public void setInvertPitch(boolean enabled) { + general.controls.invert_pitch = enabled; + } + + public void setActivationBehaviour(ActivationBehaviour behaviour) { + general.controls.activation_behaviour = behaviour; + } + + public void setDisableWhenSubmerged(boolean enabled) { + general.controls.disable_when_submerged = enabled; + } + + public void setShowHorizon(boolean enabled) { + general.hud.show_horizon = enabled; + } + + public void setEnableBanking(boolean enabled) { + general.banking.enable_banking = enabled; + } + + public void setBankingStrength(double strength) { + general.banking.banking_strength = strength; + } + + public void setSimulateControlSurfaceEfficacy(boolean enabled) { + general.banking.simulate_control_surface_efficacy = enabled; + } + + public void setEnableThrust(boolean enabled) { + general.thrust.enable_thrust = enabled; + } + + public void setMaxThrust(double thrust) { + general.thrust.max_thrust = thrust; + } + + public void setThrustAcceleration(double acceleration) { + general.thrust.thrust_acceleration = acceleration; + } + + public void setThrustParticles(boolean enabled) { + general.thrust.thrust_particles = enabled; + } + + public void setEnableEasterEggs(boolean enabled) { + general.misc.enable_easter_eggs = enabled; + } + + public void setSmoothingPitch(double pitch) { + sensitivity.camera_smoothing.pitch = pitch; + } + + public void setSmoothingYaw(double yaw) { + sensitivity.camera_smoothing.yaw = yaw; + } + + public void setSmoothingRoll(double roll) { + sensitivity.camera_smoothing.roll = roll; + } + + public void setDesktopSensitivity(Sensitivity sensitivity) { + this.sensitivity.desktop = sensitivity; + } + + public void setDesktopPitch(double pitch) { + sensitivity.desktop.pitch = pitch; + } + + public void setDesktopYaw(double yaw) { + sensitivity.desktop.yaw = yaw; + } + + public void setDesktopRoll(double roll) { + sensitivity.desktop.roll = roll; + } + + public void setControllerSensitivity(Sensitivity sensitivity) { + this.sensitivity.controller = sensitivity; + } + + public void setControllerPitch(double pitch) { + sensitivity.controller.pitch = pitch; + } + + public void setControllerYaw(double yaw) { + sensitivity.controller.yaw = yaw; + } + + public void setControllerRoll(double roll) { + sensitivity.controller.roll = roll; + } + + public void setBankingXFormula(ExpressionParser formula) { + advanced.banking_x_formula = formula; + } + + public void setBankingYFormula(ExpressionParser formula) { + advanced.banking_y_formula = formula; + } + + public void setElevatorEfficacyFormula(ExpressionParser formula) { + advanced.elevator_efficacy_formula = formula; + } + + public void setAileronEfficacyFormula(ExpressionParser formula) { + advanced.aileron_efficacy_formula = formula; + } + + public void setRudderEfficacyFormula(ExpressionParser formula) { + advanced.rudder_efficacy_formula = formula; + } + + public void save() { + saveConfigFile(CONFIG_FILE.toFile()); + } + + public RotationInstant configureRotation(RotationInstant rotationInstant, RollContext context) { + var pitch = rotationInstant.pitch(); + var yaw = rotationInstant.yaw(); + var roll = rotationInstant.roll(); + + if (!getSwitchRollAndYaw()) { + var temp = yaw; + yaw = roll; + roll = temp; + } + if (getInvertPitch()) { + pitch = -pitch; + } + + return RotationInstant.of(pitch, yaw, roll); + } + + public void notifyPlayerOfServerConfig(LimitedModConfigServer serverConfig) { + if (!serverConfig.allowThrusting() && general.thrust.enable_thrust) { + ToastUtil.toasty("thrusting_disabled_by_server"); + } + if (serverConfig.forceEnabled() && !general.mod_enabled) { + ToastUtil.toasty("mod_forced_enabled_by_server"); + } + } + + /** + * Loads config file. + * + * @param file file to load the config file from. + * @return ConfigManager object + */ + private static ModConfig loadConfigFile(File file) { + ModConfig config = null; + + if (file.exists()) { + // An existing config is present, we should use its values + try (BufferedReader fileReader = new BufferedReader( + new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8) + )) { + // Parses the config file and puts the values into config object + config = GSON.fromJson(fileReader, ModConfig.class); + } catch (IOException e) { + throw new RuntimeException("Problem occurred when trying to load config: ", e); + } + } + // gson.fromJson() can return null if file is empty + if (config == null) { + config = new ModConfig(); + } + + // Saves the file in order to write new fields if they were added + config.performMigrations(); + config.saveConfigFile(file); + return config; + } + + /** + * Saves the config to the given file. + * + * @param file file to save config to + */ + private void saveConfigFile(File file) { + try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { + GSON.toJson(this, writer); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/config/ModConfigScreen.java b/common/src/client/java/nl/enjarai/doabarrelroll/config/ModConfigScreen.java new file mode 100644 index 00000000..fc5e5a2e --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/config/ModConfigScreen.java @@ -0,0 +1,38 @@ +package nl.enjarai.doabarrelroll.config; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ConfirmScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; +import net.minecraft.util.Util; +import nl.enjarai.doabarrelroll.compat.Compat; +import nl.enjarai.doabarrelroll.compat.yacl.YACLImplementation; + +import java.net.URI; + +public class ModConfigScreen { + public static Screen create(Screen parent) { + if (!Compat.isYACLLoaded()) { + return new ConfirmScreen((result) -> { + if (result) { + Util.getOperatingSystem().open(URI.create("https://modrinth.com/mod/yacl/versions")); + } + MinecraftClient.getInstance().setScreen(parent); + }, getText("missing"), getText("missing.message"), ScreenTexts.YES, ScreenTexts.NO); + } else if (!Compat.isYACLUpToDate()) { + return new ConfirmScreen((result) -> { + if (result) { + Util.getOperatingSystem().open(URI.create("https://modrinth.com/mod/yacl/versions")); + } + MinecraftClient.getInstance().setScreen(parent); + }, getText("outdated"), getText("outdated.message"), ScreenTexts.YES, ScreenTexts.NO); + } else { + return YACLImplementation.generateConfigScreen(parent); + } + } + + private static Text getText(String key) { + return Text.translatable("config.do_a_barrel_roll.yacl." + key); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/config/serialization/ExpressionParserTypeAdapter.java b/common/src/client/java/nl/enjarai/doabarrelroll/config/serialization/ExpressionParserTypeAdapter.java new file mode 100644 index 00000000..127fd45c --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/config/serialization/ExpressionParserTypeAdapter.java @@ -0,0 +1,18 @@ +package nl.enjarai.doabarrelroll.config.serialization; + +import com.google.gson.*; +import nl.enjarai.doabarrelroll.math.ExpressionParser; + +import java.lang.reflect.Type; + +public class ExpressionParserTypeAdapter implements JsonSerializer, JsonDeserializer { + @Override + public ExpressionParser deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + return new ExpressionParser(jsonElement.getAsString()); + } + + @Override + public JsonElement serialize(ExpressionParser expressionParser, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(expressionParser.getString()); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/fabric/DoABarrelRollFabricClient.java b/common/src/client/java/nl/enjarai/doabarrelroll/fabric/DoABarrelRollFabricClient.java new file mode 100644 index 00000000..156cfee3 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/fabric/DoABarrelRollFabricClient.java @@ -0,0 +1,37 @@ +package nl.enjarai.doabarrelroll.fabric; + +import com.bawnorton.mixinsquared.api.MixinCanceller; +import com.bawnorton.mixinsquared.tools.MixinAnnotationReader; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.DoABarrelRollClient; +import nl.enjarai.doabarrelroll.ModKeybindings; +import nl.enjarai.doabarrelroll.config.ModConfig; +import nl.enjarai.doabarrelroll.fabric.net.HandshakeClientFabric; +import nl.enjarai.doabarrelroll.util.StarFoxUtil; + +import java.util.List; + +public class DoABarrelRollFabricClient implements ClientModInitializer { + @Override + public void onInitializeClient() { + DoABarrelRollClient.init(); + + ModConfig.touch(); + HandshakeClientFabric.init(); + + // Register keybindings on fabric + ModKeybindings.FABRIC.forEach(KeyBindingHelper::registerKeyBinding); + + // Init barrel rollery. + StarFoxUtil.register(); + + ClientTickEvents.END_CLIENT_TICK.register(client -> { + ModKeybindings.clientTick(client); + + StarFoxUtil.clientTick(client); + }); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/fabric/net/HandshakeClientFabric.java b/common/src/client/java/nl/enjarai/doabarrelroll/fabric/net/HandshakeClientFabric.java new file mode 100644 index 00000000..d0122145 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/fabric/net/HandshakeClientFabric.java @@ -0,0 +1,28 @@ +package nl.enjarai.doabarrelroll.fabric.net; + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.DoABarrelRollClient; +import nl.enjarai.doabarrelroll.net.RollSyncClient; +import nl.enjarai.doabarrelroll.net.ServerConfigUpdateClient; + +public class HandshakeClientFabric { + public static void init() { + ClientPlayConnectionEvents.INIT.register((handler, client) -> { + ClientPlayNetworking.registerReceiver(DoABarrelRoll.HANDSHAKE_CHANNEL, (client1, handler1, buf, responseSender) -> { + var returnBuf = DoABarrelRollClient.HANDSHAKE_CLIENT.handleConfigSync(buf); + responseSender.sendPacket(DoABarrelRoll.HANDSHAKE_CHANNEL, returnBuf); + + if (DoABarrelRollClient.HANDSHAKE_CLIENT.hasConnected()) { + RollSyncClient.startListening(); + ServerConfigUpdateClientFabric.startListening(); + } + }); + }); + + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { + DoABarrelRollClient.HANDSHAKE_CLIENT.reset(); + }); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/fabric/net/ServerConfigUpdateClientFabric.java b/common/src/client/java/nl/enjarai/doabarrelroll/fabric/net/ServerConfigUpdateClientFabric.java new file mode 100644 index 00000000..e37131af --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/fabric/net/ServerConfigUpdateClientFabric.java @@ -0,0 +1,13 @@ +package nl.enjarai.doabarrelroll.fabric.net; + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.net.ServerConfigUpdateClient; + +public class ServerConfigUpdateClientFabric { + public static void startListening() { + ClientPlayNetworking.registerReceiver(DoABarrelRoll.SERVER_CONFIG_UPDATE_CHANNEL, (client, handler, buf, responseSender) -> { + ServerConfigUpdateClient.updateAcknowledged(buf); + }); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/flight/RotationModifiers.java b/common/src/client/java/nl/enjarai/doabarrelroll/flight/RotationModifiers.java new file mode 100644 index 00000000..679e1f0a --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/flight/RotationModifiers.java @@ -0,0 +1,141 @@ +package nl.enjarai.doabarrelroll.flight; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.util.SmoothUtil; +import net.minecraft.util.math.MathHelper; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.DoABarrelRollClient; +import nl.enjarai.doabarrelroll.ModKeybindings; +import nl.enjarai.doabarrelroll.api.event.RollContext; +import nl.enjarai.doabarrelroll.api.rotation.RotationInstant; +import nl.enjarai.doabarrelroll.config.ModConfig; +import nl.enjarai.doabarrelroll.config.Sensitivity; +import nl.enjarai.doabarrelroll.math.MagicNumbers; + +import java.util.HashMap; +import java.util.Map; + +public class RotationModifiers { + public static RollContext.ConfiguresRotation buttonControls(double power) { + return (rotationInstant, context) -> { + var delta = power * context.getRenderDelta(); + var pitch = 0; + var yaw = 0; + var roll = 0; + + if (ModKeybindings.PITCH_UP.isPressed()) { + pitch -= delta; + } + if (ModKeybindings.PITCH_DOWN.isPressed()) { + pitch += delta; + } + if (ModKeybindings.YAW_LEFT.isPressed()) { + yaw -= delta; + } + if (ModKeybindings.YAW_RIGHT.isPressed()) { + yaw += delta; + } + if (ModKeybindings.ROLL_LEFT.isPressed()) { + roll -= delta; + } + if (ModKeybindings.ROLL_RIGHT.isPressed()) { + roll += delta; + } + + // Putting this in the roll value, since it'll be swapped later + return rotationInstant.add(pitch, yaw, roll); + }; + } + + public static RollContext.ConfiguresRotation smoothing(SmoothUtil pitchSmoother, SmoothUtil yawSmoother, SmoothUtil rollSmoother, Sensitivity smoothness) { + return (rotationInstant, context) -> RotationInstant.of( + smoothness.pitch == 0 ? rotationInstant.pitch() : pitchSmoother.smooth(rotationInstant.pitch(), 1 / smoothness.pitch * context.getRenderDelta()), + smoothness.yaw == 0 ? rotationInstant.yaw() : yawSmoother.smooth(rotationInstant.yaw(), 1 / smoothness.yaw * context.getRenderDelta()), + smoothness.roll == 0 ? rotationInstant.roll() : rollSmoother.smooth(rotationInstant.roll(), 1 / smoothness.roll * context.getRenderDelta()) + ); + } + + public static RotationInstant banking(RotationInstant rotationInstant, RollContext context) { + var delta = context.getRenderDelta(); + var currentRotation = context.getCurrentRotation(); + var currentRoll = currentRotation.roll() * MagicNumbers.TORAD; + + var xExpression = ModConfig.INSTANCE.getBankingXFormula().getCompiledOrDefaulting(0); + var yExpression = ModConfig.INSTANCE.getBankingYFormula().getCompiledOrDefaulting(0); + + var vars = getVars(context); + vars.put("banking_strength", ModConfig.INSTANCE.getBankingStrength()); + + var dX = xExpression.eval(vars); + var dY = yExpression.eval(vars); + + // check if we accidentally got NaN, for some reason this happens sometimes + if (Double.isNaN(dX)) dX = 0; + if (Double.isNaN(dY)) dY = 0; + + return rotationInstant.addAbsolute(dX * delta, dY * delta, currentRoll); + } + + public static RotationInstant manageThrottle(RotationInstant rotationInstant, RollContext context) { + var delta = context.getRenderDelta(); + + if (ModKeybindings.THRUST_FORWARD.isPressed()) { + DoABarrelRollClient.throttle += 0.1 * delta; + } else if (ModKeybindings.THRUST_BACKWARD.isPressed()) { + DoABarrelRollClient.throttle -= 0.1 * delta; + } else { + DoABarrelRollClient.throttle -= DoABarrelRollClient.throttle * 0.95 * delta; + } + + DoABarrelRollClient.throttle = MathHelper.clamp(DoABarrelRollClient.throttle, 0, ModConfig.INSTANCE.getMaxThrust()); + + return rotationInstant; + } + + public static RollContext.ConfiguresRotation fixNaN(String name) { + return (rotationInstant, context) -> { + if (Double.isNaN(rotationInstant.pitch())) { + rotationInstant = RotationInstant.of(0, rotationInstant.yaw(), rotationInstant.roll()); + DoABarrelRoll.LOGGER.warn("NaN found in pitch for " + name + ", setting to 0 as fallback"); + } + if (Double.isNaN(rotationInstant.yaw())) { + rotationInstant = RotationInstant.of(rotationInstant.pitch(), 0, rotationInstant.roll()); + DoABarrelRoll.LOGGER.warn("NaN found in yaw for " + name + ", setting to 0 as fallback"); + } + if (Double.isNaN(rotationInstant.roll())) { + rotationInstant = RotationInstant.of(rotationInstant.pitch(), rotationInstant.yaw(), 0); + DoABarrelRoll.LOGGER.warn("NaN found in roll for " + name + ", setting to 0 as fallback"); + } + return rotationInstant; + }; + } + + public static RotationInstant applyControlSurfaceEfficacy(RotationInstant rotationInstant, RollContext context) { + var elevatorExpression = ModConfig.INSTANCE.getElevatorEfficacyFormula().getCompiledOrDefaulting(1); + var aileronExpression = ModConfig.INSTANCE.getAileronEfficacyFormula().getCompiledOrDefaulting(1); + var rudderExpression = ModConfig.INSTANCE.getRudderEfficacyFormula().getCompiledOrDefaulting(1); + + var vars = getVars(context); + return rotationInstant.multiply(elevatorExpression.eval(vars), rudderExpression.eval(vars), aileronExpression.eval(vars)); + } + + private static Map getVars(RollContext context) { + var player = MinecraftClient.getInstance().player; + assert player != null; + + var currentRotation = context.getCurrentRotation(); + var rotationVector = player.getRotationVector(); + return new HashMap<>() {{ + put("pitch", currentRotation.pitch()); + put("yaw", currentRotation.yaw()); + put("roll", currentRotation.roll()); + put("velocity_length", player.getVelocity().length()); + put("velocity_x", player.getVelocity().getX()); + put("velocity_y", player.getVelocity().getY()); + put("velocity_z", player.getVelocity().getZ()); + put("look_x", rotationVector.getX()); + put("look_y", rotationVector.getY()); + put("look_z", rotationVector.getZ()); + }}; + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/impl/key/InputContextImpl.java b/common/src/client/java/nl/enjarai/doabarrelroll/impl/key/InputContextImpl.java new file mode 100644 index 00000000..83ef779d --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/impl/key/InputContextImpl.java @@ -0,0 +1,89 @@ +package nl.enjarai.doabarrelroll.impl.key; + +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.util.Identifier; +import nl.enjarai.doabarrelroll.api.key.InputContext; +import nl.enjarai.doabarrelroll.util.key.ContextualKeyBinding; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +public final class InputContextImpl implements InputContext { + private static final List CONTEXTS = new ReferenceArrayList<>(); + + public static List getContexts() { + return CONTEXTS; + } + + public static boolean contextsContain(KeyBinding binding) { + for (var context : InputContextImpl.getContexts()) { + if (context.getKeyBindings().contains(binding)) { + return true; + } + } + + return false; + } + + private final Identifier id; + private final Supplier activeCondition; + private final List keyBindings = new ReferenceArrayList<>(); + private final Map bindingsByKey = new HashMap<>(); + private boolean active; + + public InputContextImpl(Identifier id, Supplier activeCondition) { + this.id = id; + this.activeCondition = activeCondition; + CONTEXTS.add(this); + ClientTickEvents.START_CLIENT_TICK.register(client -> tick()); + } + + public void tick() { + boolean active = activeCondition.get(); + if (active != this.active) { + this.active = active; + KeyBinding.updatePressedStates(); + } + } + + @Override + public Identifier getId() { + return id; + } + + @Override + public boolean isActive() { + return active; + } + + @Override + public void addKeyBinding(KeyBinding keyBinding) { + Objects.requireNonNull(keyBinding); + keyBindings.add(keyBinding); + ((ContextualKeyBinding) keyBinding).doABarrelRoll$addToContext(this); + } + + @Override + public List getKeyBindings() { + return keyBindings; + } + + @Override + public KeyBinding getKeyBinding(InputUtil.Key key) { + return bindingsByKey.get(key); + } + + @Override + public void updateKeysByCode() { + bindingsByKey.clear(); + for (KeyBinding keyBinding : keyBindings) { + bindingsByKey.put(keyBinding.boundKey, keyBinding); + } + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/math/SyntaxHighlighter.java b/common/src/client/java/nl/enjarai/doabarrelroll/math/SyntaxHighlighter.java new file mode 100644 index 00000000..1519e5c8 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/math/SyntaxHighlighter.java @@ -0,0 +1,220 @@ +package nl.enjarai.doabarrelroll.math; + +import net.minecraft.text.MutableText; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import nl.enjarai.doabarrelroll.DoABarrelRoll; + +public class SyntaxHighlighter { + private static final boolean debugLog = false; + + public static Text highlightText(String text) { + MutableText formattedText = Text.literal(""); + SyntaxHighlightContext context = new SyntaxHighlightContext(text); + + if (debugLog) DoABarrelRoll.LOGGER.info("Begun syntax highlighting"); + + while (context.getCurrent() != (char)0) { + if (context.getCurrent() == '$') { //variables + formattedText.append(String.valueOf(context.getCurrent())); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Begun coloring variable"); + + while (isLetter(context.getCurrent()) || context.getCurrent() == '_') { + formattedText.append(formatText(context.getCurrent(), SyntaxType.Variable)); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Coloring variable"); + } + } else if (context.getCurrent() == '-' || context.getCurrent() == '+') { //unary operators + if (Character.isDigit(context.peek()) && context.lastIsNotValue()) { + formattedText.append(formatText(context.getCurrent(), SyntaxType.Number)); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Coloring number"); + } else if (isLetter(context.peek()) && context.lastIsNotValue()) { + formattedText.append(formatText(context.getCurrent(), SyntaxType.Function)); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Coloring function"); + } else { + formattedText.append(formatText(context.getCurrent(), SyntaxType.Operator)); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Coloring operator"); + } + } else if (Character.isDigit(context.getCurrent()) || context.getCurrent() == '.') { //numbers + formattedText.append(formatText(context.getCurrent(), SyntaxType.Number)); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Coloring number"); + } else if (isLetter(context.getCurrent())) { //functions and constants + StringBuilder builder = new StringBuilder(); + + while (isLetter(context.getCurrent()) || context.getCurrent() == '_') { + builder.append(context.getCurrent()); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Reading possible function or constant"); + } + + String builtResult = builder.toString(); + + if (isKeyword(builtResult) && context.getCurrent() == '(') { + formattedText.append(formatText(builtResult, SyntaxType.Function)); + if (debugLog) DoABarrelRoll.LOGGER.info("Coloring function"); + } else if (isConstant(builtResult)) { + formattedText.append(formatText(builtResult, SyntaxType.Constant)); + if (debugLog) DoABarrelRoll.LOGGER.info("Coloring constant"); + } else { + formattedText.append(formatText(builtResult, SyntaxType.Error)); + if (debugLog) DoABarrelRoll.LOGGER.info("Coloring error"); + } + } else if (isOperator(context.getCurrent())) { //typical operators + formattedText.append(formatText(context.getCurrent(), SyntaxType.Operator)); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Coloring operator"); + } else if (isScope(context.getCurrent())) { //parentheses + formattedText.append(formatText(context.getCurrent(), SyntaxType.Scope)); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Skipping parentheses"); + } else if (Character.isWhitespace(context.getCurrent())) { //whitespace + formattedText.append(String.valueOf(context.getCurrent())); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Skipping whitespace"); + } else { //errors + formattedText.append(formatText(context.getCurrent(), SyntaxType.Error)); + context.position++; + if (debugLog) DoABarrelRoll.LOGGER.info("Coloring errors"); + } + } + + if (debugLog) DoABarrelRoll.LOGGER.info("Finished syntax coloring"); + return formattedText; + } + + public static boolean isConstant(String str) { + switch (str) { + case "PI", "E", "TO_RAD", "TO_DEG" -> { + return true; + } + } + + return false; + } + + public static boolean isKeyword(String str) { + switch (str) { + case "sqrt", "sin", "cos", + "tan", "asin", "acos", + "atan", "abs", "exp", + "ceil", "floor", "log", + "round", "randint", "min", + "max" -> { + return true; + } + } + + return false; + } + + public static boolean isLetter(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + + public static boolean isOperator(char c) { + return c == '+' || c == '-' || c == '*' || c == '/' || c == '^'; + } + + public static boolean isScope(char c) { + return c == ',' || c == '(' || c == ')'; + } + + public static MutableText formatText(char ch, SyntaxType type) { + String str = String.valueOf(ch); + return formatText(str, type); + } + + public static MutableText formatText(String str, SyntaxType type) { + switch (type) { + case Variable -> { + return Text.literal(str).formatted(Formatting.GREEN); + } + + case Operator -> { + return Text.literal(str).formatted(Formatting.LIGHT_PURPLE); + } + + case Error -> { + return Text.literal(str).formatted(Formatting.RED); + } + + case Number -> { + return Text.literal(str).formatted(Formatting.AQUA); + } + + case Function -> { + return Text.literal(str).formatted(Formatting.YELLOW); + } + + case Constant -> { + return Text.literal(str).setStyle(Style.EMPTY.withColor(0xFFA500)); + } + + case Scope -> { + return Text.literal(str); + } + } + + return null; + } +} + +class SyntaxHighlightContext { + public int position = 0; + public String rawText; + + public SyntaxHighlightContext(String raw) { + this.rawText = raw; + } + + public String peek(int amount) { + if (position + amount >= rawText.length()) return null; + return rawText.substring(position, position + amount); + } + + public char peek() { + return getByIndex(position + 1); + } + + public char getCurrent() { + return getByIndex(position); + } + + public char getByIndex(int i) { + if (i >= rawText.length()) return (char)0; + return rawText.charAt(i); + } + + public boolean lastIsNotValue() { + int tempPos = position; + + while (tempPos > 0) { + tempPos--; + + if (SyntaxHighlighter.isOperator(getByIndex(tempPos)) + || SyntaxHighlighter.isScope(getByIndex(tempPos))) { + return true; + } else if (!Character.isWhitespace(getByIndex(tempPos))) { + break; + } + } + + return false; + } +} + +enum SyntaxType { + Variable, + Operator, + Error, + Scope, + Function, + Number, + Constant +} \ No newline at end of file diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/ClientPlayerEntityMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/ClientPlayerEntityMixin.java new file mode 100644 index 00000000..c42d4d98 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/ClientPlayerEntityMixin.java @@ -0,0 +1,35 @@ +package nl.enjarai.doabarrelroll.mixin.client; + +import com.mojang.authlib.GameProfile; +import net.minecraft.client.input.Input; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import nl.enjarai.doabarrelroll.util.MixinHooks; +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.CallbackInfo; + +@Mixin(ClientPlayerEntity.class) +public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity { + @Shadow public Input input; + + public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile) { + super(world, profile); + } + + @Inject( + method = "tickMovement", + at = @At("RETURN") + ) + public void doABarrelRoll$resetJump(CallbackInfo ci) { + if (isOnGround()) { + MixinHooks.secondJump = false; + MixinHooks.thirdJump = false; + } + + MixinHooks.wasJumping = input.jumping; + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/InGameHudMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/InGameHudMixin.java new file mode 100644 index 00000000..2663dec8 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/InGameHudMixin.java @@ -0,0 +1,55 @@ +package nl.enjarai.doabarrelroll.mixin.client; + +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.hud.InGameHud; +import nl.enjarai.doabarrelroll.DoABarrelRollClient; +import nl.enjarai.doabarrelroll.util.StarFoxUtil; +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.CallbackInfo; + +@Mixin(InGameHud.class) +public abstract class InGameHudMixin { + @Shadow private int scaledWidth; + @Shadow private int scaledHeight; + + @Inject( + method = "render", + at = @At("HEAD") + ) + private void doABarrelRoll$captureTickDelta(DrawContext context, float tickDelta, CallbackInfo ci, @Share("tickDelta") LocalFloatRef tickDeltaRef) { + tickDeltaRef.set(tickDelta); + } + + @Inject( + method = "renderCrosshair", + at = @At(value = "HEAD") + ) + private void doABarrelRoll$renderCrosshairHead(DrawContext context, CallbackInfo ci, @Share("tickDelta") LocalFloatRef tickDeltaRef) { + context.getMatrices().push(); + DoABarrelRollClient.onRenderCrosshair(context, tickDeltaRef.get(), scaledWidth, scaledHeight); + } + + @Inject( + method = "renderCrosshair", + at = @At(value = "RETURN") + ) + private void doABarrelRoll$renderCrosshairReturn(DrawContext context, CallbackInfo ci) { + context.getMatrices().pop(); + } + + @Inject( + method = "render", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/util/math/MathHelper;lerp(FFF)F" + ) + ) + private void doABarrelRoll$renderPeppy(DrawContext context, float tickDelta, CallbackInfo ci) { + StarFoxUtil.renderPeppy(context, tickDelta, scaledWidth, scaledHeight); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/LivingEntityMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/LivingEntityMixin.java new file mode 100644 index 00000000..ce81a6b4 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/LivingEntityMixin.java @@ -0,0 +1,67 @@ +package nl.enjarai.doabarrelroll.mixin.client; + +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import nl.enjarai.doabarrelroll.DoABarrelRollClient; +import nl.enjarai.doabarrelroll.ModKeybindings; +import nl.enjarai.doabarrelroll.api.event.ThrustEvents; +import nl.enjarai.doabarrelroll.config.ModConfig; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +// High priority to ensure compat with mods like Elytra Aeronautics. +@Mixin(value = LivingEntity.class, priority = 1200) +public abstract class LivingEntityMixin extends Entity { + + public LivingEntityMixin(EntityType type, World world) { + super(type, world); + } + + + @SuppressWarnings("ConstantConditions") + @ModifyArg( + method = "travel", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/entity/LivingEntity;setVelocity(Lnet/minecraft/util/math/Vec3d;)V", + ordinal = 6 + ) + ) + private Vec3d doABarrelRoll$wrapElytraVelocity(Vec3d original) { + if (!(((LivingEntity) (Object) this) instanceof ClientPlayerEntity) || !ModConfig.INSTANCE.getEnableThrust()) return original; + + Vec3d rotation = getRotationVector(); + Vec3d velocity = getVelocity(); + + if (ModConfig.INSTANCE.getThrustParticles()) { + int particleDensity = (int) MathHelper.clamp(DoABarrelRollClient.throttle * 10, 0, 10); + if (DoABarrelRollClient.throttle > 0.1 && getWorld().getTime() % (11 - particleDensity) == 0) { + var pPos = getPos().add(velocity.multiply(0.5).negate()); + getWorld().addParticle( + ParticleTypes.CAMPFIRE_SIGNAL_SMOKE, + pPos.getX(), pPos.getY(), pPos.getZ(), + 0, 0, 0 + ); + } + } + + double throttleSign = ModKeybindings.THRUST_FORWARD.isPressed() ? 1 : ModKeybindings.THRUST_BACKWARD.isPressed() ? -1 : 0; + throttleSign = ThrustEvents.modifyThrustInput(throttleSign); + double maxSpeed = ModConfig.INSTANCE.getMaxThrust(); + double speedIncrease = Math.max(maxSpeed - velocity.length(), 0) / maxSpeed * throttleSign; + double acceleration = ModConfig.INSTANCE.getThrustAcceleration() * speedIncrease; + + return original.add( + rotation.x * acceleration, + rotation.y * acceleration, + rotation.z * acceleration + ); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/PlayerEntityMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/PlayerEntityMixin.java new file mode 100644 index 00000000..3ecccea2 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/PlayerEntityMixin.java @@ -0,0 +1,62 @@ +package nl.enjarai.doabarrelroll.mixin.client; + +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.world.World; +import nl.enjarai.doabarrelroll.config.ActivationBehaviour; +import nl.enjarai.doabarrelroll.config.ModConfig; +import nl.enjarai.doabarrelroll.util.MixinHooks; +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(PlayerEntity.class) +public abstract class PlayerEntityMixin extends LivingEntity { + protected PlayerEntityMixin(EntityType entityType, World world) { + super(entityType, world); + } + + @SuppressWarnings("ConstantConditions") + @Inject( + method = "checkFallFlying()Z", + at = @At("HEAD"), + cancellable = true + ) + private void doABarrelRoll$interceptFallFlyingStart(CallbackInfoReturnable cir) { + // We do the same checks the original method does, but leave out the one about already fallFlying. + // This is needed for the hybrid mode. + if (this.isOnGround() || this.isTouchingWater() || this.hasStatusEffect(StatusEffects.LEVITATION)) { + return; + } + + var behaviour = ModConfig.INSTANCE.getActivationBehaviour(); + + if ((((PlayerEntity) (Object) this) instanceof ClientPlayerEntity) + && (behaviour == ActivationBehaviour.TRIPLE_JUMP + || behaviour == ActivationBehaviour.HYBRID + || behaviour == ActivationBehaviour.HYBRID_TOGGLE)) { + + var shouldCancel = behaviour == ActivationBehaviour.TRIPLE_JUMP; + + // This code is only reached if the player is currently jumping, + // so by checking if they were jumping last tick, we know that this is the start of a jump. + if (!MixinHooks.wasJumping) { + MixinHooks.wasJumping = true; + if (!MixinHooks.secondJump) { + MixinHooks.secondJump = true; + if (shouldCancel) cir.setReturnValue(false); + } else { + // Set thirdJump to true if we're in HYBRID mode, but toggle it in HYBRID_TOGGLE mode. + MixinHooks.thirdJump = behaviour != ActivationBehaviour.HYBRID_TOGGLE || !MixinHooks.thirdJump; + } + // Reaching this point is the only way for the function to progress, activating the Elytra. + } else { + if (shouldCancel) cir.setReturnValue(false); + } + } + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/key/KeyBindingEntryMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/key/KeyBindingEntryMixin.java new file mode 100644 index 00000000..61ba3330 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/key/KeyBindingEntryMixin.java @@ -0,0 +1,40 @@ +package nl.enjarai.doabarrelroll.mixin.client.key; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.client.gui.screen.option.ControlsListWidget; +import net.minecraft.client.option.KeyBinding; +import nl.enjarai.doabarrelroll.util.key.ContextualKeyBinding; +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; + +@Mixin(ControlsListWidget.KeyBindingEntry.class) +public abstract class KeyBindingEntryMixin { + @Shadow @Final private KeyBinding binding; + + @ModifyExpressionValue( + method = "update", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/option/KeyBinding;equals(Lnet/minecraft/client/option/KeyBinding;)Z" + ) + ) + private boolean doABarrelRoll$ignoreCertainKeyBindingConflicts(boolean original, @Local KeyBinding otherBinding) { + var firstContexts = ((ContextualKeyBinding) binding).doABarrelRoll$getContexts(); + var secondContexts = ((ContextualKeyBinding) otherBinding).doABarrelRoll$getContexts(); + + // none + none -> original + // none + has -> false + // has + none -> false + // has + has -> + // same context -> original + // different context -> false + + if (firstContexts.isEmpty() && secondContexts.isEmpty()) return original; + if (firstContexts.isEmpty() || secondContexts.isEmpty()) return false; + if (firstContexts.stream().anyMatch(secondContexts::contains)) return original; + return false; + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/key/KeyBindingMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/key/KeyBindingMixin.java new file mode 100644 index 00000000..c48a20ea --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/key/KeyBindingMixin.java @@ -0,0 +1,109 @@ +package nl.enjarai.doabarrelroll.mixin.client.key; + +import com.llamalad7.mixinextras.injector.WrapWithCondition; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import nl.enjarai.doabarrelroll.api.key.InputContext; +import nl.enjarai.doabarrelroll.impl.key.InputContextImpl; +import nl.enjarai.doabarrelroll.util.key.ContextualKeyBinding; +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; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Mixin(KeyBinding.class) +public abstract class KeyBindingMixin implements ContextualKeyBinding { + @Unique + private final ArrayList contexts = new ArrayList<>(); + + @Override + public List doABarrelRoll$getContexts() { + return contexts; + } + + @Override + public void doABarrelRoll$addToContext(InputContext context) { + contexts.add(context); + } + + private static KeyBinding getContextKeyBinding(InputUtil.Key key) { + for (var context : InputContextImpl.getContexts()) { + var binding = context.getKeyBinding(key); + if (binding != null) { + if (context.isActive()) { + return binding; + } else { + binding.setPressed(false); + } + } + } + + return null; + } + + @WrapOperation( + method = "onKeyPressed", + at = @At( + value = "INVOKE", + target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;" + ), + require = 0 // We let all these mixins fail if they need to as a temporary workaround to be compatible with Connector. + ) + private static Object doABarrelRoll$applyKeybindContext(Map map, Object key, Operation original) { + var binding = getContextKeyBinding((InputUtil.Key) key); + if (binding != null) return binding; + + return original.call(map, key); + } + + @WrapOperation( + method = "setKeyPressed", + at = @At( + value = "INVOKE", + target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;" + ), + require = 0 + ) + private static Object doABarrelRoll$applyKeybindContext2(Map map, Object key, Operation original) { + var binding = getContextKeyBinding((InputUtil.Key) key); + var originalBinding = original.call(map, key); + if (binding != null) { + if (originalBinding != null) { + originalBinding.setPressed(false); + } + return binding; + } + + return originalBinding; + } + + @Inject( + method = "updateKeysByCode", + at = @At("HEAD"), + require = 0 + ) + private static void doABarrelRoll$updateContextualKeys(CallbackInfo ci) { + for (var context : InputContextImpl.getContexts()) { + context.updateKeysByCode(); + } + } + + @WrapWithCondition( + method = "updateKeysByCode", + at = @At( + value = "INVOKE", + target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" + ), + require = 0 + ) + private static boolean doABarrelRoll$skipAddingContextualKeys(Map map, Object key, Object keyBinding) { + return !InputContextImpl.contextsContain((KeyBinding) keyBinding); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/CameraMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/CameraMixin.java new file mode 100644 index 00000000..76b66b33 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/CameraMixin.java @@ -0,0 +1,135 @@ +package nl.enjarai.doabarrelroll.mixin.client.roll; + +import com.llamalad7.mixinextras.injector.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef; +import net.minecraft.client.render.Camera; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.BlockView; +import nl.enjarai.doabarrelroll.api.RollCamera; +import nl.enjarai.doabarrelroll.api.RollEntity; +import nl.enjarai.doabarrelroll.math.MagicNumbers; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Camera.class) +public abstract class CameraMixin implements RollCamera { + @Shadow private Entity focusedEntity; + + @Unique + private boolean isRolling; + @Unique + private float lastRollBack; + @Unique + private float rollBack; + @Unique + private float roll; + @Unique + private final ThreadLocal tempRoll = new ThreadLocal<>(); + + @Inject( + method = "updateEyeHeight", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/client/render/Camera;cameraY:F", + ordinal = 0 + ) + ) + private void doABarrelRoll$interpolateRollnt(CallbackInfo ci) { + if (!((RollEntity) focusedEntity).doABarrelRoll$isRolling()) { + lastRollBack = rollBack; + rollBack -= rollBack * 0.5f; + } + } + + @Inject( + method = "update", + at = @At("HEAD") + ) + private void doABarrelRoll$captureTickDeltaAndUpdate(BlockView area, Entity focusedEntity, boolean thirdPerson, boolean inverseView, float tickDelta, CallbackInfo ci, @Share("tickDelta") LocalFloatRef tickDeltaRef) { + tickDeltaRef.set(tickDelta); + isRolling = ((RollEntity) focusedEntity).doABarrelRoll$isRolling(); + } + + @Inject( + method = "update", + at = @At("TAIL") + ) + private void doABarrelRoll$updateRollBack(BlockView area, Entity focusedEntity, boolean thirdPerson, boolean inverseView, float tickDelta, CallbackInfo ci) { + if (isRolling) { + rollBack = roll; + lastRollBack = roll; + } + } + + @WrapWithCondition( + method = "update", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/render/Camera;setRotation(FF)V", + ordinal = 0 + ) + ) + private boolean doABarrelRoll$addRoll1(Camera thiz, float yaw, float pitch, @Share("tickDelta") LocalFloatRef tickDelta) { + if (isRolling) { + tempRoll.set(((RollEntity) focusedEntity).doABarrelRoll$getRoll(tickDelta.get())); + } else { + tempRoll.set(MathHelper.lerp(tickDelta.get(), lastRollBack, rollBack)); + } + return true; + } + + @WrapWithCondition( + method = "update", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/render/Camera;setRotation(FF)V", + ordinal = 1 + ) + ) + private boolean doABarrelRoll$addRoll2(Camera thiz, float yaw, float pitch) { + tempRoll.set(-this.roll); + return true; + } + + @WrapWithCondition( + method = "update", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/render/Camera;setRotation(FF)V", + ordinal = 2 + ) + ) + private boolean doABarrelRoll$addRoll3(Camera thiz, float yaw, float pitch) { + tempRoll.set(0.0f); + return true; + } + + @ModifyArg( + method = "setRotation", + at = @At( + value = "INVOKE", + target = "Lorg/joml/Quaternionf;rotationYXZ(FFF)Lorg/joml/Quaternionf;" + ), + index = 2 + ) + private float doABarrelRoll$setRoll(float original) { + var roll = tempRoll.get(); + if (roll != null) { + this.roll = roll; + return (float) (this.roll * MagicNumbers.TORAD); + } + return original; + } + + @Override + public float doABarrelRoll$getRoll() { + return roll; + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/DebugHudMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/DebugHudMixin.java new file mode 100644 index 00000000..aa3de541 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/DebugHudMixin.java @@ -0,0 +1,43 @@ +package nl.enjarai.doabarrelroll.mixin.client.roll; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.hud.DebugHud; +import nl.enjarai.doabarrelroll.api.RollEntity; +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.ModifyArgs; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + +@Mixin(DebugHud.class) +public abstract class DebugHudMixin { + @Shadow @Final private MinecraftClient client; + + @ModifyArgs( + method = "getLeftText", + at = @At( + value = "INVOKE", + target = "Ljava/lang/String;format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", + ordinal = 6 + ) + ) + private void doABarrelRoll$modifyDebugHudText(Args args) { + var cameraEntity = client.getCameraEntity(); + if (cameraEntity == null) return; + + // Carefully insert a new number format specifier into the facing string + var originalString = (String) args.get(1); + var firstHalf = originalString.substring(0, originalString.length() - 1); + var secondHalf = originalString.substring(originalString.length() - 1); + args.set(1, firstHalf + " / %.1f" + secondHalf); + + // Add the roll value to the format arguments + var roll = ((RollEntity) client.getCameraEntity()).doABarrelRoll$getRoll(); + var fmtArgs = (Object[]) args.get(2); + var newFmtArgs = new Object[fmtArgs.length + 1]; + System.arraycopy(fmtArgs, 0, newFmtArgs, 0, fmtArgs.length); + newFmtArgs[fmtArgs.length] = roll; + args.set(2, newFmtArgs); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/GameRendererMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/GameRendererMixin.java new file mode 100644 index 00000000..53b3c54a --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/GameRendererMixin.java @@ -0,0 +1,30 @@ +package nl.enjarai.doabarrelroll.mixin.client.roll; + +import net.minecraft.client.render.Camera; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.RotationAxis; +import nl.enjarai.doabarrelroll.api.RollCamera; +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.CallbackInfo; + +@Mixin(GameRenderer.class) +public abstract class GameRendererMixin { + @Shadow @Final private Camera camera; + + @Inject( + method = "renderWorld", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/util/math/RotationAxis;rotationDegrees(F)Lorg/joml/Quaternionf;", + ordinal = 2 + ) + ) + public void doABarrelRoll$renderWorld(float tickDelta, long limitTime, MatrixStack matrix, CallbackInfo ci) { + matrix.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(((RollCamera) camera).doABarrelRoll$getRoll())); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/MouseMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/MouseMixin.java new file mode 100644 index 00000000..6f365e9e --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/MouseMixin.java @@ -0,0 +1,111 @@ +package nl.enjarai.doabarrelroll.mixin.client.roll; + +import com.llamalad7.mixinextras.injector.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalDoubleRef; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.Mouse; +import net.minecraft.client.network.ClientPlayerEntity; +import nl.enjarai.doabarrelroll.api.RollEntity; +import nl.enjarai.doabarrelroll.api.RollMouse; +import nl.enjarai.doabarrelroll.config.ModConfig; +import org.joml.Vector2d; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Mouse.class) +public abstract class MouseMixin implements RollMouse { + @Shadow @Final private MinecraftClient client; + @Shadow private double lastMouseUpdateTime; + + @Unique + private final Vector2d mouseTurnVec = new Vector2d(); + + @ModifyVariable( + method = "updateMouse", + index = 3, + at = @At( + value = "STORE", + ordinal = 0 + ) + ) + private double doABarrelRoll$captureDelta(double original, @Share("mouseDelta") LocalDoubleRef mouseDeltaRef) { + if (lastMouseUpdateTime != Double.MIN_VALUE) { + mouseDeltaRef.set(original); + } + + return original; + } + + @Inject( + method = "updateMouse", + at = @At( + value = "RETURN", + ordinal = 0 + ) + ) + private void doABarrelRoll$maintainMouseMomentum(CallbackInfo ci, @Share("mouseDelta") LocalDoubleRef mouseDeltaRef) { + if (client.player != null && !client.isPaused()) { + doABarrelRoll$updateMouse(client.player, 0, 0, mouseDeltaRef.get()); + } + } + + @WrapWithCondition( + method = "updateMouse", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/network/ClientPlayerEntity;changeLookDirection(DD)V" + ) + ) + private boolean doABarrelRoll$changeLookDirection(ClientPlayerEntity player, double cursorDeltaX, double cursorDeltaY, @Share("mouseDelta") LocalDoubleRef mouseDeltaRef) { + return !doABarrelRoll$updateMouse(player, cursorDeltaX, cursorDeltaY, mouseDeltaRef.get()); + } + + @Override + public boolean doABarrelRoll$updateMouse(ClientPlayerEntity player, double cursorDeltaX, double cursorDeltaY, double mouseDelta) { + var rollPlayer = (RollEntity) player; + + if (rollPlayer.doABarrelRoll$isRolling()) { + + if (ModConfig.INSTANCE.getMomentumBasedMouse()) { + + // add the mouse movement to the current vector and normalize if needed + mouseTurnVec.add(new Vector2d(cursorDeltaX, cursorDeltaY).mul(1f / 300)); + if (mouseTurnVec.lengthSquared() > 1.0) { + mouseTurnVec.normalize(); + } + var readyTurnVec = new Vector2d(mouseTurnVec); + + // check if the vector is within the deadzone + double deadzone = ModConfig.INSTANCE.getMomentumMouseDeadzone(); + if (readyTurnVec.lengthSquared() < deadzone * deadzone) readyTurnVec.zero(); + + // enlarge the vector and apply it to the camera + readyTurnVec.mul(1200 * (float) mouseDelta); + rollPlayer.doABarrelRoll$changeElytraLook(readyTurnVec.y, readyTurnVec.x, 0, ModConfig.INSTANCE.getDesktopSensitivity(), mouseDelta); + + } else { + + // if we are not using a momentum based mouse, we can reset it and apply the values directly + mouseTurnVec.zero(); + rollPlayer.doABarrelRoll$changeElytraLook(cursorDeltaY, cursorDeltaX, 0, ModConfig.INSTANCE.getDesktopSensitivity(), mouseDelta); + } + + return true; + } + + mouseTurnVec.zero(); + return false; + } + + @Override + public Vector2d doABarrelRoll$getMouseTurnVec() { + return mouseTurnVec; + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/PlayerEntityRendererMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/PlayerEntityRendererMixin.java new file mode 100644 index 00000000..b900ebc7 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/PlayerEntityRendererMixin.java @@ -0,0 +1,48 @@ +package nl.enjarai.doabarrelroll.mixin.client.roll; + +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.RotationAxis; +import nl.enjarai.doabarrelroll.api.RollEntity; +import org.joml.Quaternionf; +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.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerEntityRenderer.class) +public abstract class PlayerEntityRendererMixin { + private AbstractClientPlayerEntity player; + private float tickDelta; + + @Inject( + method = "setupTransforms(Lnet/minecraft/client/network/AbstractClientPlayerEntity;Lnet/minecraft/client/util/math/MatrixStack;FFF)V", + at = @At("HEAD") + ) + private void doABarrelRoll$captureOtherPlayer(AbstractClientPlayerEntity abstractClientPlayerEntity, MatrixStack matrixStack, float f, float g, float h, CallbackInfo ci) { + player = abstractClientPlayerEntity; + tickDelta = h; + } + + @ModifyArg( + method = "setupTransforms(Lnet/minecraft/client/network/AbstractClientPlayerEntity;Lnet/minecraft/client/util/math/MatrixStack;FFF)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/util/math/MatrixStack;multiply(Lorg/joml/Quaternionf;)V", + ordinal = 1 + ), + index = 0 + ) + private Quaternionf doABarrelRoll$modifyRoll(Quaternionf original) { + var rollEntity = (RollEntity) player; + + if (rollEntity.doABarrelRoll$isRolling()) { + var roll = rollEntity.doABarrelRoll$getRoll(tickDelta); + return RotationAxis.POSITIVE_Y.rotationDegrees(roll); + } + + return original; + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/entity/AbstractClientPlayerEntityMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/entity/AbstractClientPlayerEntityMixin.java new file mode 100644 index 00000000..58ab431f --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/entity/AbstractClientPlayerEntityMixin.java @@ -0,0 +1,9 @@ +package nl.enjarai.doabarrelroll.mixin.client.roll.entity; + +import net.minecraft.client.network.AbstractClientPlayerEntity; +import nl.enjarai.doabarrelroll.mixin.roll.entity.PlayerEntityMixin; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(AbstractClientPlayerEntity.class) +public abstract class AbstractClientPlayerEntityMixin extends PlayerEntityMixin { +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/entity/ClientPlayerEntityMixin.java b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/entity/ClientPlayerEntityMixin.java new file mode 100644 index 00000000..0ab3a347 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/mixin/client/roll/entity/ClientPlayerEntityMixin.java @@ -0,0 +1,130 @@ +package nl.enjarai.doabarrelroll.mixin.client.roll.entity; + +import net.minecraft.client.network.ClientPlayerEntity; +import nl.enjarai.doabarrelroll.api.event.RollContext; +import nl.enjarai.doabarrelroll.api.event.RollEvents; +import nl.enjarai.doabarrelroll.api.rotation.RotationInstant; +import nl.enjarai.doabarrelroll.config.Sensitivity; +import nl.enjarai.doabarrelroll.flight.RotationModifiers; +import nl.enjarai.doabarrelroll.math.MagicNumbers; +import nl.enjarai.doabarrelroll.net.RollSyncClient; +import org.joml.Vector3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayerEntity.class) +public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntityMixin { + @Shadow public float renderYaw; + @Shadow public float lastRenderYaw; + + @Unique + private boolean lastSentIsRolling; + @Unique + private float lastSentRoll; + + @Inject( + method = "sendMovementPackets", + at = @At("TAIL") + ) + private void doABarrelRoll$sendRollPacket(CallbackInfo ci) { + var isRolling = doABarrelRoll$isRolling(); + var rollDiff = doABarrelRoll$getRoll() - lastSentRoll; + if (isRolling != lastSentIsRolling || rollDiff != 0.0f) { + RollSyncClient.sendUpdate(this); + + lastSentIsRolling = isRolling; + lastSentRoll = doABarrelRoll$getRoll(); + } + } + + @Override + protected void doABarrelRoll$baseTickTail(CallbackInfo ci) { + // Update rolling status + doABarrelRoll$setRolling(RollEvents.shouldRoll()); + + super.doABarrelRoll$baseTickTail(ci); + } + + @Override + public void doABarrelRoll$changeElytraLook(double pitch, double yaw, double roll, Sensitivity sensitivity, double mouseDelta) { + var rotDelta = RotationInstant.of(pitch, yaw, roll); + var currentRoll = doABarrelRoll$getRoll(); + var currentRotation = RotationInstant.of( + getPitch(), + getYaw(), + currentRoll + ); + var context = RollContext.of(currentRotation, rotDelta, mouseDelta); + + context.useModifier(RotationModifiers.fixNaN("INPUT")); + RollEvents.earlyCameraModifiers(context); + context.useModifier(RotationModifiers.fixNaN("EARLY_CAMERA_MODIFIERS")); + context.useModifier((rotation, ctx) -> rotation.applySensitivity(sensitivity)); + context.useModifier(RotationModifiers.fixNaN("SENSITIVITY")); + RollEvents.lateCameraModifiers(context); + context.useModifier(RotationModifiers.fixNaN("LATE_CAMERA_MODIFIERS")); + + rotDelta = context.getRotationDelta(); + + doABarrelRoll$changeElytraLook((float) rotDelta.pitch(), (float) rotDelta.yaw(), (float) rotDelta.roll()); + } + + @Override + public void doABarrelRoll$changeElytraLook(float pitch, float yaw, float roll) { + var currentPitch = getPitch(); + var currentYaw = getYaw(); + var currentRoll = doABarrelRoll$getRoll(); + + // Convert pitch, yaw, and roll to a facing and left vector + var facing = new Vector3d(getRotationVecClient().toVector3f()); + var left = new Vector3d(1, 0, 0); + left.rotateZ(-currentRoll * MagicNumbers.TORAD); + left.rotateX(-currentPitch * MagicNumbers.TORAD); + left.rotateY(-(currentYaw + 180) * MagicNumbers.TORAD); + + + // Apply pitch + facing.rotateAxis(-0.15 * pitch * MagicNumbers.TORAD, left.x, left.y, left.z); + + // Apply yaw + var up = facing.cross(left, new Vector3d()); + facing.rotateAxis(0.15 * yaw * MagicNumbers.TORAD, up.x, up.y, up.z); + left.rotateAxis(0.15 * yaw * MagicNumbers.TORAD, up.x, up.y, up.z); + + // Apply roll + left.rotateAxis(0.15 * roll * MagicNumbers.TORAD, facing.x, facing.y, facing.z); + + + // Extract new pitch, yaw, and roll + double newPitch = -Math.asin(facing.y) * MagicNumbers.TODEG; + double newYaw = -Math.atan2(facing.x, facing.z) * MagicNumbers.TODEG; + + var normalLeft = new Vector3d(1, 0, 0).rotateY(-(newYaw + 180) * MagicNumbers.TORAD); + double newRoll = -Math.atan2(left.cross(normalLeft, new Vector3d()).dot(facing), left.dot(normalLeft)) * MagicNumbers.TODEG; + + // Calculate deltas + double deltaY = newPitch - currentPitch; + double deltaX = newYaw - currentYaw; + double deltaRoll = newRoll - currentRoll; + + // Apply vanilla pitch and yaw + changeLookDirection(deltaX / 0.15, deltaY / 0.15); + + // Apply roll + this.roll += deltaRoll; + this.prevRoll += deltaRoll; + + // fix hand spasm when wrapping yaw value + if (getYaw() < -90 && renderYaw > 90) { + renderYaw -= 360; + lastRenderYaw -= 360; + } else if (getYaw() > 90 && renderYaw < -90) { + renderYaw += 360; + lastRenderYaw += 360; + } + } +} \ No newline at end of file diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/net/HandshakeClient.java b/common/src/client/java/nl/enjarai/doabarrelroll/net/HandshakeClient.java new file mode 100644 index 00000000..9f1947d5 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/net/HandshakeClient.java @@ -0,0 +1,106 @@ +package nl.enjarai.doabarrelroll.net; + +import com.google.gson.JsonParser; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import io.netty.buffer.Unpooled; +import net.minecraft.network.PacketByteBuf; +import nl.enjarai.doabarrelroll.DoABarrelRoll; + +import java.util.Optional; +import java.util.function.Consumer; + +public class HandshakeClient { + public static final int PROTOCOL_VERSION = 3; + + private final Codec transferCodec; + private final Codec limitedTransferCodec; + private final Consumer updateCallback; + private L serverConfig = null; + private F fullServerConfig = null; + private boolean hasConnected = false; + + public HandshakeClient(Codec transferCodec, Codec limitedTransferCodec, Consumer updateCallback) { + this.transferCodec = transferCodec; + this.limitedTransferCodec = limitedTransferCodec; + this.updateCallback = updateCallback; + } + + /** + * Returns the server config if the client has received one for this server, + * returns an empty optional in any other case. + */ + public Optional getConfig() { + return Optional.ofNullable(serverConfig); + } + + public Optional getFullConfig() { + return Optional.ofNullable(fullServerConfig); + } + +// public void setConfig(@Nullable L config) { +// serverConfig = config; +// updateCallback.accept(serverConfig); +// hasConnected = serverConfig != null; +// } + + public boolean hasConnected() { + return hasConnected; + } + + public PacketByteBuf handleConfigSync(PacketByteBuf buf) { + serverConfig = null; + fullServerConfig = null; + + try { + var protocolVersion = buf.readInt(); + if (protocolVersion < 1 || protocolVersion > PROTOCOL_VERSION) { + DoABarrelRoll.LOGGER.warn("Received config with unknown protocol version: {}, will attempt to load anyway", protocolVersion); + } + + var data = buf.readString(); + var isLimited = true; + + if (protocolVersion >= 2) { + isLimited = buf.readBoolean(); + } + + if (protocolVersion == 2) { + var codec = isLimited ? limitedTransferCodec : transferCodec; + serverConfig = codec.parse(JsonOps.INSTANCE, JsonParser.parseString(data)) + .getOrThrow(false, DoABarrelRoll.LOGGER::error); + if (!isLimited) { + //noinspection unchecked + fullServerConfig = (F) serverConfig; + } + } else { + serverConfig = limitedTransferCodec.parse(JsonOps.INSTANCE, JsonParser.parseString(data)) + .getOrThrow(false, DoABarrelRoll.LOGGER::error); + if (!isLimited) { + var data2 = buf.readString(); + + fullServerConfig = transferCodec.parse(JsonOps.INSTANCE, JsonParser.parseString(data2)) + .getOrThrow(false, DoABarrelRoll.LOGGER::error); + } + } + } catch (RuntimeException e) { + DoABarrelRoll.LOGGER.error("Failed to parse config from server", e); + } + + if (serverConfig != null) { + updateCallback.accept(serverConfig); + hasConnected = true; + DoABarrelRoll.LOGGER.info("Received config from server"); + } + + var returnBuf = new PacketByteBuf(Unpooled.buffer()); + returnBuf.writeInt(PROTOCOL_VERSION); + returnBuf.writeBoolean(serverConfig != null); + return returnBuf; + } + + public void reset() { + serverConfig = null; + hasConnected = false; + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/net/RollSyncClient.java b/common/src/client/java/nl/enjarai/doabarrelroll/net/RollSyncClient.java new file mode 100644 index 00000000..edd7bca1 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/net/RollSyncClient.java @@ -0,0 +1,44 @@ +package nl.enjarai.doabarrelroll.net; + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.minecraft.util.math.MathHelper; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.DoABarrelRollClient; +import nl.enjarai.doabarrelroll.api.RollEntity; + +public class RollSyncClient { + public static void sendUpdate(RollEntity entity) { + if (DoABarrelRollClient.HANDSHAKE_CLIENT.hasConnected()) { + boolean rolling = entity.doABarrelRoll$isRolling(); + float roll = entity.doABarrelRoll$getRoll(); + + var buf = PacketByteBufs.create(); + buf.writeBoolean(rolling); + buf.writeFloat(roll); + + ClientPlayNetworking.send(DoABarrelRoll.ROLL_CHANNEL, buf); + } + } + + public static void startListening() { + ClientPlayNetworking.registerReceiver(DoABarrelRoll.ROLL_CHANNEL, (client, handler1, buf, responseSender) -> { + if (client.world == null) { + return; + } + + int entityId = buf.readInt(); + var isRolling = buf.readBoolean(); + var roll = buf.readFloat(); + + var entity = client.world.getEntityById(entityId); + if (entity == null) { + return; + } + var rollEntity = (RollEntity) entity; + + rollEntity.doABarrelRoll$setRolling(isRolling); + rollEntity.doABarrelRoll$setRoll(MathHelper.wrapDegrees(roll)); + }); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/net/ServerConfigUpdateClient.java b/common/src/client/java/nl/enjarai/doabarrelroll/net/ServerConfigUpdateClient.java new file mode 100644 index 00000000..aadb0673 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/net/ServerConfigUpdateClient.java @@ -0,0 +1,59 @@ +package nl.enjarai.doabarrelroll.net; + +import com.mojang.serialization.JsonOps; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.minecraft.network.PacketByteBuf; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.config.ModConfigServer; +import nl.enjarai.doabarrelroll.util.ToastUtil; + +public class ServerConfigUpdateClient { + private static boolean waitingForAck = false; + + public static void sendUpdate(ModConfigServer config) { + var buf = PacketByteBufs.create(); + + // Protocol version + buf.writeInt(1); + + // Config + try { + var data = ModConfigServer.CODEC.encodeStart(JsonOps.INSTANCE, config) + .getOrThrow(false, DoABarrelRoll.LOGGER::warn).toString(); + buf.writeString(data); + } catch (RuntimeException e) { + DoABarrelRoll.LOGGER.warn("Failed to send server config update to server: ", e); + } + + ClientPlayNetworking.send(DoABarrelRoll.SERVER_CONFIG_UPDATE_CHANNEL, buf); + waitingForAck = true; + } + + public static void updateAcknowledged(PacketByteBuf buf) { + if (waitingForAck) { + waitingForAck = false; + + try { + var protocolVersion = buf.readInt(); + if (protocolVersion != 1) { + DoABarrelRoll.LOGGER.warn("Received config update ack with unknown protocol version: {}, will attempt to read anyway", protocolVersion); + } + + var success = buf.readBoolean(); + if (success) { + // I don't think we need this +// var data = ModConfigServer.CODEC.decode(JsonOps.INSTANCE, JsonParser.parseString(buf.readString())) +// .getOrThrow(false, DoABarrelRoll.LOGGER::warn).getFirst(); +// DoABarrelRollClient.HANDSHAKE_CLIENT.setConfig(data); + + ToastUtil.toasty("server_config_updated"); + } else { + ToastUtil.toasty("server_config_update_failed"); + } + } catch (RuntimeException e) { + DoABarrelRoll.LOGGER.warn("Failed to read config update ack from server: ", e); + } + } + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/render/HorizonLineWidget.java b/common/src/client/java/nl/enjarai/doabarrelroll/render/HorizonLineWidget.java new file mode 100644 index 00000000..071e1f00 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/render/HorizonLineWidget.java @@ -0,0 +1,40 @@ +package nl.enjarai.doabarrelroll.render; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.util.math.MatrixStack; +import nl.enjarai.doabarrelroll.ModMath; +import nl.enjarai.doabarrelroll.math.MagicNumbers; +import org.joml.Vector2d; + +public class HorizonLineWidget extends RenderHelper { + public static void render(MatrixStack matrices, int scaledWidth, int scaledHeight, double roll, double pitch) { + int centerX = scaledWidth / 2; + int centerY = scaledHeight / 2 - 1; + roll *= -MagicNumbers.TORAD; + + var v = new Vector2d(Math.cos(roll), Math.sin(roll)); + var offset = new Vector2d(v).perpendicular().mul(pitch * scaledHeight * 0.007); + + centerX += Math.round(offset.x); + centerY += Math.round(offset.y); + + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(GlStateManager.SrcFactor.ONE_MINUS_DST_COLOR, GlStateManager.DstFactor.ONE_MINUS_SRC_COLOR, GlStateManager.SrcFactor.ONE, GlStateManager.DstFactor.ZERO); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + for (int i = 0; i < 2; i++) { + v.negate(); + + var start = v.mul(10.0, new Vector2d()); + var end = v.mul(50.0, new Vector2d()); + + ModMath.forBresenhamLine( + centerX + (int) start.x, centerY + (int) start.y, + centerX + (int) end.x, centerY + (int) end.y, + blankPixel(matrices) + ); + } + RenderSystem.defaultBlendFunc(); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/render/MomentumCrosshairWidget.java b/common/src/client/java/nl/enjarai/doabarrelroll/render/MomentumCrosshairWidget.java new file mode 100644 index 00000000..9bdb94e1 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/render/MomentumCrosshairWidget.java @@ -0,0 +1,35 @@ +package nl.enjarai.doabarrelroll.render; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.util.math.MatrixStack; +import nl.enjarai.doabarrelroll.ModMath; +import org.joml.Vector2d; + +public class MomentumCrosshairWidget extends RenderHelper { + + public static void render(MatrixStack matrices, int scaledWidth, int scaledHeight, Vector2d mouseTurnVec) { + int centerX = scaledWidth / 2; + int centerY = scaledHeight / 2 - 1; + mouseTurnVec.mul(50); + var lineVec = new Vector2d(mouseTurnVec).add( + new Vector2d(mouseTurnVec).negate().normalize().mul(Math.min(mouseTurnVec.length(), 10f))); + + if (!lineVec.equals(new Vector2d()) && mouseTurnVec.lengthSquared() > 10f * 10f) { + + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(GlStateManager.SrcFactor.ONE_MINUS_DST_COLOR, GlStateManager.DstFactor.ONE_MINUS_SRC_COLOR, GlStateManager.SrcFactor.ONE, GlStateManager.DstFactor.ZERO); + RenderSystem.setShader(GameRenderer::getPositionColorProgram); + ModMath.forBresenhamLine( + centerX, centerY, + centerX + (int) lineVec.x, centerY + (int) lineVec.y, + blankPixel(matrices) + ); + RenderSystem.defaultBlendFunc(); + } + + // change the position of the crosshair, which is rendered up the stack + matrices.translate((int) mouseTurnVec.x, (int) mouseTurnVec.y, 0); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/render/RenderHelper.java b/common/src/client/java/nl/enjarai/doabarrelroll/render/RenderHelper.java new file mode 100644 index 00000000..a99076f6 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/render/RenderHelper.java @@ -0,0 +1,26 @@ +package nl.enjarai.doabarrelroll.render; + +import net.minecraft.client.render.BufferRenderer; +import net.minecraft.client.render.Tessellator; +import net.minecraft.client.render.VertexFormat; +import net.minecraft.client.render.VertexFormats; +import net.minecraft.client.util.math.MatrixStack; + +import java.util.function.BiConsumer; + +public class RenderHelper { + public static BiConsumer blankPixel(MatrixStack matrices) { + return (x, y) -> { + int color = 0xffffffff; + var matrix = matrices.peek().getPositionMatrix(); + var bufferBuilder = Tessellator.getInstance().getBuffer(); + + bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); + bufferBuilder.vertex(matrix, (float) x, (float) y + 1, 0.0F).color(color).next(); + bufferBuilder.vertex(matrix, (float) x + 1, (float) y + 1, 0.0F).color(color).next(); + bufferBuilder.vertex(matrix, (float) x + 1, (float) y, 0.0F).color(color).next(); + bufferBuilder.vertex(matrix, (float) x, (float) y, 0.0F).color(color).next(); + BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()); + }; + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/util/MixinHooks.java b/common/src/client/java/nl/enjarai/doabarrelroll/util/MixinHooks.java new file mode 100644 index 00000000..cbd8e4ad --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/util/MixinHooks.java @@ -0,0 +1,10 @@ +package nl.enjarai.doabarrelroll.util; + +public class MixinHooks { + /** + * Used for both TRIPLE_JUMP and HYBRID activation behaviours. + */ + public static boolean secondJump = false; + public static boolean thirdJump = false; + public static boolean wasJumping = false; +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/util/StarFoxUtil.java b/common/src/client/java/nl/enjarai/doabarrelroll/util/StarFoxUtil.java new file mode 100644 index 00000000..1ef75cf1 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/util/StarFoxUtil.java @@ -0,0 +1,103 @@ +package nl.enjarai.doabarrelroll.util; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.random.Random; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.api.event.RollEvents; +import nl.enjarai.doabarrelroll.api.event.StarFox64Events; +import nl.enjarai.doabarrelroll.api.rotation.RotationInstant; +import nl.enjarai.doabarrelroll.config.ModConfig; + +public class StarFoxUtil { + private static final Random random = Random.create(); + private static final Identifier barrelRollSoundId = DoABarrelRoll.id("do_a_barrel_roll"); + private static final SoundEvent barrelRollSound = SoundEvent.of(barrelRollSoundId); + private static final Identifier barrelRollTexture1 = DoABarrelRoll.id("textures/gui/barrel_roll_1.png"); + private static final Identifier barrelRollTexture2 = DoABarrelRoll.id("textures/gui/barrel_roll_2.png"); + private static final double rollTol = 90.0; + + // Tracks the roll direction. 0 = no roll, 1 = right, -1 = left + private static double rollTracker = 0; + private static int barrelRollTimer = 0; + + public static void register() { + Registry.register(Registries.SOUND_EVENT, barrelRollSoundId, barrelRollSound); + + StarFox64Events.DOES_A_BARREL_ROLL.register(StarFoxUtil::playBarrelRollSound); + StarFox64Events.DOES_A_BARREL_ROLL.register(player -> barrelRollTimer = 30); + + RollEvents.LATE_CAMERA_MODIFIERS.register(context -> { + trackRoll(context.getRotationDelta(), context.getCurrentRotation()); + }, 999999); + } + + public static void clientTick(MinecraftClient client) { + if (barrelRollTimer > 0) { + barrelRollTimer--; + } + } + + public static void renderPeppy(DrawContext context, float tickDelta, int scaledWidth, int scaledHeight) { + if (barrelRollTimer > 0) { + int x = scaledWidth / 2 - 75; + int y = scaledHeight - 90; + int texture = barrelRollTimer % 2 == 0 ? 1 : 2; + context.drawTexture(texture == 1 ? barrelRollTexture1 : barrelRollTexture2, + x, y, 0, 0, 160, 160, 160, 160); + } + } + + private static void trackRoll(RotationInstant rotationDelta, RotationInstant currentRotation) { + var player = MinecraftClient.getInstance().player; + if (player != null && isFoxMcCloud(player)) { + double cRoll = currentRotation.roll(); + double dRoll = rotationDelta.roll(); + + // If the player crosses the threshold in any direction, set the roll tracker to that direction + if (cRoll < rollTol && cRoll + dRoll >= rollTol) { + rollTracker = 1; + } else if (cRoll > -rollTol && cRoll + dRoll <= -rollTol) { + rollTracker = -1; + } else if (rollTracker != 0) { + // Reset roll tracker if the player starts turning the other way + if (rollTracker * dRoll < 0) { + rollTracker = 0; + } + + // If the player has the roll tracker set, and crosses the opposite + // threshold in the direction of the roll tracker, do a barrel roll + if ((rollTracker > 0 && cRoll <= -rollTol) || (rollTracker < 0 && cRoll >= rollTol)) { + StarFox64Events.doesABarrelRoll(player); + rollTracker = 0; + } + } + } else { + rollTracker = 0; + } + } + + public static boolean isFoxMcCloud(PlayerEntity player) { + if (!ModConfig.INSTANCE.getEnableEasterEggs()) return false; + + ItemStack chestStack = player.getEquippedStack(EquipmentSlot.CHEST); + String name = chestStack.getName().getString(); + return chestStack.isOf(Items.ELYTRA) && (name.equals("Arwing") || name.equals("Star Fox 64") || name.contains("Do a Barrel Roll")); + } + + public static void playBarrelRollSound(PlayerEntity player) { + player.getWorld().playSoundFromEntity( + player, player, barrelRollSound, SoundCategory.PLAYERS, + 1.0F, (random.nextFloat() - random.nextFloat()) * 0.2F + 1.0F + ); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/util/ToastUtil.java b/common/src/client/java/nl/enjarai/doabarrelroll/util/ToastUtil.java new file mode 100644 index 00000000..ed2374c5 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/util/ToastUtil.java @@ -0,0 +1,16 @@ +package nl.enjarai.doabarrelroll.util; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.toast.SystemToast; +import net.minecraft.text.Text; + +public class ToastUtil { + public static void toasty(String key) { + MinecraftClient.getInstance().getToastManager().add(SystemToast.create( + MinecraftClient.getInstance(), + SystemToast.Type.TUTORIAL_HINT, + Text.translatable("toast.do_a_barrel_roll"), + Text.translatable("toast.do_a_barrel_roll." + key) + )); + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/util/Value.java b/common/src/client/java/nl/enjarai/doabarrelroll/util/Value.java new file mode 100644 index 00000000..ed9d3136 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/util/Value.java @@ -0,0 +1,19 @@ +package nl.enjarai.doabarrelroll.util; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public interface Value extends Consumer, Supplier { + static Value of(T value) { + return new Value<>() { + @Override + public void accept(T t) { + } + + @Override + public T get() { + return value; + } + }; + } +} diff --git a/common/src/client/java/nl/enjarai/doabarrelroll/util/key/ContextualKeyBinding.java b/common/src/client/java/nl/enjarai/doabarrelroll/util/key/ContextualKeyBinding.java new file mode 100644 index 00000000..b980db33 --- /dev/null +++ b/common/src/client/java/nl/enjarai/doabarrelroll/util/key/ContextualKeyBinding.java @@ -0,0 +1,11 @@ +package nl.enjarai.doabarrelroll.util.key; + +import nl.enjarai.doabarrelroll.api.key.InputContext; + +import java.util.List; + +public interface ContextualKeyBinding { + void doABarrelRoll$addToContext(InputContext context); + + List doABarrelRoll$getContexts(); +} diff --git a/common/src/client/resources/do-a-barrel-roll.client.mixins.json b/common/src/client/resources/do-a-barrel-roll.client.mixins.json new file mode 100644 index 00000000..e53808d2 --- /dev/null +++ b/common/src/client/resources/do-a-barrel-roll.client.mixins.json @@ -0,0 +1,23 @@ +{ + "required": true, + "package": "nl.enjarai.doabarrelroll.mixin.client", + "compatibilityLevel": "JAVA_17", + "client": [ + "ClientPlayerEntityMixin", + "InGameHudMixin", + "LivingEntityMixin", + "PlayerEntityMixin", + "key.KeyBindingEntryMixin", + "key.KeyBindingMixin", + "roll.CameraMixin", + "roll.DebugHudMixin", + "roll.GameRendererMixin", + "roll.MouseMixin", + "roll.PlayerEntityRendererMixin", + "roll.entity.AbstractClientPlayerEntityMixin", + "roll.entity.ClientPlayerEntityMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/common/src/client/resources/do-a-barrel-roll.compat.cameraoverhaul.mixins.json b/common/src/client/resources/do-a-barrel-roll.compat.cameraoverhaul.mixins.json new file mode 100644 index 00000000..b641ba9c --- /dev/null +++ b/common/src/client/resources/do-a-barrel-roll.compat.cameraoverhaul.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "nl.enjarai.doabarrelroll.compat.cameraoverhaul.mixin", + "plugin": "nl.enjarai.doabarrelroll.compat.cameraoverhaul.CameraOverhaulPlugin", + "compatibilityLevel": "JAVA_17", + "client": [ + "CameraSystemMixin" + ], + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/common/src/client/resources/do-a-barrel-roll.compat.controlify.mixins.json b/common/src/client/resources/do-a-barrel-roll.compat.controlify.mixins.json new file mode 100644 index 00000000..9220e00b --- /dev/null +++ b/common/src/client/resources/do-a-barrel-roll.compat.controlify.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "nl.enjarai.doabarrelroll.compat.controlify.mixin", + "plugin": "nl.enjarai.doabarrelroll.compat.controlify.ControlifyPlugin", + "compatibilityLevel": "JAVA_17", + "client": [ + "InGameInputHandlerMixin" + ], + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/DoABarrelRoll.java b/common/src/main/java/nl/enjarai/doabarrelroll/DoABarrelRoll.java new file mode 100644 index 00000000..b2af3124 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/DoABarrelRoll.java @@ -0,0 +1,45 @@ +package nl.enjarai.doabarrelroll; + +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.util.Identifier; +import nl.enjarai.cicada.api.conversation.ConversationManager; +import nl.enjarai.cicada.api.util.CicadaEntrypoint; +import nl.enjarai.cicada.api.util.JsonSource; +import nl.enjarai.cicada.api.util.ProperLogger; +import nl.enjarai.doabarrelroll.api.event.ServerEvents; +import nl.enjarai.doabarrelroll.config.ModConfigServer; +import nl.enjarai.doabarrelroll.net.HandshakeServer; +import nl.enjarai.doabarrelroll.net.ServerConfigHolder; +import org.slf4j.Logger; + +public class DoABarrelRoll implements CicadaEntrypoint { + public static final String MODID = "do_a_barrel_roll"; + public static final Logger LOGGER = ProperLogger.getLogger(MODID); + + public static ServerConfigHolder CONFIG_HOLDER; + public static HandshakeServer HANDSHAKE_SERVER; + public static final Identifier HANDSHAKE_CHANNEL = id("handshake"); + public static final Identifier SERVER_CONFIG_UPDATE_CHANNEL = id("server_config_update"); + public static final Identifier ROLL_CHANNEL = id("player_roll"); + + public static Identifier id(String path) { + return new Identifier(MODID, path); + } + + public static void init() { + var configFile = FabricLoader.getInstance().getConfigDir().resolve(DoABarrelRoll.MODID + "-server.json"); + + CONFIG_HOLDER = new ServerConfigHolder<>(configFile, + ModConfigServer.CODEC, ModConfigServer.DEFAULT, ServerEvents::updateServerConfig); + HANDSHAKE_SERVER = new HandshakeServer(CONFIG_HOLDER, player -> !ModConfigServer.canModify(player)); + } + + @Override + public void registerConversations(ConversationManager conversationManager) { + conversationManager.registerSource( + JsonSource.fromUrl("https://raw.githubusercontent.com/enjarai/do-a-barrel-roll/1.20.2/dev/src/main/resources/cicada/do-a-barrel-roll/conversations.json") + .or(JsonSource.fromResource("cicada/do-a-barrel-roll/conversations.json")), + LOGGER::info + ); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/api/RollEntity.java b/common/src/main/java/nl/enjarai/doabarrelroll/api/RollEntity.java new file mode 100644 index 00000000..bd983a6b --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/api/RollEntity.java @@ -0,0 +1,19 @@ +package nl.enjarai.doabarrelroll.api; + +import nl.enjarai.doabarrelroll.config.Sensitivity; + +public interface RollEntity { + void doABarrelRoll$changeElytraLook(double pitch, double yaw, double roll, Sensitivity sensitivity, double mouseDelta); + + void doABarrelRoll$changeElytraLook(float pitch, float yaw, float roll); + + boolean doABarrelRoll$isRolling(); + + void doABarrelRoll$setRolling(boolean rolling); + + float doABarrelRoll$getRoll(); + + float doABarrelRoll$getRoll(float tickDelta); + + void doABarrelRoll$setRoll(float roll); +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/api/event/Event.java b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/Event.java new file mode 100644 index 00000000..ceeed231 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/Event.java @@ -0,0 +1,18 @@ +package nl.enjarai.doabarrelroll.api.event; + +import java.util.List; +import java.util.function.Supplier; + +public interface Event { + void register(T listener); + + void register(T listener, int priority); + + void register(T listener, Supplier enabled); + + void register(T listener, int priority, Supplier enabled); + + void unregister(T listener); + + List getListeners(); +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollContext.java b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollContext.java new file mode 100644 index 00000000..10c47e17 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollContext.java @@ -0,0 +1,27 @@ +package nl.enjarai.doabarrelroll.api.event; + +import nl.enjarai.doabarrelroll.api.rotation.RotationInstant; +import nl.enjarai.doabarrelroll.impl.event.RollContextImpl; + +import java.util.function.BooleanSupplier; + +public interface RollContext { + static RollContext of(RotationInstant currentRotation, RotationInstant rotationDelta, double delta) { + return new RollContextImpl(currentRotation, rotationDelta, delta); + } + + RotationInstant getCurrentRotation(); + + RotationInstant getRotationDelta(); + + double getRenderDelta(); + + RollContext useModifier(ConfiguresRotation modifier, BooleanSupplier condition); + + RollContext useModifier(ConfiguresRotation modifier); + + @FunctionalInterface + interface ConfiguresRotation { + RotationInstant apply(RotationInstant rotationInstant, RollContext context); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollEvents.java b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollEvents.java new file mode 100644 index 00000000..7b182052 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollEvents.java @@ -0,0 +1,50 @@ +package nl.enjarai.doabarrelroll.api.event; + +import nl.enjarai.doabarrelroll.impl.event.EventImpl; + +public interface RollEvents { + /** + * If any listener returns true, roll will be unlocked. + */ + Event SHOULD_ROLL_CHECK = new EventImpl<>(); + + interface ShouldRollCheckEvent { + boolean shouldRoll(); + } + + static boolean shouldRoll() { + for (var listener : SHOULD_ROLL_CHECK.getListeners()) { + if (listener.shouldRoll()) { + return true; + } + } + + return false; + } + + /** + * Modifiers registered here will be applied before sensitivity. So will be affected by it. + */ + Event EARLY_CAMERA_MODIFIERS = new EventImpl<>(); + + static void earlyCameraModifiers(RollContext context) { + for (var listener : EARLY_CAMERA_MODIFIERS.getListeners()) { + listener.applyCameraModifiers(context); + } + } + + /** + * Modifiers registered here will be applied after sensitivity. So will not be affected by it. + */ + Event LATE_CAMERA_MODIFIERS = new EventImpl<>(); + + static void lateCameraModifiers(RollContext context) { + for (var listener : LATE_CAMERA_MODIFIERS.getListeners()) { + listener.applyCameraModifiers(context); + } + } + + interface CameraModifiersEvent { + void applyCameraModifiers(RollContext context); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollGroup.java b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollGroup.java new file mode 100644 index 00000000..1e1a4611 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/RollGroup.java @@ -0,0 +1,59 @@ +package nl.enjarai.doabarrelroll.api.event; + +import net.minecraft.util.Identifier; +import nl.enjarai.doabarrelroll.impl.event.RollGroupImpl; + +import java.util.function.Supplier; + +/** + * A group of conditions that determine whether the camera should be rolling and what effects should be applied. + * Instances can be directly used as conditions for registered events. + * + *

Usually, you'll want to use {@link RollGroup#of(Identifier)} to get an instance of this class. + * You can then use {@link RollGroup#trueIf(Supplier)}, {@link RollGroup#falseUnless(Supplier)} or + * {@link Event#register(Object)} to add conditions. + * + *

It is usually recommended to store your instance in a static final field, so you can easily use it in your + * event registrations. + * + *

A basic example: + *

{@code
+ * class ModClass {
+ *     public static final RollGroup ROLL_GROUP = RollGroup.of(new Identifier("my_mod", "my_roll_group"));
+ *
+ *     public static void onInitialize() {
+ *         ROLL_GROUP.trueIf(() -> yourCondition);
+ *
+ *         RollEvents.EARLY_CAMERA_MODIFIERS.register((delta, current) -> delta
+ *                 .useModifier(Your::modifier),
+ *                 10, ROLL_GROUP);
+ *     }
+ * }
+ * }
+ * + *

The conditions are checked in order of priority, and the first condition that returns {@link TriState#TRUE} + * will cause the camera to roll. If no condition returns {@link TriState#TRUE}, the camera will not roll. + * If a condition returns {@link TriState#FALSE}, the camera will not roll and no further conditions will be + * checked. If a condition returns {@link TriState#PASS}, the next condition will be checked. + */ +public interface RollGroup extends Supplier, Event { + /** + * Gets the RollGroup instance for the given identifier. + * If no instance exists, a new one is created and registered to {@link RollEvents#SHOULD_ROLL_CHECK} + */ + static RollGroup of(Identifier id) { + return RollGroupImpl.instances.computeIfAbsent(id, id2 -> new RollGroupImpl()); + } + + void trueIf(Supplier condition, int priority); + + void trueIf(Supplier condition); + + void falseUnless(Supplier condition, int priority); + + void falseUnless(Supplier condition); + + interface RollCondition { + TriState shouldRoll(); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/api/event/ServerEvents.java b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/ServerEvents.java new file mode 100644 index 00000000..0b1b4a88 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/ServerEvents.java @@ -0,0 +1,19 @@ +package nl.enjarai.doabarrelroll.api.event; + +import net.minecraft.server.MinecraftServer; +import nl.enjarai.doabarrelroll.config.ModConfigServer; +import nl.enjarai.doabarrelroll.impl.event.EventImpl; + +public interface ServerEvents { + Event SERVER_CONFIG_UPDATE = new EventImpl<>(); + + interface ServerConfigUpdateEvent { + void updateServerConfig(MinecraftServer server, ModConfigServer config); + } + + static void updateServerConfig(MinecraftServer server, ModConfigServer config) { + for (var listener : SERVER_CONFIG_UPDATE.getListeners()) { + listener.updateServerConfig(server, config); + } + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/api/event/ThrustEvents.java b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/ThrustEvents.java new file mode 100644 index 00000000..c46ae184 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/ThrustEvents.java @@ -0,0 +1,22 @@ +package nl.enjarai.doabarrelroll.api.event; + +import nl.enjarai.doabarrelroll.impl.event.EventImpl; + +public interface ThrustEvents { + /** + * Use this event to register inputs that modify thrust. + */ + Event MODIFY_THRUST_INPUT = new EventImpl<>(); + + interface ModifyThrustInputEvent { + double modify(double input); + } + + static double modifyThrustInput(double input) { + for (var listener : MODIFY_THRUST_INPUT.getListeners()) { + input = listener.modify(input); + } + + return input; + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/api/event/TriState.java b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/TriState.java new file mode 100644 index 00000000..9a74c00a --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/api/event/TriState.java @@ -0,0 +1,7 @@ +package nl.enjarai.doabarrelroll.api.event; + +public enum TriState { + TRUE, + FALSE, + PASS +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/api/rotation/RotationInstant.java b/common/src/main/java/nl/enjarai/doabarrelroll/api/rotation/RotationInstant.java new file mode 100644 index 00000000..9c4b70fc --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/api/rotation/RotationInstant.java @@ -0,0 +1,38 @@ +package nl.enjarai.doabarrelroll.api.rotation; + +import nl.enjarai.doabarrelroll.config.Sensitivity; +import nl.enjarai.doabarrelroll.impl.rotation.RotationInstantImpl; + +import java.util.function.BooleanSupplier; + +public interface RotationInstant { + static RotationInstant of(double pitch, double yaw, double roll) { + return new RotationInstantImpl(pitch, yaw, roll); + } + + double pitch(); + + double yaw(); + + double roll(); + + RotationInstant add(double pitch, double yaw, double roll); + + RotationInstant multiply(double pitch, double yaw, double roll); + + /** + * Add absolute upright rotation to this rolled rotation, taking roll into account. + */ + RotationInstant addAbsolute(double x, double y, double currentRoll); + + RotationInstant applySensitivity(Sensitivity sensitivity); + + RotationInstant useModifier(ConfiguresRotation modifier, BooleanSupplier condition); + + RotationInstant useModifier(ConfiguresRotation modifier); + + @FunctionalInterface + interface ConfiguresRotation { + RotationInstant apply(RotationInstant rotationInstant); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/config/KineticDamage.java b/common/src/main/java/nl/enjarai/doabarrelroll/config/KineticDamage.java new file mode 100644 index 00000000..17392637 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/config/KineticDamage.java @@ -0,0 +1,19 @@ +package nl.enjarai.doabarrelroll.config; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; + +public enum KineticDamage { + VANILLA, + HIGH_SPEED, + NONE, + INSTANT_KILL; + + public static final Codec CODEC = Codec.STRING.comapFlatMap(name -> { + try { + return DataResult.success(valueOf(name)); + } catch (IllegalArgumentException e) { + return DataResult.error(() -> "Unknown kinetic damage type: " + name); + } + }, KineticDamage::name); +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/config/LimitedModConfigServer.java b/common/src/main/java/nl/enjarai/doabarrelroll/config/LimitedModConfigServer.java new file mode 100644 index 00000000..e66d1f1b --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/config/LimitedModConfigServer.java @@ -0,0 +1,22 @@ +package nl.enjarai.doabarrelroll.config; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +public interface LimitedModConfigServer { + LimitedModConfigServer OPERATOR = new Impl(true, false); + + static Codec getCodec() { + return RecordCodecBuilder.create(instance -> instance.group( + Codec.BOOL.optionalFieldOf("allowThrusting", ModConfigServer.DEFAULT.allowThrusting()).forGetter(LimitedModConfigServer::allowThrusting), + Codec.BOOL.optionalFieldOf("forceEnabled", ModConfigServer.DEFAULT.forceEnabled()).forGetter(LimitedModConfigServer::forceEnabled) + ).apply(instance, Impl::new)); + } + + boolean allowThrusting(); + + boolean forceEnabled(); + + record Impl(boolean allowThrusting, boolean forceEnabled) implements LimitedModConfigServer { + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/config/ModConfigServer.java b/common/src/main/java/nl/enjarai/doabarrelroll/config/ModConfigServer.java new file mode 100644 index 00000000..d9394622 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/config/ModConfigServer.java @@ -0,0 +1,51 @@ +package nl.enjarai.doabarrelroll.config; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.text.Text; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.net.SyncableConfig; +import nl.enjarai.doabarrelroll.net.ValidatableConfig; + +public record ModConfigServer(boolean allowThrusting, + boolean forceEnabled, + boolean forceInstalled, + int installedTimeout, + KineticDamage kineticDamage) implements SyncableConfig, LimitedModConfigServer, ValidatableConfig { + public static final ModConfigServer DEFAULT = new ModConfigServer( + false, false, false, 40, KineticDamage.VANILLA); + + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.BOOL.optionalFieldOf("allowThrusting", DEFAULT.allowThrusting()).forGetter(ModConfigServer::allowThrusting), + Codec.BOOL.optionalFieldOf("forceEnabled", DEFAULT.forceEnabled()).forGetter(ModConfigServer::forceEnabled), + Codec.BOOL.optionalFieldOf("forceInstalled", DEFAULT.forceInstalled()).forGetter(ModConfigServer::forceInstalled), + Codec.INT.optionalFieldOf("installedTimeout", DEFAULT.installedTimeout()).forGetter(ModConfigServer::installedTimeout), + KineticDamage.CODEC.optionalFieldOf("kineticDamage", DEFAULT.kineticDamage()).forGetter(ModConfigServer::kineticDamage) + ).apply(instance, ModConfigServer::new)); + + @Override + public Integer getSyncTimeout() { + return forceInstalled ? installedTimeout : null; + } + + @Override + public Text getSyncTimeoutMessage() { + return Text.of("Please install Do a Barrel Roll 2.4.0 or later to play on this server."); + } + + @Override + public LimitedModConfigServer getLimited(ServerPlayNetworkHandler handler) { + return Permissions.check(handler.getPlayer(), DoABarrelRoll.MODID + ".ignore_config", 2) ? LimitedModConfigServer.OPERATOR : this; + } + + public static boolean canModify(ServerPlayNetworkHandler net) { + return Permissions.check(net.getPlayer(), DoABarrelRoll.MODID + ".configure", 3); + } + + @Override + public boolean isValid() { + return installedTimeout > 0; + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/config/MutableConfigServer.java b/common/src/main/java/nl/enjarai/doabarrelroll/config/MutableConfigServer.java new file mode 100644 index 00000000..72ce68be --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/config/MutableConfigServer.java @@ -0,0 +1,25 @@ +package nl.enjarai.doabarrelroll.config; + +public class MutableConfigServer { + public boolean allowThrusting; + public boolean forceEnabled; + public boolean forceInstalled; + public int installedTimeout; + public KineticDamage kineticDamage; + + public MutableConfigServer(ModConfigServer config) { + this.allowThrusting = config.allowThrusting(); + this.forceEnabled = config.forceEnabled(); + this.forceInstalled = config.forceInstalled(); + this.installedTimeout = config.installedTimeout(); + this.kineticDamage = config.kineticDamage(); + } + + public ModConfigServer toImmutable() { + return new ModConfigServer( + this.allowThrusting, this.forceEnabled, + this.forceInstalled, this.installedTimeout, + this.kineticDamage + ); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/config/Sensitivity.java b/common/src/main/java/nl/enjarai/doabarrelroll/config/Sensitivity.java new file mode 100644 index 00000000..ce8ce7ec --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/config/Sensitivity.java @@ -0,0 +1,16 @@ +package nl.enjarai.doabarrelroll.config; + +public class Sensitivity { + public double pitch = 1; + public double yaw = 0.4; + public double roll = 1; + + public Sensitivity() { + } + + public Sensitivity(double pitch, double yaw, double roll) { + this.pitch = pitch; + this.yaw = yaw; + this.roll = roll; + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/fabric/DoABarrelRollFabric.java b/common/src/main/java/nl/enjarai/doabarrelroll/fabric/DoABarrelRollFabric.java new file mode 100644 index 00000000..55e99a3a --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/fabric/DoABarrelRollFabric.java @@ -0,0 +1,38 @@ +package nl.enjarai.doabarrelroll.fabric; + +import com.bawnorton.mixinsquared.api.MixinCanceller; +import com.bawnorton.mixinsquared.tools.MixinAnnotationReader; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.config.ModConfigServer; +import nl.enjarai.doabarrelroll.fabric.net.HandshakeServerFabric; + +import java.util.List; + +public class DoABarrelRollFabric implements ModInitializer, MixinCanceller { + @Override + public void onInitialize() { + // Init server and client common code. + DoABarrelRoll.init(); + + // Register server-side listeners for config syncing, this is done on + // both client and server to ensure everything works in LAN worlds as well. + HandshakeServerFabric.init(); + } + + @Override + public boolean shouldCancel(List targetClassNames, String mixinClassName) { + if (mixinClassName.equals("com.anthonyhilyard.equipmentcompare.mixin.KeyMappingMixin") && MixinAnnotationReader.getPriority(mixinClassName) == 1000) { + DoABarrelRoll.LOGGER.warn("Equipment Compare detected, disabling their overly invasive keybinding mixin. Report any relevant issues to them."); + DoABarrelRoll.LOGGER.warn("If the author of Equipment Compare is reading this: see #31 on your github. Once the issue is resolved, you can set the priority of this mixin to anything other than 1000 to stop it being disabled."); + return true; + } + if (mixinClassName.equals("me.fzzyhmstrs.keybind_fix.mixins.KeybindingMixin") && MixinAnnotationReader.getPriority(mixinClassName) == 1000) { + DoABarrelRoll.LOGGER.warn("Keybind Fix detected, disabling their overly invasive keybinding mixin (Ironic, I know). Report any relevant issues to them."); + DoABarrelRoll.LOGGER.warn("If the author of Keybind Fix is reading this: please don't use unconditionally cancelled injects... try looking into MixinExtras! Once the issue is resolved, you can set the priority of this mixin to anything other than 1000 to stop it being disabled."); + return true; + } + return false; + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/fabric/net/HandshakeServerFabric.java b/common/src/main/java/nl/enjarai/doabarrelroll/fabric/net/HandshakeServerFabric.java new file mode 100644 index 00000000..8ede30b6 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/fabric/net/HandshakeServerFabric.java @@ -0,0 +1,48 @@ +package nl.enjarai.doabarrelroll.fabric.net; + +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.server.network.ServerPlayerEntity; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.api.event.ServerEvents; +import nl.enjarai.doabarrelroll.net.HandshakeServer; +import nl.enjarai.doabarrelroll.net.RollSyncServer; + +public class HandshakeServerFabric { + public static void init() { + ServerPlayConnectionEvents.INIT.register((handler, server) -> { + ServerPlayNetworking.registerReceiver(handler, DoABarrelRoll.HANDSHAKE_CHANNEL, (server1, player, handler1, buf, responseSender) -> { + var reply = DoABarrelRoll.HANDSHAKE_SERVER.clientReplied(handler1, buf); + if (reply == HandshakeServer.HandshakeState.ACCEPTED) { + RollSyncServer.startListening(handler1); + ServerConfigUpdaterFabric.startListening(handler1); + } else if (reply == HandshakeServer.HandshakeState.RESEND) { + // Resending can happen when the client has a different protocol version than expected. + sendHandshake(player); + } + }); + + // The initial handshake is sent in the CommandManagerMixin. + }); + + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { + DoABarrelRoll.HANDSHAKE_SERVER.playerDisconnected(handler); + }); + + ServerTickEvents.END_SERVER_TICK.register(DoABarrelRoll.HANDSHAKE_SERVER::tick); + + ServerEvents.SERVER_CONFIG_UPDATE.register((server, config) -> { + for (var player : server.getPlayerManager().getPlayerList()) { + sendHandshake(player); + } + }); + } + + public static void sendHandshake(ServerPlayerEntity player) { + ServerPlayNetworking.send(player, DoABarrelRoll.HANDSHAKE_CHANNEL, + DoABarrelRoll.HANDSHAKE_SERVER.getConfigSyncBuf(player.networkHandler)); + + DoABarrelRoll.HANDSHAKE_SERVER.configSentToClient(player.networkHandler); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/fabric/net/ServerConfigUpdaterFabric.java b/common/src/main/java/nl/enjarai/doabarrelroll/fabric/net/ServerConfigUpdaterFabric.java new file mode 100644 index 00000000..794ae2b0 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/fabric/net/ServerConfigUpdaterFabric.java @@ -0,0 +1,16 @@ +package nl.enjarai.doabarrelroll.fabric.net; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import nl.enjarai.doabarrelroll.DoABarrelRoll; + +public class ServerConfigUpdaterFabric { + public static void startListening(ServerPlayNetworkHandler handler) { + ServerPlayNetworking.registerReceiver(handler, DoABarrelRoll.SERVER_CONFIG_UPDATE_CHANNEL, (server, player, handler1, buf, responseSender) -> { + responseSender.sendPacket( + DoABarrelRoll.SERVER_CONFIG_UPDATE_CHANNEL, + DoABarrelRoll.CONFIG_HOLDER.clientSendsUpdate(player, buf) + ); + }); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/impl/event/EventImpl.java b/common/src/main/java/nl/enjarai/doabarrelroll/impl/event/EventImpl.java new file mode 100644 index 00000000..54b90178 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/impl/event/EventImpl.java @@ -0,0 +1,81 @@ +package nl.enjarai.doabarrelroll.impl.event; + +import com.google.common.collect.ImmutableList; +import nl.enjarai.doabarrelroll.api.event.Event; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +public class EventImpl implements Event { + private final List listeners = new ArrayList<>(); + + @Override + public void register(T listener) { + listeners.add(new Entry(listener, 0, () -> true)); + } + + @Override + public void register(T listener, int priority) { + listeners.add(new Entry(listener, priority, () -> true)); + } + + @Override + public void register(T listener, Supplier enabled) { + listeners.add(new Entry(listener, 0, enabled)); + } + + @Override + public void register(T listener, int priority, Supplier enabled) { + listeners.add(new Entry(listener, priority, enabled)); + } + + @Override + public void unregister(T listener) { + listeners.removeIf(entry -> entry.listener == listener); + } + + @Override + public List getListeners() { + var result = ImmutableList.builder(); + + listeners.sort(null); + for (var entry : listeners) { + if (entry.condition.get()) { + result.add(entry.listener); + } + } + + return result.build(); + } + + private class Entry implements Comparable { + private final T listener; + private final int index; + private final Supplier condition; + + public Entry(T listener, int index, Supplier condition) { + this.listener = listener; + this.index = index; + this.condition = condition; + } + + public T getListener() { + return listener; + } + + public int getIndex() { + return index; + } + + public Supplier getCondition() { + return condition; + } + + @Override + public int compareTo(@NotNull EventImpl.Entry o) { + return Integer.compare(index, o.index); + } + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/impl/event/RollContextImpl.java b/common/src/main/java/nl/enjarai/doabarrelroll/impl/event/RollContextImpl.java new file mode 100644 index 00000000..99d296de --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/impl/event/RollContextImpl.java @@ -0,0 +1,45 @@ +package nl.enjarai.doabarrelroll.impl.event; + +import nl.enjarai.doabarrelroll.api.event.RollContext; +import nl.enjarai.doabarrelroll.api.rotation.RotationInstant; + +import java.util.function.BooleanSupplier; + +public final class RollContextImpl implements RollContext { + private final RotationInstant currentRotation; + private RotationInstant rotationDelta; + private final double renderDelta; + + public RollContextImpl(RotationInstant currentRotation, RotationInstant rotationDelta, double renderDelta) { + this.currentRotation = currentRotation; + this.rotationDelta = rotationDelta; + this.renderDelta = renderDelta; + } + + @Override + public RollContext useModifier(ConfiguresRotation modifier, BooleanSupplier condition) { + rotationDelta = rotationDelta.useModifier(rotationInstant -> modifier.apply(rotationInstant, this), condition); + return this; + } + + @Override + public RollContext useModifier(ConfiguresRotation modifier) { + rotationDelta = rotationDelta.useModifier(rotationInstant -> modifier.apply(rotationInstant, this)); + return this; + } + + @Override + public RotationInstant getCurrentRotation() { + return currentRotation; + } + + @Override + public RotationInstant getRotationDelta() { + return rotationDelta; + } + + @Override + public double getRenderDelta() { + return renderDelta; + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/impl/event/RollGroupImpl.java b/common/src/main/java/nl/enjarai/doabarrelroll/impl/event/RollGroupImpl.java new file mode 100644 index 00000000..e0653b0f --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/impl/event/RollGroupImpl.java @@ -0,0 +1,48 @@ +package nl.enjarai.doabarrelroll.impl.event; + +import net.minecraft.util.Identifier; +import nl.enjarai.doabarrelroll.api.event.RollEvents; +import nl.enjarai.doabarrelroll.api.event.RollGroup; +import nl.enjarai.doabarrelroll.api.event.TriState; + +import java.util.HashMap; +import java.util.function.Supplier; + +public class RollGroupImpl extends EventImpl implements RollGroup { + public static final HashMap instances = new HashMap<>(); + + public RollGroupImpl() { + RollEvents.SHOULD_ROLL_CHECK.register(this::get); + } + + @Override + public void trueIf(Supplier condition, int priority) { + register(() -> condition.get() ? TriState.TRUE : TriState.PASS, priority); + } + + @Override + public void trueIf(Supplier condition) { + trueIf(condition, 0); + } + + @Override + public void falseUnless(Supplier condition, int priority) { + register(() -> condition.get() ? TriState.PASS : TriState.FALSE, priority); + } + + @Override + public void falseUnless(Supplier condition) { + falseUnless(condition, 0); + } + + @Override + public Boolean get() { + for (var condition : getListeners()) { + var result = condition.shouldRoll(); + if (result != TriState.PASS) { + return result == TriState.TRUE; + } + } + return false; + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/impl/rotation/RotationInstantImpl.java b/common/src/main/java/nl/enjarai/doabarrelroll/impl/rotation/RotationInstantImpl.java new file mode 100644 index 00000000..adc42ea4 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/impl/rotation/RotationInstantImpl.java @@ -0,0 +1,44 @@ +package nl.enjarai.doabarrelroll.impl.rotation; + +import nl.enjarai.doabarrelroll.api.rotation.RotationInstant; +import nl.enjarai.doabarrelroll.config.Sensitivity; + +import java.util.function.BooleanSupplier; + +public record RotationInstantImpl(double pitch, double yaw, double roll) implements RotationInstant { + @Override + public RotationInstant add(double pitch, double yaw, double roll) { + return new RotationInstantImpl(this.pitch + pitch, this.yaw + yaw, this.roll + roll); + } + + @Override + public RotationInstant multiply(double pitch, double yaw, double roll) { + return new RotationInstantImpl(this.pitch * pitch, this.yaw * yaw, this.roll * roll); + } + + @Override + public RotationInstant addAbsolute(double x, double y, double currentRoll) { + double cos = Math.cos(currentRoll); + double sin = Math.sin(currentRoll); + return new RotationInstantImpl(this.pitch - y * cos - x * sin, this.yaw - y * sin + x * cos, this.roll); + } + + @Override + public RotationInstant applySensitivity(Sensitivity sensitivity) { + return new RotationInstantImpl( + pitch * sensitivity.pitch, + yaw * sensitivity.yaw, + roll * sensitivity.roll + ); + } + + @Override + public RotationInstant useModifier(ConfiguresRotation modifier, BooleanSupplier condition) { + return condition.getAsBoolean() ? modifier.apply(this) : this; + } + + @Override + public RotationInstant useModifier(ConfiguresRotation modifier) { + return useModifier(modifier, () -> true); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/math/Expression.java b/common/src/main/java/nl/enjarai/doabarrelroll/math/Expression.java new file mode 100644 index 00000000..6cae69bd --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/math/Expression.java @@ -0,0 +1,7 @@ +package nl.enjarai.doabarrelroll.math; + +import java.util.Map; + +public interface Expression { + double eval(Map vars); +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/math/ExpressionParser.java b/common/src/main/java/nl/enjarai/doabarrelroll/math/ExpressionParser.java new file mode 100644 index 00000000..d92a86df --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/math/ExpressionParser.java @@ -0,0 +1,184 @@ +package nl.enjarai.doabarrelroll.math; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class ExpressionParser extends Parser { + private static final Random RANDOM = new Random(); + + private Expression compiled; + private RuntimeException error; + + public static Expression parse(String string) { + return new ExpressionParser(string).build(); + } + + public ExpressionParser(String string) { + super(string); + } + + public Expression getCompiled() { + if (compiled == null && error == null) { + try { + return build(); + } catch (RuntimeException e) { + error = e; + return null; + } + } + return compiled; + } + + public Expression getCompiledOrDefaulting(double defaultValue) { + return hasError() ? vars -> defaultValue : getCompiled(); + } + + public RuntimeException getError() { + getCompiled(); + return error; + } + + public boolean hasError() { + getCompiled(); + return error != null; + } + + public Expression build() { + nextChar(); + var x = parseExpression(); + if (pos < string.length()) throw new RuntimeException("Unexpected character '" + ch + "' at position " + pos); + compiled = x; + return x; + } + + // Grammar: + // expression = term | expression `+` term | expression `-` term + // term = factor | term `*` factor | term `/` factor + // factor = `+` factor | `-` factor | `(` expression `)` | number + // | functionName `(` expression `)` | functionName factor + // | factor `^` factor + private Expression parseExpression() { + var x = parseTerm(); + while (true) { + if (weat('+')) { // addition + var a = x; + var b = parseTerm(); + x = vars -> a.eval(vars) + b.eval(vars); + } else if (weat('-')) { // subtraction + var a = x; + var b = parseTerm(); + x = vars -> a.eval(vars) - b.eval(vars); + } else return x; + } + } + + private Expression parseTerm() { + var x = parseFactor(); + while (true) { + if (weat('*')) { // multiplication + var a = x; + var b = parseFactor(); + x = vars -> a.eval(vars) * b.eval(vars); + } else if (weat('/')) { // division + var a = x; + var b = parseFactor(); + x = vars -> a.eval(vars) / b.eval(vars); + } else return x; + } + } + + private Expression parseFactor() { + if (weat('+')) { // unary plus + var a = parseFactor(); + return vars -> +a.eval(vars); + } + if (weat('-')) { // unary minus + var a = parseFactor(); + return vars -> -a.eval(vars); + } + Expression x; + var startPos = pos; + if (weat('(')) { // parentheses + x = parseExpression(); + if (!weat(')')) throw new RuntimeException("Missing ')' at position " + pos); + } else if (ch >= '0' && ch <= '9' || ch == '.') { // number literals + while (ch >= '0' && ch <= '9' || ch == '.') nextChar(); + var a = Double.parseDouble(string.substring(startPos, pos)); + x = vars -> a; + } else if (isVariableChar()) { + while (isVariableChar()) nextChar(); + var name = string.substring(startPos, pos); + List args = new ArrayList<>(); + if (weat('(')) { // functions + do { + args.add(parseExpression()); + } while (weat(',')); + if (!weat(')')) throw new RuntimeException("Missing ')' after argument to '" + name + "'"); + x = switch (args.size()) { + case 1 -> { + var a = args.get(0); + yield switch (name) { + case "sqrt" -> vars -> Math.sqrt(a.eval(vars)); + case "sin" -> vars -> Math.sin(a.eval(vars)); + case "cos" -> vars -> Math.cos(a.eval(vars)); + case "tan" -> vars -> Math.tan(a.eval(vars)); + case "asin" -> vars -> Math.asin(a.eval(vars)); + case "acos" -> vars -> Math.acos(a.eval(vars)); + case "atan" -> vars -> Math.atan(a.eval(vars)); + case "abs" -> vars -> Math.abs(a.eval(vars)); + case "ceil" -> vars -> Math.ceil(a.eval(vars)); + case "floor" -> vars -> Math.floor(a.eval(vars)); + case "log" -> vars -> Math.log(a.eval(vars)); + case "round" -> vars -> Math.round(a.eval(vars)); + case "randint" -> vars -> RANDOM.nextInt((int) a.eval(vars)); + default -> + throw new RuntimeException("Unknown function '" + name + "' for 1 arg at position " + (pos - name.length())); + }; + } + case 2 -> { + var a = args.get(0); + var b = args.get(1); + yield switch (name) { + case "min" -> vars -> Math.min(a.eval(vars), b.eval(vars)); + case "max" -> vars -> Math.max(a.eval(vars), b.eval(vars)); + case "randint" -> vars -> { + var av = a.eval(vars); + return av + RANDOM.nextInt((int) (b.eval(vars) - av)); + }; + default -> + throw new RuntimeException("Unknown function '" + name + "' for 2 args at position " + (pos - name.length())); + }; + } + default -> + throw new RuntimeException("Unknown function '" + name + "' for " + args.size() + " args at position " + (pos - name.length())); + }; + } else { // constants + var a = switch (name) { + case "PI" -> Math.PI; + case "E" -> Math.E; + case "TO_RAD" -> Math.PI / 180; + case "TO_DEG" -> 180 / Math.PI; + default -> + throw new RuntimeException("Unknown constant '" + name + "' at position " + (pos - name.length())); + }; + x = vars -> a; + } + } else if (weat('$')) { + while (isVariableChar()) nextChar(); + var variable = string.substring(startPos + 1, pos); + x = vars -> { + var value = vars.get(variable); + return value != null ? value : 0; // TODO better error handling + }; + } else { + throw new RuntimeException("Unexpected '" + ch + "' at position " + pos); + } + if (weat('^')) { // exponentiation + var a = x; + var b = parseFactor(); + x = vars -> Math.pow(a.eval(vars), b.eval(vars)); + } + return x; + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/math/MagicNumbers.java b/common/src/main/java/nl/enjarai/doabarrelroll/math/MagicNumbers.java new file mode 100644 index 00000000..222308fa --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/math/MagicNumbers.java @@ -0,0 +1,6 @@ +package nl.enjarai.doabarrelroll.math; + +public class MagicNumbers { + public static final double TORAD = Math.PI / 180; + public static final double TODEG = 1 / TORAD; +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/math/Parser.java b/common/src/main/java/nl/enjarai/doabarrelroll/math/Parser.java new file mode 100644 index 00000000..ec0433d1 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/math/Parser.java @@ -0,0 +1,51 @@ +package nl.enjarai.doabarrelroll.math; + +public abstract class Parser { + protected final String string; + protected int pos = -1; + protected char ch; + + protected Parser(String string) { + this.string = string; + } + + protected void nextChar() { + ch = ++pos < string.length() ? string.charAt(pos) : 0; + } + + protected boolean weat(char charToEat) { + while (Character.isWhitespace(ch)) nextChar(); + return eat(charToEat); + } + + protected boolean eat(char charToEat) { + if (ch == charToEat) { + nextChar(); + return true; + } + return false; + } + + protected boolean isVariableChar() { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '-' || ch == '_' || ch == ':'; + } + + public String getString() { + return string; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Parser parser = (Parser) o; + + return string.equals(parser.string); + } + + @Override + public int hashCode() { + return string.hashCode(); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/mixin/CommandManagerMixin.java b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/CommandManagerMixin.java new file mode 100644 index 00000000..be46db79 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/CommandManagerMixin.java @@ -0,0 +1,22 @@ +package nl.enjarai.doabarrelroll.mixin; + +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.network.ServerPlayerEntity; +import nl.enjarai.doabarrelroll.fabric.net.HandshakeServerFabric; +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; + +@Mixin(CommandManager.class) +public abstract class CommandManagerMixin { + @Inject( + method = "sendCommandTree", + at = @At(value = "RETURN") + ) + private void doABarrelRoll$doHandshake(ServerPlayerEntity player, CallbackInfo ci) { + // We do the handshake here since, aside from on join, this method will most likely also trigger + // in any situation where the player's permissions change + HandshakeServerFabric.sendHandshake(player); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/mixin/LivingEntityMixin.java b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/LivingEntityMixin.java new file mode 100644 index 00000000..4430251e --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/LivingEntityMixin.java @@ -0,0 +1,35 @@ +package nl.enjarai.doabarrelroll.mixin; + +import net.minecraft.entity.LivingEntity; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Slice; + +@Mixin(LivingEntity.class) +public abstract class LivingEntityMixin { + @ModifyVariable( + method = "travel", + slice = @Slice( + from = @At( + value = "INVOKE", + target = "Lnet/minecraft/util/math/Vec3d;horizontalLength()D", + ordinal = 1 + ) + ), + at = @At("STORE"), + index = 21, + require = 0 // We let this mixin fail if it needs to as a temporary workaround to be compatible with Connector. + ) + private float doABarrelRoll$modifyKineticDamage(float original) { + var damageType = DoABarrelRoll.CONFIG_HOLDER.instance.kineticDamage(); + + return switch (damageType) { + case VANILLA -> original; + case HIGH_SPEED -> original - 2.0f; + case NONE -> 0.0f; + case INSTANT_KILL -> Float.MAX_VALUE; + }; + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/EntityTrackerEntryMixin.java b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/EntityTrackerEntryMixin.java new file mode 100644 index 00000000..e16a2bb7 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/EntityTrackerEntryMixin.java @@ -0,0 +1,40 @@ +package nl.enjarai.doabarrelroll.mixin.roll; + +import net.minecraft.entity.Entity; +import net.minecraft.server.network.EntityTrackerEntry; +import nl.enjarai.doabarrelroll.api.RollEntity; +import nl.enjarai.doabarrelroll.net.RollSyncServer; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(EntityTrackerEntry.class) +public abstract class EntityTrackerEntryMixin { + @Shadow @Final private Entity entity; + + @Unique + private boolean lastIsRolling; + @Unique + private float lastRoll; + + @Inject( + method = "tick", + at = @At("TAIL") + ) + private void doABarrelRoll$syncRollS2C(CallbackInfo ci) { + var rollEntity = (RollEntity) entity; + var isRolling = rollEntity.doABarrelRoll$isRolling(); + var roll = rollEntity.doABarrelRoll$getRoll(); + + if (isRolling != lastIsRolling || roll != lastRoll) { + RollSyncServer.sendUpdates(entity); + + lastIsRolling = isRolling; + lastRoll = roll; + } + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/EntityMixin.java b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/EntityMixin.java new file mode 100644 index 00000000..feed1544 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/EntityMixin.java @@ -0,0 +1,50 @@ +package nl.enjarai.doabarrelroll.mixin.roll.entity; + +import net.minecraft.entity.Entity; +import net.minecraft.util.math.Vec3d; +import nl.enjarai.doabarrelroll.api.RollEntity; +import nl.enjarai.doabarrelroll.config.Sensitivity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(Entity.class) +public abstract class EntityMixin implements RollEntity { + @Shadow public abstract float getPitch(); + @Shadow public abstract float getYaw(); + @Shadow public abstract void setPitch(float pitch); + @Shadow public abstract void setYaw(float yaw); + @Shadow public abstract void changeLookDirection(double cursorDeltaX, double cursorDeltaY); + + @Shadow public abstract Vec3d getRotationVecClient(); + + @Override + public void doABarrelRoll$changeElytraLook(double pitch, double yaw, double roll, Sensitivity sensitivity, double mouseDelta) { + } + + @Override + public void doABarrelRoll$changeElytraLook(float pitch, float yaw, float roll) { + } + + @Override + public boolean doABarrelRoll$isRolling() { + return false; + } + + @Override + public void doABarrelRoll$setRolling(boolean rolling) { + } + + @Override + public float doABarrelRoll$getRoll() { + return 0; + } + + @Override + public float doABarrelRoll$getRoll(float tickDelta) { + return 0; + } + + @Override + public void doABarrelRoll$setRoll(float roll) { + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/LivingEntityMixin.java b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/LivingEntityMixin.java new file mode 100644 index 00000000..85052f96 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/LivingEntityMixin.java @@ -0,0 +1,17 @@ +package nl.enjarai.doabarrelroll.mixin.roll.entity; + +import net.minecraft.entity.LivingEntity; +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; + +@Mixin(LivingEntity.class) +public abstract class LivingEntityMixin extends EntityMixin { + @Inject( + method = "baseTick", + at = @At("TAIL") + ) + protected void doABarrelRoll$baseTickTail(CallbackInfo ci) { + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/PlayerEntityMixin.java b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/PlayerEntityMixin.java new file mode 100644 index 00000000..6643b966 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/mixin/roll/entity/PlayerEntityMixin.java @@ -0,0 +1,66 @@ +package nl.enjarai.doabarrelroll.mixin.roll.entity; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Util; +import net.minecraft.util.math.MathHelper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerEntity.class) +public abstract class PlayerEntityMixin extends LivingEntityMixin { + @Unique + protected boolean isRolling; + @Unique + protected float prevRoll; + @Unique + protected float roll; + + @Override + protected void doABarrelRoll$baseTickTail(CallbackInfo ci) { + prevRoll = doABarrelRoll$getRoll(); + + if (!doABarrelRoll$isRolling()) { + doABarrelRoll$setRoll(0.0f); + } + } + + @Override + public boolean doABarrelRoll$isRolling() { + return isRolling; + } + + @Override + public void doABarrelRoll$setRolling(boolean rolling) { + isRolling = rolling; + } + + @Override + public float doABarrelRoll$getRoll() { + return roll; + } + + @Override + public float doABarrelRoll$getRoll(float tickDelta) { + if (tickDelta == 1.0f) { + return doABarrelRoll$getRoll(); + } + return MathHelper.lerp(tickDelta, prevRoll, doABarrelRoll$getRoll()); + } + + @Override + public void doABarrelRoll$setRoll(float roll) { + if (!Float.isFinite(roll)) { + Util.error("Invalid entity rotation: " + roll + ", discarding."); + return; + } + var lastRoll = doABarrelRoll$getRoll(); + this.roll = roll; + + if (roll < -90 && lastRoll > 90) { + prevRoll -= 360; + } else if (roll > 90 && lastRoll < -90) { + prevRoll += 360; + } + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/net/HandshakeServer.java b/common/src/main/java/nl/enjarai/doabarrelroll/net/HandshakeServer.java new file mode 100644 index 00000000..12762223 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/net/HandshakeServer.java @@ -0,0 +1,192 @@ +package nl.enjarai.doabarrelroll.net; + +import com.google.gson.JsonElement; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; +import io.netty.buffer.Unpooled; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.config.LimitedModConfigServer; +import nl.enjarai.doabarrelroll.config.ModConfigServer; +import nl.enjarai.doabarrelroll.util.DelayedRunnable; + +import java.util.Map; +import java.util.WeakHashMap; +import java.util.function.Function; + +public class HandshakeServer { + // Protocol version 1: + // | Protocol version (int) | Config data (string) | + // Protocol version 2: + // | Protocol version (int) | Limited/full config data (string) | Is limited (boolean) | + // Protocol version 3: + // | Protocol version (int) | Limited config data (string) | Is limited (boolean) | [Full config data (string)] (only if not limited) | + public static final int PROTOCOL_VERSION = 3; + + private final ServerConfigHolder configHolder; + private final Map syncStates = new WeakHashMap<>(); + private final Map scheduledKicks = new WeakHashMap<>(); + private final Function getsLimitedCheck; + private final Codec transferCodec = ModConfigServer.CODEC; + private final Codec limitedTransferCodec = LimitedModConfigServer.getCodec(); + + public HandshakeServer(ServerConfigHolder configHolder, Function getsLimitedCheck) { + this.configHolder = configHolder; + this.getsLimitedCheck = getsLimitedCheck; + } + + public void tick(MinecraftServer server) { + var it = scheduledKicks.entrySet().iterator(); + while (it.hasNext()) { + var entry = it.next(); + if (entry.getValue().isDone()) { + it.remove(); + } else { + entry.getValue().tick(); + } + } + } + + public ClientInfo getHandshakeState(ServerPlayerEntity player) { + return getHandshakeState(player.networkHandler); + } + + public ClientInfo getHandshakeState(ServerPlayNetworkHandler handler) { + return syncStates.computeIfAbsent(handler, key -> new ClientInfo(HandshakeState.NOT_SENT, PROTOCOL_VERSION, true)); + } + + public PacketByteBuf getConfigSyncBuf(ServerPlayNetworkHandler handler) { + return getConfigSyncBuf(handler, getHandshakeState(handler).protocolVersion); + } + + @SuppressWarnings("NonStrictComparisonCanBeEquality") + public PacketByteBuf getConfigSyncBuf(ServerPlayNetworkHandler handler, int protocolVersion) { + protocolVersion = Math.min(protocolVersion, PROTOCOL_VERSION); + var buf = new PacketByteBuf(Unpooled.buffer()); + + // Protocol version + buf.writeInt(protocolVersion); + + // Config data + var isLimited = getsLimitedCheck.apply(handler); + getHandshakeState(handler).isLimited = isLimited; + var config = configHolder.instance; + DataResult data; + if (protocolVersion == 2) { + Codec codec = isLimited ? limitedTransferCodec : transferCodec; + data = codec.encodeStart(JsonOps.INSTANCE, config); + } else { + data = limitedTransferCodec.encodeStart(JsonOps.INSTANCE, config.getLimited(handler)); + } + try { + buf.writeString(data.getOrThrow(false, DoABarrelRoll.LOGGER::error).toString()); + } catch (RuntimeException e) { + DoABarrelRoll.LOGGER.error("Failed to encode config", e); + buf.writeString("{}"); + } + + if (protocolVersion >= 2) { + // Limited status + buf.writeBoolean(isLimited); + } + + if (protocolVersion >= 3 && !isLimited) { + // Operator modifiable config + var data2 = transferCodec.encodeStart(JsonOps.INSTANCE, config); + try { + buf.writeString(data2.getOrThrow(false, DoABarrelRoll.LOGGER::error).toString()); + } catch (RuntimeException e) { + DoABarrelRoll.LOGGER.error("Failed to encode config", e); + buf.writeString("{}"); + } + } + + return buf; + } + + public void configSentToClient(ServerPlayNetworkHandler handler) { + getHandshakeState(handler).state = HandshakeState.SENT; + + var config = configHolder.instance; + if (config.getSyncTimeout() != null) { + scheduledKicks.put(handler, new DelayedRunnable(config.getSyncTimeout(), () -> { + if (getHandshakeState(handler).state != HandshakeState.ACCEPTED) { + DoABarrelRoll.LOGGER.warn( + "{} did not accept config syncing, config indicates we kick them.", + handler.getPlayer().getName().getString() + ); + handler.disconnect(config.getSyncTimeoutMessage()); + } + })); + } + } + + public HandshakeState clientReplied(ServerPlayNetworkHandler handler, PacketByteBuf buf) { + var info = getHandshakeState(handler); + var player = handler.getPlayer(); + + if (info.state == HandshakeState.SENT) { + try { + var protocolVersion = buf.readInt(); + if (protocolVersion < 1 || protocolVersion > PROTOCOL_VERSION) { + DoABarrelRoll.LOGGER.warn( + "Client of {} sent unknown protocol version, expected range 1-{}, got {}. Will attempt to proceed anyway.", + player.getName().getString(), + PROTOCOL_VERSION, + protocolVersion + ); + } + + if (protocolVersion == 2 && info.protocolVersion != 2) { + DoABarrelRoll.LOGGER.info("Client of {} is using an older protocol version, resending.", player.getName().getString()); + info.state = HandshakeState.RESEND; + } else if (buf.readBoolean()) { + DoABarrelRoll.LOGGER.info("Client of {} accepted server config.", player.getName().getString()); + info.state = HandshakeState.ACCEPTED; + } else { + DoABarrelRoll.LOGGER.warn( + "Client of {} failed to process server config, check client logs find what went wrong.", + player.getName().getString()); + info.state = HandshakeState.FAILED; + } + info.protocolVersion = protocolVersion; + } catch (IndexOutOfBoundsException e) { + DoABarrelRoll.LOGGER.warn( + "Client of {} sent invalid config reply.", + player.getName().getString() + ); + info.state = HandshakeState.FAILED; + } + } + + return info.state; + } + + public void playerDisconnected(ServerPlayNetworkHandler handler) { + syncStates.remove(handler); + } + + public static class ClientInfo { + public HandshakeState state; + public int protocolVersion; + public boolean isLimited; + + public ClientInfo(HandshakeState state, int protocolVersion, boolean isLimited) { + this.state = state; + this.protocolVersion = protocolVersion; + this.isLimited = isLimited; + } + } + + public enum HandshakeState { + NOT_SENT, + SENT, + ACCEPTED, + FAILED, + RESEND + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/net/RollSyncServer.java b/common/src/main/java/nl/enjarai/doabarrelroll/net/RollSyncServer.java new file mode 100644 index 00000000..0871bfc7 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/net/RollSyncServer.java @@ -0,0 +1,40 @@ +package nl.enjarai.doabarrelroll.net; + +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PlayerLookup; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.entity.Entity; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.util.math.MathHelper; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.api.RollEntity; + +public class RollSyncServer { + public static void startListening(ServerPlayNetworkHandler handler) { + ServerPlayNetworking.registerReceiver(handler, DoABarrelRoll.ROLL_CHANNEL, (server, player, handler1, buf, responseSender) -> { + var rollPlayer = (RollEntity) player; + + var isRolling = buf.readBoolean(); + var roll = buf.readFloat(); + + rollPlayer.doABarrelRoll$setRolling(isRolling); + rollPlayer.doABarrelRoll$setRoll(isRolling ? MathHelper.wrapDegrees(roll) : 0); + }); + } + + public static void sendUpdates(Entity entity) { + var rollEntity = (RollEntity) entity; + var isRolling = rollEntity.doABarrelRoll$isRolling(); + var roll = rollEntity.doABarrelRoll$getRoll(); + + var buf = PacketByteBufs.create(); + buf.writeInt(entity.getId()); + buf.writeBoolean(isRolling); + buf.writeFloat(roll); + + PlayerLookup.tracking(entity).stream() + .filter(player -> DoABarrelRoll.HANDSHAKE_SERVER.getHandshakeState(player).state == HandshakeServer.HandshakeState.ACCEPTED) + .filter(player -> player != entity) + .forEach(player -> ServerPlayNetworking.send(player, DoABarrelRoll.ROLL_CHANNEL, buf)); + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/net/ServerConfigHolder.java b/common/src/main/java/nl/enjarai/doabarrelroll/net/ServerConfigHolder.java new file mode 100644 index 00000000..e527fabc --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/net/ServerConfigHolder.java @@ -0,0 +1,156 @@ +package nl.enjarai.doabarrelroll.net; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParser; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.config.ModConfigServer; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.function.BiConsumer; + +public class ServerConfigHolder { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + public final Path configFile; + public final Codec codec; + public final T defaultConfig; + private BiConsumer updateCallback; + public T instance; + + public ServerConfigHolder(Path configFile, Codec codec, T defaultConfig, BiConsumer updateCallback) { + this.configFile = configFile; + this.codec = codec; + this.defaultConfig = defaultConfig; + this.updateCallback = updateCallback; + + load(); + } + + public void load() { + T config = null; + + if (Files.exists(configFile)) { + // An existing config is present, we should use its values + try (BufferedReader fileReader = new BufferedReader( + new InputStreamReader(Files.newInputStream(configFile), StandardCharsets.UTF_8) + )) { + // Parses the config file and puts the values into config object + config = codec.decode(JsonOps.INSTANCE, JsonParser.parseReader(fileReader)) + .getOrThrow(false, e -> { + throw new RuntimeException(e); + }) + .getFirst(); + } catch (IOException | RuntimeException e) { + DoABarrelRoll.LOGGER.error("Failed to parse server config file, regenerating: ", e); + } + } + // config will be null if the file doesn't exist or if it failed to parse + if (config == null || !config.isValid()) { + config = defaultConfig; + } + + instance = config; + // Saves the file in order to write new fields if they were added + save(); + } + + public void save() { + try { + // Creates the file if it doesn't exist + Files.createDirectories(configFile.getParent()); + // Writes the config to the file + Files.writeString(configFile, GSON.toJson(codec.encodeStart(JsonOps.INSTANCE, instance) + .getOrThrow(false, e -> { + throw new RuntimeException(e); + })), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException | RuntimeException e) { + DoABarrelRoll.LOGGER.error("Failed to save server config file: ", e); + } + } + + public PacketByteBuf clientSendsUpdate(ServerPlayerEntity player, PacketByteBuf buf) { + var info = DoABarrelRoll.HANDSHAKE_SERVER.getHandshakeState(player); + var accepted = info.state == HandshakeServer.HandshakeState.ACCEPTED; + var hasPermission = ModConfigServer.canModify(player.networkHandler); + + // Only players that have accepted the handshake and have permission can update the config + if (!accepted || !hasPermission) { + DoABarrelRoll.LOGGER.warn( + "Client of {} tried to update the server config, but is not allowed to. Rejecting.", + player.getName().getString() + ); + return getFailureBuf(); + } + + try { + var protocolVersion = buf.readInt(); + if (protocolVersion != 1) { + DoABarrelRoll.LOGGER.warn( + "Client of {} sent unknown protocol version for server config update, expected 1, got {}. Will attempt to proceed anyway.", + player.getName().getString(), + protocolVersion + ); + } + + var data = buf.readString(); + var newConfig = codec.parse(JsonOps.INSTANCE, JsonParser.parseString(data)) + .getOrThrow(false, DoABarrelRoll.LOGGER::error); + + if (!newConfig.isValid()) { + throw new RuntimeException("Config arrived, but contains invalid values"); + } + + // We send our response in another try block to make errors make sense + try { + data = codec.encodeStart(JsonOps.INSTANCE, newConfig) + .getOrThrow(false, DoABarrelRoll.LOGGER::error).toString(); + + var res = PacketByteBufs.create(); + res.writeInt(1); + res.writeBoolean(true); + res.writeString(data); + + DoABarrelRoll.LOGGER.info( + "{} updated the server config.", + player.getName().getString() + ); + + // Only set our instance if everything else succeeds + instance = newConfig; + updateCallback.accept(player.getServer(), instance); + save(); + return res; + } catch (RuntimeException e) { + DoABarrelRoll.LOGGER.error("Failed to encode config", e); + return getFailureBuf(); + } + } catch (RuntimeException e) { + DoABarrelRoll.LOGGER.warn( + "Client of {} sent invalid server config update, rejecting.", + player.getName().getString(), + e + ); + return getFailureBuf(); + } + } + + private PacketByteBuf getFailureBuf() { + var res = PacketByteBufs.create(); + res.writeInt(1); + res.writeBoolean(false); + return res; + } +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/net/SyncableConfig.java b/common/src/main/java/nl/enjarai/doabarrelroll/net/SyncableConfig.java new file mode 100644 index 00000000..c5af0e23 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/net/SyncableConfig.java @@ -0,0 +1,12 @@ +package nl.enjarai.doabarrelroll.net; + +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.text.Text; + +public interface SyncableConfig { + Integer getSyncTimeout(); + + Text getSyncTimeoutMessage(); + + L getLimited(ServerPlayNetworkHandler handler); +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/net/ValidatableConfig.java b/common/src/main/java/nl/enjarai/doabarrelroll/net/ValidatableConfig.java new file mode 100644 index 00000000..ee2cd6e2 --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/net/ValidatableConfig.java @@ -0,0 +1,5 @@ +package nl.enjarai.doabarrelroll.net; + +public interface ValidatableConfig { + boolean isValid(); +} diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/platform/Services.java b/common/src/main/java/nl/enjarai/doabarrelroll/platform/Services.java new file mode 100644 index 00000000..e134644e --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/platform/Services.java @@ -0,0 +1,28 @@ +package nl.enjarai.doabarrelroll.platform; + +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.platform.services.PlatformHelper; + +import java.util.ServiceLoader; + +// Service loaders are a built-in Java feature that allow us to locate implementations of an interface that vary from one +// environment to another. In the context of MultiLoader we use this feature to access a mock API in the common code that +// is swapped out for the platform specific implementation at runtime. +public class Services { + // In this example we provide a platform helper which provides information about what platform the mod is running on. + // For example this can be used to check if the code is running on Forge vs Fabric, or to ask the modloader if another + // mod is loaded. + public static final PlatformHelper PLATFORM = load(PlatformHelper.class); + + // This code is used to load a service for the current environment. Your implementation of the service must be defined + // manually by including a text file in META-INF/services named with the fully qualified class name of the service. + // Inside the file you should write the fully qualified class name of the implementation to load for the platform. For + // example our file on Forge points to ForgePlatformHelper while Fabric points to FabricPlatformHelper. + public static T load(Class clazz) { + final T loadedService = ServiceLoader.load(clazz) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); + DoABarrelRoll.LOGGER.debug("Loaded {} for service {}", loadedService, clazz); + return loadedService; + } +} \ No newline at end of file diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/platform/services/PlatformHelper.java b/common/src/main/java/nl/enjarai/doabarrelroll/platform/services/PlatformHelper.java new file mode 100644 index 00000000..bf60b21f --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/platform/services/PlatformHelper.java @@ -0,0 +1,21 @@ +package nl.enjarai.doabarrelroll.platform.services; + +import net.fabricmc.loader.api.FabricLoader; + +public interface PlatformHelper { + /** + * Gets the name of the current platform + * + * @return The name of the current platform. + */ + String getPlatformName(); + + /** + * Gets the name of the environment type as a string. + * + * @return The name of the environment type. + */ + static String getEnvironmentName() { + return FabricLoader.getInstance().isDevelopmentEnvironment() ? "development" : "production"; + } +} \ No newline at end of file diff --git a/common/src/main/java/nl/enjarai/doabarrelroll/util/DelayedRunnable.java b/common/src/main/java/nl/enjarai/doabarrelroll/util/DelayedRunnable.java new file mode 100644 index 00000000..7e506b9b --- /dev/null +++ b/common/src/main/java/nl/enjarai/doabarrelroll/util/DelayedRunnable.java @@ -0,0 +1,22 @@ +package nl.enjarai.doabarrelroll.util; + +public class DelayedRunnable { + private final Runnable runnable; + private final int delay; + private int ticks = 0; + + public DelayedRunnable(int delay, Runnable runnable) { + this.runnable = runnable; + this.delay = delay; + } + + public void tick() { + if (++ticks >= delay) { + runnable.run(); + } + } + + public boolean isDone() { + return ticks >= delay; + } +} diff --git a/common/src/main/resources/do_a_barrel_roll.accesswidener b/common/src/main/resources/do_a_barrel_roll.accesswidener new file mode 100644 index 00000000..5c86bfd7 --- /dev/null +++ b/common/src/main/resources/do_a_barrel_roll.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named + +accessible field net/minecraft/client/option/KeyBinding boundKey Lnet/minecraft/client/util/InputUtil$Key; \ No newline at end of file diff --git a/common/src/main/resources/do_a_barrel_roll.mixins.json b/common/src/main/resources/do_a_barrel_roll.mixins.json new file mode 100644 index 00000000..f16e84d3 --- /dev/null +++ b/common/src/main/resources/do_a_barrel_roll.mixins.json @@ -0,0 +1,23 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "nl.enjarai.doabarrelroll.mixin", + "refmap": "${mod_id}.refmap.json", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "CommandManagerMixin", + "LivingEntityMixin", + "roll.EntityTrackerEntryMixin", + "roll.entity.EntityMixin", + "roll.entity.LivingEntityMixin", + "roll.entity.PlayerEntityMixin" + ], + "client": [ + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} + diff --git a/common/src/main/resources/pack.mcmeta b/common/src/main/resources/pack.mcmeta new file mode 100644 index 00000000..52854ec4 --- /dev/null +++ b/common/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "${mod_name}", + "pack_format": 8 + } +} \ No newline at end of file diff --git a/fabric/build.gradle b/fabric/build.gradle new file mode 100644 index 00000000..34915c68 --- /dev/null +++ b/fabric/build.gradle @@ -0,0 +1,90 @@ +plugins { + id 'java' + id 'idea' + id 'maven-publish' + id 'dev.architectury.loom' +} +base { + archivesName = "${mod_name}-fabric-${minecraft_version}" +} +dependencies { + minecraft "com.mojang:minecraft:${minecraft_version}" + mappings "net.fabricmc:yarn:${yarn_version}:v2" + modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_version}" + implementation project(":common") + + include modImplementation("nl.enjarai:cicada-lib:${project.cicada_version}") { + exclude group: "net.fabricmc.fabric-api" + } + + modImplementation("dev.isxander.yacl:yet-another-config-lib-fabric:${project.yacl_version}") { + exclude group: "net.fabricmc" + } + + modCompileOnly("dev.isxander:controlify:${project.controlify_version}") { + exclude group: "net.fabricmc" + exclude group: "maven.modrinth" + exclude group: "com.github.CaffeineMC" + } + + // Mod Menu integration. + modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}" + + implementation(include("com.github.bawnorton.mixinsquared:mixinsquared-fabric:${project.mixin_squared_version}")) + + include(modImplementation("me.lucko:fabric-permissions-api:${project.permissions_api_version}")) +} + +loom { + if (project(":common").file("src/main/resources/${mod_id}.accesswidener").exists()) { + accessWidenerPath.set(project(":common").file("src/main/resources/${mod_id}.accesswidener")) + } + mixin { + defaultRefmapName.set("${mod_id}.refmap.json") + } + runs { + client { + client() + setConfigName("Fabric Client") + ideConfigGenerated(true) + runDir("run") + } + server { + server() + setConfigName("Fabric Server") + ideConfigGenerated(true) + runDir("run") + } + } +} + +sourceSets.main.resources.srcDir 'src/main/generated' + +tasks.withType(JavaCompile).configureEach { + source(project(":common").sourceSets.main.allSource) +} +tasks.withType(Javadoc).configureEach { + source(project(":common").sourceSets.main.allJava) +} +tasks.named("sourcesJar", Jar) { + from(project(":common").sourceSets.main.allSource) +} + +processResources { + from project(":common").sourceSets.main.resources +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId base.archivesName.get() + from components.java + } + } + repositories { + maven { + url "file://" + System.getenv("local_maven") + } + } +} diff --git a/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/cameraoverhaul/CameraOverhaulPlugin.java b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/cameraoverhaul/CameraOverhaulPlugin.java new file mode 100644 index 00000000..f5329bd9 --- /dev/null +++ b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/cameraoverhaul/CameraOverhaulPlugin.java @@ -0,0 +1,12 @@ +package nl.enjarai.doabarrelroll.compat.cameraoverhaul; + +import nl.enjarai.doabarrelroll.compat.CompatMixinPlugin; + +import java.util.Set; + +public class CameraOverhaulPlugin implements CompatMixinPlugin { + @Override + public Set getRequiredMods() { + return Set.of("cameraoverhaul"); + } +} diff --git a/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/cameraoverhaul/mixin/CameraSystemMixin.java b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/cameraoverhaul/mixin/CameraSystemMixin.java new file mode 100644 index 00000000..bc98308d --- /dev/null +++ b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/cameraoverhaul/mixin/CameraSystemMixin.java @@ -0,0 +1,69 @@ +package nl.enjarai.doabarrelroll.compat.cameraoverhaul.mixin; + +import net.minecraft.client.MinecraftClient; +import nl.enjarai.doabarrelroll.api.RollEntity; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Pseudo +@Mixin(targets = "mirsario.cameraoverhaul.common.systems.CameraSystem") +public abstract class CameraSystemMixin { + private boolean allowModifications() { + return !(MinecraftClient.getInstance().getCameraEntity() instanceof RollEntity rollEntity && rollEntity.doABarrelRoll$isRolling()); + } + + @Dynamic + @ModifyArg( + method = "OnCameraUpdate(Lnet/minecraft/entity/Entity;Lnet/minecraft/client/render/Camera;Lmirsario/cameraoverhaul/core/structures/Transform;F)V", + at = @At( + value = "INVOKE", + target = "Lmirsario/cameraoverhaul/common/systems/CameraSystem;VerticalVelocityPitchOffset(Lmirsario/cameraoverhaul/core/structures/Transform;Lmirsario/cameraoverhaul/core/structures/Transform;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Vec2f;DFF)V" + ), + index = 5 + ) + private float doABarrelRoll$cancelVerticalVelocityPitchOffset(float original) { + return allowModifications() ? original : 0f; + } + + @Dynamic + @ModifyArg( + method = "OnCameraUpdate(Lnet/minecraft/entity/Entity;Lnet/minecraft/client/render/Camera;Lmirsario/cameraoverhaul/core/structures/Transform;F)V", + at = @At( + value = "INVOKE", + target = "Lmirsario/cameraoverhaul/common/systems/CameraSystem;ForwardVelocityPitchOffset(Lmirsario/cameraoverhaul/core/structures/Transform;Lmirsario/cameraoverhaul/core/structures/Transform;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Vec2f;DFF)V" + ), + index = 5 + ) + private float doABarrelRoll$cancelForwardVelocityPitchOffset(float original) { + return allowModifications() ? original : 0f; + } + + @Dynamic + @ModifyArg( + method = "OnCameraUpdate(Lnet/minecraft/entity/Entity;Lnet/minecraft/client/render/Camera;Lmirsario/cameraoverhaul/core/structures/Transform;F)V", + at = @At( + value = "INVOKE", + target = "Lmirsario/cameraoverhaul/common/systems/CameraSystem;YawDeltaRollOffset(Lmirsario/cameraoverhaul/core/structures/Transform;Lmirsario/cameraoverhaul/core/structures/Transform;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Vec2f;DFFF)V" + ), + index = 5 + ) + private float doABarrelRoll$cancelYawDeltaRollOffset(float original) { + return allowModifications() ? original : 0f; + } + + @Dynamic + @ModifyArg( + method = "OnCameraUpdate(Lnet/minecraft/entity/Entity;Lnet/minecraft/client/render/Camera;Lmirsario/cameraoverhaul/core/structures/Transform;F)V", + at = @At( + value = "INVOKE", + target = "Lmirsario/cameraoverhaul/common/systems/CameraSystem;StrafingRollOffset(Lmirsario/cameraoverhaul/core/structures/Transform;Lmirsario/cameraoverhaul/core/structures/Transform;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Vec2f;DFF)V" + ), + index = 5 + ) + private float doABarrelRoll$cancelStrafingRollOffset(float original) { + return allowModifications() ? original : 0f; + } +} diff --git a/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/ControlifyCompat.java b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/ControlifyCompat.java new file mode 100644 index 00000000..766a2e38 --- /dev/null +++ b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/ControlifyCompat.java @@ -0,0 +1,124 @@ +package nl.enjarai.doabarrelroll.compat.controlify; + +import dev.isxander.controlify.api.ControlifyApi; +import dev.isxander.controlify.api.bind.BindingSupplier; +import dev.isxander.controlify.api.bind.ControlifyBindingsApi; +import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint; +import dev.isxander.controlify.bindings.GamepadBinds; +import net.minecraft.text.Text; +import net.minecraft.util.math.MathHelper; +import nl.enjarai.doabarrelroll.DoABarrelRoll; +import nl.enjarai.doabarrelroll.DoABarrelRollClient; +import nl.enjarai.doabarrelroll.api.event.RollContext; +import nl.enjarai.doabarrelroll.api.event.RollEvents; +import nl.enjarai.doabarrelroll.api.event.ThrustEvents; +import nl.enjarai.doabarrelroll.api.rotation.RotationInstant; +import nl.enjarai.doabarrelroll.config.ModConfig; + +public class ControlifyCompat implements ControlifyEntrypoint { + public static BindingSupplier PITCH_UP; + public static BindingSupplier PITCH_DOWN; + public static BindingSupplier ROLL_LEFT; + public static BindingSupplier ROLL_RIGHT; + public static BindingSupplier YAW_LEFT; + public static BindingSupplier YAW_RIGHT; + public static BindingSupplier THRUST_FORWARD; + public static BindingSupplier THRUST_BACKWARD; + + private RotationInstant applyToRotation(RotationInstant rotationDelta, RollContext context) { + var controller = ControlifyApi.get().currentController(); + var sensitivity = ModConfig.INSTANCE.getControllerSensitivity(); + + if (PITCH_UP.onController(controller) == null) return rotationDelta; + + double multiplier = context.getRenderDelta() * 1200; + + float pitchAxis = PITCH_DOWN.onController(controller).state() - PITCH_UP.onController(controller).state(); + float yawAxis = YAW_RIGHT.onController(controller).state() - YAW_LEFT.onController(controller).state(); + float rollAxis = ROLL_RIGHT.onController(controller).state() - ROLL_LEFT.onController(controller).state(); + + pitchAxis *= multiplier * sensitivity.pitch; + yawAxis *= multiplier * sensitivity.yaw; + rollAxis *= multiplier * sensitivity.roll; + + return rotationDelta.add(pitchAxis, yawAxis, rollAxis); + } + + public static double getThrustModifier() { + if (ControlifyApi.get().getCurrentController().isEmpty()) { + return 0; + } + var controller = ControlifyApi.get().getCurrentController().get(); + + float forward = THRUST_FORWARD.onController(controller).state(); + float backward = THRUST_BACKWARD.onController(controller).state(); + return forward - backward; + } + + public static RotationInstant manageThrottle(RotationInstant rotationInstant, RollContext context) { + var delta = context.getRenderDelta(); + + DoABarrelRollClient.throttle += getThrustModifier() * delta; + DoABarrelRollClient.throttle = MathHelper.clamp(DoABarrelRollClient.throttle, 0, ModConfig.INSTANCE.getMaxThrust()); + + return rotationInstant; + } + + @Override + public void onControlifyPreInit(ControlifyApi controlifyApi) { + var bindings = ControlifyBindingsApi.get(); + PITCH_UP = bindings.registerBind(DoABarrelRoll.id("pitch_up"), builder -> builder + .defaultBind(GamepadBinds.RIGHT_STICK_FORWARD) + .category(Text.translatable("controlify.category.do_a_barrel_roll.do_a_barrel_roll")) + .name(Text.translatable("controlify.bind.do_a_barrel_roll.pitch_up")) + ); + PITCH_DOWN = bindings.registerBind(DoABarrelRoll.id("pitch_down"), builder -> builder + .defaultBind(GamepadBinds.RIGHT_STICK_BACKWARD) + .category(Text.translatable("controlify.category.do_a_barrel_roll.do_a_barrel_roll")) + .name(Text.translatable("controlify.bind.do_a_barrel_roll.pitch_down")) + ); + ROLL_LEFT = bindings.registerBind(DoABarrelRoll.id("roll_left"), builder -> builder + .defaultBind(GamepadBinds.RIGHT_STICK_LEFT) + .category(Text.translatable("controlify.category.do_a_barrel_roll.do_a_barrel_roll")) + .name(Text.translatable("controlify.bind.do_a_barrel_roll.roll_left")) + ); + ROLL_RIGHT = bindings.registerBind(DoABarrelRoll.id("roll_right"), builder -> builder + .defaultBind(GamepadBinds.RIGHT_STICK_RIGHT) + .category(Text.translatable("controlify.category.do_a_barrel_roll.do_a_barrel_roll")) + .name(Text.translatable("controlify.bind.do_a_barrel_roll.roll_right")) + ); + YAW_LEFT = bindings.registerBind(DoABarrelRoll.id("yaw_left"), builder -> builder + .defaultBind(GamepadBinds.LEFT_STICK_LEFT) + .category(Text.translatable("controlify.category.do_a_barrel_roll.do_a_barrel_roll")) + .name(Text.translatable("controlify.bind.do_a_barrel_roll.yaw_left")) + ); + YAW_RIGHT = bindings.registerBind(DoABarrelRoll.id("yaw_right"), builder -> builder + .defaultBind(GamepadBinds.LEFT_STICK_RIGHT) + .category(Text.translatable("controlify.category.do_a_barrel_roll.do_a_barrel_roll")) + .name(Text.translatable("controlify.bind.do_a_barrel_roll.yaw_right")) + ); + THRUST_FORWARD = bindings.registerBind(DoABarrelRoll.id("thrust_forward"), builder -> builder + .defaultBind(GamepadBinds.LEFT_STICK_FORWARD) + .category(Text.translatable("controlify.category.do_a_barrel_roll.do_a_barrel_roll")) + .name(Text.translatable("controlify.bind.do_a_barrel_roll.thrust_forward")) + ); + THRUST_BACKWARD = bindings.registerBind(DoABarrelRoll.id("thrust_backward"), builder -> builder + .defaultBind(GamepadBinds.LEFT_STICK_BACKWARD) + .category(Text.translatable("controlify.category.do_a_barrel_roll.do_a_barrel_roll")) + .name(Text.translatable("controlify.bind.do_a_barrel_roll.thrust_backward")) + ); + + RollEvents.EARLY_CAMERA_MODIFIERS.register(context -> context + .useModifier(ControlifyCompat::manageThrottle, ModConfig.INSTANCE::getEnableThrust), + 8, DoABarrelRollClient::isFallFlying); + RollEvents.LATE_CAMERA_MODIFIERS.register(context -> context + .useModifier(this::applyToRotation), + 5, DoABarrelRollClient::isFallFlying); + + ThrustEvents.MODIFY_THRUST_INPUT.register(input -> input + getThrustModifier()); + } + + @Override + public void onControllersDiscovered(ControlifyApi controlifyApi) { + } +} diff --git a/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/ControlifyPlugin.java b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/ControlifyPlugin.java new file mode 100644 index 00000000..8b822172 --- /dev/null +++ b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/ControlifyPlugin.java @@ -0,0 +1,12 @@ +package nl.enjarai.doabarrelroll.compat.controlify; + +import nl.enjarai.doabarrelroll.compat.CompatMixinPlugin; + +import java.util.Set; + +public class ControlifyPlugin implements CompatMixinPlugin { + @Override + public Set getRequiredMods() { + return Set.of("controlify"); + } +} diff --git a/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/mixin/InGameInputHandlerMixin.java b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/mixin/InGameInputHandlerMixin.java new file mode 100644 index 00000000..45902461 --- /dev/null +++ b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/controlify/mixin/InGameInputHandlerMixin.java @@ -0,0 +1,28 @@ +package nl.enjarai.doabarrelroll.compat.controlify.mixin; + +import dev.isxander.controlify.ingame.InGameInputHandler; +import net.minecraft.client.MinecraftClient; +import nl.enjarai.doabarrelroll.api.RollEntity; +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.CallbackInfo; + +@Mixin(InGameInputHandler.class) +public abstract class InGameInputHandlerMixin { + @Shadow @Final private MinecraftClient minecraft; + + @Inject( + method = "processPlayerLook", + at = @At("HEAD"), + cancellable = true, + remap = false + ) + private void doABarrelRoll$cancelDefaultLookHandling(CallbackInfo ci) { + if (minecraft.player instanceof RollEntity rollEntity && rollEntity.doABarrelRoll$isRolling()) { + ci.cancel(); + } + } +} diff --git a/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/modmenu/ModMenuIntegration.java b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/modmenu/ModMenuIntegration.java new file mode 100644 index 00000000..1adc45db --- /dev/null +++ b/fabric/src/main/java/nl/enjarai/doabarrelroll/compat/modmenu/ModMenuIntegration.java @@ -0,0 +1,21 @@ +package nl.enjarai.doabarrelroll.compat.modmenu; + +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ConfirmScreen; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; +import net.minecraft.util.Util; +import nl.enjarai.doabarrelroll.compat.Compat; +import nl.enjarai.doabarrelroll.compat.yacl.YACLImplementation; +import nl.enjarai.doabarrelroll.config.ModConfigScreen; + +import java.net.URI; + +public class ModMenuIntegration implements ModMenuApi { + @Override + public ConfigScreenFactory getModConfigScreenFactory() { + return ModConfigScreen::create; + } +} diff --git a/fabric/src/main/java/nl/enjarai/doabarrelroll/platform/FabricPlatformHelper.java b/fabric/src/main/java/nl/enjarai/doabarrelroll/platform/FabricPlatformHelper.java new file mode 100644 index 00000000..c515c215 --- /dev/null +++ b/fabric/src/main/java/nl/enjarai/doabarrelroll/platform/FabricPlatformHelper.java @@ -0,0 +1,10 @@ +package nl.enjarai.doabarrelroll.platform; + +import nl.enjarai.doabarrelroll.platform.services.PlatformHelper; + +public class FabricPlatformHelper implements PlatformHelper { + @Override + public String getPlatformName() { + return "Fabric"; + } +} diff --git a/fabric/src/main/resources/META-INF/services/nl.enjarai.doabarrelroll.platform.services.PlatformHelper b/fabric/src/main/resources/META-INF/services/nl.enjarai.doabarrelroll.platform.services.PlatformHelper new file mode 100644 index 00000000..6933884e --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/nl.enjarai.doabarrelroll.platform.services.PlatformHelper @@ -0,0 +1 @@ +nl.enjarai.doabarrelroll.platform.FabricPlatformHelper \ No newline at end of file diff --git a/fabric/src/main/resources/do_a_barrel_roll.fabric.mixins.json b/fabric/src/main/resources/do_a_barrel_roll.fabric.mixins.json new file mode 100644 index 00000000..f6527df4 --- /dev/null +++ b/fabric/src/main/resources/do_a_barrel_roll.fabric.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "nl.enjarai.doabarrelroll.mixin", + "refmap": "${mod_id}.refmap.json", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..07d449fa --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,73 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${mod_version}", + "name": "${mod_name}", + "description": "${mod_description}", + "authors": [ + "${mod_author}" + ], + "contributors": [ + "${mod_credits}" + ], + "contact": { + "homepage": "${mod_url}", + "sources": "${mod_sources_url}", + "issues": "${mod_issue_tracker_url}" + }, + "license": "${mod_license}", + "icon": "assets/${mod_id}/icon.png", + "environment": "*", + "entrypoints": { + "client": [ + "nl.enjarai.doabarrelroll.fabric.DoABarrelRollFabricClient" + ], + "main": [ + "nl.enjarai.doabarrelroll.fabric.DoABarrelRollFabric" + ], + "modmenu": [ + "nl.enjarai.doabarrelroll.compat.modmenu.ModMenuIntegration" + ], + "cicada": [ + "nl.enjarai.doabarrelroll.DoABarrelRoll" + ], + "controlify": [ + "nl.enjarai.doabarrelroll.compat.controlify.ControlifyCompat" + ], + "mixinsquared": [ + "nl.enjarai.doabarrelroll.fabric.DoABarrelRollFabric" + ] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}.fabric.mixins.json" + ], + "accessWidener": "${mod_id}.accesswidener", + + "suggests": { + "yet_another_config_lib_v3": ">=3.1.0" + }, + "depends": { + "fabricloader": ">=0.15", + "fabric": "*", + "minecraft": "${fabric_minecraft_version_range}", + "java": ">=17", + "cicada": "*" + }, + "breaks": { + "optifabric": "*" + }, + + "custom": { + "modmenu": { + "links": { + "modmenu.discord": "https://discord.gg/WcYsDDQtyR" + } + }, + "projects": { + "modrinth": "6FtRfnLg", + "curseforge": 663658 + } + } +} + \ No newline at end of file diff --git a/forge/build.gradle b/forge/build.gradle new file mode 100644 index 00000000..6ff62867 --- /dev/null +++ b/forge/build.gradle @@ -0,0 +1,92 @@ +plugins { + id 'java' + id 'idea' + id 'maven-publish' + id 'dev.architectury.loom' +} + +base { + archivesName = "${mod_name}-forge-${minecraft_version}" +} + +loom { + forge { + mixinConfig "${mod_id}.mixins.json" + mixinConfig "${mod_id}.forge.mixins.json" + + convertAccessWideners = true + } + + mixin { + defaultRefmapName.set("${mod_id}.refmap.json") + } + + runs { + client { + client() + setConfigName("Forge Client") + ideConfigGenerated(true) + runDir("run") + } + server { + server() + setConfigName("Forge Server") + ideConfigGenerated(true) + runDir("run") + } + } +} + +sourceSets.main.resources.srcDir 'src/generated/resources' + +repositories { + maven { + name = "Sinytra" + url = "https://maven.su5ed.dev/releases" + } + mavenLocal() +} + +dependencies { + minecraft "com.mojang:minecraft:${minecraft_version}" + forge "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + mappings "net.fabricmc:yarn:${yarn_version}:v2" + + modImplementation "dev.su5ed.sinytra.fabric-api:fabric-api:${forge_fabric_version}" + + compileOnly project(":common") + + modImplementation("dev.isxander.yacl:yet-another-config-lib-forge:${project.yacl_version}") { + exclude group: "net.fabricmc" + } + + implementation(include("com.github.bawnorton.mixinsquared:mixinsquared-forge:${project.mixin_squared_version}")) +} + +tasks.withType(JavaCompile).configureEach { + source(project(":common").sourceSets.main.allSource) +} +tasks.withType(Javadoc).configureEach { + source(project(":common").sourceSets.main.allJava) +} +tasks.named("sourcesJar", Jar) { + from(project(":common").sourceSets.main.allSource) +} + +processResources { + from project(":common").sourceSets.main.resources +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId base.archivesName.get() + from components.java + } + } + repositories { + maven { + url "file://" + System.getenv("local_maven") + } + } +} diff --git a/forge/gradle.properties b/forge/gradle.properties new file mode 100644 index 00000000..0c22b907 --- /dev/null +++ b/forge/gradle.properties @@ -0,0 +1,2 @@ +loom.platform=forge +mod_version=${file.jarVersion} \ No newline at end of file diff --git a/forge/src/main/java/nl/enjarai/doabarrelroll/platform/ForgePlatformHelper.java b/forge/src/main/java/nl/enjarai/doabarrelroll/platform/ForgePlatformHelper.java new file mode 100644 index 00000000..4ffa1c7d --- /dev/null +++ b/forge/src/main/java/nl/enjarai/doabarrelroll/platform/ForgePlatformHelper.java @@ -0,0 +1,10 @@ +package nl.enjarai.doabarrelroll.platform; + +import nl.enjarai.doabarrelroll.platform.services.PlatformHelper; + +public class ForgePlatformHelper implements PlatformHelper { + @Override + public String getPlatformName() { + return "Forge"; + } +} \ No newline at end of file diff --git a/forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml new file mode 100644 index 00000000..d03e3a9e --- /dev/null +++ b/forge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,72 @@ +# This is an example mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml +# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader = "javafml" #mandatory +loaderVersion="${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. +# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. +# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. +license = "${mod_license}" +# A URL to refer people to when problems occur with this mod +#issueTrackerURL="${mod_issue_tracker_url}" #optional +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory +# The modid of the mod +modId = "${mod_id}" #mandatory +# The version number of the mod +version = "${mod_version}" #mandatory +# A display name for the mod +displayName = "${mod_name}" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/ +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional +# A URL for the "homepage" for this mod, displayed in the mod UI +#displayURL="${mod_url}" #optional (displayed in the mod UI) +# A file name (in the root of the mod JAR) containing a logo for display +logoFile = "multiloader.png" #optional +# A text field displayed in the mod UI +credits = "${mod_credits}" #optional +# A text field displayed in the mod UI +authors = "${mod_author}" #optional +# Display Test controls the display for your mod in the server connection screen +# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. +# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. +# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. +# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. +# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. +#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) + +# The description text for the mod (multi line!) (#mandatory) +description='''${mod_description}''' +[[dependencies.'${mod_id}']] #optional + # the modid of the dependency + modId = "forge" #mandatory + # Does this dependency have to exist - if not, ordering below must be specified + mandatory = true #mandatory + # The version range of the dependency + versionRange = "${forge_version_range}" #mandatory + # An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory + # BEFORE - This mod is loaded BEFORE the dependency + # AFTER - This mod is loaded AFTER the dependency + ordering = "NONE" + # Side this dependency is applied on - BOTH, CLIENT, or SERVER + side = "BOTH" # Side this dependency is applied on - 'BOTH', 'CLIENT' or 'SERVER' +[[dependencies.'${mod_id}']] + modId = "minecraft" + mandatory = true + # This version range declares a minimum of the current minecraft version up to but not including the next major version + versionRange = "${forge_minecraft_version_range}" + ordering = "NONE" + side = "BOTH" +[[dependencies.'${mod_id}']] + modId = "fabric_api" + mandatory = true + # This version range declared a minimum of the current forgified fabric api version up to any later version + versionRange = "[${forge_fabric_version},)" + +# Features are specific properties of the game environment, that you may want to declare you require. This example declares +# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't +# stop your mod loading on the server for example. +#[features.${mod_id}] +#openGLVersion="[3.2,)" diff --git a/forge/src/main/resources/META-INF/services/nl.enjarai.doabarrelroll.platform.services.PlatformHelper b/forge/src/main/resources/META-INF/services/nl.enjarai.doabarrelroll.platform.services.PlatformHelper new file mode 100644 index 00000000..74b37777 --- /dev/null +++ b/forge/src/main/resources/META-INF/services/nl.enjarai.doabarrelroll.platform.services.PlatformHelper @@ -0,0 +1 @@ +nl.enjarai.doabarrelroll.platform.ForgePlatformHelper \ No newline at end of file diff --git a/forge/src/main/resources/do_a_barrel_roll.forge.mixins.json b/forge/src/main/resources/do_a_barrel_roll.forge.mixins.json new file mode 100644 index 00000000..408e7947 --- /dev/null +++ b/forge/src/main/resources/do_a_barrel_roll.forge.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "nl.enjarai.doabarrelroll.mixin", + "refmap": "${mod_id}.refmap.json", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 06d0b8d6..3617620c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,23 +1,54 @@ -org.gradle.jvmargs=-Xmx4096M +# Gradle +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false -minecraft_version=1.20 +# Project +mod_version=3.4.0 +group=com.example.examplemod -archives_base_name=do-a-barrel-roll -mod_version=3.3.7 -maven_group=nl.enjarai +# Common +minecraft_version=1.20.1 +yarn_version=1.20.1+build.10 -yarn_version=1.20+build.1 +# Forge +# The Forge version must agree with the Minecraft version to get a valid artifact +forge_version=47.1.3 +# The Forge version range can use any version of Forge as bounds or match the loader version range +forge_version_range=[47.1.3,) +# The loader version range can only use the major version of Forge/FML as bounds +loader_version_range=[47,) +# The Minecraft version range can use any release version of Minecraft as bounds. +# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly +# as they do not follow standard versioning conventions. +forge_minecraft_version_range=[1.20.1,1.21) +# Forgified API version +forge_fabric_version=0.86.1+1.7.2+1.20.1 +# Forgified Fabric Loader version +forge_fabric_loader_version=2.2.1+0.14.21+1.20.1 -loader_version=0.14.24 -fabric_version=0.83.0+1.20 +# Fabric +fabric_version=0.86.1+1.20.1 +fabric_loader_version=0.15.3 +fabric_minecraft_version_range=>=1.20- <=1.20.1 +# Mod options +mod_name=Do a Barrel Roll +mod_author=enjarai +mod_credits=Originally based on Cool Elytra Roll by Jorbon, mod icon by Mizeno +mod_id=do_a_barrel_roll +mod_description=Microsoft flight simulator for Minecraft elytras. +mod_license=GPL-3.0 +mod_url=https://enjarai.dev/ +mod_sources_url=https://github.com/enjarai/do-a-barrel-roll +mod_issue_tracker_url=https://github.com/enjarai/do-a-barrel-roll/issues + +# Dependencies cicada_version=0.5.0+1.20.1-minus # https://modrinth.com/mod/modmenu/versions#all-versions modmenu_version=7.2.1 # https://github.com/isXander/YetAnotherConfigLib/releases -yacl_version=3.1.0+1.20 +yacl_version=3.2.1+1.20 # https://github.com/isXander/Controlify/releases controlify_version=1.3.0-beta.2+1.20 -mixin_extras_version=0.2.0 mixin_squared_version=0.1.1 -permissions_api_version=0.2-SNAPSHOT +permissions_api_version=0.2-SNAPSHOT \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 9e784a62..2433ae41 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,9 +1,19 @@ pluginManagement { repositories { - maven { url "https://maven.fabricmc.net/" } - maven { url "https://maven.enjarai.nl/mirrors"} - mavenCentral() gradlePluginPortal() + maven { + name = 'Forge' + url = 'https://maven.minecraftforge.net/' + } + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + maven { + name = 'Architectury' + url = "https://maven.architectury.dev/" + } + maven { url "https://maven.enjarai.nl/mirrors"} } resolutionStrategy { eachPlugin { @@ -17,5 +27,12 @@ pluginManagement { } } -rootProject.name = "do-a-barrel-roll" +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' +} +// This should match the folder name of the project, or else IDEA may complain (see https://youtrack.jetbrains.com/issue/IDEA-317606) +rootProject.name = 'do-a-barrel-roll' +include("common") +include("fabric") +include("forge")