commit a967ba29522122dd6c53ef9dee81dc04fc35a259 Author: TheBrokenRail Date: Tue Aug 11 17:00:26 2020 -0400 1.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fb81fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# gradle + +.gradle/ +build/ +out/ +classes/ + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# vscode + +.settings/ +.vscode/ +bin/ +.classpath +.project + +# fabric + +run/ + +remappedSrc/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..40f7511 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +**1.0.0** +* Initial Release \ No newline at end of file diff --git a/FILE_FORMAT.md b/FILE_FORMAT.md new file mode 100644 index 0000000..d1b7b9a --- /dev/null +++ b/FILE_FORMAT.md @@ -0,0 +1,22 @@ +# File Format +- All emotes are stored in ``data//emotes/.emote`` +- All lines that start with ``#`` are comments +- Every non-comment line represents a frame of animation +- In ideal conditions emotes run at 20FPS +- All frames are made up of part rotations separated by spaces +- Parts can only be specified once in a frame + +## Part Rotation Format +``` + +``` + +## Available Parts +- ``left_leg`` +- ``right_leg`` +- ``left_arm`` +- ``right_arm`` +- ``main_arm`` +- ``off_arm`` +- ``body`` +- ``left_arm``/``right_arm`` is not compatible with ``main_arm``/``off_arm`` \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..e5fd2b6 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,32 @@ +pipeline { + agent { + docker { + image 'openjdk:11-jdk' + } + } + stages { + stage('Build') { + steps { + sh './gradlew build' + } + post { + success { + archiveArtifacts artifacts: 'build/libs/*', fingerprint: true + } + } + } + stage('Publish') { + when { + expression { + return sh(returnStdout: true, script: 'git tag --contains').trim().length() > 0 + } + } + steps { + sh './gradlew publish' + withCredentials([string(credentialsId: 'curseforge_key', variable: 'CURSEFORGE_KEY')]) { + sh './gradlew -Pcurseforge.api_key="${CURSEFORGE_KEY}" curseforge' + } + } + } + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..886b254 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 TheBrokenRail + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c344c85 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Gestus +A server-side mod that adds data-driven emotes to Minecraft that can be viewed by vanilla clients. + +## File Format +[View File Format](FILE_FORMAT.md) + +## Changelog +[View Changelog](CHANGELOG.md) + +## CurseForge +[View CurseForge](https://www.curseforge.com/minecraft/mc-mods/gestus) \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e5ee3ed --- /dev/null +++ b/build.gradle @@ -0,0 +1,105 @@ +plugins { + id "fabric-loom" version "0.5-SNAPSHOT" + id "com.matthewprenger.cursegradle" version "1.4.0" + id "maven-publish" +} + +compileJava { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +version = "${project.mod_version}+${project.minecraft_version}" +group = project.maven_group as Object + +loom { + accessWidener "src/main/resources/gestus.accesswidener" +} + +dependencies { + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.minecraft_version}+build.${project.yarn_build}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.fabric_loader_version}" + + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}" +} + +publishing { + publications { + //noinspection GroovyAssignabilityCheck + mavenJava(MavenPublication) { + //noinspection GroovyAssignabilityCheck + artifact(remapJar) { + //noinspection GroovyAssignabilityCheck + builtBy remapJar + } + } + } + repositories { + maven { + url "/data/maven" + } + } +} + +processResources { + inputs.property "version", project.version + + from(sourceSets.main.resources.srcDirs) { + include "fabric.mod.json" + expand "version": project.version + } + + from(sourceSets.main.resources.srcDirs) { + exclude "fabric.mod.json" + } +} + +// ensure that the encoding is set to UTF-8, no matter what the system default is +// this fixes some edge cases with special characters not displaying correctly +// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} + +// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task +// if it is present. +// If you remove this task, sources will not be generated. +task sourcesJar(type: Jar, dependsOn: classes) { + classifier "sources" + from sourceSets.main.allSource +} + +jar { + from "LICENSE" +} + +javadoc { + title "Gestus v${version}" +} + +if (project.hasProperty("curseforge.api_key")) { + curseforge { + apiKey = project.getProperty("curseforge.api_key") + //noinspection GroovyAssignabilityCheck + project { + id = project.curseforge_id + changelog = "A changelog can be found at https://gitea.thebrokenrail.com/TheBrokenRail/Gestus/src/branch/master/CHANGELOG.md" + releaseType = mod_version.startsWith("0") || mod_version.endsWith("-unstable") ? "beta" : "release" + addGameVersion project.simple_minecraft_version + addGameVersion "Fabric" + mainArtifact(remapJar) { + displayName = "Gestus v${mod_version} for ${project.minecraft_version}" + } + afterEvaluate { + uploadTask.dependsOn("remapJar") + } + relations { + requiredDependency "fabric-api" + } + } + options { + forgeGradleIntegration = false + } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..ecf51ca --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs = -Xmx1G + +# Fabric Properties + # check these on https://fabricmc.net/use + minecraft_version = 1.16.2 + curseforge_id = 401707 + simple_minecraft_version = 1.16.2 + yarn_build = 1 + fabric_loader_version = 0.9.0+build.204 + +# Mod Properties + mod_version = 1.0.0 + maven_group = com.thebrokenrail + +# Dependencies + # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api + fabric_api_version = 0.17.2+build.396-1.16 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..62d4c05 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bbe11aa --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Aug 04 12:47:39 EDT 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..fbd7c51 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..5093609 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..6984092 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,12 @@ +pluginManagement { + repositories { + jcenter() + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} + +rootProject.name = 'gestus' \ No newline at end of file diff --git a/src/main/java/com/thebrokenrail/gestus/Gestus.java b/src/main/java/com/thebrokenrail/gestus/Gestus.java new file mode 100644 index 0000000..70bec3c --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/Gestus.java @@ -0,0 +1,39 @@ +package com.thebrokenrail.gestus; + +import com.thebrokenrail.gestus.command.EmoteCommand; +import com.thebrokenrail.gestus.entity.FakePlayerEntity; +import com.thebrokenrail.gestus.util.ServerPlayerEntityExtension; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRegistry; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder; +import net.minecraft.entity.EntityDimensions; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.SpawnGroup; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +public class Gestus implements ModInitializer { + public static final String NAMESPACE = "gestus"; + + public static final EntityType FAKE_PLAYER_ENTITY_TYPE = FabricEntityTypeBuilder.create(SpawnGroup.MISC, (EntityType.EntityFactory) FakePlayerEntity::new).disableSaving().disableSummon().dimensions(EntityDimensions.fixed(0f, 0f)).fireImmune().build(); + + @Override + public void onInitialize() { + Registry.register(Registry.ENTITY_TYPE, new Identifier(NAMESPACE, "fake_player"), FAKE_PLAYER_ENTITY_TYPE); + FabricDefaultAttributeRegistry.register(FAKE_PLAYER_ENTITY_TYPE, LivingEntity.createLivingAttributes()); + + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> EmoteCommand.register(dispatcher)); + } + + /** + * Play Custom Emote + * @param player Player + * @param emote Emote ID + */ + public static void playCustomEmote(ServerPlayerEntity player, Identifier emote) { + ((ServerPlayerEntityExtension) player).getShadow().playCustomEmote(emote); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/client/GestusClient.java b/src/main/java/com/thebrokenrail/gestus/client/GestusClient.java new file mode 100644 index 0000000..b2d4bfa --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/client/GestusClient.java @@ -0,0 +1,16 @@ +package com.thebrokenrail.gestus.client; + +import com.thebrokenrail.gestus.Gestus; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.rendereregistry.v1.EntityRendererRegistry; +import net.minecraft.client.render.entity.ArmorStandEntityRenderer; + +@Environment(EnvType.CLIENT) +public class GestusClient implements ClientModInitializer { + @Override + public void onInitializeClient() { + EntityRendererRegistry.INSTANCE.register(Gestus.FAKE_PLAYER_ENTITY_TYPE, (dispatcher, context) -> new ArmorStandEntityRenderer(dispatcher)); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/command/EmoteCommand.java b/src/main/java/com/thebrokenrail/gestus/command/EmoteCommand.java new file mode 100644 index 0000000..016c677 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/command/EmoteCommand.java @@ -0,0 +1,55 @@ +package com.thebrokenrail.gestus.command; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.thebrokenrail.gestus.Gestus; +import com.thebrokenrail.gestus.emote.EmoteRegistry; +import net.minecraft.command.argument.EntityArgumentType; +import net.minecraft.command.argument.IdentifierArgumentType; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.CommandSource; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.LiteralText; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Identifier; + +import java.util.Collection; +import java.util.Collections; + +public class EmoteCommand { + private static final DynamicCommandExceptionType NOT_FOUND_EXCEPTION = new DynamicCommandExceptionType(object -> new LiteralText(new TranslatableText("command." + Gestus.NAMESPACE + ".emote.unknown_emote", object).getString())); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(CommandManager.literal("emote") + .then(CommandManager.argument("emote", IdentifierArgumentType.identifier()) + .suggests((ctx, builder) -> ctx.getSource() != null ? CommandSource.suggestIdentifiers(EmoteRegistry.getAll(), builder) : Suggestions.empty()) + .executes(ctx -> run(ctx, Collections.singletonList(ctx.getSource().getPlayer()))) + .then(CommandManager.argument("player", EntityArgumentType.players()) + .requires(source -> source.hasPermissionLevel(2)) + .executes(ctx -> run(ctx, EntityArgumentType.getPlayers(ctx, "player"))) + ) + ) + ); + } + + private static int run(CommandContext ctx, Collection players) throws CommandSyntaxException { + Identifier emote = IdentifierArgumentType.getIdentifier(ctx, "emote"); + if (EmoteRegistry.get(emote) != null) { + int i = 0; + for (ServerPlayerEntity player : players) { + if (player != null) { + Gestus.playCustomEmote(player, emote); + i++; + } + } + ctx.getSource().sendFeedback(new LiteralText(new TranslatableText("command." + Gestus.NAMESPACE + ".emote.playing_emote").getString()), false); + return i; + } else { + throw NOT_FOUND_EXCEPTION.create(emote); + } + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/emote/Emote.java b/src/main/java/com/thebrokenrail/gestus/emote/Emote.java new file mode 100644 index 0000000..4ce5aaa --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/emote/Emote.java @@ -0,0 +1,88 @@ +package com.thebrokenrail.gestus.emote; + +import com.thebrokenrail.gestus.emote.exception.EmoteSyntaxException; +import net.minecraft.util.math.EulerAngle; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Emote { + public final EmoteFrame[] frames; + + private static int getArmMode(EmotePart part) { + if (part == EmotePart.LEFT_ARM || part == EmotePart.RIGHT_ARM) { + return 1; + } else if (part == EmotePart.MAIN_ARM || part == EmotePart.OFF_ARM) { + return 2; + } else { + return 0; + } + } + + public Emote(String[] lines) throws EmoteSyntaxException { + List list = new ArrayList<>(); + + EmotePart[] parts = EmotePart.values(); + + for (int lineNum = 0; lineNum < lines.length; lineNum++) { + String line = lines[lineNum].trim(); + + if (line.equals("maintain") && list.size() > 0) { + list.add(list.get(list.size() - 1)); + } else if (!line.startsWith("#")) { + int armMode = 0; + + Map map = new HashMap<>(); + + String[] lineParts = line.split(" "); + + for (int i = 0; i < lineParts.length; i++) { + String linePart = lineParts[i]; + + boolean found = false; + EmotePart selectedPart = null; + for (EmotePart part : parts) { + if (linePart.equals(part.getName())) { + selectedPart = part; + found = true; + break; + } + } + + if (!found) { + throw new EmoteSyntaxException(lineNum, "Invalid Emote Part: " + linePart); + } else if (map.containsKey(selectedPart)) { + throw new EmoteSyntaxException(lineNum, "Duplicate Emote Part: " + linePart); + } else if (armMode == 0) { + armMode = getArmMode(selectedPart); + } else if (armMode > 0) { + int newMode = getArmMode(selectedPart); + if (newMode != 0 && newMode != armMode) { + throw new EmoteSyntaxException(lineNum, "Conflicting Arm Modes"); + } + } + + try { + String yawStr = lineParts[++i]; + String pitchStr = lineParts[++i]; + String rollStr = lineParts[++i]; + + float yaw = Float.parseFloat(yawStr); + float pitch = -Float.parseFloat(pitchStr); + float roll = Float.parseFloat(rollStr); + + map.put(selectedPart, new EulerAngle(pitch, yaw, roll)); + } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) { + throw new EmoteSyntaxException(lineNum, "No Rotation Specified: " + selectedPart.getName()); + } + } + + list.add(new EmoteFrame(map)); + } + } + + frames = list.toArray(new EmoteFrame[0]); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/emote/EmoteFrame.java b/src/main/java/com/thebrokenrail/gestus/emote/EmoteFrame.java new file mode 100644 index 0000000..66c234b --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/emote/EmoteFrame.java @@ -0,0 +1,38 @@ +package com.thebrokenrail.gestus.emote; + +import net.minecraft.util.math.EulerAngle; + +import java.util.HashMap; +import java.util.Map; + +public class EmoteFrame { + public final Map rightHanded; + public final Map leftHanded; + + public EmoteFrame(Map data) { + rightHanded = resolve(data, false); + leftHanded = resolve(data, true); + } + + private static Map resolve(Map data, boolean leftHanded) { + Map result = new HashMap<>(); + for (Map.Entry entry : data.entrySet()) { + if (entry.getKey() == EmotePart.MAIN_ARM) { + if (leftHanded) { + result.put(EmotePart.LEFT_ARM, new EulerAngle(entry.getValue().getPitch(), -entry.getValue().getYaw(), -entry.getValue().getRoll())); + } else { + result.put(EmotePart.RIGHT_ARM, entry.getValue()); + } + } else if (entry.getKey() == EmotePart.OFF_ARM) { + if (leftHanded) { + result.put(EmotePart.RIGHT_ARM, new EulerAngle(entry.getValue().getPitch(), -entry.getValue().getYaw(), -entry.getValue().getRoll())); + } else { + result.put(EmotePart.LEFT_ARM, entry.getValue()); + } + } else { + result.put(entry.getKey(), entry.getValue()); + } + } + return result; + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/emote/EmoteLayer.java b/src/main/java/com/thebrokenrail/gestus/emote/EmoteLayer.java new file mode 100644 index 0000000..caf05a4 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/emote/EmoteLayer.java @@ -0,0 +1,62 @@ +package com.thebrokenrail.gestus.emote; + +import net.minecraft.util.Identifier; +import net.minecraft.util.math.EulerAngle; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class EmoteLayer { + private int frame = 0; + private Identifier current; + private final boolean loop; + + private boolean leftHanded = false; + + public EmoteLayer(Identifier start, boolean loop) { + current = start; + this.loop = loop; + } + + public Identifier getCurrent() { + return current; + } + + public void play(Identifier current, boolean leftHanded) { + if (!Objects.equals(this.current, current) || this.leftHanded != leftHanded) { + this.current = current; + this.leftHanded = leftHanded; + frame = 0; + } + } + + public boolean isPlaying() { + return current != null; + } + + public boolean isLeftHanded() { + return leftHanded; + } + + public Map next() { + if (current != null) { + Emote emote = EmoteRegistry.get(current); + if (emote != null) { + EmoteFrame result = emote.frames[frame]; + frame++; + if (frame >= emote.frames.length) { + if (loop) { + frame = frame % emote.frames.length; + } else { + frame = 0; + current = null; + } + } + return leftHanded ? result.leftHanded : result.rightHanded; + } + } + frame = 0; + return new HashMap<>(); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/emote/EmotePart.java b/src/main/java/com/thebrokenrail/gestus/emote/EmotePart.java new file mode 100644 index 0000000..79af54a --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/emote/EmotePart.java @@ -0,0 +1,21 @@ +package com.thebrokenrail.gestus.emote; + +public enum EmotePart { + MAIN_ARM("main_arm"), + OFF_ARM("off_arm"), + RIGHT_ARM("right_arm"), + LEFT_ARM("left_arm"), + RIGHT_LEG("right_leg"), + LEFT_LEG("left_leg"), + BODY("body"); + + private final String name; + + EmotePart(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/emote/EmoteRegistry.java b/src/main/java/com/thebrokenrail/gestus/emote/EmoteRegistry.java new file mode 100644 index 0000000..7b9f82c --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/emote/EmoteRegistry.java @@ -0,0 +1,75 @@ +package com.thebrokenrail.gestus.emote; + +import com.thebrokenrail.gestus.emote.exception.EmoteLoadingException; +import com.thebrokenrail.gestus.emote.exception.EmoteSyntaxException; +import net.minecraft.resource.Resource; +import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.SynchronousResourceReloadListener; +import net.minecraft.util.Identifier; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class EmoteRegistry { + private static final Map map = new HashMap<>(); + + private static final String DATA_TYPE = "emotes"; + private static final String FILE_EXTENSION = ".emote"; + + public static class ReloadListener implements SynchronousResourceReloadListener { + public static final ReloadListener INSTANCE = new ReloadListener(); + + private ReloadListener() { + } + + @Override + public void apply(ResourceManager manager) { + EmoteRegistry.reload(manager); + } + } + + public static void reload(ResourceManager resourceManager) throws EmoteLoadingException { + map.clear(); + + Collection files = resourceManager.findResources(DATA_TYPE, str -> str.endsWith(FILE_EXTENSION)); + for (Identifier file : files) { + Identifier id = new Identifier(file.getNamespace(), file.getPath().substring(DATA_TYPE.length() + 1, file.getPath().length() - FILE_EXTENSION.length())); + + try { + Resource resource = resourceManager.getResource(file); + + InputStream inputStream = resource.getInputStream(); + + List result = new ArrayList<>(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + for (String line; (line = reader.readLine()) != null; ) { + result.add(line); + } + reader.close(); + + Emote emote = new Emote(result.toArray(new String[0])); + + map.put(id, emote); + } catch (IOException | EmoteSyntaxException e) { + throw new EmoteLoadingException(id, e); + } + } + } + + public static Emote get(Identifier id) { + return map.get(id); + } + + public static Set getAll() { + return map.keySet(); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/emote/exception/EmoteLoadingException.java b/src/main/java/com/thebrokenrail/gestus/emote/exception/EmoteLoadingException.java new file mode 100644 index 0000000..1fdf882 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/emote/exception/EmoteLoadingException.java @@ -0,0 +1,9 @@ +package com.thebrokenrail.gestus.emote.exception; + +import net.minecraft.util.Identifier; + +public class EmoteLoadingException extends RuntimeException { + public EmoteLoadingException(Identifier id, Exception cause) { + super(String.format("Unable To Load: %s", id.toString()), cause); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/emote/exception/EmoteSyntaxException.java b/src/main/java/com/thebrokenrail/gestus/emote/exception/EmoteSyntaxException.java new file mode 100644 index 0000000..37de908 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/emote/exception/EmoteSyntaxException.java @@ -0,0 +1,7 @@ +package com.thebrokenrail.gestus.emote.exception; + +public class EmoteSyntaxException extends Exception { + public EmoteSyntaxException(int lineNum, String message) { + super(lineNum + ": " + message); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/entity/FakePlayerEntity.java b/src/main/java/com/thebrokenrail/gestus/entity/FakePlayerEntity.java new file mode 100644 index 0000000..ec35ccc --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/entity/FakePlayerEntity.java @@ -0,0 +1,271 @@ +package com.thebrokenrail.gestus.entity; + +import com.thebrokenrail.gestus.Gestus; +import com.thebrokenrail.gestus.emote.EmoteLayer; +import com.thebrokenrail.gestus.emote.EmotePart; +import com.thebrokenrail.gestus.mixin.ArmorStandEntityAccessor; +import com.thebrokenrail.gestus.util.ServerPlayerEntityExtension; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.item.CrossbowItem; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.Arm; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.UseAction; +import net.minecraft.util.math.EulerAngle; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.Map; + +@SuppressWarnings("EntityConstructor") +public class FakePlayerEntity extends ArmorStandEntity { + private static final int STILL_LAYER = 0; + private static final int WALK_LAYER = 1; + private static final int HIT_LAYER = 2; + private static final int CUSTOM_LAYER = 3; + private static final int AIM_LAYER = 4; + + private static final Identifier STILL_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/still"); // Still Layer + + private static final Identifier WALK_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/walk"); // Walk Layer + private static final Identifier SPRINT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/sprint"); // Walk Layer + private static final Identifier RIDE_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/ride"); // Walk Layer + + private static final Identifier HIT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/hit"); // Hit Layer + + private static final Identifier AIM_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/aim"); // Aim Layer + private static final Identifier TRIDENT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/trident"); // Aim Layer + private static final Identifier SHIELD_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/shield"); // Aim Layer + + public final ServerPlayerEntity player; + + private final EmoteLayer[] layers; + + public FakePlayerEntity(EntityType entityType, World world, Text customName, ServerPlayerEntity player) { + super(entityType, world); + setSilent(true); + ((ArmorStandEntityAccessor) this).callSetShowArms(true); + ((ArmorStandEntityAccessor) this).callSetHideBasePlate(true); + setInvulnerable(true); + setNoGravity(true); + setCustomName(customName); + setCustomNameVisible(true); + noClip = true; + this.player = player; + + layers = new EmoteLayer[5]; + layers[STILL_LAYER] = new EmoteLayer(STILL_EMOTE, true); // Still Layer + layers[WALK_LAYER] = new EmoteLayer(null, false); // Walk Layer + layers[HIT_LAYER] = new EmoteLayer(null, false); // Hit Layer + layers[CUSTOM_LAYER] = new EmoteLayer(null, false); // Custom Layer + layers[AIM_LAYER] = new EmoteLayer(null, false); // Aim Layer + } + + public FakePlayerEntity(EntityType entityType, World world) { + this(entityType, world, new LiteralText("Invalid"), null); + if (world.isClient()) { + throw new UnsupportedOperationException(); + } + } + + @Override + public void tick() { + super.tick(); + if (getEntityWorld().isClient()) { + throw new UnsupportedOperationException(); + } else if (player == null || player.removed || ((ServerPlayerEntityExtension) player).getShadow() != this) { + remove(); + } + } + + private void applyPose() { + for (EmoteLayer layer : layers) { + applyPose(layer); + } + } + + private void applyPose(EmoteLayer layer) { + Map data = layer.next(); + for (Map.Entry entry : data.entrySet()) { + TrackedData tracker; + switch (entry.getKey()) { + case RIGHT_ARM: { + tracker = TRACKER_RIGHT_ARM_ROTATION; + break; + } + case LEFT_ARM: { + tracker = TRACKER_LEFT_ARM_ROTATION; + break; + } + case RIGHT_LEG: { + tracker = TRACKER_RIGHT_LEG_ROTATION; + break; + } + case LEFT_LEG: { + tracker = TRACKER_LEFT_LEG_ROTATION; + break; + } + case BODY: { + tracker = TRACKER_BODY_ROTATION; + break; + } + default: { + throw new UnsupportedOperationException(); + } + } + getDataTracker().set(tracker, entry.getValue()); + } + } + + private static final double MINIMUM_MOVEMENT_FOR_ANIMATION = 0.02d; + + private void updateWalk() { + if (player.hasVehicle()) { + layers[WALK_LAYER].play(RIDE_EMOTE, false); + } else if (!layers[WALK_LAYER].isPlaying()) { + Vec3d lastPos = ((ServerPlayerEntityExtension) player).getLastPos(); + Vec3d pos = player.getPos(); + if (lastPos != null && pos != null && (Math.abs(pos.getX() - lastPos.getX()) >= MINIMUM_MOVEMENT_FOR_ANIMATION || Math.abs(pos.getZ() - lastPos.getZ()) >= MINIMUM_MOVEMENT_FOR_ANIMATION)) { + if (player.isSprinting()) { + layers[WALK_LAYER].play(SPRINT_EMOTE, false); + } else { + layers[WALK_LAYER].play(WALK_EMOTE, false); + } + } + } + } + + private boolean updateAim(Hand hand, boolean active) { + ItemStack stack = player.getStackInHand(hand); + + Identifier emote = null; + + UseAction action = null; + if (player.getItemUseTimeLeft() > 0 && active) { + action = stack.getUseAction(); + } else if (!player.handSwinging && stack.getItem() == Items.CROSSBOW && CrossbowItem.isCharged(stack)) { + action = UseAction.CROSSBOW; + } + + if (action != null) { + switch (action) { + case BLOCK: { + emote = SHIELD_EMOTE; + break; + } + case CROSSBOW: + case BOW: { + emote = AIM_EMOTE; + break; + } + case SPEAR: { + emote = TRIDENT_EMOTE; + break; + } + } + } + + if (emote != null) { + layers[AIM_LAYER].play(emote, (hand == Hand.OFF_HAND) != isLeftHanded()); + return true; + } else { + return false; + } + } + + private void updateAim() { + Hand hand = player.getActiveHand(); + if (!updateAim(hand, true)) { + updateAim(hand == Hand.MAIN_HAND ? Hand.OFF_HAND : Hand.MAIN_HAND, false); + } + } + + private ItemStack head = null; + + private void updateHead() { + if (head == null) { + head = new ItemStack(Items.PLAYER_HEAD); + CompoundTag tag = new CompoundTag(); + tag.putString("SkullOwner", player.getGameProfile().getName()); + Items.PLAYER_HEAD.postProcessTag(tag); + head.setTag(tag); + } + } + + private ItemStack getFallbackItem(EquipmentSlot slot, ItemStack stack, boolean invisible) { + if (stack.isEmpty() && !invisible) { + switch (slot) { + case HEAD: { + updateHead(); + return head; + } + case CHEST: { + return new ItemStack(Items.LEATHER_CHESTPLATE); + } + case LEGS: { + return new ItemStack(Items.LEATHER_LEGGINGS); + } + case FEET: { + return new ItemStack(Items.LEATHER_BOOTS); + } + default: { + return ItemStack.EMPTY; + } + } + } else { + return stack; + } + } + + private boolean isLeftHanded() { + return player.getMainArm() == Arm.LEFT; + } + + private void updateEquipment() { + for (EquipmentSlot slot : EquipmentSlot.values()) { + ItemStack target = getFallbackItem(slot, player.getEquippedStack(slot).copy(), isInvisible()); + if (!getEquippedStack(slot).isItemEqual(target)) { + equipStack(slot, target); + } + } + if (isLeftHanded()) { + ItemStack main = getEquippedStack(EquipmentSlot.MAINHAND); + ItemStack off = getEquippedStack(EquipmentSlot.OFFHAND); + equipStack(EquipmentSlot.OFFHAND, main); + equipStack(EquipmentSlot.MAINHAND, off); + } + } + + public void update() { + updateEquipment(); + + updateWalk(); + + updateAim(); + + applyPose(); + } + + public void playCustomEmote(Identifier id) { + layers[CUSTOM_LAYER].play(id, isLeftHanded()); + } + + public void hit(Hand hand) { + layers[HIT_LAYER].play(HIT_EMOTE, (hand == Hand.OFF_HAND) != isLeftHanded()); + } + + @Override + public boolean damage(DamageSource source, float amount) { + return player.damage(source, amount); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/ArmorStandEntityAccessor.java b/src/main/java/com/thebrokenrail/gestus/mixin/ArmorStandEntityAccessor.java new file mode 100644 index 0000000..6da63c3 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/ArmorStandEntityAccessor.java @@ -0,0 +1,14 @@ +package com.thebrokenrail.gestus.mixin; + +import net.minecraft.entity.decoration.ArmorStandEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(ArmorStandEntity.class) +public interface ArmorStandEntityAccessor { + @Invoker + void callSetShowArms(boolean showArms); + + @Invoker + void callSetHideBasePlate(boolean hideBasePlate); +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/DataTrackerAccessor.java b/src/main/java/com/thebrokenrail/gestus/mixin/DataTrackerAccessor.java new file mode 100644 index 0000000..a716ce6 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/DataTrackerAccessor.java @@ -0,0 +1,12 @@ +package com.thebrokenrail.gestus.mixin; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.data.DataTracker; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(DataTracker.class) +public interface DataTrackerAccessor { + @Accessor + Entity getTrackedEntity(); +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/EntityAccessor.java b/src/main/java/com/thebrokenrail/gestus/mixin/EntityAccessor.java new file mode 100644 index 0000000..2826c16 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/EntityAccessor.java @@ -0,0 +1,14 @@ +package com.thebrokenrail.gestus.mixin; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.data.TrackedData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Entity.class) +public interface EntityAccessor { + @Accessor + static TrackedData getFLAGS() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/EntityTrackerUpdateS2CPacketAccessor.java b/src/main/java/com/thebrokenrail/gestus/mixin/EntityTrackerUpdateS2CPacketAccessor.java new file mode 100644 index 0000000..6730107 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/EntityTrackerUpdateS2CPacketAccessor.java @@ -0,0 +1,23 @@ +package com.thebrokenrail.gestus.mixin; + +import net.minecraft.entity.data.DataTracker; +import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(EntityTrackerUpdateS2CPacket.class) +public interface EntityTrackerUpdateS2CPacketAccessor { + @Accessor + int getId(); + + @Accessor + void setId(int id); + + @Accessor + List> getTrackedValues(); + + @Accessor + void setTrackedValues(List> trackedValues); +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/MixinEntityTrackerEntry.java b/src/main/java/com/thebrokenrail/gestus/mixin/MixinEntityTrackerEntry.java new file mode 100644 index 0000000..6110ee6 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/MixinEntityTrackerEntry.java @@ -0,0 +1,27 @@ +package com.thebrokenrail.gestus.mixin; + +import com.mojang.datafixers.util.Pair; +import com.thebrokenrail.gestus.util.Util; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.item.ItemStack; +import net.minecraft.server.network.EntityTrackerEntry; +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.ModifyArg; + +import java.util.List; + +@Mixin(EntityTrackerEntry.class) +public class MixinEntityTrackerEntry { + @Shadow + @Final + private Entity entity; + + @ModifyArg(at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityEquipmentUpdateS2CPacket;(ILjava/util/List;)V"), method = "*", index = 1) + public List> modifyEquipment(List> list) { + return Util.modifyEquipment(entity, list); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/MixinLivingEntity.java b/src/main/java/com/thebrokenrail/gestus/mixin/MixinLivingEntity.java new file mode 100644 index 0000000..f00fb6d --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/MixinLivingEntity.java @@ -0,0 +1,20 @@ +package com.thebrokenrail.gestus.mixin; + +import com.mojang.datafixers.util.Pair; +import com.thebrokenrail.gestus.util.Util; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.util.List; + +@Mixin(LivingEntity.class) +public class MixinLivingEntity { + @ModifyArg(at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityEquipmentUpdateS2CPacket;(ILjava/util/List;)V"), method = "*", index = 1) + public List> modifyEquipment(List> list) { + return Util.modifyEquipment((LivingEntity) (Object) this, list); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/MixinMobSpawnS2CPacket.java b/src/main/java/com/thebrokenrail/gestus/mixin/MixinMobSpawnS2CPacket.java new file mode 100644 index 0000000..2081f12 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/MixinMobSpawnS2CPacket.java @@ -0,0 +1,25 @@ +package com.thebrokenrail.gestus.mixin; + +import com.thebrokenrail.gestus.entity.FakePlayerEntity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.network.packet.s2c.play.MobSpawnS2CPacket; +import net.minecraft.util.registry.Registry; +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(MobSpawnS2CPacket.class) +public class MixinMobSpawnS2CPacket { + @Shadow + private int entityTypeId; + + @Inject(at = @At("RETURN"), method = "(Lnet/minecraft/entity/LivingEntity;)V") + public void init(LivingEntity entity, CallbackInfo info) { + if (entity instanceof FakePlayerEntity) { + entityTypeId = Registry.ENTITY_TYPE.getRawId(EntityType.ARMOR_STAND); + } + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/MixinServerPlayNetworkHandler.java b/src/main/java/com/thebrokenrail/gestus/mixin/MixinServerPlayNetworkHandler.java new file mode 100644 index 0000000..a2e9992 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/MixinServerPlayNetworkHandler.java @@ -0,0 +1,44 @@ +package com.thebrokenrail.gestus.mixin; + +import com.thebrokenrail.gestus.entity.FakePlayerEntity; +import com.thebrokenrail.gestus.util.Util; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import net.minecraft.entity.Entity; +import net.minecraft.network.Packet; +import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket; +import net.minecraft.network.packet.s2c.play.MobSpawnS2CPacket; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerPlayNetworkHandler.class) +public class MixinServerPlayNetworkHandler { + @Shadow + public ServerPlayerEntity player; + + @Inject(at = @At("HEAD"), method = "sendPacket(Lnet/minecraft/network/Packet;Lio/netty/util/concurrent/GenericFutureListener;)V", cancellable = true) + public void sendPacket(Packet packet, GenericFutureListener> listener, CallbackInfo info) { + if (packet instanceof MobSpawnS2CPacket) { + MobSpawnS2CPacketAccessor packetData = (MobSpawnS2CPacketAccessor) packet; + Entity entity = player.getEntityWorld().getEntityById(packetData.getId()); + if (entity instanceof FakePlayerEntity && ((FakePlayerEntity) entity).player.getUuid().equals(player.getUuid())) { + info.cancel(); + } + } + } + + @ModifyVariable(at = @At("HEAD"), method = "sendPacket(Lnet/minecraft/network/Packet;Lio/netty/util/concurrent/GenericFutureListener;)V", argsOnly = true) + public Packet modifyPacket(Packet packet) { + if (packet instanceof EntityTrackerUpdateS2CPacket && ((EntityTrackerUpdateS2CPacketAccessor) packet).getId() == player.getEntityId()) { + return Util.modifyTracker(player, (EntityTrackerUpdateS2CPacket) packet); + } else { + return packet; + } + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/MixinServerPlayerEntity.java b/src/main/java/com/thebrokenrail/gestus/mixin/MixinServerPlayerEntity.java new file mode 100644 index 0000000..9044e76 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/MixinServerPlayerEntity.java @@ -0,0 +1,96 @@ +package com.thebrokenrail.gestus.mixin; + +import com.mojang.authlib.GameProfile; +import com.thebrokenrail.gestus.Gestus; +import com.thebrokenrail.gestus.entity.FakePlayerEntity; +import com.thebrokenrail.gestus.util.ServerPlayerEntityExtension; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.EulerAngle; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerPlayerEntity.class) +public abstract class MixinServerPlayerEntity extends PlayerEntity implements ServerPlayerEntityExtension { + @Unique + private FakePlayerEntity shadow; + + public MixinServerPlayerEntity(World world, BlockPos pos, float yaw, GameProfile profile) { + super(world, pos, yaw, profile); + } + + private void updateShadow() { + if (shadow != null && (shadow.getEntityWorld() != getEntityWorld() || shadow.removed)) { + if (!shadow.removed) { + shadow.remove(); + } + shadow = null; + } + boolean spawn = false; + if (shadow == null) { + shadow = new FakePlayerEntity(Gestus.FAKE_PLAYER_ENTITY_TYPE, getEntityWorld(), getDisplayName(), (ServerPlayerEntity) (Object) this); + spawn = true; + } + + boolean invisible = hasStatusEffect(StatusEffects.INVISIBILITY); + boolean glowing = hasStatusEffect(StatusEffects.GLOWING); + + shadow.setInvisible(invisible); + shadow.setGlowing(glowing); + + shadow.setWorld(getEntityWorld()); + shadow.refreshPositionAndAngles(getX(), getY(), getZ(), yaw, pitch); + + shadow.setHeadRotation(new EulerAngle(pitch, getHeadYaw() - yaw, 0f)); + shadow.setVelocity(getVelocity()); + + shadow.setCustomNameVisible(!invisible && !isSneaking()); + + shadow.update(); + + if (spawn) { + getEntityWorld().spawnEntity(shadow); + } + } + + @Unique + private Vec3d lastPos = null; + + @Inject(at = @At("HEAD"), method = "tick") + public void tick(CallbackInfo info) { + setInvisible(true); + setGlowing(false); + updateShadow(); + lastPos = getPos(); + } + + @Override + public Vec3d getLastPos() { + return lastPos; + } + + @Override + public FakePlayerEntity getShadow() { + return shadow; + } + + @Override + public void remove() { + super.remove(); + shadow.remove(); + } + + @Override + public void swingHand(Hand hand, boolean bl) { + super.swingHand(hand, bl); + shadow.hit(hand); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/MixinServerResourceManager.java b/src/main/java/com/thebrokenrail/gestus/mixin/MixinServerResourceManager.java new file mode 100644 index 0000000..2b068c8 --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/MixinServerResourceManager.java @@ -0,0 +1,22 @@ +package com.thebrokenrail.gestus.mixin; + +import com.thebrokenrail.gestus.emote.EmoteRegistry; +import net.minecraft.resource.ReloadableResourceManager; +import net.minecraft.resource.ServerResourceManager; +import net.minecraft.server.command.CommandManager; +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(ServerResourceManager.class) +public class MixinServerResourceManager { + @Shadow @Final private ReloadableResourceManager resourceManager; + + @Inject(at = @At("RETURN"), method = "") + public void init(CommandManager.RegistrationEnvironment registrationEnvironment, int i, CallbackInfo info) { + resourceManager.registerListener(EmoteRegistry.ReloadListener.INSTANCE); + } +} diff --git a/src/main/java/com/thebrokenrail/gestus/mixin/MobSpawnS2CPacketAccessor.java b/src/main/java/com/thebrokenrail/gestus/mixin/MobSpawnS2CPacketAccessor.java new file mode 100644 index 0000000..d6c87fb --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/mixin/MobSpawnS2CPacketAccessor.java @@ -0,0 +1,11 @@ +package com.thebrokenrail.gestus.mixin; + +import net.minecraft.network.packet.s2c.play.MobSpawnS2CPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(MobSpawnS2CPacket.class) +public interface MobSpawnS2CPacketAccessor { + @Accessor + int getId(); +} diff --git a/src/main/java/com/thebrokenrail/gestus/util/ServerPlayerEntityExtension.java b/src/main/java/com/thebrokenrail/gestus/util/ServerPlayerEntityExtension.java new file mode 100644 index 0000000..e5251ab --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/util/ServerPlayerEntityExtension.java @@ -0,0 +1,9 @@ +package com.thebrokenrail.gestus.util; + +import com.thebrokenrail.gestus.entity.FakePlayerEntity; +import net.minecraft.util.math.Vec3d; + +public interface ServerPlayerEntityExtension { + FakePlayerEntity getShadow(); + Vec3d getLastPos(); +} diff --git a/src/main/java/com/thebrokenrail/gestus/util/Util.java b/src/main/java/com/thebrokenrail/gestus/util/Util.java new file mode 100644 index 0000000..06c91fa --- /dev/null +++ b/src/main/java/com/thebrokenrail/gestus/util/Util.java @@ -0,0 +1,65 @@ +package com.thebrokenrail.gestus.util; + +import com.mojang.datafixers.util.Pair; +import com.thebrokenrail.gestus.mixin.EntityAccessor; +import com.thebrokenrail.gestus.mixin.EntityTrackerUpdateS2CPacketAccessor; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket; + +import java.util.ArrayList; +import java.util.List; + +public class Util { + public static List> modifyEquipment(Entity entity, List> list) { + if (entity instanceof PlayerEntity) { + List> newList = new ArrayList<>(); + for (EquipmentSlot slot : EquipmentSlot.values()) { + newList.add(Pair.of(slot, ItemStack.EMPTY)); + } + return newList; + } else { + return list; + } + } + + private static final int INVISIBILITY_FLAG = 5; + private static final int GLOWING_FLAG = 6; + + private static byte setFlag(byte data, int index, boolean value) { + if (value) { + return (byte) (data | 1 << index); + } else { + return (byte) (data & ~(1 << index)); + } + } + + public static EntityTrackerUpdateS2CPacket modifyTracker(PlayerEntity entity, EntityTrackerUpdateS2CPacket packet) { + TrackedData flags = EntityAccessor.getFLAGS(); + + List> newEntries = new ArrayList<>(); + + List> entries = ((EntityTrackerUpdateS2CPacketAccessor) packet).getTrackedValues(); + for (DataTracker.Entry entry : entries) { + if (entry.getData() == flags) { + byte data = (Byte) entry.get(); + data = setFlag(data, INVISIBILITY_FLAG, entity.hasStatusEffect(StatusEffects.INVISIBILITY)); + data = setFlag(data, GLOWING_FLAG, entity.hasStatusEffect(StatusEffects.GLOWING)); + newEntries.add(new DataTracker.Entry<>(flags, data)); + } else { + newEntries.add(entry); + } + } + + EntityTrackerUpdateS2CPacket newPacket = new EntityTrackerUpdateS2CPacket(); + ((EntityTrackerUpdateS2CPacketAccessor) newPacket).setId(((EntityTrackerUpdateS2CPacketAccessor) packet).getId()); + ((EntityTrackerUpdateS2CPacketAccessor) newPacket).setTrackedValues(newEntries); + + return newPacket; + } +} diff --git a/src/main/resources/assets/gestus/lang/en_us.json b/src/main/resources/assets/gestus/lang/en_us.json new file mode 100644 index 0000000..43486cd --- /dev/null +++ b/src/main/resources/assets/gestus/lang/en_us.json @@ -0,0 +1,5 @@ +{ + "entity.gestus.fake_player": "Fake Player", + "command.gestus.emote.unknown_emote": "Unknown Emote: %s", + "command.gestus.emote.playing_emote": "Playing Emote" +} \ No newline at end of file diff --git a/src/main/resources/data/gestus/emotes/builtin/aim.emote b/src/main/resources/data/gestus/emotes/builtin/aim.emote new file mode 100644 index 0000000..7e14163 --- /dev/null +++ b/src/main/resources/data/gestus/emotes/builtin/aim.emote @@ -0,0 +1 @@ +main_arm 0 90 0 off_arm 40 90 0 \ No newline at end of file diff --git a/src/main/resources/data/gestus/emotes/builtin/hit.emote b/src/main/resources/data/gestus/emotes/builtin/hit.emote new file mode 100644 index 0000000..f057d2a --- /dev/null +++ b/src/main/resources/data/gestus/emotes/builtin/hit.emote @@ -0,0 +1,9 @@ +main_arm 0 0 0 +main_arm -8.5 17 0 +main_arm -17 34 0 +main_arm -25.5 51 0 +main_arm -34 68 0 +main_arm -25.5 51 0 +main_arm -17 34 0 +main_arm -8.5 17 0 +main_arm 0 0 0 \ No newline at end of file diff --git a/src/main/resources/data/gestus/emotes/builtin/ride.emote b/src/main/resources/data/gestus/emotes/builtin/ride.emote new file mode 100644 index 0000000..94640c4 --- /dev/null +++ b/src/main/resources/data/gestus/emotes/builtin/ride.emote @@ -0,0 +1 @@ +right_leg 24 71 0 left_leg -24 71 0 \ No newline at end of file diff --git a/src/main/resources/data/gestus/emotes/builtin/shield.emote b/src/main/resources/data/gestus/emotes/builtin/shield.emote new file mode 100644 index 0000000..691e6a4 --- /dev/null +++ b/src/main/resources/data/gestus/emotes/builtin/shield.emote @@ -0,0 +1 @@ +main_arm -42 90 0 \ No newline at end of file diff --git a/src/main/resources/data/gestus/emotes/builtin/sprint.emote b/src/main/resources/data/gestus/emotes/builtin/sprint.emote new file mode 100644 index 0000000..ed4d5e3 --- /dev/null +++ b/src/main/resources/data/gestus/emotes/builtin/sprint.emote @@ -0,0 +1,19 @@ +right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0 +right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0 +right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0 +right_arm 0 42 0 left_arm 0 -42 0 left_leg 0 42 0 right_leg 0 -42 0 +right_arm 0 56 0 left_arm 0 -56 0 left_leg 0 56 0 right_leg 0 -56 0 +right_arm 0 56 0 left_arm 0 -56 0 left_leg 0 56 0 right_leg 0 -56 0 +right_arm 0 42 0 left_arm 0 -42 0 left_leg 0 42 0 right_leg 0 -42 0 +right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0 +right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0 +right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0 +right_arm 0 -14 0 left_arm 0 14 0 left_leg 0 -14 0 right_leg 0 14 0 +right_arm 0 -28 0 left_arm 0 28 0 left_leg 0 -28 0 right_leg 0 28 0 +right_arm 0 -42 0 left_arm 0 42 0 left_leg 0 -42 0 right_leg 0 42 0 +right_arm 0 -56 0 left_arm 0 56 0 left_leg 0 -56 0 right_leg 0 56 0 +right_arm 0 -56 0 left_arm 0 56 0 left_leg 0 -56 0 right_leg 0 56 0 +right_arm 0 -42 0 left_arm 0 42 0 left_leg 0 -42 0 right_leg 0 42 0 +right_arm 0 -28 0 left_arm 0 28 0 left_leg 0 -28 0 right_leg 0 28 0 +right_arm 0 -14 0 left_arm 0 14 0 left_leg 0 -14 0 right_leg 0 14 0 +right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0 \ No newline at end of file diff --git a/src/main/resources/data/gestus/emotes/builtin/still.emote b/src/main/resources/data/gestus/emotes/builtin/still.emote new file mode 100644 index 0000000..7f63891 --- /dev/null +++ b/src/main/resources/data/gestus/emotes/builtin/still.emote @@ -0,0 +1 @@ +main_arm 0 0 0 off_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0 body 0 0 0 \ No newline at end of file diff --git a/src/main/resources/data/gestus/emotes/builtin/trident.emote b/src/main/resources/data/gestus/emotes/builtin/trident.emote new file mode 100644 index 0000000..51e2368 --- /dev/null +++ b/src/main/resources/data/gestus/emotes/builtin/trident.emote @@ -0,0 +1 @@ +main_arm 0 180 0 \ No newline at end of file diff --git a/src/main/resources/data/gestus/emotes/builtin/walk.emote b/src/main/resources/data/gestus/emotes/builtin/walk.emote new file mode 100644 index 0000000..391be6d --- /dev/null +++ b/src/main/resources/data/gestus/emotes/builtin/walk.emote @@ -0,0 +1,23 @@ +right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0 +right_arm 0 7 0 left_arm 0 -7 0 left_leg 0 7 0 right_leg 0 -7 0 +right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0 +right_arm 0 21 0 left_arm 0 -21 0 left_leg 0 21 0 right_leg 0 -21 0 +right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0 +right_arm 0 35 0 left_arm 0 -35 0 left_leg 0 35 0 right_leg 0 -35 0 +right_arm 0 35 0 left_arm 0 -35 0 left_leg 0 35 0 right_leg 0 -35 0 +right_arm 0 28 0 left_arm 0 -28 0 left_leg 0 28 0 right_leg 0 -28 0 +right_arm 0 21 0 left_arm 0 -21 0 left_leg 0 21 0 right_leg 0 -21 0 +right_arm 0 14 0 left_arm 0 -14 0 left_leg 0 14 0 right_leg 0 -14 0 +right_arm 0 7 0 left_arm 0 -7 0 left_leg 0 7 0 right_leg 0 -7 0 +right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0 +right_arm 0 -7 0 left_arm 0 7 0 left_leg 0 -7 0 right_leg 0 7 0 +right_arm 0 -14 0 left_arm 0 14 0 left_leg 0 -14 0 right_leg 0 14 0 +right_arm 0 -21 0 left_arm 0 21 0 left_leg 0 -21 0 right_leg 0 21 0 +right_arm 0 -28 0 left_arm 0 28 0 left_leg 0 -28 0 right_leg 0 28 0 +right_arm 0 -35 0 left_arm 0 35 0 left_leg 0 -35 0 right_leg 0 35 0 +right_arm 0 -35 0 left_arm 0 35 0 left_leg 0 -35 0 right_leg 0 35 0 +right_arm 0 -28 0 left_arm 0 28 0 left_leg 0 -28 0 right_leg 0 28 0 +right_arm 0 -21 0 left_arm 0 21 0 left_leg 0 -21 0 right_leg 0 21 0 +right_arm 0 -14 0 left_arm 0 14 0 left_leg 0 -14 0 right_leg 0 14 0 +right_arm 0 -7 0 left_arm 0 7 0 left_leg 0 -7 0 right_leg 0 7 0 +right_arm 0 0 0 left_arm 0 0 0 left_leg 0 0 0 right_leg 0 0 0 \ No newline at end of file diff --git a/src/main/resources/data/gestus/emotes/custom/wave.emote b/src/main/resources/data/gestus/emotes/custom/wave.emote new file mode 100644 index 0000000..2b3d696 --- /dev/null +++ b/src/main/resources/data/gestus/emotes/custom/wave.emote @@ -0,0 +1,40 @@ +main_arm 0 0 0 +main_arm 0 0 16 +main_arm 0 0 32 +main_arm 0 0 48 +main_arm 0 0 64 +main_arm 0 0 80 +main_arm 0 0 96 +main_arm 0 0 112 +main_arm 0 0 128 +main_arm 0 0 144 +main_arm 0 0 145 +main_arm 0 0 140 +main_arm 0 0 135 +main_arm 0 0 130 +main_arm 0 0 125 +main_arm 0 0 120 +main_arm 0 0 125 +main_arm 0 0 130 +main_arm 0 0 135 +main_arm 0 0 140 +main_arm 0 0 145 +main_arm 0 0 145 +main_arm 0 0 140 +main_arm 0 0 135 +main_arm 0 0 130 +main_arm 0 0 125 +main_arm 0 0 120 +main_arm 0 0 125 +main_arm 0 0 130 +main_arm 0 0 135 +main_arm 0 0 140 +main_arm 0 0 145 +main_arm 0 0 112 +main_arm 0 0 96 +main_arm 0 0 80 +main_arm 0 0 64 +main_arm 0 0 48 +main_arm 0 0 32 +main_arm 0 0 16 +main_arm 0 0 0 \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..17cab5e --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,40 @@ +{ + "schemaVersion": 1, + "id": "gestus", + "version": "${version}", + "name": "Gestus", + "description": "A server mod that adds data-driven emotes to Minecraft that can be viewed by vanilla clients.", + "authors": [ + "TheBrokenRail" + ], + "contact": { + "homepage": "https://thebrokenrail.com/", + "sources": "https://gitea.thebrokenrail.com/TheBrokenRail/Gestus.git", + "issues": "https://gitea.thebrokenrail.com/TheBrokenRail/Gestus/issues" + }, + "license": "MIT", + "environment": "*", + "entrypoints": { + "main": [ + "com.thebrokenrail.gestus.Gestus" + ], + "client": [ + "com.thebrokenrail.gestus.client.GestusClient" + ] + }, + "mixins": [ + "gestus.mixins.json" + ], + "accessWidener": "gestus.accesswidener", + "depends": { + "fabricloader": ">=0.7.4", + "fabric": "*", + "minecraft": "1.16.x" + }, + "custom": { + "modupdater": { + "strategy": "curseforge", + "projectID": 401707 + } + } +} \ No newline at end of file diff --git a/src/main/resources/gestus.accesswidener b/src/main/resources/gestus.accesswidener new file mode 100644 index 0000000..daac67d --- /dev/null +++ b/src/main/resources/gestus.accesswidener @@ -0,0 +1,2 @@ +accessWidener v1 named +accessible class net/minecraft/server/world/ThreadedAnvilChunkStorage$EntityTracker \ No newline at end of file diff --git a/src/main/resources/gestus.mixins.json b/src/main/resources/gestus.mixins.json new file mode 100644 index 0000000..1e8d900 --- /dev/null +++ b/src/main/resources/gestus.mixins.json @@ -0,0 +1,21 @@ +{ + "required": true, + "package": "com.thebrokenrail.gestus.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "ArmorStandEntityAccessor", + "DataTrackerAccessor", + "EntityAccessor", + "EntityTrackerUpdateS2CPacketAccessor", + "MixinEntityTrackerEntry", + "MixinLivingEntity", + "MixinMobSpawnS2CPacket", + "MixinServerPlayerEntity", + "MixinServerPlayNetworkHandler", + "MixinServerResourceManager", + "MobSpawnS2CPacketAccessor" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file