diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml deleted file mode 100644 index ae3da24..0000000 --- a/.github/workflows/gradle-publish.yml +++ /dev/null @@ -1,44 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created -# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle - -name: SCCore Packages - -on: - release: - types: [created] - -jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - server-id: github # Value of the distributionManagement/repository/id field of the pom.xml - settings-path: ${{ github.workspace }} # location for the settings.xml file - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - - - name: Build with Gradle - run: ./gradlew build - - # The USERNAME and TOKEN need to correspond to the credentials environment variables used in - # the publishing section of your build.gradle - - name: Publish to GitHub Packages - run: ./gradlew publish - env: - USERNAME: ${{ github.actor }} - TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 60f5c25..99b91f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .idea .gradle +.github build run run-data \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 97cf26d..a04dede 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ mapping_version=2023.09.03-1.20.1 mod_id=sccore mod_name=SnowyCrescentCore mod_license=GNU AGPL 3.0 -mod_version=1.20.1-0.0.8-hotfix +mod_version=1.20.1-0.1.0-pre mod_group_id=com.linearpast mod_authors=LostInLinearPast mod_description=A lib about capability and player animator. diff --git a/gradlew b/gradlew deleted file mode 100644 index f5feea6..0000000 --- a/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original 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. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# 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 ;; #( - MSYS* | 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 - if ! command -v java >/dev/null 2>&1 - then - 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 -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# 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"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 9d21a21..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@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 -@rem SPDX-License-Identifier: Apache-2.0 -@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=. -@rem This is normally unused -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% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -: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 %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 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! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/sccore/animation/sccore/animation.layer.json b/sccore/animation/sccore/animation.layer.json deleted file mode 100644 index 7ee2ac9..0000000 --- a/sccore/animation/sccore/animation.layer.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "key": "sccore:normal_layers", - "priority": 42 - }, - { - "key": "sccore:ride_layers", - "priority": 43 - } -] \ No newline at end of file diff --git a/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac b/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac index 56226a7..eefa914 100644 --- a/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac +++ b/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac @@ -1,2 +1,2 @@ -// 1.20.1 2025-10-30T21:02:30.6951333 Languages: zh_cn -feac50843a3ec6d5a3ad4c4e917d0f19a2b857cb assets/sccore/lang/zh_cn.json +// 1.20.1 2025-11-26T10:22:37.4821986 Languages: zh_cn +bfd0415418a7b7d284a9b445fd35a7242782beea assets/sccore/lang/zh_cn.json diff --git a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 index 4b8c14a..579b6a3 100644 --- a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 +++ b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 @@ -1,2 +1,2 @@ -// 1.20.1 2025-10-30T21:02:30.6972105 Languages: en_us -165168d6118e3dde833169d2ab1bc0028d425d55 assets/sccore/lang/en_us.json +// 1.20.1 2025-11-26T10:22:37.4835694 Languages: en_us +874336226401ae3f556eec3296550b37f72d8831 assets/sccore/lang/en_us.json diff --git a/src/generated/resources/assets/sccore/lang/en_us.json b/src/generated/resources/assets/sccore/lang/en_us.json index 2fe98bf..7fc321f 100644 --- a/src/generated/resources/assets/sccore/lang/en_us.json +++ b/src/generated/resources/assets/sccore/lang/en_us.json @@ -1,39 +1,30 @@ { - "translation.sccore.animation.command_cooldown": "You cannot execute this command, cooling down: %s seconds.", - "translation.sccore.animation.unknown_animation": "Unknown animation %s, please check if your resource packs is complete.", - "translation.sccore.animation.without_animation_ride_entity": "Command run fail, full or unsupported animations.", - "translation.sccore.command.animation.accept_apply_expired": "Application expired.(%s minute(s))", + "translation.sccore.animation.animation_cooldown": "You cannot perform this operation: Cooling down (%s second(s)).", + "translation.sccore.animation.animation_expire": "You cannot perform this operation: It has expired.", + "translation.sccore.animation.animation_operation_unsupported": "Error: Unsupported operation.", + "translation.sccore.animation.animation_out_range": "You cannot perform this operation: The distance is not within %s blocks.", + "translation.sccore.animation.animation_resource_not_found": "Error: Resource not found, please check if there are any errors in the resource or operation.", "translation.sccore.command.animation.accept_apply_success": "%s has accepted the application of %s.", - "translation.sccore.command.animation.accept_apply_too_far": "You are too far apart. (%s block(s))", - "translation.sccore.command.animation.accept_invite_expired": "Invite expired.(%s minute(s))", "translation.sccore.command.animation.accept_invite_success": "Invitation accepted.", - "translation.sccore.command.animation.accept_invite_too_far": "You are too far apart. (%s block(s))", "translation.sccore.command.animation.accept_message_click": "Click here to accept.", - "translation.sccore.command.animation.accept_request_expired": "Request expired.(%s minute(s))", "translation.sccore.command.animation.accept_request_success": "Request accepted.", "translation.sccore.command.animation.animation_json_path": "%s", - "translation.sccore.command.animation.animation_layer_not_present": "Animation layer is not present.", - "translation.sccore.command.animation.animation_not_present": "Animation is not present.", "translation.sccore.command.animation.animation_to_json": "The animation %s has been stored in the path on %s:", "translation.sccore.command.animation.applied_join_message": "%S§b§l Apply for §r to join your animation. ", - "translation.sccore.command.animation.apply_expired": "%s has accepted your animation application but the application has expired. (%s minute(s))", "translation.sccore.command.animation.apply_join_message": "Application sent.", "translation.sccore.command.animation.apply_success": "%s has accepted your animation application.", - "translation.sccore.command.animation.apply_too_far": "%s has accepted your animation application but you are too far apart. (%s block(s))", "translation.sccore.command.animation.clear_animations": "Animation cleared.", "translation.sccore.command.animation.command_run_fail": "Command run fail.", "translation.sccore.command.animation.command_run_success": "Command run success.", - "translation.sccore.command.animation.invite_expired": "%s has accepted your animation invitation but the invitation has expired. (%s minute(s))", "translation.sccore.command.animation.invite_message": "Invitation sent.", "translation.sccore.command.animation.invite_success": "%s has accepted your animation invitation.", - "translation.sccore.command.animation.invite_too_far": "%s has accepted your animation invitation but you are too far apart. (%s block(s))", "translation.sccore.command.animation.invited_message": "%s§c§l invites§r you to animation: %s. ", + "translation.sccore.command.animation.list_animation_resource": "The %2$s on %1$s has : %s", "translation.sccore.command.animation.play_animation_fail": "Fail to play animation with: %s", "translation.sccore.command.animation.play_animation_success": "Successfully played animation on %s player(s).", "translation.sccore.command.animation.refresh_animations": "Animation refreshed.", "translation.sccore.command.animation.remove_animation_fail": "Fail to remove animation with: %s", "translation.sccore.command.animation.remove_animation_success": "Successfully removed animation on %s player(s).", - "translation.sccore.command.animation.request_expired": "%s has accepted your animation request but the request has expired. (%s minute(s))", "translation.sccore.command.animation.request_message": "Request sent.", "translation.sccore.command.animation.request_success": "%s has accepted your animation request.", "translation.sccore.command.animation.requested_message": "%s§d§l requests§r you to animation: %s. " diff --git a/src/generated/resources/assets/sccore/lang/zh_cn.json b/src/generated/resources/assets/sccore/lang/zh_cn.json index b1e830a..cf29b01 100644 --- a/src/generated/resources/assets/sccore/lang/zh_cn.json +++ b/src/generated/resources/assets/sccore/lang/zh_cn.json @@ -1,39 +1,30 @@ { - "translation.sccore.animation.command_cooldown": "你不能执行该指令,冷却中:%s 秒。", - "translation.sccore.animation.unknown_animation": "未知的动画%s,请检查你的资源包是否完整。", - "translation.sccore.animation.without_animation_ride_entity": "命令执行错误,已满人或不支持的动画。", - "translation.sccore.command.animation.accept_apply_expired": "申请已超时。(%s分钟)", + "translation.sccore.animation.animation_cooldown": "你不能执行该操作: 冷却中(%s 秒)。", + "translation.sccore.animation.animation_expire": "你不能执行该操作: 已过期。", + "translation.sccore.animation.animation_operation_unsupported": "错误: 不支持这样做。", + "translation.sccore.animation.animation_out_range": "你不能执行该操作: 距离不在%s格以内。", + "translation.sccore.animation.animation_resource_not_found": "错误: 资源未找到,请检查资源或操作是否有误。", "translation.sccore.command.animation.accept_apply_success": "%s 接受了 %s 的申请。", - "translation.sccore.command.animation.accept_apply_too_far": "你们距离太远了。(%s格)", - "translation.sccore.command.animation.accept_invite_expired": "邀请已超时。(%s分钟)", "translation.sccore.command.animation.accept_invite_success": "已接受邀请。", - "translation.sccore.command.animation.accept_invite_too_far": "你们距离太远了。(%s格)", "translation.sccore.command.animation.accept_message_click": "单击此处同意。", - "translation.sccore.command.animation.accept_request_expired": "请求已超时。(%s分钟)", "translation.sccore.command.animation.accept_request_success": "已接受请求。", "translation.sccore.command.animation.animation_json_path": "%s", - "translation.sccore.command.animation.animation_layer_not_present": "动画层不存在。", - "translation.sccore.command.animation.animation_not_present": "动画不存在。", "translation.sccore.command.animation.animation_to_json": "动画%s已经存储到%s路径:", "translation.sccore.command.animation.applied_join_message": "%s§b§l 申请§r加入动画。", - "translation.sccore.command.animation.apply_expired": "%s 接受了你的动画申请,但是申请超时了。(%s分钟)", "translation.sccore.command.animation.apply_join_message": "已发送申请。", "translation.sccore.command.animation.apply_success": "%s 接受了你的动画申请。", - "translation.sccore.command.animation.apply_too_far": "%s 接受了你的动画申请,但你们距离太远了。(%s格)", "translation.sccore.command.animation.clear_animations": "动画已清除。", "translation.sccore.command.animation.command_run_fail": "命令执行失败。", "translation.sccore.command.animation.command_run_success": "命令执行成功。", - "translation.sccore.command.animation.invite_expired": "%s 接受了你的动画邀请,但是邀请超时了。(%s分钟)", "translation.sccore.command.animation.invite_message": "已发送邀请。", "translation.sccore.command.animation.invite_success": "%s 接受了你的动画邀请。", - "translation.sccore.command.animation.invite_too_far": "%s 接受了你的动画邀请,但你们距离太远了。(%s格)", "translation.sccore.command.animation.invited_message": "%s§c§l 邀请§r你进行动画:%s。", + "translation.sccore.command.animation.list_animation_resource": "%s侧的%s有:%s", "translation.sccore.command.animation.play_animation_fail": "在这些玩家上播放动画失败:%s", "translation.sccore.command.animation.play_animation_success": "在%s个玩家上播放动画成功。", "translation.sccore.command.animation.refresh_animations": "动画同步状态已刷新。", "translation.sccore.command.animation.remove_animation_fail": "在这些玩家上移除动画失败:%s", "translation.sccore.command.animation.remove_animation_success": "在%s个玩家上移除动画成功。", - "translation.sccore.command.animation.request_expired": "%s 接受了你的动画请求,但是请求超时了。(%s分钟)", "translation.sccore.command.animation.request_message": "已发送请求。", "translation.sccore.command.animation.request_success": "%s 接受了你的动画请求。", "translation.sccore.command.animation.requested_message": "%s§d§l 请求§r你进行动画:%s。" diff --git a/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java b/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java index 14bc973..e1b770d 100644 --- a/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java +++ b/src/main/java/com/linearpast/sccore/SnowyCrescentCore.java @@ -1,7 +1,7 @@ package com.linearpast.sccore; -import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.helper.IAnimationHelper; import com.linearpast.sccore.capability.CapabilityUtils; import com.linearpast.sccore.core.ModChannel; import com.linearpast.sccore.core.ModCommands; @@ -34,7 +34,7 @@ public class SnowyCrescentCore { CapabilityUtils.registerHandler(forgeBus); ModChannel.register(); - AnimationUtils.register(forgeBus, modBus); + IAnimationHelper.register(forgeBus, modBus); ModCommands.registerCommands(forgeBus, modBus); if(!FMLEnvironment.production || Boolean.getBoolean(ENABLE_EXAMPLES_PROPERTY_KEY)) { diff --git a/src/main/java/com/linearpast/sccore/animation/AnimationApi.java b/src/main/java/com/linearpast/sccore/animation/AnimationApi.java new file mode 100644 index 0000000..3532b84 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/AnimationApi.java @@ -0,0 +1,33 @@ +package com.linearpast.sccore.animation; + +import com.linearpast.sccore.animation.helper.*; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; + +import java.util.Set; + +public class AnimationApi { + public static AnimationApi getInstance() { + return new AnimationApi(); + } + + public AnimationHelper getAnimationHelper() { + return AnimationHelper.INSTANCE; + } + + public RawAnimationHelper getRawAnimationHelper() { + return RawAnimationHelper.INSTANCE; + } + + public JsonHelper getJsonHelper(MinecraftServer server) { + return JsonHelper.getHelper(server); + } + + public IAnimationHelper getHelperFromAnimKey(ResourceLocation location) { + return new HelperGetterFromAnimation(location).getHelper(); + } + + public Set> getAllHelpers() { + return IHelperGetter.HELPERS; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java b/src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java deleted file mode 100644 index 9f17804..0000000 --- a/src/main/java/com/linearpast/sccore/animation/AnimationPlayer.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.linearpast.sccore.animation; - -import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.capability.AnimationDataCapability; -import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; -import com.linearpast.sccore.animation.data.Animation; -import com.linearpast.sccore.animation.entity.AnimationRideEntity; -import com.linearpast.sccore.animation.mixin.IMixinKeyframeAnimationPlayer; -import com.linearpast.sccore.animation.network.toclient.SyncAnimationPacket; -import com.linearpast.sccore.animation.network.toserver.PlayAnimationRequestPacket; -import com.linearpast.sccore.animation.network.toserver.PlayAnimationRidePacket; -import com.linearpast.sccore.core.ModChannel; -import com.linearpast.sccore.core.datagen.ModLang; -import dev.kosmx.playerAnim.api.layered.IAnimation; -import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer; -import dev.kosmx.playerAnim.api.layered.ModifierLayer; -import dev.kosmx.playerAnim.api.layered.modifier.AbstractFadeModifier; -import dev.kosmx.playerAnim.core.data.KeyframeAnimation; -import dev.kosmx.playerAnim.core.util.Ease; -import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.AbstractClientPlayer; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import org.jetbrains.annotations.Nullable; - -import java.util.Objects; -import java.util.UUID; - -public class AnimationPlayer { - - public static void requestAnimationToServer(@Nullable AbstractClientPlayer player, ResourceLocation layer, @Nullable ResourceLocation animation) { - UUID uuid = null; - if(player != null) uuid = player.getUUID(); - ModChannel.sendToServer(new PlayAnimationRequestPacket(uuid, layer, animation)); - } - - public static boolean serverPlayAnimation(ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) { - IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null); - if(data == null) return false; - if(animation != null) { - return data.mergeAnimation(layer, animation); - } else { - return data.removeAnimation(layer); - } - } - - public static boolean playAnimationWithRide(ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation, boolean force){ - if(animation != null) { - return AnimationRideEntity.create(serverPlayer, layer, animation, force); - } else { - serverPlayer.unRide(); - AnimationDataCapability.getCapability(serverPlayer).ifPresent(IAnimationCapability::removeRiderAnimation); - return true; - } - } - - public static void requestAnimationRideToServer(ResourceLocation layer, @Nullable ResourceLocation animation, boolean force) { - ModChannel.sendToServer(new PlayAnimationRidePacket(layer, animation, force)); - } - - public static void clearAnimation(ServerPlayer serverPlayer) { - IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null); - if(data == null) return; - data.clearAnimations(); - } - - public static void syncAnimation(ServerPlayer player, ServerPlayer target) { - ModChannel.sendToPlayer(new SyncAnimationPacket(player.getUUID(), target.getUUID()), player); - ModChannel.sendToPlayer(new SyncAnimationPacket(player.getUUID(), target.getUUID()), target); - } - - @SuppressWarnings("unchecked") - @OnlyIn(Dist.CLIENT) - public static void syncAnimation(AbstractClientPlayer clientPlayer, AbstractClientPlayer target) { - try { - IAnimationCapability clientPlayerData = AnimationDataCapability.getCapability(clientPlayer).orElse(null); - if(clientPlayerData == null) return; - IAnimationCapability targetData = AnimationDataCapability.getCapability(target).orElse(null); - if(targetData == null) return; - ResourceLocation clientPlayerLayer = clientPlayerData.getRiderAnimLayer(); - ResourceLocation targetLayer = targetData.getRiderAnimLayer(); - if(clientPlayerLayer == null || targetLayer == null) return; - ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess - .getPlayerAssociatedData(clientPlayer).get(clientPlayerLayer); - ModifierLayer targetModifierLayer = (ModifierLayer) PlayerAnimationAccess - .getPlayerAssociatedData(target).get(targetLayer); - if(modifierLayer == null || targetModifierLayer == null) return; - IMixinKeyframeAnimationPlayer animation = (IMixinKeyframeAnimationPlayer) modifierLayer.getAnimation(); - KeyframeAnimationPlayer targetAnimation = (KeyframeAnimationPlayer) targetModifierLayer.getAnimation(); - if(animation == null || targetAnimation == null) return; - int currentTick = targetAnimation.getCurrentTick(); - animation.sccore$setCurrentTick(currentTick); - } catch (Exception ignored) {} - } - - @SuppressWarnings("unchecked") - @OnlyIn(Dist.CLIENT) - public static void playAnimation(@Nullable AbstractClientPlayer clientPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) { - try { - LocalPlayer localPlayer = Minecraft.getInstance().player; - if(clientPlayer == null) clientPlayer = localPlayer; - if(clientPlayer == null) return; - ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess - .getPlayerAssociatedData(clientPlayer).get(layer); - if(animation == null) { - if(modifierLayer != null) { - modifierLayer.replaceAnimationWithFade( - AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE), - null - ); - } - return; - } - Animation anim = AnimationUtils.getAnimation(animation); - if(anim == null) return; - if(modifierLayer == null) return; - KeyframeAnimation keyframeAnimation = anim.getAnimation(); - if(keyframeAnimation == null) { - if(localPlayer == null) return; - localPlayer.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.UNKNOWN_ANIMATION.getKey(), - animation.toString() - ).withStyle(ChatFormatting.RED)); - return; - }; - Objects.requireNonNull(modifierLayer).replaceAnimationWithFade( - AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE), - new KeyframeAnimationPlayer(keyframeAnimation) - ); - }catch (Exception e) { - SnowyCrescentCore.log.error("Failed to play animation : {}", animation, e); - } - } -} diff --git a/src/main/java/com/linearpast/sccore/animation/AnimationUtils.java b/src/main/java/com/linearpast/sccore/animation/AnimationUtils.java deleted file mode 100644 index f984980..0000000 --- a/src/main/java/com/linearpast/sccore/animation/AnimationUtils.java +++ /dev/null @@ -1,434 +0,0 @@ -package com.linearpast.sccore.animation; - -import com.linearpast.sccore.animation.capability.AnimationDataCapability; -import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; -import com.linearpast.sccore.animation.data.Animation; -import com.linearpast.sccore.animation.data.Ride; -import com.linearpast.sccore.animation.entity.AnimationRideEntity; -import com.linearpast.sccore.animation.event.PlayerTickEvent; -import com.linearpast.sccore.animation.event.client.CameraAnglesModify; -import com.linearpast.sccore.animation.event.client.ClientPlayerTick; -import com.linearpast.sccore.animation.event.client.EntityRendererRegisterEvent; -import com.linearpast.sccore.animation.network.toserver.RefreshAnimationPacket; -import com.linearpast.sccore.animation.register.AnimationCapabilities; -import com.linearpast.sccore.animation.register.AnimationChannels; -import com.linearpast.sccore.animation.register.AnimationEntities; -import com.linearpast.sccore.animation.register.AnimationRegistry; -import com.linearpast.sccore.core.ModChannel; -import com.linearpast.sccore.core.ModLazyRun; -import dev.kosmx.playerAnim.api.layered.IAnimation; -import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer; -import dev.kosmx.playerAnim.api.layered.ModifierLayer; -import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; -import net.minecraft.client.player.AbstractClientPlayer; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.player.Player; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.common.util.FakePlayer; -import net.minecraftforge.eventbus.api.IEventBus; -import org.jetbrains.annotations.Nullable; - -import java.util.HashSet; -import java.util.Set; - -/** - * Animation Util. May be you can call it Api. - */ -public class AnimationUtils { - public static final String AnimModId = "playeranimator"; - public static final ModLazyRun ANIMATION_RUNNER = new ModLazyRun(AnimModId) { - @Override - public void addCommonListener(IEventBus forgeBus, IEventBus modBus) { - AnimationEntities.register(modBus); - forgeBus.addListener(AnimationRegistry::onServerStarted); - forgeBus.addListener(AnimationRegistry::onPlayerLoggedIn); - forgeBus.addListener(PlayerTickEvent::onPlayerTickEvent); - } - - @Override - public void addClientListener(IEventBus forgeBus, IEventBus modBus) { - forgeBus.addListener(CameraAnglesModify::changeCameraView); - modBus.addListener(EntityRendererRegisterEvent::registerEntityRenderer); - forgeBus.addListener(ClientPlayerTick::onPlayerTick); - forgeBus.addListener(ClientPlayerTick::delayRuns); - } - }; - - /** - *
-     * Play animation.
-     * If run in Dist.CLIENT, player can be null, it will play animation only client.
-     * If animation be null, it will remove animation on layer.
-     * 
- * @param player Target player - * @param layer Target layer - * @param animation Animation - * @return If success - */ - public static boolean playAnimation(@Nullable Player player, ResourceLocation layer, @Nullable ResourceLocation animation) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> { - if(isAnimationLayerPresent(layer) && (animation == null || isAnimationPresent(animation))) { - if(player instanceof ServerPlayer serverPlayer) { - if(serverPlayer instanceof FakePlayer) return false; - return AnimationPlayer.serverPlayAnimation(serverPlayer, layer, animation); - }else if(player == null || player instanceof AbstractClientPlayer) { - AnimationPlayer.requestAnimationToServer((AbstractClientPlayer) player, layer, animation); - return true; - } - } - return false; - }); - } - - /** - * Client send request to server and run play animation.
- * Only play animation with client self. - * @param layer Target layer - * @param animation Target animation - * @return If success - */ - @OnlyIn(Dist.CLIENT) - public static boolean requestAnimationClient(@Nullable AbstractClientPlayer player, ResourceLocation layer, @Nullable ResourceLocation animation) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> { - if(isAnimationLayerPresent(layer) && (animation == null || isAnimationPresent(animation))) { - AnimationPlayer.requestAnimationToServer(player, layer, animation); - return true; - } - return false; - }); - } - - /** - *
-     * Play animation with ride. Player will ride an entity, then play animation.
-     * When player unride, animation will be remove.
-     * If run in Dist.CLIENT, the serverPlayer can be null.
-     * If animation be null, it will call function: {@link ServerPlayer#unRide()}
-     * If player is riding and the "force" is false, it will return false
-     * 
- * @param serverPlayer Target player - * @param layer Target layer - * @param animation Animation - * @param force If force to ride and play animation - * @return If success - */ - public static boolean playAnimationWithRide(@Nullable ServerPlayer serverPlayer, ResourceLocation layer, @Nullable ResourceLocation animation, boolean force) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> { - if(isAnimationLayerPresent(layer) && (animation == null || isAnimationPresent(animation))) { - Animation anim = AnimationUtils.getAnimation(animation); - if(anim != null && anim.getRide() == null) return false; - if(serverPlayer != null) { - if(serverPlayer instanceof FakePlayer) return false; - if(serverPlayer.getVehicle() != null && force) serverPlayer.unRide(); - else if(serverPlayer.getVehicle() != null) return false; - return AnimationPlayer.playAnimationWithRide(serverPlayer, layer, animation, true); - } else { - AnimationPlayer.requestAnimationRideToServer(layer, animation, force); - return true; - } - } - return false; - }); - } - - /** - * Remove animation. - * @see AnimationUtils#playAnimation - * @param player Target player - * @param layer Target layer - * @return If success - */ - public static boolean removeAnimation(@Nullable Player player, ResourceLocation layer) { - return playAnimation(player, layer, null); - } - - /** - * Get animation which is playing now on player.
- * If layer is null, it will return the first playing animation which can be found. - * @param player Target player - * @param layer Target layer - * @return Playing animation resource location - */ - @Nullable - public static ResourceLocation getAnimationPlaying(Player player, @Nullable ResourceLocation layer) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> { - IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); - if(data == null) return null; - if(layer == null){ - for (ResourceLocation value : data.getAnimations().values()) { - if(value != null) return value; - } - } else if (isAnimationLayerPresent(layer)) { - if(data.isAnimationPresent(layer)){ - return data.getAnimation(layer); - } - } - return null; - }); - } - - /** - * Test if layer exist animation which is not stop. - *

- * Only in dist client - * @param player Target player - * @param layer Target layer - * @return True when the currentTick not larger than stopTick - */ - @OnlyIn(Dist.CLIENT) - @SuppressWarnings("unchecked") - public static boolean isClientAnimationNotStop(AbstractClientPlayer player, @Nullable ResourceLocation layer) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> { - try { - Set resourceLocations = new HashSet<>(); - if(layer == null) resourceLocations = AnimationRegistry.getLayers().keySet(); - else resourceLocations.add(layer); - for (ResourceLocation location : resourceLocations) { - ModifierLayer animationModifierLayer = (ModifierLayer) PlayerAnimationAccess - .getPlayerAssociatedData(player).get(location); - if(animationModifierLayer == null) continue; - KeyframeAnimationPlayer animation = (KeyframeAnimationPlayer) animationModifierLayer.getAnimation(); - if(animation == null) return false; - int currentTick = animation.getCurrentTick(); - int stopTick = animation.getStopTick(); - return currentTick <= stopTick; - } - } catch (Exception ignored) {} - return false; - }); - } - - /** - * Test if layer exist animation which is not end. - *

- * Only in dist client - * @param player Target player - * @param layer Target layer - * @return True when animation is loop, or currentTick not larger than endTick - */ - @OnlyIn(Dist.CLIENT) - @SuppressWarnings("unchecked") - public static boolean isClientAnimationNotEnd(AbstractClientPlayer player, @Nullable ResourceLocation layer) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> { - try { - Set resourceLocations = new HashSet<>(); - if(layer == null) resourceLocations = AnimationRegistry.getLayers().keySet(); - else resourceLocations.add(layer); - for (ResourceLocation location : resourceLocations) { - ModifierLayer animationModifierLayer = (ModifierLayer) PlayerAnimationAccess - .getPlayerAssociatedData(player).get(location); - if(animationModifierLayer == null) continue; - KeyframeAnimationPlayer animation = (KeyframeAnimationPlayer) animationModifierLayer.getAnimation(); - if(animation == null) return false; - int currentTick = animation.getCurrentTick(); - boolean isLoop = animation.getData().isInfinite; - int endTick = animation.getData().endTick; - return isLoop || currentTick <= endTick; - } - } catch (Exception ignored) {} - return false; - }); - } - - /** - * Sync animation tick to client - * @param player Player - * @param target Target player - */ - public static void syncAnimation(ServerPlayer player, ServerPlayer target) { - ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.syncAnimation(player, target)); - } - - /** - * Sync animation tick on client - * @param player Player - * @param target Target player - */ - @OnlyIn(Dist.CLIENT) - public static void syncAnimation(AbstractClientPlayer player, AbstractClientPlayer target) { - ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.syncAnimation(player, target)); - } - - /** - * Join animation. - * @param player Will join player - * @param target Joined player - * @param force If force - * @return If success - */ - public static boolean joinAnimation(ServerPlayer player, ServerPlayer target, boolean force) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> { - Entity vehicle = target.getVehicle(); - if(!(vehicle instanceof AnimationRideEntity rideEntity)) return false; - int playerCount = rideEntity.getPlayers().size(); - Animation animation = rideEntity.getAnimation(); - if(animation == null) return false; - Ride ride = animation.getRide(); - if(ride == null) return false; - int maxCount = ride.getComponentAnimations().size(); - if(playerCount >= maxCount) return false; - return player.startRiding(vehicle, force); - }); - } - - /** - *

-     * Start animation together...
-     * The max participants' count is depend on your animation.
-     * Max count = Size of {@link Ride#getComponentAnimations()}
-     * 
- * @param player Leader - * @param layer Target layer - * @param animation Animation location - * @param force If force start leader - * @param participants Participants - */ - public static void startAnimationTogether( - ServerPlayer player, - ResourceLocation layer, - ResourceLocation animation, - boolean force, - ServerPlayer ... participants - ) { - AnimationUtils.playAnimationWithRide(player, layer, animation, force); - for (ServerPlayer participant : participants) { - AnimationUtils.joinAnimation(participant, player, force); - } - } - - /** - * Detach animation - * @param player Player - */ - public static void detachAnimation(ServerPlayer player) { - ANIMATION_RUNNER.testLoadedAndRun(() -> { - if(player.getVehicle() instanceof AnimationRideEntity) { - player.unRide(); - } - }); - } - - /** - * Clear Player animations. - * @param serverPlayer Target player - */ - public static void clearAnimation(ServerPlayer serverPlayer) { - ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationPlayer.clearAnimation(serverPlayer)); - } - - /** - * Test if layer exist and has been invite. - * @param layer Target layer - * @return If layer exist and has been invite - */ - public static boolean isAnimationLayerPresent(ResourceLocation layer) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationRegistry.getLayers().containsKey(layer)); - } - - /** - * Test if animation exist and has been invite. - * @param location Animation resource location - * @return If animation exist and has been invited - */ - public static boolean isAnimationPresent(ResourceLocation location) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationRegistry.getAnimations().containsKey(location)); - } - - /** - * The register handler - * @param forgeBus Forge event bus - * @param modBus Mod event bus - */ - public static void register(IEventBus forgeBus, IEventBus modBus){ - AnimationUtils.ANIMATION_RUNNER.testLoadedAndRun(() -> { - AnimationCapabilities.registerAnimationCapability(); - AnimationChannels.registerChannel(); - }); - AnimationUtils.ANIMATION_RUNNER.testLoadedAndAddListener(forgeBus, modBus); - } - - /** - * Get animation data from animation resource location.
- * You will get null if you use it too early.
- * Suggest only use it in game has loaded. - * @param location Animation resource location - * @return Animation data - */ - @Nullable - public static Animation getAnimation(ResourceLocation location) { - return AnimationRegistry.getAnimations().getOrDefault(location, null); - } - - /** - * Get the LyingType when there are animations which playing on player.
- * And It will return the first which be found. - * @param player Target player - * @return The first LyingType it find. - */ - @Nullable - public static Animation.LyingType getSideView(Player player) { - return ANIMATION_RUNNER.testLoadedAndCall(() -> { - IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); - if(data == null) return null; - Animation.LyingType lyingType = null; - for (ResourceLocation value : data.getAnimations().values()) { - Animation animation = getAnimation(value); - if(animation == null) return null; - Animation.LyingType type = animation.getLyingType(); - if(type == null) continue; - switch (type) { - case FRONT,BACK -> {} - case LEFT,RIGHT -> lyingType = animation.getLyingType(); - } - } - return lyingType; - }); - } - - /** - * Get the HeightModifier when there are animations which playing on player.
- * And It will return the first which be found. - * @param player Target player - * @return The first HeightModifier it find. - */ - public static float getHeightModifier(Player player) { - Float result = ANIMATION_RUNNER.testLoadedAndCall(() -> { - IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); - if (data == null) return 1.0f; - float heightModifier = 1.0f; - for (ResourceLocation value : data.getAnimations().values()) { - Animation animation = getAnimation(value); - if (animation == null) continue; - float animationHeightModifier = animation.getHeightModifier(); - heightModifier = Math.min(heightModifier, animationHeightModifier); - } - return heightModifier; - }); - return result == null ? 1.0f : result; - } - - /** - * Test if animation is playing
- * if not, it will remove the animation resource location on client - *

- * Only in dist client - * @param clientPlayer Target player - */ - @OnlyIn(Dist.CLIENT) - public static void refreshAnimation(AbstractClientPlayer clientPlayer) { - IAnimationCapability data = AnimationDataCapability.getCapability(clientPlayer).orElse(null); - if(data == null) return; - Set oldLayers = new HashSet<>(data.getAnimations().keySet()); - boolean dirty = false; - for (ResourceLocation layer : Set.copyOf(oldLayers)) { - if (!isClientAnimationNotStop(clientPlayer, layer)) { - oldLayers.remove(layer); - dirty = true; - } - } - if(dirty) ModChannel.sendToServer(new RefreshAnimationPacket(oldLayers)); - } -} diff --git a/src/main/java/com/linearpast/sccore/animation/capability/AnimationDataCapability.java b/src/main/java/com/linearpast/sccore/animation/capability/AnimationDataCapability.java index 540c375..4586a18 100644 --- a/src/main/java/com/linearpast/sccore/animation/capability/AnimationDataCapability.java +++ b/src/main/java/com/linearpast/sccore/animation/capability/AnimationDataCapability.java @@ -1,8 +1,8 @@ package com.linearpast.sccore.animation.capability; import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.AnimationUtils; import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.helper.AnimationHelper; import com.linearpast.sccore.animation.network.toclient.AnimationCapabilityPacket; import com.linearpast.sccore.animation.register.AnimationRegistry; import com.linearpast.sccore.capability.CapabilityUtils; @@ -35,7 +35,7 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen public void mergeAnimations(Map animations) { animations.forEach((key, value) -> { if (AnimationRegistry.getLayers().containsKey(key)) { - if (AnimationUtils.isAnimationPresent(value)) { + if (AnimationHelper.INSTANCE.isAnimationPresent(value)) { if(Objects.equals(rideAnimLayer, key)) { removeRiderAnimation(); } @@ -49,7 +49,7 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen @Override public boolean mergeAnimation(ResourceLocation layer, ResourceLocation animation) { if (AnimationRegistry.getLayers().containsKey(layer)) { - if (AnimationUtils.isAnimationPresent(animation)) { + if (AnimationHelper.INSTANCE.isAnimationPresent(animation)) { if(Objects.equals(rideAnimLayer, layer)) { removeRiderAnimation(); } @@ -74,12 +74,12 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen @Nullable @Override public ResourceLocation getAnimation(ResourceLocation layer) { - return animMap.get(layer); + return animMap.getOrDefault(layer, null); } @Override public Map getAnimations() { - return animMap; + return Map.copyOf(animMap); } @Override @@ -105,8 +105,8 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen @Override public void setRiderAnimation(@NotNull ResourceLocation layer, @NotNull ResourceLocation animation) { - if(AnimationUtils.isAnimationLayerPresent(layer)) { - if(AnimationUtils.isAnimationPresent(animation)) { + if(AnimationHelper.INSTANCE.isAnimationLayerPresent(layer)) { + if(AnimationHelper.INSTANCE.isAnimationPresent(animation)) { this.rideAnimLayer = layer; this.rideAnimation = animation; if(animMap.get(layer) != null) { @@ -163,19 +163,24 @@ public class AnimationDataCapability extends SimplePlayerCapabilitySync implemen if(tag.contains(RideAnimation)) this.rideAnimation = new ResourceLocation(tag.getString(RideAnimation)); } + @Override + public ResourceLocation getKey() { + return key; + } + @Override public SimpleCapabilityPacket getDefaultPacket() { - return new AnimationCapabilityPacket(serializeNBT()); + return new AnimationCapabilityPacket(this); } @Override public void attachInit(Player player) { Map map = new HashMap<>(this.animMap); map.forEach((key, value) -> { - if(!AnimationUtils.isAnimationLayerPresent(key)) this.animMap.remove(key); - if(!AnimationUtils.isAnimationPresent(value)) this.animMap.remove(key); + if(!AnimationHelper.INSTANCE.isAnimationLayerPresent(key)) this.animMap.remove(key); + if(!AnimationHelper.INSTANCE.isAnimationPresent(value)) this.animMap.remove(key); }); - if(rideAnimLayer != null && !AnimationUtils.isAnimationLayerPresent(rideAnimLayer)) { + if(rideAnimLayer != null && !AnimationHelper.INSTANCE.isAnimationLayerPresent(rideAnimLayer)) { removeRiderAnimation(); } } diff --git a/src/main/java/com/linearpast/sccore/animation/capability/RawAnimationDataCapability.java b/src/main/java/com/linearpast/sccore/animation/capability/RawAnimationDataCapability.java new file mode 100644 index 0000000..8a10f74 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/capability/RawAnimationDataCapability.java @@ -0,0 +1,172 @@ +package com.linearpast.sccore.animation.capability; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.helper.AnimationHelper; +import com.linearpast.sccore.animation.network.toclient.RawAnimationCapabilityPacket; +import com.linearpast.sccore.animation.register.AnimationRegistry; +import com.linearpast.sccore.capability.CapabilityUtils; +import com.linearpast.sccore.capability.data.ICapabilitySync; +import com.linearpast.sccore.capability.data.player.SimplePlayerCapabilitySync; +import com.linearpast.sccore.capability.network.SimpleCapabilityPacket; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.server.ServerLifecycleHooks; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class RawAnimationDataCapability extends SimplePlayerCapabilitySync { + public static final ResourceLocation key = new ResourceLocation(SnowyCrescentCore.MODID, "raw_animation_data"); + + public static final String AnimMap = "AnimMap"; + public static final String RideAnimLayer = "RideAnimLayer"; + public static final String RideAnimation = "RideAnimation"; + + private final Map animMap = new HashMap<>(); + private ResourceLocation rideAnimLayer; + private ResourceLocation rideAnimation; + + public boolean mergeAnimation(ResourceLocation layer, ResourceLocation animation) { + if (AnimationRegistry.getLayers().containsKey(layer)) { + if(Objects.equals(rideAnimLayer, layer)) { + removeRiderAnimation(); + } + this.animMap.put(layer, animation); + setDirty(true); + return true; + } + return false; + } + + public boolean removeAnimation(ResourceLocation layer) { + ResourceLocation remove = this.animMap.remove(layer); + if(remove != null) { + setDirty(true); + return true; + } + return false; + } + + @Nullable + public ResourceLocation getAnimation(ResourceLocation layer) { + return animMap.getOrDefault(layer, null); + } + + public Map getAnimations() { + return Map.copyOf(animMap); + } + + public void clearAnimations() { + this.animMap.clear(); + setDirty(true); + } + + public boolean isAnimationPresent(ResourceLocation layer) { + return animMap.containsKey(layer); + } + + public ResourceLocation getRiderAnimLayer() { + return rideAnimLayer; + } + + public ResourceLocation getRiderAnimation() { + return rideAnimation; + } + + public void setRiderAnimation(@NotNull ResourceLocation layer, @NotNull ResourceLocation animation) { + if(AnimationHelper.INSTANCE.isAnimationLayerPresent(layer)) { + MinecraftServer currentServer = ServerLifecycleHooks.getCurrentServer(); + if(currentServer == null) return; + ServerPlayer serverPlayer = currentServer.getPlayerList().getPlayer(this.getOwnerUUID()); + if(serverPlayer == null) return; + IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null); + if(data == null) return; + if(data.getRiderAnimation() != null) return; + if(data.getRiderAnimLayer() != null) { + data.removeRiderAnimation(); + return; + } + this.rideAnimLayer = layer; + this.rideAnimation = animation; + if(animMap.get(layer) != null) { + animMap.remove(layer); + } + setDirty(true); + } + } + + public void removeRiderAnimation() { + this.rideAnimLayer = null; + this.rideAnimation = null; + setDirty(true); + } + + @Override + public void copyFrom(ICapabilitySync oldData) { + IAnimationCapability data = (IAnimationCapability) oldData; + this.animMap.clear(); + this.animMap.putAll(data.getAnimations()); + this.rideAnimLayer = data.getRiderAnimLayer(); + this.rideAnimation = data.getRiderAnimation(); + } + + @Override + public CompoundTag toTag(CompoundTag tag) { + if(!animMap.isEmpty()) { + CompoundTag animMapTag = new CompoundTag(); + animMap.forEach((string, animation) -> + animMapTag.putString(string.toString(), animation.toString()) + ); + tag.put(AnimMap, animMapTag); + } + if(rideAnimLayer != null) tag.putString(RideAnimLayer, rideAnimLayer.toString()); + if(rideAnimation != null) tag.putString(RideAnimation, rideAnimation.toString()); + return tag; + } + + @Override + public void fromTag(CompoundTag tag) { + this.animMap.clear(); + this.rideAnimLayer = null; + this.rideAnimation = null; + if(tag.contains(AnimMap)) { + CompoundTag animMapTag = tag.getCompound(AnimMap); + animMapTag.getAllKeys().forEach(key -> this.animMap.put( + new ResourceLocation(key), + new ResourceLocation(animMapTag.getString(key)) + )); + } + if(tag.contains(RideAnimLayer)) this.rideAnimLayer = new ResourceLocation(tag.getString(RideAnimLayer)); + if(tag.contains(RideAnimation)) this.rideAnimation = new ResourceLocation(tag.getString(RideAnimation)); + } + + @Override + public ResourceLocation getKey() { + return key; + } + + @Override + public SimpleCapabilityPacket getDefaultPacket() { + return new RawAnimationCapabilityPacket(this); + } + + @Override + public void attachInit(Player entity) { + clearAnimations(); + removeRiderAnimation(); + } + + public static Optional getCapability(Player player){ + return Optional.ofNullable(CapabilityUtils.getPlayerCapability( + player, RawAnimationDataCapability.key, RawAnimationDataCapability.class + )); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/ApplyCommand.java b/src/main/java/com/linearpast/sccore/animation/command/ApplyCommand.java new file mode 100644 index 0000000..0fd8ad9 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/ApplyCommand.java @@ -0,0 +1,131 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.command.exception.ApiBackException; +import com.linearpast.sccore.animation.helper.AnimationHelper; +import com.linearpast.sccore.animation.helper.IAnimationHelper; +import com.linearpast.sccore.animation.helper.IHelperGetter; +import com.linearpast.sccore.animation.utils.ApiBack; +import com.linearpast.sccore.core.configs.ModConfigs; +import com.linearpast.sccore.core.datagen.ModLang; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; + +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +public class ApplyCommand { + public static void register(LiteralArgumentBuilder animCommand) { + animCommand + .then(literal("apply") + .then(argument("target", EntityArgument.player()) + .executes(ApplyCommand::apply) + ) + .then(literal("accept") + .then(argument("player", EntityArgument.player()) + .executes(ApplyCommand::acceptApply) + ) + ) + ); + } + + private static int apply(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + ServerPlayer target = EntityArgument.getPlayer(context, "target"); + ServerPlayer player = source.getPlayerOrException(); + + Entity vehicle = target.getVehicle(); + if(vehicle == null) throw new ApiBackException(ApiBack.UNSUPPORTED); + + ApiBack back = AnimationHelper.INSTANCE.apply(player, target); + if(back == ApiBack.COOLDOWN) { + int cooldown = ModConfigs.Server.applyCooldown.get(); + throw ApiBackException.withCooldown(cooldown); + } + if(back != ApiBack.SUCCESS) throw new ApiBackException(back); + + //click event + Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sccore anim apply accept " + player.getName().getString()) + ).withUnderlined(true); + + //send message to all participants + for (Entity passenger : vehicle.getPassengers()) { + passenger.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.APPLIED_JOIN_MESSAGE.getKey(), + player.getName().copy() + ).append(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_MESSAGE_CLICK.getKey() + ).setStyle(pStyle))); + } + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.APPLY_JOIN_MESSAGE.getKey() + ).withStyle(ChatFormatting.GREEN), true); + return 1; + } catch (ApiBackException e) { + source.sendFailure(e.getCommandFailBack().withStyle(ChatFormatting.RED)); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + } + return 0; + } + + private static int acceptApply(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + ServerPlayer player = source.getPlayerOrException(); + ServerPlayer applier = EntityArgument.getPlayer(context, "applier"); + + Entity vehicle = player.getVehicle(); + if(vehicle == null) throw new ApiBackException(ApiBack.UNSUPPORTED); + + ApiBack back = ApiBack.RESOURCE_NOT_FOUND; + for (IAnimationHelper helper : IHelperGetter.HELPERS) { + back = helper.acceptApply(player, applier); + if(back == ApiBack.SUCCESS) break; + } + if(back == ApiBack.OUT_RANGE) throw ApiBackException.withOutRange(ModConfigs.Server.applyValidDistance.get()); + if(back != ApiBack.SUCCESS) throw new ApiBackException(back); + + //define message + MutableComponent successMessage = Component.translatable( + ModLang.TranslatableMessage.ACCEPT_APPLY_SUCCESS.getKey(), + player.getName().copy(), applier.getName().copy() + ).withStyle(ChatFormatting.GREEN); + + //send message + source.sendSuccess(() -> successMessage, true); + for (Entity passenger : vehicle.getPassengers()) { + if(!passenger.getUUID().equals(player.getUUID()) && !passenger.getUUID().equals(applier.getUUID())) { + passenger.sendSystemMessage(successMessage); + } + } + applier.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.APPLY_SUCCESS.getKey(), + player.getName().copy() + ).withStyle(ChatFormatting.GREEN)); + } catch (ApiBackException e) { + source.sendFailure(e.getCommandFailBack().withStyle(ChatFormatting.RED)); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + return 0; + } + return 1; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/ApplyJoinAnimCommand.java b/src/main/java/com/linearpast/sccore/animation/command/ApplyJoinAnimCommand.java deleted file mode 100644 index a0406bd..0000000 --- a/src/main/java/com/linearpast/sccore/animation/command/ApplyJoinAnimCommand.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.linearpast.sccore.animation.command; - -import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.AnimationUtils; -import com.linearpast.sccore.animation.entity.AnimationRideEntity; -import com.linearpast.sccore.core.configs.ModConfigs; -import com.linearpast.sccore.core.datagen.ModLang; -import com.mojang.brigadier.arguments.BoolArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import net.minecraft.ChatFormatting; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.arguments.EntityArgument; -import net.minecraft.network.chat.ClickEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.Style; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.Entity; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static net.minecraft.commands.Commands.argument; -import static net.minecraft.commands.Commands.literal; - -public class ApplyJoinAnimCommand { - private static final Map> applies = new HashMap<>(); - record ApplyRecord(long time, boolean isForce){} - private static final Map lastAppliedMap = new HashMap<>(); - public static void register(LiteralArgumentBuilder animCommand) { - animCommand - .then(literal("joinApply") - .then(argument("target", EntityArgument.player()) - .executes(ApplyJoinAnimCommand::tryJoinAnimation) - .then(argument("force", BoolArgumentType.bool()) - .executes(ApplyJoinAnimCommand::tryJoinAnimation) - ) - ) - .then(literal("accept") - .then(argument("player", EntityArgument.player()) - .executes(ApplyJoinAnimCommand::acceptJoinAnimation) - ) - ) - ); - } - - private static int tryJoinAnimation(CommandContext context) { - CommandSourceStack source = context.getSource(); - try { - boolean force = false; - try {force = BoolArgumentType.getBool(context, "force");} - catch (Exception ignored) {} - ServerPlayer target = EntityArgument.getPlayer(context, "target"); - ServerPlayer player; - try {player = EntityArgument.getPlayer(context, "player");} - catch (Exception e) { player = source.getPlayerOrException(); } - - Entity vehicle = target.getVehicle(); - if(!(vehicle instanceof AnimationRideEntity rideEntity) || !rideEntity.canAddPassenger(player)) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.WITHOUT_ANIMATION_RIDE_ENTITY.getKey() - ).withStyle(ChatFormatting.RED)); - return 0; - } - - //cooldown - Long lastApplied = lastAppliedMap.getOrDefault(player.getUUID(), null); - long now = System.currentTimeMillis(); - int applyCooldown = ModConfigs.Server.applyCooldown.get() * 1000; - if(!(lastApplied == null || now - lastApplied > applyCooldown)) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_COOLDOWN.getKey() - ).withStyle(ChatFormatting.RED)); - return 0; - } - lastAppliedMap.put(player.getUUID(), now); - - UUID rideEntityUUID = rideEntity.getUUID(); - Map applyRecordMap = applies.getOrDefault(rideEntityUUID, new HashMap<>()); - applyRecordMap.put(player.getUUID(), new ApplyRecord(now, force)); - applies.put(rideEntityUUID, applyRecordMap); - - //click event - Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( - new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sccore anim joinApply accept " + player.getName().getString()) - ).withUnderlined(true); - - //send message to all participants - for (Entity passenger : rideEntity.getPassengers()) { - passenger.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.APPLIED_JOIN_MESSAGE.getKey(), - player.getName().copy() - ).append(Component.translatable( - ModLang.TranslatableMessage.ACCEPT_MESSAGE_CLICK.getKey() - ).setStyle(pStyle))); - } - source.sendSuccess(() -> Component.translatable( - ModLang.TranslatableMessage.APPLY_JOIN_MESSAGE.getKey() - ).withStyle(ChatFormatting.GREEN), true); - } catch (Exception e) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() - ).withStyle(ChatFormatting.RED)); - SnowyCrescentCore.log.error(e.getMessage()); - return 0; - } - return 1; - } - - private static int acceptJoinAnimation(CommandContext context) { - CommandSourceStack source = context.getSource(); - try { - ServerPlayer target = source.getPlayerOrException(); - ServerPlayer player = EntityArgument.getPlayer(context, "player"); - if(!(target.getVehicle() instanceof AnimationRideEntity rideEntity) || !rideEntity.canAddPassenger(player)) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.WITHOUT_ANIMATION_RIDE_ENTITY.getKey() - ).withStyle(ChatFormatting.RED)); - return 0; - } - - Map applyRecordMap = applies.getOrDefault(rideEntity.getUUID(), null); - if(applyRecordMap == null) throw new Exception(); - ApplyRecord applyRecord = applyRecordMap.getOrDefault(player.getUUID(), null); - - //test if expired - long now = System.currentTimeMillis(); - Integer applyDuration = ModConfigs.Server.applyDuration.get(); - if(now - applyRecord.time > applyDuration * 1000 || applyDuration == 0) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.ACCEPT_APPLY_EXPIRED.getKey(), - applyDuration - ).withStyle(ChatFormatting.RED)); - player.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.APPLY_EXPIRED.getKey(), - target.getName().copy(), - applyDuration - ).withStyle(ChatFormatting.RED)); - return 0; - } - - //test if in range - Integer applyDistance = ModConfigs.Server.applyDistance.get(); - if(player.position().distanceToSqr(rideEntity.position()) > applyDistance * applyDistance || applyDistance == 0) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.ACCEPT_APPLY_TOO_FAR.getKey(), - applyDistance - ).withStyle(ChatFormatting.RED)); - player.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.APPLY_TOO_FAR.getKey(), - target.getName().copy(), - applyDistance - ).withStyle(ChatFormatting.RED)); - return 0; - } - applyRecordMap.remove(player.getUUID()); - applies.put(player.getUUID(), applyRecordMap); - - //define message - MutableComponent successMessage = Component.translatable( - ModLang.TranslatableMessage.ACCEPT_APPLY_SUCCESS.getKey(), - target.getName().copy(), - player.getName().copy() - ).withStyle(ChatFormatting.GREEN); - - //play - AnimationUtils.joinAnimation(player, target, applyRecord.isForce); - - //send message - source.sendSuccess(() -> successMessage, true); - for (Entity passenger : rideEntity.getPassengers()) { - if(!passenger.getUUID().equals(target.getUUID()) && !passenger.getUUID().equals(player.getUUID())) { - passenger.sendSystemMessage(successMessage); - } - } - player.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.APPLY_SUCCESS.getKey(), - target.getName().copy() - ).withStyle(ChatFormatting.GREEN)); - } catch (Exception e) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() - ).withStyle(ChatFormatting.RED)); - SnowyCrescentCore.log.error(e.getMessage()); - return 0; - } - return 1; - } -} diff --git a/src/main/java/com/linearpast/sccore/animation/command/CombineAnimCommand.java b/src/main/java/com/linearpast/sccore/animation/command/CombineAnimCommand.java deleted file mode 100644 index a0df00b..0000000 --- a/src/main/java/com/linearpast/sccore/animation/command/CombineAnimCommand.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.linearpast.sccore.animation.command; - -import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.AnimationUtils; -import com.linearpast.sccore.animation.command.argument.AnimationArgument; -import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument; -import com.linearpast.sccore.core.configs.ModConfigs; -import com.linearpast.sccore.core.datagen.ModLang; -import com.mojang.brigadier.arguments.BoolArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import net.minecraft.ChatFormatting; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.arguments.EntityArgument; -import net.minecraft.network.chat.ClickEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static net.minecraft.commands.Commands.argument; -import static net.minecraft.commands.Commands.literal; - -public class CombineAnimCommand { - private static final Map lastInvitedMap = new HashMap<>(); - record InviteRecord(long time, ResourceLocation layer, ResourceLocation animation, boolean isForce){} - private static final Map> invites = new HashMap<>(); - public static void register(LiteralArgumentBuilder animCommand) { - animCommand.then(literal("invite") - .then(argument("player", EntityArgument.player()) - .then(argument("layer", AnimationLayerArgument.layer()) - .then(argument("anim", AnimationArgument.animation()) - .executes(CombineAnimCommand::invite) - .then(argument("force", BoolArgumentType.bool()) - .executes(CombineAnimCommand::invite) - ) - ) - ) - ) - .then(literal("accept") - .then(argument("player", EntityArgument.player()) - .executes(CombineAnimCommand::acceptInvite) - ) - ) - ); - } - - private static int invite(CommandContext context) { - CommandSourceStack source = context.getSource(); - try { - //get info - boolean force = false; - try {force = BoolArgumentType.getBool(context, "force");} - catch (Exception ignored) {} - ServerPlayer player = source.getPlayerOrException(); - ServerPlayer target = EntityArgument.getPlayer(context, "player"); - - //cooldown - Long lastInvited = lastInvitedMap.getOrDefault(player.getUUID(), null); - long now = System.currentTimeMillis(); - int inviteCooldown = ModConfigs.Server.inviteCooldown.get() * 1000; - if(!(lastInvited == null || now - lastInvited > inviteCooldown)) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_COOLDOWN.getKey() - ).withStyle(ChatFormatting.RED)); - return 0; - } - lastInvitedMap.put(player.getUUID(), now); - - String layerString = AnimationLayerArgument.getLayer(context, "layer"); - String animString = AnimationArgument.getAnimation(context, "anim"); - ResourceLocation layer = new ResourceLocation(layerString); - ResourceLocation anim = new ResourceLocation(animString); - - //test info present - boolean animationPresent = AnimationUtils.isAnimationPresent(anim); - boolean animationLayerPresent = AnimationUtils.isAnimationLayerPresent(layer); - if(!animationLayerPresent || !animationPresent) throw new Exception(); - - //update static cache - Map inviteRecordMap = invites.getOrDefault(player.getUUID(), new HashMap<>()); - inviteRecordMap.put(target.getUUID(), new InviteRecord(System.currentTimeMillis(), layer, anim, force)); - invites.put(player.getUUID(), inviteRecordMap); - - //click event - Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( - new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sccore anim invite accept " + player.getName().getString()) - ).withUnderlined(true); - //send message - target.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.INVITED_MESSAGE.getKey(), - player.getName().copy(), - anim.toString() - ).append(Component.translatable( - ModLang.TranslatableMessage.ACCEPT_MESSAGE_CLICK.getKey() - ).setStyle(pStyle))); - source.sendSuccess(() -> Component.translatable( - ModLang.TranslatableMessage.INVITE_MESSAGE.getKey() - ).withStyle(ChatFormatting.GREEN), true); - } catch (Exception e) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() - ).withStyle(ChatFormatting.RED)); - SnowyCrescentCore.log.error(e.getMessage()); - return 0; - } - return 1; - } - - private static int acceptInvite(CommandContext context) { - CommandSourceStack source = context.getSource(); - try { - ServerPlayer target = source.getPlayerOrException(); - ServerPlayer player = EntityArgument.getPlayer(context, "player"); - - Map inviteRecordMap = invites.getOrDefault(player.getUUID(), null); - if(inviteRecordMap == null) throw new Exception(); - InviteRecord inviteRecord = inviteRecordMap.getOrDefault(target.getUUID(), null); - if(inviteRecord == null) throw new Exception(); - - //test if expired - long now = System.currentTimeMillis(); - Integer inviteDuration = ModConfigs.Server.inviteDuration.get(); - if(now - inviteRecord.time > inviteDuration * 1000 || inviteDuration == 0) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.ACCEPT_INVITE_EXPIRED.getKey(), - inviteDuration - ).withStyle(ChatFormatting.RED)); - player.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.INVITE_EXPIRED.getKey(), - target.getName().copy(), - inviteDuration - ).withStyle(ChatFormatting.RED)); - return 0; - } - - //test if in range - Integer inviteDistance = ModConfigs.Server.inviteDistance.get(); - if(player.position().distanceToSqr(target.position()) > inviteDistance * inviteDistance || inviteDistance == 0) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.ACCEPT_INVITE_TOO_FAR.getKey(), - inviteDistance - ).withStyle(ChatFormatting.RED)); - player.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.INVITE_TOO_FAR.getKey(), - target.getName().copy(), - inviteDistance - ).withStyle(ChatFormatting.RED)); - return 0; - } - inviteRecordMap.remove(target.getUUID()); - invites.put(player.getUUID(), inviteRecordMap); - - //play animation - AnimationUtils.startAnimationTogether(player, inviteRecord.layer, inviteRecord.animation, inviteRecord.isForce, target); - - //send message - source.sendSuccess(() -> Component.translatable( - ModLang.TranslatableMessage.ACCEPT_INVITE_SUCCESS.getKey() - ).withStyle(ChatFormatting.GREEN), true); - player.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.INVITE_SUCCESS.getKey(), - target.getName().copy() - ).withStyle(ChatFormatting.GREEN)); - } catch (Exception e) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() - ).withStyle(ChatFormatting.RED)); - SnowyCrescentCore.log.error(e.getMessage()); - return 0; - } - return 1; - } -} diff --git a/src/main/java/com/linearpast/sccore/animation/command/InviteCommand.java b/src/main/java/com/linearpast/sccore/animation/command/InviteCommand.java new file mode 100644 index 0000000..dac2bca --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/InviteCommand.java @@ -0,0 +1,141 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.command.argument.AnimationArgument; +import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument; +import com.linearpast.sccore.animation.command.exception.ApiBackException; +import com.linearpast.sccore.animation.data.AnimationData; +import com.linearpast.sccore.animation.data.RawAnimationData; +import com.linearpast.sccore.animation.helper.HelperGetterFromAnimation; +import com.linearpast.sccore.animation.helper.IAnimationHelper; +import com.linearpast.sccore.animation.helper.IHelperGetter; +import com.linearpast.sccore.animation.utils.ApiBack; +import com.linearpast.sccore.core.configs.ModConfigs; +import com.linearpast.sccore.core.datagen.ModLang; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +public class InviteCommand { + public static void register(LiteralArgumentBuilder animCommand) { + animCommand.then(literal("invite") + .then(argument("players", EntityArgument.players()) + .then(argument("layer", AnimationLayerArgument.layer()) + .then(argument("anim", AnimationArgument.animation()) + .executes(InviteCommand::invite) + ) + ) + ) + .then(literal("accept") + .then(argument("player", EntityArgument.player()) + .executes(InviteCommand::acceptInvite) + ) + ) + ); + } + + private static int invite(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + ServerPlayer player = source.getPlayerOrException(); + Collection players = EntityArgument.getPlayers(context, "players"); + + String layerString = AnimationLayerArgument.getLayer(context, "layer"); + String animString = AnimationArgument.getAnimation(context, "anim"); + ResourceLocation layer = new ResourceLocation(layerString); + ResourceLocation anim = new ResourceLocation(animString); + + //test info present + IAnimationHelper helper = HelperGetterFromAnimation.create(anim).getHelper(); + if (helper == null) throw new ApiBackException(ApiBack.RESOURCE_NOT_FOUND); + AnimationData animationData; + if(helper.isAnimationPresent(anim)) animationData = helper.getAnimation(anim); + else animationData = RawAnimationData.create(anim); + + List targets = players.stream().map(Entity::getUUID).toList(); + ApiBack back = helper.invite(player, layer, animationData, targets); + if(back == ApiBack.COOLDOWN) { + int cooldown = ModConfigs.Server.inviteCooldown.get(); + throw ApiBackException.withCooldown(cooldown); + } + if(back != ApiBack.SUCCESS) throw new ApiBackException(back); + + //click event + Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sccore anim invite accept " + player.getName().getString()) + ).withUnderlined(true); + //send message + for (ServerPlayer target : players) { + target.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.INVITED_MESSAGE.getKey(), + player.getName().copy(), + anim.toString() + ).append(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_MESSAGE_CLICK.getKey() + ).setStyle(pStyle))); + } + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.INVITE_MESSAGE.getKey() + ).withStyle(ChatFormatting.GREEN), true); + return 1; + } catch (ApiBackException e) { + source.sendFailure(e.getCommandFailBack().withStyle(ChatFormatting.RED)); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + } + return 0; + } + + private static int acceptInvite(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + ServerPlayer player = source.getPlayerOrException(); + ServerPlayer inviter = EntityArgument.getPlayer(context, "player"); + + //play animation + ApiBack back = ApiBack.RESOURCE_NOT_FOUND; + for (IAnimationHelper helper : IHelperGetter.HELPERS) { + back = helper.acceptInvite(player, inviter); + if(back == ApiBack.SUCCESS) break; + } + if(back == ApiBack.OUT_RANGE) throw ApiBackException.withOutRange(ModConfigs.Server.inviteValidDistance.get()); + if(back != ApiBack.SUCCESS) throw new ApiBackException(back); + + //send message + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.ACCEPT_INVITE_SUCCESS.getKey() + ).withStyle(ChatFormatting.GREEN), true); + inviter.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.INVITE_SUCCESS.getKey(), + player.getName().copy() + ).withStyle(ChatFormatting.GREEN)); + return 1; + } catch (ApiBackException e) { + source.sendFailure(e.getCommandFailBack().withStyle(ChatFormatting.RED)); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + } + return 0; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/GenerateJsonCommand.java b/src/main/java/com/linearpast/sccore/animation/command/JsonCommand.java similarity index 53% rename from src/main/java/com/linearpast/sccore/animation/command/GenerateJsonCommand.java rename to src/main/java/com/linearpast/sccore/animation/command/JsonCommand.java index 8af7476..c884588 100644 --- a/src/main/java/com/linearpast/sccore/animation/command/GenerateJsonCommand.java +++ b/src/main/java/com/linearpast/sccore/animation/command/JsonCommand.java @@ -1,10 +1,7 @@ package com.linearpast.sccore.animation.command; import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.data.Animation; -import com.linearpast.sccore.animation.data.util.AnimJson; -import com.linearpast.sccore.animation.data.util.AnimLayerJson; -import com.linearpast.sccore.animation.register.AnimationRegistry; +import com.linearpast.sccore.animation.helper.JsonHelper; import com.linearpast.sccore.core.datagen.ModLang; import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; @@ -13,24 +10,19 @@ import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; -import net.minecraft.server.MinecraftServer; -import net.minecraft.world.level.storage.LevelResource; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.Comparator; import static net.minecraft.commands.Commands.argument; import static net.minecraft.commands.Commands.literal; -public class GenerateJsonCommand { +public class JsonCommand { public static void register(LiteralArgumentBuilder animCommand) { animCommand.then(literal("json").requires(cs -> cs.hasPermission(2)) - .then(literal("clearFile").executes(GenerateJsonCommand::clearJson)) + .then(literal("clearFile").executes(JsonCommand::clearJson)) .then(literal("generate") .then(literal("anim") - .then(literal("example").executes(GenerateJsonCommand::generateExample)) + .then(literal("example").executes(JsonCommand::generateExample)) .executes(context -> generateJson(context, false, false)) .then(argument("reset", BoolArgumentType.bool()) .executes(context -> @@ -50,60 +42,25 @@ public class GenerateJsonCommand { ); } - //clear path, remove file. - private static void clearPath(Path dir) throws IOException { - if (!Files.exists(dir)) return; - try (var pathStream = Files.walk(dir)) { - pathStream.sorted(Comparator.reverseOrder()).forEach(path -> { - try { - Files.delete(path); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } catch (RuntimeException ignored) {} - } - private static int generateJson(CommandContext context, boolean isLayer, boolean isReset) { CommandSourceStack source = context.getSource(); try { - //get animation path - Path animationPath = getAnimationPath(source); - if (!Files.exists(animationPath)) { - try {Files.createDirectories(animationPath);} - catch (IOException e) { throw new RuntimeException(e); } - } - if(isReset) clearPath(animationPath); + //generate + JsonHelper helper = JsonHelper.getHelper(source.getServer()); + Path path = helper.generateJson(isLayer, isReset); - //generate json layer or animation - if(isLayer) { - //generate - Path path = AnimLayerJson.Writer.syntaxImmediately(animationPath); - //send message - MutableComponent component = Component.translatable( - ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), - "layer", "Server" - ).withStyle(ChatFormatting.GREEN); - component.append(Component.translatable( - ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), - path.toString() - )); - source.sendSuccess(() -> component, true); - } else { - //generate - for (Animation value : AnimationRegistry.getAnimations().values()) { - AnimJson.Writer.stream(animationPath, value).syntax(); - } - //send message - MutableComponent component = Component.translatable( - ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), - "anim", "Server" - ).withStyle(ChatFormatting.GREEN); - component.append(Component.translatable( - ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), - animationPath.toString() - )); - } + if(path == null) throw new Exception(); + MutableComponent component; + String key = ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(); + if(isLayer) component = Component.translatable(key, "layer", "Server"); + else component = Component.translatable(key, "anim", "Server"); + + component.withStyle(ChatFormatting.GREEN).append(Component.translatable( + ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), + path.toString() + )); + + source.sendSuccess(() -> component, true); } catch (Exception e) { source.sendFailure(Component.translatable( ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() @@ -118,8 +75,7 @@ public class GenerateJsonCommand { CommandSourceStack source = context.getSource(); try { //clear path - Path animationPath = getAnimationPath(source); - clearPath(animationPath); + JsonHelper.getHelper(source.getServer()).clearPath(); } catch (Exception e) { source.sendFailure(Component.translatable( ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() @@ -134,8 +90,10 @@ public class GenerateJsonCommand { CommandSourceStack source = context.getSource(); try { //generate - Path animationPath = getAnimationPath(source); - Path path = AnimJson.Writer.syntaxExample(animationPath); + JsonHelper helper = JsonHelper.getHelper(source.getServer()); + Path path = helper.generateExample(); + if(path == null) throw new Exception(); + //send message MutableComponent component = Component.translatable( ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), @@ -154,15 +112,4 @@ public class GenerateJsonCommand { } return 1; } - - /** - * Get animation path - * @param source command source - * @return path - */ - private static Path getAnimationPath(CommandSourceStack source) { - MinecraftServer server = source.getServer(); - Path dataPackPath = server.getWorldPath(LevelResource.DATAPACK_DIR); - return dataPackPath.resolve("animation"); - } } diff --git a/src/main/java/com/linearpast/sccore/animation/command/ListServerCommand.java b/src/main/java/com/linearpast/sccore/animation/command/ListServerCommand.java new file mode 100644 index 0000000..3a7908f --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/ListServerCommand.java @@ -0,0 +1,73 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.animation.register.AnimationRegistry; +import com.linearpast.sccore.core.datagen.ModLang; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; + +import java.util.List; +import java.util.Random; + +import static net.minecraft.commands.Commands.literal; + +public class ListServerCommand { + public static void register(LiteralArgumentBuilder animCommand) { + animCommand.then(literal("list") + .then(literal("layers").executes(ListServerCommand::listLayers)) + .then(literal("serverAnimations") + .executes(ListServerCommand::listAnimations) + ) + ); + } + + private static int listAnimations(CommandContext context) { + try { + CommandSourceStack source = context.getSource(); + List list = AnimationRegistry.getAnimations().keySet().stream().toList(); + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.LIST_ANIMATION_RESOURCE.getKey(), + "Server", "Animations", getString(list) + ), false); + return 1; + } catch (Exception ignored) {} + return 0; + } + + private static int listLayers(CommandContext context) { + try { + CommandSourceStack source = context.getSource(); + List list = AnimationRegistry.getLayers().keySet().stream().toList(); + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.LIST_ANIMATION_RESOURCE.getKey(), + "Server", "Layers", getString(list) + ), false); + return 1; + } catch (Exception ignored) {} + return 0; + } + + public static Component getString(List list) { + MutableComponent component = Component.empty(); + Random random = new Random(System.currentTimeMillis()); + for (int i = 0; i < list.size(); i++) { + component.append(Component.literal(list.get(i).toString()).withStyle(randomColor(random))); + if(i < list.size()-1){ + component.append(", ").withStyle(ChatFormatting.WHITE); + } + } + component.append(".").withStyle(Style.EMPTY.withColor(ChatFormatting.RED)); + return component; + } + + private static ChatFormatting randomColor(Random random){ + int i = random.nextInt(14) + 1; + ChatFormatting byId = ChatFormatting.getById(i); + return byId == null ? ChatFormatting.WHITE : byId; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/PlayAnimCommand.java b/src/main/java/com/linearpast/sccore/animation/command/PlayCommand.java similarity index 52% rename from src/main/java/com/linearpast/sccore/animation/command/PlayAnimCommand.java rename to src/main/java/com/linearpast/sccore/animation/command/PlayCommand.java index 3752911..c47e185 100644 --- a/src/main/java/com/linearpast/sccore/animation/command/PlayAnimCommand.java +++ b/src/main/java/com/linearpast/sccore/animation/command/PlayCommand.java @@ -1,14 +1,15 @@ package com.linearpast.sccore.animation.command; import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.AnimationUtils; import com.linearpast.sccore.animation.command.argument.AnimationArgument; import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument; -import com.linearpast.sccore.animation.entity.AnimationRideEntity; +import com.linearpast.sccore.animation.command.exception.ApiBackException; +import com.linearpast.sccore.animation.data.AnimationData; +import com.linearpast.sccore.animation.helper.*; +import com.linearpast.sccore.animation.utils.ApiBack; import com.linearpast.sccore.core.datagen.ModLang; import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; @@ -18,6 +19,7 @@ import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; @@ -25,41 +27,52 @@ import java.util.Set; import static net.minecraft.commands.Commands.argument; import static net.minecraft.commands.Commands.literal; -public class PlayAnimCommand { +public class PlayCommand { public static void register(LiteralArgumentBuilder animCommand){ - RequiredArgumentBuilder animCommandParam = argument("layer", AnimationLayerArgument.layer()) - .then(argument("animation", AnimationArgument.animation()) - .executes(context -> playAnimation(context, false)) - .then(argument("withRide", BoolArgumentType.bool()) - .executes(context -> playAnimation( - context, BoolArgumentType.getBool(context, "withRide") - )) - .then(argument("forced", BoolArgumentType.bool()) - .executes(context -> playAnimation( - context, BoolArgumentType.getBool(context, "withRide")) + animCommand + .then(literal("play") + .then(argument("players", EntityArgument.players()) + .requires(cs -> cs.hasPermission(2)) + .then(argument("layer", AnimationLayerArgument.layer()) + .then(argument("animation", AnimationArgument.animation()) + .executes(context -> playAnimation(context, false)) + .then(argument("withRide", BoolArgumentType.bool()) + .executes(context -> playAnimation( + context, BoolArgumentType.getBool(context, "withRide") + )) + .then(argument("forced", BoolArgumentType.bool()) + .executes(context -> playAnimation( + context, BoolArgumentType.getBool(context, "withRide")) + ) + ) + ) ) ) ) - ); - animCommand - .then(literal("playSelf") - .then(animCommandParam)) - .then(literal("play") - .then(argument("players", EntityArgument.players()) - .requires(cs -> cs.hasPermission(2)) - .then(animCommandParam) - )) - .then(literal("remove") - .executes(PlayAnimCommand::clearAnimation) + .then(literal("self") + .then(argument("layer", AnimationLayerArgument.layer()) + .then(argument("animation", AnimationArgument.animation()) + .executes(context -> playAnimation(context, false)) + .then(argument("withRide", BoolArgumentType.bool()) + .executes(context -> playAnimation( + context, BoolArgumentType.getBool(context, "withRide") + )) + ) + ) + ) + ) + ) + .then(literal("clear") + .executes(PlayCommand::clearAnimation) .then(argument("players", EntityArgument.players()) .requires(cs -> cs.hasPermission(2)) - .executes(PlayAnimCommand::clearAnimation) + .executes(PlayCommand::clearAnimation) .then(argument("layer", AnimationLayerArgument.layer()) - .executes(PlayAnimCommand::removeAnimation) + .executes(PlayCommand::removeAnimation) ) ) .then(argument("layer", AnimationLayerArgument.layer()) - .executes(PlayAnimCommand::removeAnimation) + .executes(PlayCommand::removeAnimation) ) ); } @@ -67,56 +80,43 @@ public class PlayAnimCommand { private static int playAnimation(CommandContext context, boolean withRide) { CommandSourceStack source = context.getSource(); try { - Collection players = null; - ServerPlayer player = null; - try {players = EntityArgument.getPlayers(context, "players");} - catch (Exception ignored) { player = source.getPlayerOrException(); } - String animation = AnimationArgument.getAnimation(context, "animation"); - String layer = AnimationLayerArgument.getLayer(context, "layer"); - ResourceLocation layerLocation = new ResourceLocation(layer); - ResourceLocation animLocation = new ResourceLocation(animation); - boolean animationPresent = AnimationUtils.isAnimationPresent(animLocation); - boolean layerPresent = AnimationUtils.isAnimationLayerPresent(layerLocation); - if(!animationPresent) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.ANIMATION_NOT_PRESENT.getKey() - ).withStyle(ChatFormatting.RED)); - return 0; - } - if(!layerPresent) { - source.sendFailure(Component.literal( - ModLang.TranslatableMessage.ANIMATION_LAYER_NOT_PRESENT.getKey() - ).withStyle(ChatFormatting.RED)); - return 0; - } + Collection targets = new ArrayList<>(); + ServerPlayer player = source.getPlayerOrException(); + try { targets.addAll(EntityArgument.getPlayers(context, "players"));} + catch (Exception ignored) {} + String layerString = AnimationLayerArgument.getLayer(context, "layer"); + String animString = AnimationArgument.getAnimation(context, "animation"); + ResourceLocation layer = new ResourceLocation(layerString); + ResourceLocation anim = new ResourceLocation(animString); //play with players - if(players != null) { - Set playerSet = Set.copyOf(players); - Collection finalPlayers = players; + IAnimationHelper helper = HelperGetterFromAnimation.create(anim).getHelper(); + if (helper == null) throw new ApiBackException(ApiBack.RESOURCE_NOT_FOUND); + AnimationData animationData = helper.getAnimation(anim); + if(animationData == null) throw new ApiBackException(ApiBack.RESOURCE_NOT_FOUND); + if(!targets.isEmpty()) { + Set playerSet = Set.copyOf(targets); playerSet.forEach(p -> { if(withRide) { boolean forced = false; try { forced = BoolArgumentType.getBool(context, "forced");} catch (Exception ignored) {} - if(AnimationUtils.playAnimationWithRide(p, layerLocation, animLocation, forced)) { - finalPlayers.remove(p); - } + ApiBack back = helper.playAnimationWithRide(p, layer, animationData, forced); + if(back == ApiBack.SUCCESS) targets.remove(p); } else { - if (AnimationUtils.playAnimation(p, layerLocation, animLocation)) { - finalPlayers.remove(p); - } + ApiBack back = helper.playAnimation(p, layer, animationData); + if (back == ApiBack.SUCCESS) targets.remove(p); } }); - int successNum = playerSet.size() - players.size(); + int successNum = playerSet.size() - targets.size(); if(successNum > 0) { source.sendSuccess(() -> Component.translatable( ModLang.TranslatableMessage.PLAY_ANIMATION_SUCCESS.getKey(), successNum ).withStyle(ChatFormatting.GREEN), true); } - List list = players.stream().toList(); + List list = targets.stream().toList(); if(!list.isEmpty()) { MutableComponent failPlayers = Component.literal(""); for (int i = 0; i < list.size(); i++) { @@ -131,70 +131,56 @@ public class PlayAnimCommand { failPlayers ).withStyle(ChatFormatting.RED)); } - } + } else { + //play with self + ApiBack back; + RawAnimationHelper instance = RawAnimationHelper.INSTANCE; + if(withRide) back = instance.playAnimationWithRide(player, layer, animationData, false); + else back = instance.playAnimation(player, layer, animationData); + + if(back != ApiBack.SUCCESS) throw new ApiBackException(back); - //play with self - if(player != null) { - if(withRide) { - boolean forced = false; - try { forced = BoolArgumentType.getBool(context, "forced");} - catch (Exception ignored) {} - AnimationUtils.playAnimationWithRide(player, layerLocation, animLocation, forced); - } else { - AnimationUtils.playAnimation(player, layerLocation, animLocation); - } source.sendSuccess(() -> Component.translatable( ModLang.TranslatableMessage.COMMAND_RUN_SUCCESS.getKey() ).withStyle(ChatFormatting.GREEN), true); } + return 1; } catch (Exception e) { source.sendFailure(Component.translatable( ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() ).withStyle(ChatFormatting.RED)); SnowyCrescentCore.log.error(e.getMessage()); - return 0; + } - return 1; + return 0; } private static int removeAnimation(CommandContext context) { CommandSourceStack source = context.getSource(); try { - Collection players = null; - ServerPlayer player = null; - try {players = EntityArgument.getPlayers(context, "players");} - catch (Exception ignored) { player = source.getPlayerOrException(); } + Collection targets = new ArrayList<>(); + ServerPlayer player = source.getPlayerOrException(); + try { targets.addAll(EntityArgument.getPlayers(context, "players"));} + catch (Exception ignored) {} String layer = AnimationLayerArgument.getLayer(context, "layer"); ResourceLocation layerLocation = new ResourceLocation(layer); - boolean layerPresent = AnimationUtils.isAnimationLayerPresent(layerLocation); - if(!layerPresent) { - source.sendFailure(Component.literal( - ModLang.TranslatableMessage.ANIMATION_LAYER_NOT_PRESENT.getKey() - ).withStyle(ChatFormatting.RED)); - return 0; - } //remove with players - if(players != null) { - Set playerSet = Set.copyOf(players); - Collection finalPlayers = players; + AnimationHelper instance = AnimationHelper.INSTANCE; + if(!targets.isEmpty()) { + Set playerSet = Set.copyOf(targets); playerSet.forEach(p -> { - if(p.getVehicle() instanceof AnimationRideEntity rideEntity && rideEntity.getLayer().equals(layerLocation)) { - p.unRide(); - finalPlayers.remove(p); - } - if (AnimationUtils.removeAnimation(p, layerLocation)) { - finalPlayers.remove(p); - } + ApiBack back = instance.removeAnimation(p, layerLocation); + if (back == ApiBack.SUCCESS) targets.remove(p); }); - int successNum = playerSet.size() - players.size(); + int successNum = playerSet.size() - targets.size(); if(successNum > 0) { source.sendSuccess(() -> Component.translatable( ModLang.TranslatableMessage.REMOVE_ANIMATION_SUCCESS.getKey(), successNum ).withStyle(ChatFormatting.GREEN), true); } - List list = players.stream().toList(); + List list = targets.stream().toList(); if(!list.isEmpty()) { MutableComponent failPlayers = Component.literal(""); for (int i = 0; i < list.size(); i++) { @@ -209,26 +195,24 @@ public class PlayAnimCommand { failPlayers ).withStyle(ChatFormatting.RED)); } - } + } else { + ApiBack back = instance.removeAnimation(player, layerLocation); + if (back != ApiBack.SUCCESS) throw new ApiBackException(back); - //remove with self - if(player != null) { - if(player.getVehicle() instanceof AnimationRideEntity rideEntity && rideEntity.getLayer().equals(layerLocation)) { - player.unRide(); - } - AnimationUtils.removeAnimation(player, layerLocation); source.sendSuccess(() -> Component.translatable( ModLang.TranslatableMessage.COMMAND_RUN_SUCCESS.getKey() ).withStyle(ChatFormatting.GREEN), true); } + return 1; + } catch (ApiBackException e){ + source.sendFailure(e.getCommandFailBack().withStyle(ChatFormatting.RED)); } catch (Exception e) { source.sendFailure(Component.translatable( ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() ).withStyle(ChatFormatting.RED)); SnowyCrescentCore.log.error(e.getMessage()); - return 0; } - return 1; + return 0; } private static int clearAnimation(CommandContext context) { @@ -237,22 +221,23 @@ public class PlayAnimCommand { Collection players; try {players = EntityArgument.getPlayers(context, "players");} catch (Exception ignored) { players = Set.of(source.getPlayerOrException()); } - Set.copyOf(players).forEach(player -> { - if(player.getVehicle() instanceof AnimationRideEntity) { - player.unRide(); - } - AnimationUtils.clearAnimation(player); - }); + Set.copyOf(players).forEach(player -> IHelperGetter.HELPERS.forEach( + helper -> { + helper.clearAnimations(player); + helper.detachAnimation(player); + } + )); source.sendSuccess(() -> Component.translatable( ModLang.TranslatableMessage.CLEAR_ANIMATIONS.getKey() ).withStyle(ChatFormatting.GREEN), true); + return 1; } catch (Exception e) { source.sendFailure(Component.translatable( ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() ).withStyle(ChatFormatting.RED)); SnowyCrescentCore.log.error(e.getMessage()); - return 0; } - return 1; + return 0; + } } diff --git a/src/main/java/com/linearpast/sccore/animation/command/RequestAnimCommand.java b/src/main/java/com/linearpast/sccore/animation/command/RequestAnimCommand.java deleted file mode 100644 index f8b60db..0000000 --- a/src/main/java/com/linearpast/sccore/animation/command/RequestAnimCommand.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.linearpast.sccore.animation.command; - -import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.AnimationUtils; -import com.linearpast.sccore.animation.command.argument.AnimationArgument; -import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument; -import com.linearpast.sccore.core.configs.ModConfigs; -import com.linearpast.sccore.core.datagen.ModLang; -import com.mojang.brigadier.arguments.BoolArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import net.minecraft.ChatFormatting; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.arguments.EntityArgument; -import net.minecraft.network.chat.ClickEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static net.minecraft.commands.Commands.argument; -import static net.minecraft.commands.Commands.literal; - -/** - * Request target player play animation. - */ -public class RequestAnimCommand { - private static final Map lastRequestedMap = new HashMap<>(); - record InviteRecord(long time, ResourceLocation layer, ResourceLocation animation, boolean withRide, boolean isForce) {} - private static final Map> invites = new HashMap<>(); - public static void register(LiteralArgumentBuilder animCommand) { - animCommand.then(literal("request") - .then(argument("player", EntityArgument.player()).then( - argument("layer", AnimationLayerArgument.layer()) - .then(argument("animation", AnimationArgument.animation()) - .requires(cs -> cs.hasPermission(2)) - .executes(context -> invite(context, false)) - .then(argument("withRide", BoolArgumentType.bool()) - .executes(context -> invite( - context, BoolArgumentType.getBool(context, "withRide") - )) - .then(argument("forced", BoolArgumentType.bool()) - .executes(context -> invite( - context, BoolArgumentType.getBool(context, "withRide")) - ) - ) - ) - ) - )) - .then(literal("accept") - .then(argument("player", EntityArgument.player()) - .executes(RequestAnimCommand::accept) - ) - ) - ); - } - - private static int invite(CommandContext context, boolean withRide) { - CommandSourceStack source = context.getSource(); - try { - //get info - boolean force = false; - try { - force = BoolArgumentType.getBool(context, "force"); - } catch (Exception ignored) {} - ServerPlayer player = source.getPlayerOrException(); - ServerPlayer target = EntityArgument.getPlayer(context, "player"); - - //cooldown - Long lastRequested = lastRequestedMap.getOrDefault(player.getUUID(), null); - long now = System.currentTimeMillis(); - int requestCooldown = ModConfigs.Server.requestCooldown.get() * 1000; - if(!(lastRequested == null || now - lastRequested > requestCooldown)) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_COOLDOWN.getKey() - ).withStyle(ChatFormatting.RED)); - return 0; - } - lastRequestedMap.put(player.getUUID(), now); - - String layerString = AnimationLayerArgument.getLayer(context, "layer"); - String animString = AnimationArgument.getAnimation(context, "animation"); - ResourceLocation layer = new ResourceLocation(layerString); - ResourceLocation anim = new ResourceLocation(animString); - - //test info present - boolean animationPresent = AnimationUtils.isAnimationPresent(anim); - boolean animationLayerPresent = AnimationUtils.isAnimationLayerPresent(layer); - if(!animationLayerPresent || !animationPresent) throw new Exception(); - - //update static cache - Map inviteRecordMap = invites.getOrDefault(player.getUUID(), new HashMap<>()); - inviteRecordMap.put(target.getUUID(), new InviteRecord(System.currentTimeMillis(), layer, anim, force, withRide)); - invites.put(player.getUUID(), inviteRecordMap); - - //click event - Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( - new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sccore anim request accept " + player.getName().getString()) - ).withUnderlined(true); - - //send message - target.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.REQUESTED_MESSAGE.getKey(), - player.getName().copy(), - anim.toString() - ).append(Component.translatable( - ModLang.TranslatableMessage.ACCEPT_MESSAGE_CLICK.getKey() - ).setStyle(pStyle))); - source.sendSuccess(() -> Component.translatable( - ModLang.TranslatableMessage.REQUEST_MESSAGE.getKey() - ).withStyle(ChatFormatting.GREEN), true); - } catch (Exception e) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() - ).withStyle(ChatFormatting.RED)); - SnowyCrescentCore.log.error(e.getMessage()); - return 0; - } - return 1; - } - - private static int accept(CommandContext context) { - CommandSourceStack source = context.getSource(); - try { - ServerPlayer target = source.getPlayerOrException(); - ServerPlayer player = EntityArgument.getPlayer(context, "player"); - - //get request record and test - Map inviteRecordMap = invites.getOrDefault(player.getUUID(), null); - if(inviteRecordMap == null) throw new Exception(); - InviteRecord inviteRecord = inviteRecordMap.getOrDefault(target.getUUID(), null); - if(inviteRecord == null) throw new Exception(); - long now = System.currentTimeMillis(); - Integer requestDuration = ModConfigs.Server.requestDuration.get(); - - //test if expired - if(now - inviteRecord.time > requestDuration * 1000 || requestDuration == 0) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.ACCEPT_REQUEST_EXPIRED.getKey(), - requestDuration - ).withStyle(ChatFormatting.RED)); - player.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.REQUEST_EXPIRED.getKey(), - target.getName().copy(), - requestDuration - ).withStyle(ChatFormatting.RED)); - return 0; - } - inviteRecordMap.remove(target.getUUID()); - invites.put(player.getUUID(), inviteRecordMap); - boolean withRide = inviteRecord.withRide; - - //play - if(withRide) { - AnimationUtils.playAnimationWithRide(target, inviteRecord.layer, inviteRecord.animation, inviteRecord.isForce); - }else { - AnimationUtils.playAnimation(target, inviteRecord.layer, inviteRecord.animation); - } - - //send message - source.sendSuccess(() -> Component.translatable( - ModLang.TranslatableMessage.ACCEPT_REQUEST_SUCCESS.getKey() - ).withStyle(ChatFormatting.GREEN), true); - player.sendSystemMessage(Component.translatable( - ModLang.TranslatableMessage.REQUEST_SUCCESS.getKey(), - target.getName().copy() - ).withStyle(ChatFormatting.GREEN)); - } catch (Exception e) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() - ).withStyle(ChatFormatting.RED)); - SnowyCrescentCore.log.error(e.getMessage()); - return 0; - } - return 1; - } -} diff --git a/src/main/java/com/linearpast/sccore/animation/command/RequestCommand.java b/src/main/java/com/linearpast/sccore/animation/command/RequestCommand.java new file mode 100644 index 0000000..4bf887a --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/RequestCommand.java @@ -0,0 +1,140 @@ +package com.linearpast.sccore.animation.command; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.command.argument.AnimationArgument; +import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument; +import com.linearpast.sccore.animation.command.exception.ApiBackException; +import com.linearpast.sccore.animation.data.AnimationData; +import com.linearpast.sccore.animation.data.RawAnimationData; +import com.linearpast.sccore.animation.helper.HelperGetterFromAnimation; +import com.linearpast.sccore.animation.helper.IAnimationHelper; +import com.linearpast.sccore.animation.helper.IHelperGetter; +import com.linearpast.sccore.animation.utils.ApiBack; +import com.linearpast.sccore.core.configs.ModConfigs; +import com.linearpast.sccore.core.datagen.ModLang; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +/** + * Request target player play animation. + */ +public class RequestCommand { + public static void register(LiteralArgumentBuilder animCommand) { + animCommand.then(literal("request") + .then(argument("player", EntityArgument.player()).then( + argument("layer", AnimationLayerArgument.layer()) + .then(argument("animation", AnimationArgument.animation()) + .requires(cs -> cs.hasPermission(2)) + .executes(context -> request(context, false)) + .then(argument("withRide", BoolArgumentType.bool()) + .executes(context -> request( + context, BoolArgumentType.getBool(context, "withRide") + )) + ) + ) + )) + .then(literal("acceptRequest") + .then(argument("player", EntityArgument.player()) + .executes(RequestCommand::acceptRequest) + ) + ) + ); + } + + private static int request(CommandContext context, boolean withRide) { + CommandSourceStack source = context.getSource(); + try { + ServerPlayer player = source.getPlayerOrException(); + ServerPlayer target = EntityArgument.getPlayer(context, "player"); + String layerString = AnimationLayerArgument.getLayer(context, "layer"); + String animString = AnimationArgument.getAnimation(context, "animation"); + ResourceLocation layer = new ResourceLocation(layerString); + ResourceLocation anim = new ResourceLocation(animString); + + IAnimationHelper helper = HelperGetterFromAnimation.create(anim).getHelper(); + if (helper == null) throw new ApiBackException(ApiBack.RESOURCE_NOT_FOUND); + AnimationData animationData; + if(helper.isAnimationPresent(anim)) animationData = helper.getAnimation(anim); + else animationData = RawAnimationData.create(anim); + + ApiBack back = helper.request(player, target, layer, animationData, withRide); + if(back == ApiBack.COOLDOWN) { + int cooldown = ModConfigs.Server.requestCooldown.get(); + throw ApiBackException.withCooldown(cooldown); + } + if(back != ApiBack.SUCCESS) throw new ApiBackException(back); + + //click event + Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sccore anim request acceptRequest " + player.getName().getString()) + ).withUnderlined(true); + + //send message + target.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.REQUESTED_MESSAGE.getKey(), + player.getName().copy(), + anim.toString() + ).append(Component.translatable( + ModLang.TranslatableMessage.ACCEPT_MESSAGE_CLICK.getKey() + ).setStyle(pStyle))); + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.REQUEST_MESSAGE.getKey() + ).withStyle(ChatFormatting.GREEN), true); + return 1; + } catch (ApiBackException e) { + source.sendFailure(e.getCommandFailBack().withStyle(ChatFormatting.RED)); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + } + return 0; + } + + private static int acceptRequest(CommandContext context) { + CommandSourceStack source = context.getSource(); + try { + ServerPlayer player = source.getPlayerOrException(); + ServerPlayer requestor = EntityArgument.getPlayer(context, "requestor"); + + //play + ApiBack back = ApiBack.RESOURCE_NOT_FOUND; + for (IAnimationHelper helper : IHelperGetter.HELPERS) { + back = helper.acceptRequest(player, requestor); + if(back == ApiBack.SUCCESS) break; + } + if(back != ApiBack.SUCCESS) throw new ApiBackException(back); + + //send message + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.ACCEPT_REQUEST_SUCCESS.getKey() + ).withStyle(ChatFormatting.GREEN), true); + requestor.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.REQUEST_SUCCESS.getKey(), + player.getName().copy() + ).withStyle(ChatFormatting.GREEN)); + return 1; + } catch (ApiBackException e) { + source.sendFailure(e.getCommandFailBack().withStyle(ChatFormatting.RED)); + } catch (Exception e) { + source.sendFailure(Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() + ).withStyle(ChatFormatting.RED)); + SnowyCrescentCore.log.error(e.getMessage()); + } + return 0; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationArgument.java b/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationArgument.java index 6a69e34..773395c 100644 --- a/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationArgument.java +++ b/src/main/java/com/linearpast/sccore/animation/command/argument/AnimationArgument.java @@ -1,7 +1,8 @@ package com.linearpast.sccore.animation.command.argument; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.GenericAnimationData; import com.linearpast.sccore.animation.register.AnimationRegistry; +import com.linearpast.sccore.animation.register.RawAnimationRegistry; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -13,6 +14,9 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.DistExecutor; import org.jetbrains.annotations.Nullable; import java.util.Collection; @@ -28,13 +32,22 @@ public class AnimationArgument implements ArgumentType { animation -> Component.literal("Unknow animation : " + animation.toString()) ); - private final Supplier> animationNames; + private static Supplier> animationNames; public AnimationArgument() { - this.animationNames = AnimationArgument::getAnimationNames; + resetAnimationNames(); + } + + public static void resetAnimationNames() { + Set set = new HashSet<>(getAnimationNames()); + Set strings = DistExecutor.unsafeCallWhenOn(Dist.CLIENT, () -> + AnimationArgument::getAnimationNamesClient + ); + if (strings != null && !strings.isEmpty()) set.addAll(strings); + animationNames = () -> set; } private static Set getAnimationNames(){ - HashSet set = new HashSet<>(); + Set set = new HashSet<>(); AnimationRegistry.getAnimations().forEach((key, value) -> { String name = value.getName(); if(name != null && !set.contains(name)) { @@ -44,13 +57,22 @@ public class AnimationArgument implements ArgumentType { return set; } + @OnlyIn(Dist.CLIENT) + private static Set getAnimationNamesClient() { + Set set = new HashSet<>(); + RawAnimationRegistry.getAnimations().keySet().forEach(location -> + set.add(location.toString()) + ); + return set; + } + public static AnimationArgument animation() { return new AnimationArgument(); } @Nullable private static ResourceLocation getAnimationByName(String name) { - for (Animation animation : AnimationRegistry.getAnimations().values()) { + for (GenericAnimationData animation : AnimationRegistry.getAnimations().values()) { if (Objects.equals(animation.getName(), name)) { return animation.getKey(); } diff --git a/src/main/java/com/linearpast/sccore/animation/command/client/GenerateJsonClientCommand.java b/src/main/java/com/linearpast/sccore/animation/command/client/GenerateJsonClientCommand.java deleted file mode 100644 index e5ac488..0000000 --- a/src/main/java/com/linearpast/sccore/animation/command/client/GenerateJsonClientCommand.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.linearpast.sccore.animation.command.client; - -import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.data.Animation; -import com.linearpast.sccore.animation.data.util.AnimJson; -import com.linearpast.sccore.animation.data.util.AnimLayerJson; -import com.linearpast.sccore.animation.register.AnimationRegistry; -import com.linearpast.sccore.core.datagen.ModLang; -import com.mojang.brigadier.arguments.BoolArgumentType; -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.network.chat.ClickEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.Style; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Comparator; - -import static net.minecraft.commands.Commands.argument; -import static net.minecraft.commands.Commands.literal; - -public class GenerateJsonClientCommand { - public static void register(LiteralArgumentBuilder animCommand) { - animCommand.then(literal("jsonClient") - .then(argument("path", StringArgumentType.string()) - .suggests((context, builder) -> { - try { - File gameDirectory = Minecraft.getInstance().gameDirectory; - Path animation = gameDirectory.toPath().resolve(SnowyCrescentCore.MODID).resolve("animation"); - if(!animation.toFile().exists()) { - Files.createDirectories(animation); - } - String replace = animation.toString().replace("\\", "\\\\"); - builder.suggest("\"" + replace + "\""); - return builder.buildFuture(); - } catch (Exception e) { return builder.buildFuture(); } - }) - .then(literal("clearFile").executes(GenerateJsonClientCommand::clearJson)) - .then(literal("generate") - .then(literal("anim") - .then(literal("example").executes(GenerateJsonClientCommand::generateExample)) - .executes(context -> generateJson(context, false, false)) - .then(argument("reset", BoolArgumentType.bool()) - .executes(context -> - generateJson(context, false, BoolArgumentType.getBool(context, "reset")) - ) - ) - ) - .then(literal("layer") - .executes(context -> generateJson(context, true, false)) - .then(argument("reset", BoolArgumentType.bool()) - .executes(context -> - generateJson(context, true, BoolArgumentType.getBool(context, "reset")) - ) - ) - ) - ) - ) - ); - } - - private static void clearPath(Path dir) throws IOException { - if (!Files.exists(dir)) return; - try (var pathStream = Files.walk(dir)) { - pathStream.sorted(Comparator.reverseOrder()).forEach(path -> { - try { - Files.delete(path); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } catch (RuntimeException ignored) {} - } - - private static int generateJson(CommandContext context, boolean isLayer, boolean isReset) { - CommandSourceStack source = context.getSource(); - try { - String pathString = StringArgumentType.getString(context, "path"); - Path animationPath = Minecraft.getInstance().gameDirectory.toPath().resolve(pathString).resolve("animation"); - if (!Files.exists(animationPath)) { - try {Files.createDirectories(animationPath);} - catch (IOException e) { throw new RuntimeException(e); } - } - if(isReset) clearPath(animationPath); - if(isLayer) { - Path path = AnimLayerJson.Writer.syntaxImmediately(animationPath); - MutableComponent component = Component.translatable( - ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), - "layer", "Client" - ); - Style style = Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, animationPath.toString())) - .withColor(ChatFormatting.GREEN).withBold(true).withUnderlined(true); - component.append(Component.translatable( - ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), - path.toString() - ).setStyle(style)); - source.sendSuccess(() -> component, true); - } else { - for (Animation value : AnimationRegistry.getAnimations().values()) { - AnimJson.Writer.stream(animationPath, value).syntax(); - } - MutableComponent component = Component.translatable( - ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), - "anim", "Client" - ); - Style style = Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, animationPath.toString())) - .withColor(ChatFormatting.GREEN).withBold(true).withUnderlined(true); - component.append(Component.translatable( - ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), - animationPath.toString() - ).withStyle(style)); - source.sendSuccess(() -> component, true); - } - } catch (Exception e) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() - ).withStyle(ChatFormatting.RED)); - SnowyCrescentCore.log.error(e.getMessage()); - return 0; - } - return 1; - } - - private static int clearJson(CommandContext context) { - CommandSourceStack source = context.getSource(); - try { - String pathString = StringArgumentType.getString(context, "path"); - Path animationPath = Minecraft.getInstance().gameDirectory.toPath().resolve(pathString).resolve("animation"); - clearPath(animationPath); - source.sendSuccess(() -> Component.translatable( - ModLang.TranslatableMessage.COMMAND_RUN_SUCCESS.getKey() - ), true); - } catch (Exception e) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() - ).withStyle(ChatFormatting.RED)); - SnowyCrescentCore.log.error(e.getMessage()); - return 0; - } - return 1; - } - - private static int generateExample(CommandContext context) { - CommandSourceStack source = context.getSource(); - try { - String pathString = StringArgumentType.getString(context, "path"); - Path animationPath = Minecraft.getInstance().gameDirectory.toPath().resolve(pathString); - Path path = AnimJson.Writer.syntaxExample(animationPath); - MutableComponent component = Component.translatable( - ModLang.TranslatableMessage.ANIMATION_TO_JSON.getKey(), - "anim example", "Client" - ); - Style style = Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, path.toString())) - .withColor(ChatFormatting.GREEN).withBold(true).withUnderlined(true); - component.append(Component.translatable( - ModLang.TranslatableMessage.ANIMATION_JSON_PATH.getKey(), - path.toString() - ).setStyle(style)); - source.sendSuccess(() -> component, true); - } catch (Exception e) { - source.sendFailure(Component.translatable( - ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey() - ).withStyle(ChatFormatting.RED)); - SnowyCrescentCore.log.error(e.getMessage()); - return 0; - } - return 1; - } -} diff --git a/src/main/java/com/linearpast/sccore/animation/command/client/ListClientCommand.java b/src/main/java/com/linearpast/sccore/animation/command/client/ListClientCommand.java new file mode 100644 index 0000000..5fc9bba --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/client/ListClientCommand.java @@ -0,0 +1,38 @@ +package com.linearpast.sccore.animation.command.client; + +import com.linearpast.sccore.animation.command.ListServerCommand; +import com.linearpast.sccore.animation.register.RawAnimationRegistry; +import com.linearpast.sccore.core.datagen.ModLang; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.List; + +import static net.minecraft.commands.Commands.literal; + +@OnlyIn(Dist.CLIENT) +public class ListClientCommand { + public static void register(LiteralArgumentBuilder animCommand) { + animCommand.then(literal("list").then(literal("clientAnimations") + .executes(ListClientCommand::listAnimations)) + ); + } + + private static int listAnimations(CommandContext context) { + try { + CommandSourceStack source = context.getSource(); + List list = RawAnimationRegistry.getAnimations().keySet().stream().toList(); + source.sendSuccess(() -> Component.translatable( + ModLang.TranslatableMessage.LIST_ANIMATION_RESOURCE.getKey(), + "Client", "Animations", ListServerCommand.getString(list) + ), false); + return 1; + } catch (Exception ignored) {} + return 0; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/client/RefreshAnimCommand.java b/src/main/java/com/linearpast/sccore/animation/command/client/RefreshCommand.java similarity index 86% rename from src/main/java/com/linearpast/sccore/animation/command/client/RefreshAnimCommand.java rename to src/main/java/com/linearpast/sccore/animation/command/client/RefreshCommand.java index dc69772..31ca466 100644 --- a/src/main/java/com/linearpast/sccore/animation/command/client/RefreshAnimCommand.java +++ b/src/main/java/com/linearpast/sccore/animation/command/client/RefreshCommand.java @@ -1,7 +1,7 @@ package com.linearpast.sccore.animation.command.client; import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.helper.AnimationHelper; import com.linearpast.sccore.core.datagen.ModLang; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; @@ -16,9 +16,9 @@ import net.minecraftforge.api.distmarker.OnlyIn; import static net.minecraft.commands.Commands.literal; @OnlyIn(Dist.CLIENT) -public class RefreshAnimCommand { +public class RefreshCommand { public static void register(LiteralArgumentBuilder animCommand){ - animCommand.then(literal("refresh").executes(RefreshAnimCommand::refresh)); + animCommand.then(literal("refresh").executes(RefreshCommand::refresh)); } private static int refresh(CommandContext ctx){ @@ -27,7 +27,7 @@ public class RefreshAnimCommand { Minecraft instance = Minecraft.getInstance(); LocalPlayer player = instance.player; if(player == null) throw new RuntimeException(); - AnimationUtils.refreshAnimation(player); + AnimationHelper.INSTANCE.refreshAnimation(player); source.sendSuccess(() -> Component.translatable( ModLang.TranslatableMessage.REFRESH_ANIMATIONS.getKey() ).withStyle(ChatFormatting.GREEN), true); diff --git a/src/main/java/com/linearpast/sccore/animation/command/exception/ApiBackException.java b/src/main/java/com/linearpast/sccore/animation/command/exception/ApiBackException.java new file mode 100644 index 0000000..8fb60eb --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/exception/ApiBackException.java @@ -0,0 +1,48 @@ +package com.linearpast.sccore.animation.command.exception; + +import com.linearpast.sccore.animation.utils.ApiBack; +import com.linearpast.sccore.core.datagen.ModLang; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class ApiBackException extends Exception { + private final ApiBack apiBack; + private Object[] args; + public ApiBackException(ApiBack apiBack) { + this.apiBack = apiBack; + } + + public ApiBackException(ApiBack apiBack, Object... args) { + this.apiBack = apiBack; + this.args = args; + } + + public static ApiBackException withCooldown(Integer cooldown) { + return new ApiBackException(ApiBack.COOLDOWN, cooldown); + } + + public static ApiBackException withOutRange(Integer distance) { + return new ApiBackException(ApiBack.OUT_RANGE, distance); + } + + public MutableComponent getCommandFailBack() { + if(args != null && args.length > 0) { + return Component.translatable(getLang(), args); + } else { + return Component.translatable(getLang()); + } + } + + public String getLang() { + return switch (apiBack) { + case FAIL -> ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey(); + case UNSUPPORTED -> ModLang.TranslatableMessage.ANIMATION_OPERATION_UNSUPPORTED.getKey(); + case COOLDOWN -> ModLang.TranslatableMessage.ANIMATION_COOLDOWN.getKey(); + case OUT_RANGE -> ModLang.TranslatableMessage.ANIMATION_OUT_RANGE.getKey(); + case OPERATION_EXPIRE -> ModLang.TranslatableMessage.ANIMATION_EXPIRE.getKey(); + case RESOURCE_NOT_FOUND -> ModLang.TranslatableMessage.ANIMATION_RESOURCE_NOT_FOUND.getKey(); + case SUCCESS -> ModLang.TranslatableMessage.COMMAND_RUN_SUCCESS.getKey(); + case BE_CANCELLED -> ModLang.TranslatableMessage.ANIMATION_OPERATION_CANCELLED.getKey(); + }; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/command/exception/CommandComponentException.java b/src/main/java/com/linearpast/sccore/animation/command/exception/CommandComponentException.java new file mode 100644 index 0000000..e2d0e68 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/command/exception/CommandComponentException.java @@ -0,0 +1,34 @@ +package com.linearpast.sccore.animation.command.exception; + +import com.linearpast.sccore.core.datagen.ModLang; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; + +public class CommandComponentException extends Exception { + private final Component component; + private final boolean isCommandFail; + public CommandComponentException(ModLang.TranslatableMessage message, Object ... args) { + super("Expected command exception."); + this.component = Component.translatable( + message.getKey(), + args + ).withStyle(ChatFormatting.RED); + this.isCommandFail = true; + } + public CommandComponentException(ModLang.TranslatableMessage message, Style style, Object ... args) { + super("Expected command exception."); + this.component = Component.translatable( + message.getKey(), + args + ).withStyle(style); + this.isCommandFail = false; + } + + public Component getCommandFailBack() { + return isCommandFail ? Component.translatable( + ModLang.TranslatableMessage.COMMAND_RUN_FAIL.getKey(), + component + ).withStyle(ChatFormatting.RED) : component; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/data/AnimationData.java b/src/main/java/com/linearpast/sccore/animation/data/AnimationData.java new file mode 100644 index 0000000..7001f4b --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/data/AnimationData.java @@ -0,0 +1,95 @@ +package com.linearpast.sccore.animation.data; + +import dev.kosmx.playerAnim.core.data.KeyframeAnimation; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.util.INBTSerializable; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class AnimationData implements INBTSerializable { + protected ResourceLocation key; + protected @Nullable Ride ride; + + public ResourceLocation getKey() { + return key; + } + + public AnimationData withRide(Ride ride) { + this.ride = ride; + return this; + } + + @Nullable + @OnlyIn(Dist.CLIENT) + public KeyframeAnimation getAnimation() { + return PlayerAnimationRegistry.getAnimation(key); + } + + public @Nullable Ride getRide() { + return ride; + } + + @Override + public CompoundTag serializeNBT() { + CompoundTag tag = new CompoundTag(); + tag.putString("key", key.toString()); + if (ride != null) { + CompoundTag rideTag = new CompoundTag(); + Vec3 offset = ride.getOffset(); + rideTag.putDouble("x", offset.x); + rideTag.putDouble("y", offset.y); + rideTag.putDouble("z", offset.z); + rideTag.putInt("existTick", ride.getExistTick()); + rideTag.putFloat("xRot", ride.getXRot()); + rideTag.putFloat("yRot", ride.getYRot()); + List componentAnimations = ride.getComponentAnimations(); + ListTag listTag = new ListTag(); + for (ResourceLocation animation : componentAnimations) { + listTag.add(StringTag.valueOf(animation.toString())); + } + rideTag.put("subAnimations", listTag); + tag.put("ride", rideTag); + } + return tag; + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + String string = nbt.getString("key"); + this.key = new ResourceLocation(string); + try { + if(nbt.contains("ride")) { + CompoundTag rideTag = nbt.getCompound("ride"); + Vec3 offset = new Vec3( + rideTag.getDouble("x"), + rideTag.getDouble("y"), + rideTag.getDouble("z") + ); + int existTick = rideTag.getInt("existTick"); + float xRot = rideTag.getFloat("xRot"); + float yRot = rideTag.getFloat("yRot"); + List componentAnimations = new ArrayList<>(); + rideTag.getList("subAnimations", 8).forEach(tag -> + componentAnimations.add(new ResourceLocation(tag.getAsString())) + ); + this.ride = Ride.create() + .withOffset(offset) + .withExistTick(existTick) + .withXRot(xRot) + .withYRot(yRot) + .setComponentAnimations(componentAnimations); + return; + } + } catch (Exception ignored) {} + this.ride = null; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/data/Animation.java b/src/main/java/com/linearpast/sccore/animation/data/GenericAnimationData.java similarity index 74% rename from src/main/java/com/linearpast/sccore/animation/data/Animation.java rename to src/main/java/com/linearpast/sccore/animation/data/GenericAnimationData.java index da590f7..7025395 100644 --- a/src/main/java/com/linearpast/sccore/animation/data/Animation.java +++ b/src/main/java/com/linearpast/sccore/animation/data/GenericAnimationData.java @@ -3,28 +3,28 @@ package com.linearpast.sccore.animation.data; import dev.kosmx.playerAnim.core.data.KeyframeAnimation; import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry; import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; import org.jetbrains.annotations.Nullable; import java.util.regex.Pattern; -public class Animation { +public class GenericAnimationData extends AnimationData { private @Nullable String name; - private final ResourceLocation key; - private KeyframeAnimation animation; private float heightModifier = 1.0f; private float camYaw; private float camPitch; private float camRoll; private float camY; - private @Nullable Animation.LyingType lyingType; - private @Nullable Ride ride; + private @Nullable GenericAnimationData.LyingType lyingType; - Animation(ResourceLocation key) { + GenericAnimationData(ResourceLocation key) { this.key = key; } + public GenericAnimationData(){} - public static Animation create(ResourceLocation name) { - return new Animation(name); + public static GenericAnimationData create(ResourceLocation name) { + return new GenericAnimationData(name); } public enum LyingType { @@ -52,7 +52,7 @@ public class Animation { } } - public Animation withLyingType(@Nullable Animation.LyingType lyingType) { + public GenericAnimationData withLyingType(@Nullable GenericAnimationData.LyingType lyingType) { this.lyingType = lyingType; if(lyingType == null) return this; this.camY = -1.3f; @@ -72,37 +72,37 @@ public class Animation { return this; } - public Animation withHeightModifier(float heightModifier) { + public GenericAnimationData withHeightModifier(float heightModifier) { this.heightModifier = heightModifier; return this; } - public Animation withCamYaw(float camYaw) { + public GenericAnimationData withCamYaw(float camYaw) { this.camYaw = camYaw; return this; } - public Animation withCamPitch(float camPitch) { + public GenericAnimationData withCamPitch(float camPitch) { this.camPitch = camPitch; return this; } - public Animation withCamRoll(float camRoll) { + public GenericAnimationData withCamRoll(float camRoll) { this.camRoll = camRoll; return this; } - public Animation withCamY(float camY) { + public GenericAnimationData withCamY(float camY) { this.camY = camY; return this; } - public Animation withRide(Ride ride) { + public GenericAnimationData withRide(Ride ride) { this.ride = ride; return this; } - public Animation withName(String name) { + public GenericAnimationData withName(String name) { String regex = "^[a-zA-Z0-9_-]+$"; Pattern pattern = Pattern.compile(regex); if (!pattern.matcher(name).matches()) { @@ -131,11 +131,12 @@ public class Animation { } @Nullable + @OnlyIn(Dist.CLIENT) public KeyframeAnimation getAnimation() { return PlayerAnimationRegistry.getAnimation(key); } - public @Nullable Animation.LyingType getLyingType() { + public @Nullable GenericAnimationData.LyingType getLyingType() { return lyingType; } @@ -147,14 +148,6 @@ public class Animation { return heightModifier; } - public @Nullable Ride getRide() { - return ride; - } - - public ResourceLocation getKey() { - return key; - } - public @Nullable String getName() { return name; } diff --git a/src/main/java/com/linearpast/sccore/animation/data/RawAnimationData.java b/src/main/java/com/linearpast/sccore/animation/data/RawAnimationData.java new file mode 100644 index 0000000..1019875 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/data/RawAnimationData.java @@ -0,0 +1,19 @@ +package com.linearpast.sccore.animation.data; + +import net.minecraft.resources.ResourceLocation; + +public class RawAnimationData extends AnimationData{ + RawAnimationData(ResourceLocation key) { + this.key = key; + } + public RawAnimationData(){} + + public static RawAnimationData create(ResourceLocation name) { + return new RawAnimationData(name); + } + + public RawAnimationData withRide(Ride ride) { + this.ride = ride; + return this; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/data/util/AnimJson.java b/src/main/java/com/linearpast/sccore/animation/data/util/AnimJson.java index 8485605..b92fb01 100644 --- a/src/main/java/com/linearpast/sccore/animation/data/util/AnimJson.java +++ b/src/main/java/com/linearpast/sccore/animation/data/util/AnimJson.java @@ -2,7 +2,7 @@ package com.linearpast.sccore.animation.data.util; import com.google.gson.*; import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.GenericAnimationData; import com.linearpast.sccore.animation.data.Ride; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.phys.Vec3; @@ -53,16 +53,16 @@ public class AnimJson { return new Reader(jsonElement); } - public Animation parse() { + public GenericAnimationData parse() { return fromJson(); } - public Animation fromJson() { + public GenericAnimationData fromJson() { try { JsonObject json = originElement.getAsJsonObject(); - Animation animation = Animation.create(new ResourceLocation(json.get(Key).getAsString())); + GenericAnimationData animation = GenericAnimationData.create(new ResourceLocation(json.get(Key).getAsString())); if(json.has(Name)) animation.withName(json.get(Name).getAsString()); - if(json.has(LyingType)) animation.withLyingType(Animation.LyingType.valueOf(json.get(LyingType).getAsString())); + if(json.has(LyingType)) animation.withLyingType(GenericAnimationData.LyingType.valueOf(json.get(LyingType).getAsString())); animation.withHeightModifier(json.get(HeightModifier).getAsFloat()) .withCamY(json.get(CamY).getAsFloat()) .withCamPitch(json.get(CamPitch).getAsFloat()) @@ -100,25 +100,25 @@ public class AnimJson { public static class Writer { private static final String example = "example"; private final @Nullable Path file; - private final Animation animation; - Writer(@Nullable Path file, Animation animation) { + private final GenericAnimationData animation; + Writer(@Nullable Path file, GenericAnimationData animation) { this.animation = animation; this.file = file; } - public static Writer stream(Path path, Animation animation) { + public static Writer stream(Path path, GenericAnimationData animation) { return new Writer(path, animation); } - public static Writer stream(Animation animation) { + public static Writer stream(GenericAnimationData animation) { return new Writer(null, animation); } public static Path syntaxExample(Path directory) throws Exception { ResourceLocation exampleLocation = new ResourceLocation(SnowyCrescentCore.MODID, Writer.example); - Animation example = Animation.create(exampleLocation) + GenericAnimationData example = GenericAnimationData.create(exampleLocation) .withName(Writer.example) - .withLyingType(Animation.LyingType.RIGHT) + .withLyingType(GenericAnimationData.LyingType.RIGHT) .withHeightModifier(0.3f) .withCamY(-1.3f) .withCamPitch(-90.0f) diff --git a/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java b/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java index d673189..f8ef8c5 100644 --- a/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java +++ b/src/main/java/com/linearpast/sccore/animation/entity/AnimationRideEntity.java @@ -1,23 +1,21 @@ package com.linearpast.sccore.animation.entity; -import com.linearpast.sccore.animation.AnimationUtils; import com.linearpast.sccore.animation.capability.AnimationDataCapability; import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.GenericAnimationData; import com.linearpast.sccore.animation.data.Ride; +import com.linearpast.sccore.animation.helper.AnimationHelper; import com.linearpast.sccore.animation.register.AnimationEntities; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.network.NetworkHooks; -import net.minecraftforge.server.ServerLifecycleHooks; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -28,16 +26,12 @@ public class AnimationRideEntity extends Entity { this.noPhysics = true; } - private static final String Animation = "Animation"; - private static final String PlayerUUID = "PlayerUUID"; - private static final String Layer = "Layer"; - private final Set players = new HashSet<>(); private final Map animationPair = new HashMap<>(); - private Animation animation; + private GenericAnimationData animation; private ServerPlayer player; private ResourceLocation layer; - public AnimationRideEntity(ServerPlayer pPlayer, ResourceLocation layer, Animation animation) { + public AnimationRideEntity(ServerPlayer pPlayer, ResourceLocation layer, GenericAnimationData animation) { this(pPlayer.level()); this.player = pPlayer; this.layer = layer; @@ -63,7 +57,7 @@ public class AnimationRideEntity extends Entity { return player; } - public Animation getAnimation() { + public GenericAnimationData getAnimation() { return animation; } @@ -71,36 +65,10 @@ public class AnimationRideEntity extends Entity { protected void defineSynchedData() {} @Override - protected void readAdditionalSaveData(@NotNull CompoundTag pCompound) { - String string = pCompound.getString(Animation); - if(!string.isEmpty()) { - ResourceLocation rl = new ResourceLocation(string); - Animation anim = AnimationUtils.getAnimation(rl); - if(anim != null) { - this.animation = anim; - } - } - - if(pCompound.contains(PlayerUUID)) { - MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); - if(server != null) { - this.player = server.getPlayerList().getPlayer(pCompound.getUUID(PlayerUUID)); - } - } - - if(pCompound.contains(Layer)) { - this.layer = new ResourceLocation(pCompound.getString(Layer)); - } - } + protected void readAdditionalSaveData(@NotNull CompoundTag pCompound) {} @Override - protected void addAdditionalSaveData(@NotNull CompoundTag pCompound) { - pCompound.putUUID(PlayerUUID, player.getUUID()); - pCompound.putString(Layer, layer.toString()); - if(animation != null) { - pCompound.putString(Animation, animation.getKey().toString()); - } - } + protected void addAdditionalSaveData(@NotNull CompoundTag pCompound) {} @Override public void tick() { @@ -134,7 +102,7 @@ public class AnimationRideEntity extends Entity { } public static boolean create(ServerPlayer pPlayer, ResourceLocation layer, ResourceLocation animation, boolean force) { - Animation anim = AnimationUtils.getAnimation(animation); + GenericAnimationData anim = AnimationHelper.INSTANCE.getAnimation(animation); if(anim == null) return false; if(anim.getRide() == null) return false; IAnimationCapability data = AnimationDataCapability.getCapability(pPlayer).orElse(null); @@ -195,7 +163,7 @@ public class AnimationRideEntity extends Entity { IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null); if(data == null) return; data.setRiderAnimation(layer, animLocation); - AnimationUtils.syncAnimation(serverPlayer, player); + AnimationHelper.INSTANCE.syncAnimation(serverPlayer, player); players.add(serverPlayer); } } @@ -204,16 +172,16 @@ public class AnimationRideEntity extends Entity { protected void removePassenger(@NotNull Entity entity) { super.removePassenger(entity); if(entity instanceof ServerPlayer serverPlayer) { - AnimationUtils.removeAnimation(serverPlayer, layer); + AnimationHelper.INSTANCE.removeAnimation(serverPlayer, layer); players.remove(serverPlayer); new HashMap<>(animationPair).forEach((key, value) -> { if(Objects.equals(value, serverPlayer.getUUID())) { animationPair.put(key, null); } }); - IAnimationCapability data = AnimationDataCapability.getCapability(serverPlayer).orElse(null); - if(data == null) return; - data.removeRiderAnimation(); + AnimationDataCapability.getCapability(serverPlayer).ifPresent( + IAnimationCapability::removeRiderAnimation + ); } } diff --git a/src/main/java/com/linearpast/sccore/animation/entity/RawAnimationRideEntity.java b/src/main/java/com/linearpast/sccore/animation/entity/RawAnimationRideEntity.java new file mode 100644 index 0000000..c8ecc9c --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/entity/RawAnimationRideEntity.java @@ -0,0 +1,198 @@ +package com.linearpast.sccore.animation.entity; + +import com.linearpast.sccore.animation.capability.RawAnimationDataCapability; +import com.linearpast.sccore.animation.data.RawAnimationData; +import com.linearpast.sccore.animation.data.Ride; +import com.linearpast.sccore.animation.helper.RawAnimationHelper; +import com.linearpast.sccore.animation.register.AnimationEntities; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.network.NetworkHooks; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class RawAnimationRideEntity extends Entity { + public RawAnimationRideEntity(Level pLevel) { + super(AnimationEntities.RIDE.get(), pLevel); + this.noPhysics = true; + } + + private final Set players = new HashSet<>(); + private final Map animationPair = new HashMap<>(); + private RawAnimationData rawAnimation; + private ServerPlayer player; + private ResourceLocation layer; + public RawAnimationRideEntity(ServerPlayer pPlayer, ResourceLocation layer, RawAnimationData rawAnimation) { + this(pPlayer.level()); + this.player = pPlayer; + this.layer = layer; + this.rawAnimation = rawAnimation; + Ride ride = rawAnimation.getRide(); + if(ride != null) { + List componentAnimations = ride.getComponentAnimations(); + for (ResourceLocation componentAnimation : componentAnimations) { + animationPair.put(componentAnimation, null); + } + } + } + + public ResourceLocation getLayer() { + return layer; + } + + public Set getPlayers() { + return players; + } + + public ServerPlayer getPlayer() { + return player; + } + + public RawAnimationData getAnimation() { + return rawAnimation; + } + + @Override + protected void defineSynchedData() {} + + @Override + protected void readAdditionalSaveData(@NotNull CompoundTag pCompound) {} + + @Override + protected void addAdditionalSaveData(@NotNull CompoundTag pCompound) {} + + @Override + public void tick() { + super.tick(); + if(!this.level().isClientSide) { + Ride ride = rawAnimation == null ? null : rawAnimation.getRide(); + if(!this.getPassengers().contains(player) || (ride != null && ride.getExistTick() > 0 && this.tickCount >= ride.getExistTick())) { + this.remove(RemovalReason.DISCARDED); + } + } + } + + @Override + public double getPassengersRidingOffset() { + return 0.0; + } + + @Override + protected boolean canRide(@NotNull Entity entity) { + return true; + } + + @Override + public boolean shouldRiderSit() { + return false; + } + + @Override + public @NotNull Packet getAddEntityPacket(){ + return NetworkHooks.getEntitySpawningPacket(this); + } + + @Nullable + public static RawAnimationRideEntity create(ServerPlayer pPlayer, ResourceLocation layer, RawAnimationData animation) { + if(animation.getRide() == null) return null; + RawAnimationDataCapability data = RawAnimationDataCapability.getCapability(pPlayer).orElse(null); + if(data == null) return null; + data.setRiderAnimation(layer, animation.getKey()); + RawAnimationRideEntity seat = new RawAnimationRideEntity(pPlayer, layer, animation); + float xRot = animation.getRide().getXRot(); + float yRot = animation.getRide().getYRot(); + if(xRot == 0 && yRot == 0) seat.setRot(pPlayer.getXRot(), pPlayer.getYRot()); + else seat.setRot(yRot, xRot); + Vec3 pos = pPlayer.position(); + pos.add(animation.getRide().getOffset()); + seat.setPos(pos.x, pos.y + 0.35f, pos.z); + pPlayer.level().addFreshEntity(seat); + pPlayer.startRiding(seat, false); + return seat; + } + + @Override + protected void positionRider(@NotNull Entity pPassenger, @NotNull MoveFunction pCallback) { + super.positionRider(pPassenger, pCallback); + pPassenger.setYBodyRot(this.getYRot()); + } + + @Override + public void onPassengerTurned(@NotNull Entity pEntityToUpdate) { + pEntityToUpdate.setYBodyRot(this.getYRot()); + } + + @Override + public @NotNull Vec3 getDismountLocationForPassenger(@NotNull LivingEntity entity) { + Ride ride = rawAnimation.getRide(); + if(ride != null) { + Vec3 position = entity.position(); + return position.subtract(ride.getOffset()); + } + return entity.position(); + } + + @Override + protected void addPassenger(@NotNull Entity entity) { + int passengerNum = getPassengers().size(); + super.addPassenger(entity); + if(passengerNum == 0) return; + if(entity instanceof ServerPlayer serverPlayer) { + Ride ride = rawAnimation.getRide(); + if(ride == null) return; + List componentAnimations = ride.getComponentAnimations(); + if(componentAnimations.isEmpty()) return; + if(passengerNum > componentAnimations.size()) return; + ResourceLocation animLocation = null; + for (ResourceLocation location : animationPair.keySet()) { + if(animationPair.get(location) == null) + animLocation = location; + } + if(animLocation == null) return; + animationPair.put(animLocation, serverPlayer.getUUID()); + RawAnimationDataCapability data = RawAnimationDataCapability.getCapability(serverPlayer).orElse(null); + if(data == null) return; + data.setRiderAnimation(layer, animLocation); + RawAnimationHelper.INSTANCE.syncAnimation(serverPlayer, player); + players.add(serverPlayer); + } + } + + @Override + protected void removePassenger(@NotNull Entity entity) { + super.removePassenger(entity); + if(entity instanceof ServerPlayer serverPlayer) { + RawAnimationHelper.INSTANCE.removeAnimation(serverPlayer, layer); + players.remove(serverPlayer); + new HashMap<>(animationPair).forEach((key, value) -> { + if(Objects.equals(value, serverPlayer.getUUID())) { + animationPair.put(key, null); + } + }); + RawAnimationDataCapability.getCapability(serverPlayer).ifPresent( + RawAnimationDataCapability::removeRiderAnimation + ); + } + } + + @Override + public boolean canAddPassenger(@NotNull Entity pPassenger) { + if(!(pPassenger instanceof ServerPlayer)) return false; + int size = players.size(); + Ride ride = rawAnimation.getRide(); + if(ride == null) return false; + int maxSize = ride.getComponentAnimations().size(); + boolean flag1 = size < maxSize; + boolean flag2 = !(pPassenger.getVehicle() instanceof AnimationRideEntity); + return flag1 && flag2; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/event/PlayerTickEvent.java b/src/main/java/com/linearpast/sccore/animation/event/PlayerTickEvent.java index 9366912..0cb1488 100644 --- a/src/main/java/com/linearpast/sccore/animation/event/PlayerTickEvent.java +++ b/src/main/java/com/linearpast/sccore/animation/event/PlayerTickEvent.java @@ -1,22 +1,32 @@ package com.linearpast.sccore.animation.event; import com.linearpast.sccore.animation.capability.AnimationDataCapability; -import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.capability.RawAnimationDataCapability; import com.linearpast.sccore.animation.entity.AnimationRideEntity; +import com.linearpast.sccore.animation.entity.RawAnimationRideEntity; import net.minecraft.world.entity.player.Player; import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; public class PlayerTickEvent { + @SubscribeEvent public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event) { if (event.side.isServer() && event.phase == TickEvent.Phase.END) { if(event.player.tickCount % 20 == 0) { Player player = event.player; if(!(player.getVehicle() instanceof AnimationRideEntity)){ - IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); - if(data == null) return; - if(data.getRiderAnimLayer() != null) { - data.removeRiderAnimation(); - } + AnimationDataCapability.getCapability(player).ifPresent(capability -> { + if(capability.getRiderAnimLayer() != null) { + capability.removeRiderAnimation(); + } + }); + } + if(!(player.getVehicle() instanceof RawAnimationRideEntity)){ + RawAnimationDataCapability.getCapability(player).ifPresent(capability -> { + if(capability.getRiderAnimLayer() != null) { + capability.removeRiderAnimation(); + } + }); } } } diff --git a/src/main/java/com/linearpast/sccore/animation/event/client/CameraAnglesModify.java b/src/main/java/com/linearpast/sccore/animation/event/client/CameraAnglesModify.java index 38c4996..b982fe1 100644 --- a/src/main/java/com/linearpast/sccore/animation/event/client/CameraAnglesModify.java +++ b/src/main/java/com/linearpast/sccore/animation/event/client/CameraAnglesModify.java @@ -1,15 +1,16 @@ package com.linearpast.sccore.animation.event.client; -import com.linearpast.sccore.animation.AnimationUtils; import com.linearpast.sccore.animation.capability.AnimationDataCapability; import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.GenericAnimationData; +import com.linearpast.sccore.animation.helper.AnimationHelper; import dev.kosmx.playerAnim.core.util.MathHelper; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.event.ViewportEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; import java.util.Comparator; @@ -22,6 +23,7 @@ public class CameraAnglesModify { private static float currentPitch = 0.0F; private static float currentRoll = 0.0F; + @SubscribeEvent public static void changeCameraView(ViewportEvent.ComputeCameraAngles event){ Minecraft minecraft = Minecraft.getInstance(); @@ -30,10 +32,10 @@ public class CameraAnglesModify { if (player != null) { IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); if(data == null) return; - Animation animation = null; + GenericAnimationData animation = null; try { animation = data.getAnimations().values().stream() - .map(AnimationUtils::getAnimation) + .map(AnimationHelper.INSTANCE::getAnimation) .min(Comparator.comparingDouble(anim -> { if (anim == null) return 1.0f; return anim.getHeightModifier(); diff --git a/src/main/java/com/linearpast/sccore/animation/event/client/ClientPlayerTick.java b/src/main/java/com/linearpast/sccore/animation/event/client/ClientPlayerEvent.java similarity index 83% rename from src/main/java/com/linearpast/sccore/animation/event/client/ClientPlayerTick.java rename to src/main/java/com/linearpast/sccore/animation/event/client/ClientPlayerEvent.java index 2f7c906..cd40d3b 100644 --- a/src/main/java/com/linearpast/sccore/animation/event/client/ClientPlayerTick.java +++ b/src/main/java/com/linearpast/sccore/animation/event/client/ClientPlayerEvent.java @@ -1,27 +1,30 @@ package com.linearpast.sccore.animation.event.client; -import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.helper.AnimationHelper; import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.world.entity.player.Player; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; import java.util.HashMap; import java.util.Map; @OnlyIn(Dist.CLIENT) -public class ClientPlayerTick { +public class ClientPlayerEvent { + @SubscribeEvent public static void onPlayerTick(TickEvent.PlayerTickEvent event) { if (event.side.isClient() && event.phase == TickEvent.Phase.START) { Player player = event.player; if(player.tickCount % 10 != 0) return; if (!(player instanceof AbstractClientPlayer clientPlayer)) return; - AnimationUtils.refreshAnimation(clientPlayer); + AnimationHelper.INSTANCE.refreshAnimation(clientPlayer); } } public static Map> runs = new HashMap<>(); + @SubscribeEvent public static void delayRuns(TickEvent.PlayerTickEvent event) { if (event.side.isClient() && event.phase == TickEvent.Phase.END) { Map.copyOf(runs).forEach((runnable, countMap) -> { diff --git a/src/main/java/com/linearpast/sccore/animation/event/client/EntityRendererRegisterEvent.java b/src/main/java/com/linearpast/sccore/animation/event/client/EntityRendererRegisterEvent.java index 3638fec..e844309 100644 --- a/src/main/java/com/linearpast/sccore/animation/event/client/EntityRendererRegisterEvent.java +++ b/src/main/java/com/linearpast/sccore/animation/event/client/EntityRendererRegisterEvent.java @@ -3,8 +3,10 @@ package com.linearpast.sccore.animation.event.client; import com.linearpast.sccore.animation.entity.renderer.AnimationRideRenderer; import com.linearpast.sccore.animation.register.AnimationEntities; import net.minecraftforge.client.event.EntityRenderersEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; public class EntityRendererRegisterEvent { + @SubscribeEvent public static void registerEntityRenderer(EntityRenderersEvent.RegisterRenderers event) { event.registerEntityRenderer(AnimationEntities.RIDE.get(), AnimationRideRenderer::new); } diff --git a/src/main/java/com/linearpast/sccore/animation/event/create/AnimationEvent.java b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationEvent.java new file mode 100644 index 0000000..65ea9b2 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationEvent.java @@ -0,0 +1,117 @@ +package com.linearpast.sccore.animation.event.create; + +import com.linearpast.sccore.animation.data.AnimationData; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.Cancelable; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.fml.LogicalSide; +import org.jetbrains.annotations.Nullable; + +@Cancelable +@Event.HasResult +public class AnimationEvent extends Event { + public final LogicalSide side; + + AnimationEvent(LogicalSide side) { + this.side = side; + } + + public enum Type { + APPLY, INVITE, REQUEST + } + + public static class Send extends AnimationEvent { + private int validTick; + private int cooldownTick; + public final Type type; + public Send(LogicalSide side, int validTick, int cooldownTick, Type type) { + super(side); + this.validTick = validTick; + this.cooldownTick = cooldownTick; + this.type = type; + } + + public int getValidTick() { + return validTick; + } + public int getCooldownTick() { + return cooldownTick; + } + + public void setValidTick(int validTick) { + this.validTick = validTick; + } + public void setCooldownTick(int cooldownTick) { + this.cooldownTick = cooldownTick; + } + } + + public static class Accept extends AnimationEvent { + private int validDistance; + public final Type type; + public Accept(Type type) { + super(LogicalSide.SERVER); + this.type = type; + } + public Accept(Type type, int validDistance) { + this(type); + this.validDistance = validDistance; + } + + public int getValidDistance() { + return validDistance; + } + public void setValidDistance(int validDistance) { + this.validDistance = validDistance; + } + } + + public static class Play extends AnimationEvent { + private final @Nullable Player player; + private final ResourceLocation layer; + private final AnimationData animation; + public Play(LogicalSide side, @Nullable Player player, ResourceLocation layer, AnimationData animation) { + super(side); + this.player = player; + this.layer = layer; + this.animation = animation; + } + + public @Nullable Player getPlayer() { + return player; + } + public ResourceLocation getLayer() { + return layer; + } + public AnimationData getAnimation() { + return animation; + } + } + + public static class Join extends AnimationEvent { + private final Player player; + private final Player target; + private boolean force; + public Join(Player player, Player target, boolean force) { + super(LogicalSide.SERVER); + this.player = player; + this.target = target; + this.force = force; + } + + public Player getPlayer() { + return player; + } + public Player getTarget() { + return target; + } + public boolean isForce() { + return force; + } + + public void setForce(boolean force) { + this.force = force; + } + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/event/create/AnimationLayerRegisterEvent.java b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationLayerRegisterEvent.java deleted file mode 100644 index 02b8e3a..0000000 --- a/src/main/java/com/linearpast/sccore/animation/event/create/AnimationLayerRegisterEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.linearpast.sccore.animation.event.create; - -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.eventbus.api.Event; - -import java.util.HashMap; -import java.util.Map; - -/** - * You can listen this event to invite an animation layer
- * It is only useful in server - */ -public class AnimationLayerRegisterEvent extends Event { - private final Map layers = new HashMap<>(); - - public Map getLayers() { - return layers; - } - - public void registerLayer(ResourceLocation key, Integer value) { - layers.put(key, value); - } -} diff --git a/src/main/java/com/linearpast/sccore/animation/event/create/AnimationRegisterEvent.java b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationRegisterEvent.java index 4fa15f3..6d11eb6 100644 --- a/src/main/java/com/linearpast/sccore/animation/event/create/AnimationRegisterEvent.java +++ b/src/main/java/com/linearpast/sccore/animation/event/create/AnimationRegisterEvent.java @@ -1,24 +1,50 @@ package com.linearpast.sccore.animation.event.create; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.GenericAnimationData; +import com.linearpast.sccore.animation.data.RawAnimationData; +import com.linearpast.sccore.animation.register.RawAnimationRegistry; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.eventbus.api.Event; import java.util.HashMap; import java.util.Map; -/** - * You can listen this event to invite an animation
- * It is only useful in server - */ public class AnimationRegisterEvent extends Event { - private final Map animations = new HashMap<>(); + public static class Layer extends AnimationRegisterEvent { + private final Map layers = new HashMap<>(); - public Map getAnimations() { - return new HashMap<>(animations); + public Map getLayers() { + return layers; + } + + public void registerLayer(ResourceLocation key, Integer value) { + layers.put(key, value); + } } - public void registerAnimation(ResourceLocation location, Animation animation) { - animations.put(location, animation); + public static class Animation extends AnimationRegisterEvent { + private final Map animations = new HashMap<>(); + + public Map getAnimations() { + return new HashMap<>(animations); + } + + public void registerAnimation(ResourceLocation location, GenericAnimationData animation) { + animations.put(location, animation); + } + } + + public static class RawAnimation extends AnimationRegisterEvent { + private final Map animations = new HashMap<>(); + + public Map getAnimations() { + return new HashMap<>(animations); + } + + public void registerAnimation(ResourceLocation location, RawAnimationData animation) { + if (RawAnimationRegistry.validateLocation(location)) { + animations.put(location, animation); + } + } } } diff --git a/src/main/java/com/linearpast/sccore/animation/helper/AnimationHelper.java b/src/main/java/com/linearpast/sccore/animation/helper/AnimationHelper.java new file mode 100644 index 0000000..17d5ebf --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/helper/AnimationHelper.java @@ -0,0 +1,211 @@ +package com.linearpast.sccore.animation.helper; + +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.data.AnimationData; +import com.linearpast.sccore.animation.data.GenericAnimationData; +import com.linearpast.sccore.animation.entity.AnimationRideEntity; +import com.linearpast.sccore.animation.register.AnimationRegistry; +import com.linearpast.sccore.animation.utils.AnimationUtils; +import com.linearpast.sccore.animation.utils.ApiBack; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.util.FakePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +/** + * Animation Util. May be you can call it Api. + */ +public class AnimationHelper implements IAnimationHelper { + public static final AnimationHelper INSTANCE = new AnimationHelper(); + + /** + * Get the LyingType when there are animations which playing on player.
+ * And It will return the first which be found. + * @param player Target player + * @return The first LyingType it find. + */ + @Nullable + public GenericAnimationData.LyingType getSideView(Player player) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if(data == null) return null; + GenericAnimationData.LyingType lyingType = null; + for (ResourceLocation value : data.getAnimations().values()) { + GenericAnimationData animation = getAnimation(value); + if(animation == null) return null; + GenericAnimationData.LyingType type = animation.getLyingType(); + if(type == null) continue; + switch (type) { + case FRONT,BACK -> {} + case LEFT,RIGHT -> lyingType = animation.getLyingType(); + } + } + return lyingType; + }); + } + + /** + * Get the HeightModifier when there are animations which playing on player.
+ * And It will return the first which be found. + * @param player Target player + * @return The first HeightModifier it find. + */ + public float getHeightModifier(Player player) { + Float result = ANIMATION_RUNNER.testLoadedAndCall(() -> { + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if (data == null) return 1.0f; + float heightModifier = 1.0f; + for (ResourceLocation value : data.getAnimations().values()) { + GenericAnimationData animation = getAnimation(value); + if (animation == null) continue; + float animationHeightModifier = animation.getHeightModifier(); + heightModifier = Math.min(heightModifier, animationHeightModifier); + } + return heightModifier; + }); + return result == null ? 1.0f : result; + } + + + @Override + public @Nullable GenericAnimationData getAnimation(ResourceLocation location) { + return AnimationRegistry.getAnimations().getOrDefault(location, null); + } + + @Override + public @Nullable GenericAnimationData getAnimation(CompoundTag tag) { + return new GenericAnimationData(){{deserializeNBT(tag);}}; + } + + @Override + public @Nullable IAnimationCapability getCapability(Player player) { + return AnimationDataCapability.getCapability(player).orElse(null); + } + + @Override + public void clearAnimations(ServerPlayer serverPlayer) { + ANIMATION_RUNNER.testLoadedAndRun(() -> { + Optional.ofNullable(getCapability(serverPlayer)).ifPresent(IAnimationCapability::clearAnimations); + detachAnimation(serverPlayer); + }); + } + + @Override + public boolean isAnimationPresent(ResourceLocation location) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> AnimationRegistry.getAnimations().containsKey(location)); + } + + @Override + public ApiBack detachAnimation(ServerPlayer player) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(player.getVehicle() instanceof AnimationRideEntity) { + player.stopRiding(); + return ApiBack.SUCCESS; + } + return ApiBack.UNSUPPORTED; + }); + } + + @Override + public ApiBack joinAnimationServer(ServerPlayer player, ServerPlayer target, boolean force) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + Entity vehicle = target.getVehicle(); + if(vehicle instanceof AnimationRideEntity) { + boolean result = player.startRiding(vehicle, force); + return result ? ApiBack.SUCCESS : ApiBack.FAIL; + } + return ApiBack.UNSUPPORTED; + }); + } + + @Override + @OnlyIn(Dist.CLIENT) + public void refreshAnimation(AbstractClientPlayer clientPlayer) { + ANIMATION_RUNNER.testLoadedAndRun(() -> { + IAnimationCapability data = getCapability(clientPlayer); + if(data == null) return; + Set oldLayers = new HashSet<>(data.getAnimations().keySet()); + for (ResourceLocation layer : Set.copyOf(oldLayers)) { + if (AnimationUtils.isClientAnimationStop(clientPlayer, layer)) { + removeAnimation(clientPlayer, layer); + } + } + }); + } + + @Override + public @Nullable ResourceLocation getAnimationPlaying(Player player, @Nullable ResourceLocation layer) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + IAnimationCapability data = getCapability(player); + if(data == null) return null; + if(layer == null){ + for (ResourceLocation value : data.getAnimations().values()) { + if(value != null) return value; + } + } else if (isAnimationLayerPresent(layer)) { + if(data.isAnimationPresent(layer)){ + return data.getAnimation(layer); + } + } + return null; + }); + } + + @Override + public ApiBack removeAnimation(@NotNull ServerPlayer serverPlayer, ResourceLocation layer) { + boolean result = ANIMATION_RUNNER.testLoadedAndCall(() -> Optional.ofNullable(getCapability(serverPlayer)) + .map(data -> data.removeAnimation(layer)).orElse(false)); + return result ? ApiBack.SUCCESS : ApiBack.FAIL; + } + + @Override + public ApiBack playAnimationWithRide(@NotNull ServerPlayer player, ResourceLocation layer, AnimationData animation, boolean force) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + ResourceLocation key = animation.getKey(); + if(!isAnimationLayerPresent(layer) || !isAnimationPresent(key)) + return ApiBack.RESOURCE_NOT_FOUND; + if(animation.getRide() == null) + return ApiBack.RESOURCE_NOT_FOUND; + if(player instanceof FakePlayer) + return ApiBack.UNSUPPORTED; + boolean flag = player.getVehicle() != null; + if(flag && force) player.unRide(); + else if(flag) return ApiBack.UNSUPPORTED; + boolean result = AnimationRideEntity.create(player, layer, key, force); + return result ? ApiBack.SUCCESS : ApiBack.FAIL; + }); + } + + @Override + public ApiBack playAnimationServer(@NotNull ServerPlayer player, ResourceLocation layer, AnimationData animation) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + ResourceLocation key = animation.getKey(); + if(!isAnimationLayerPresent(layer) || !isAnimationPresent(key)) + return ApiBack.RESOURCE_NOT_FOUND; + if(player instanceof FakePlayer) + return ApiBack.UNSUPPORTED; + Boolean flag = Optional.ofNullable(getCapability(player)).map(data -> + data.mergeAnimation(layer, key)).orElse(false); + return flag ? ApiBack.SUCCESS : ApiBack.FAIL; + }); + } + + public ApiBack playAnimation(@NotNull ServerPlayer player, ResourceLocation layer, ResourceLocation animation) { + return playAnimation(player, layer, getAnimation(animation)); + } + public ApiBack playAnimationWithRide(@NotNull ServerPlayer player, ResourceLocation layer, ResourceLocation animation, boolean force) { + return playAnimationWithRide(player, layer, getAnimation(animation), force); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/helper/HelperGetterFromAnimation.java b/src/main/java/com/linearpast/sccore/animation/helper/HelperGetterFromAnimation.java new file mode 100644 index 0000000..75cedfa --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/helper/HelperGetterFromAnimation.java @@ -0,0 +1,20 @@ +package com.linearpast.sccore.animation.helper; + +import net.minecraft.resources.ResourceLocation; + +public class HelperGetterFromAnimation implements IHelperGetter { + private final ResourceLocation location; + + public HelperGetterFromAnimation(ResourceLocation location) { + this.location = location; + } + + public static IHelperGetter create(ResourceLocation location) { + return new HelperGetterFromAnimation(location); + } + + @Override + public boolean filter(IAnimationHelper helper) { + return helper.isAnimationPresent(location); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/helper/IAnimationHelper.java b/src/main/java/com/linearpast/sccore/animation/helper/IAnimationHelper.java new file mode 100644 index 0000000..90b7530 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/helper/IAnimationHelper.java @@ -0,0 +1,637 @@ +package com.linearpast.sccore.animation.helper; + +import com.linearpast.sccore.animation.data.AnimationData; +import com.linearpast.sccore.animation.event.PlayerTickEvent; +import com.linearpast.sccore.animation.event.client.CameraAnglesModify; +import com.linearpast.sccore.animation.event.client.ClientPlayerEvent; +import com.linearpast.sccore.animation.event.client.EntityRendererRegisterEvent; +import com.linearpast.sccore.animation.event.create.AnimationEvent; +import com.linearpast.sccore.animation.network.toclient.SyncAnimationPacket; +import com.linearpast.sccore.animation.network.toserver.*; +import com.linearpast.sccore.animation.register.AnimationCapabilities; +import com.linearpast.sccore.animation.register.AnimationChannels; +import com.linearpast.sccore.animation.register.AnimationEntities; +import com.linearpast.sccore.animation.register.AnimationRegistry; +import com.linearpast.sccore.animation.utils.AnimationUtils; +import com.linearpast.sccore.animation.utils.ApiBack; +import com.linearpast.sccore.capability.data.ICapabilitySync; +import com.linearpast.sccore.core.ModChannel; +import com.linearpast.sccore.core.ModLazyRun; +import com.linearpast.sccore.core.configs.ModConfigs; +import dev.kosmx.playerAnim.core.data.KeyframeAnimation; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.LogicalSide; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Animation helper interface + * @param Animation data + * @param Capability + */ +@SuppressWarnings({"UnusedReturnValue", "unused"}) +public interface IAnimationHelper>{ + String AnimModId = "playeranimator"; + /** + * Lazy runner + */ + ModLazyRun ANIMATION_RUNNER = new ModLazyRun(AnimModId) { + @Override + public void addCommonListener(IEventBus forgeBus, IEventBus modBus) { + AnimationEntities.register(modBus); + forgeBus.register(AnimationRegistry.class); + forgeBus.register(PlayerTickEvent.class); + } + + @Override + public void addClientListener(IEventBus forgeBus, IEventBus modBus) { + modBus.register(EntityRendererRegisterEvent.class); + forgeBus.register(CameraAnglesModify.class); + forgeBus.register(ClientPlayerEvent.class); + } + }; + + //Cache info record, not persistent + record ApplyAnimationRecord(UUID target, int expireTick) {} + record InviteAnimationRecord(ResourceLocation layer, AnimationData animation, int expireTick, List targets) {} + record RequestAnimationRecord(ResourceLocation layer, AnimationData animation, int expireTick, UUID target, boolean isRide) {} + + //Apply & invite & request history record + Map applyMap = new ConcurrentHashMap<>(); + Map inviteMap = new ConcurrentHashMap<>(); + Map requestMap = new ConcurrentHashMap<>(); + + //Last apply & invite & request tick map + Map lastApplyTickMap = new ConcurrentHashMap<>(); + Map lastInviteTickMap = new ConcurrentHashMap<>(); + Map lastRequestTickMap = new ConcurrentHashMap<>(); + + /** + * Equal to {@link AnimationRegistry#getLayers()} + * @return Resource location set + */ + default Set getLayers() { + return Set.copyOf(AnimationRegistry.getLayers().keySet()); + } + + /** + * Get animation by location + * @param location location + * @return Animation data + */ + @Nullable + D getAnimation(ResourceLocation location); + + /** + * Get animation by Tag (deserializeNBT) + * @param tag tag + * @return Animation data + */ + @Nullable + D getAnimation(CompoundTag tag); + + /** + * Get capability + * @param player player + * @return Capability + */ + @Nullable + C getCapability(Player player); + + /** + * To register handler + * @param forgeBus Forge event bus + * @param modBus Mod event bus + */ + static void register(IEventBus forgeBus, IEventBus modBus){ + ANIMATION_RUNNER.testLoadedAndRun(() -> { + AnimationCapabilities.registerAnimationCapability(); + AnimationChannels.registerChannel(); + }); + ANIMATION_RUNNER.testLoadedAndAddListener(forgeBus, modBus); + } + + //clear animations + void clearAnimations(ServerPlayer serverPlayer); + //according to location, judge if animation is present + boolean isAnimationPresent(ResourceLocation location); + //stop riding + ApiBack detachAnimation(ServerPlayer player); + //start ride + ApiBack joinAnimationServer(ServerPlayer player, ServerPlayer target, boolean force); + + /** + * Trigger event and let implementation class handle + * @param player player + * @param target target + * @param force is force + * @return Api back + */ + default ApiBack joinAnimation(ServerPlayer player, ServerPlayer target, boolean force){ + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + AnimationEvent.Join playEvent = new AnimationEvent.Join(player, target, force); + boolean post = MinecraftForge.EVENT_BUS.post(playEvent); + Event.Result eventResult = playEvent.getResult(); + if(post || eventResult == Event.Result.DENY) return ApiBack.BE_CANCELLED; + return joinAnimationServer(player, target, playEvent.isForce()); + }); + } + + /** + * Sync animation tick to client + * @param player Player + * @param target Target player + */ + default void syncAnimation(ServerPlayer player, ServerPlayer target) { + ANIMATION_RUNNER.testLoadedAndRun(() -> { + ModChannel.sendToPlayer(new SyncAnimationPacket(player.getUUID(), target.getUUID()), player); + ModChannel.sendToPlayer(new SyncAnimationPacket(player.getUUID(), target.getUUID()), target); + }); + } + + /** + * Sync animation tick on client + * @param player Player + * @param target Target player + */ + @OnlyIn(Dist.CLIENT) + default void syncAnimation(AbstractClientPlayer player, AbstractClientPlayer target) { + ANIMATION_RUNNER.testLoadedAndRun(() -> AnimationUtils.syncAnimation(player, target)); + } + + /** + * Refresh animation throw capability + * @param clientPlayer player + */ + @OnlyIn(Dist.CLIENT) + void refreshAnimation(AbstractClientPlayer clientPlayer); + + /** + * Refresh animation on client, it will not sync to capability + * @param clientPlayer player + */ + @OnlyIn(Dist.CLIENT) + default void refreshAnimationUnsafe(AbstractClientPlayer clientPlayer) { + ANIMATION_RUNNER.testLoadedAndRun(() -> { + Set oldLayers = new HashSet<>(AnimationRegistry.getLayers().keySet()); + for (ResourceLocation layer : Set.copyOf(oldLayers)) { + if (AnimationUtils.isClientAnimationStop(clientPlayer, layer)) { + removeAnimation(clientPlayer, layer); + } + } + }); + } + + /** + * Test if layer exist and has been invite. + * @param layer Target layer + * @return If layer exist and has been invited + */ + default boolean isAnimationLayerPresent(ResourceLocation layer) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> getLayers().contains(layer)); + } + + /** + * Get animation which is playing now on player.
+ * If layer is null, it will return the first playing animation which can be found. + * @param player Target player + * @param layer Target layer + * @return Playing animation resource location + */ + @Nullable + ResourceLocation getAnimationPlaying(Player player, @Nullable ResourceLocation layer); + + /** + * Remove animation. + * @param player Target player + * @param layer Target layer + * @return If success + */ + @OnlyIn(Dist.CLIENT) + default ApiBack removeAnimation(@Nullable AbstractClientPlayer player, ResourceLocation layer) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + AnimationUtils.removeAnimation(player, layer); + return ApiBack.SUCCESS; + }); + } + ApiBack removeAnimation(@NotNull ServerPlayer serverPlayer, ResourceLocation layer); + + /** + *

+     * Play animation with ride. Player will ride an entity, then play animation.
+     * When player unride, animation will be remove.
+     * If player is riding and the "force" is false, it will return false
+     * 
+ * @param player Target player + * @param layer Target layer + * @param animation Animation + * @param force If force to ride and play animation + * @return If success + */ + @OnlyIn(Dist.CLIENT) + default ApiBack playAnimationWithRide(@Nullable AbstractClientPlayer player, ResourceLocation layer, ResourceLocation animation, boolean force) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(isAnimationLayerPresent(layer) && isAnimationPresent(animation)) + return ApiBack.RESOURCE_NOT_FOUND; + UUID uuid = player == null ? null : player.getUUID(); + AnimationData anim = getAnimation(animation); + if(anim == null || anim.getRide() == null) return ApiBack.RESOURCE_NOT_FOUND; + ModChannel.sendToServer(new PlayAnimationRidePacket(anim, layer, animation, uuid, force)); + return ApiBack.SUCCESS; + }); + } + ApiBack playAnimationWithRide(@NotNull ServerPlayer player, ResourceLocation layer, AnimationData animationTag, boolean force); + + /** + *
+     * Play animation.
+     * If run in Dist.CLIENT, player can be null, it will play animation only client.
+     * If animation be null, it will remove animation on layer.
+     * 
+ * @param player Target player + * @param layer Target layer + * @param animation Animation + * @return If success + */ + @OnlyIn(Dist.CLIENT) + default ApiBack playAnimation(@Nullable AbstractClientPlayer player, ResourceLocation layer, ResourceLocation animation) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(isAnimationLayerPresent(layer) && isAnimationPresent(animation)) + return ApiBack.RESOURCE_NOT_FOUND; + UUID uuid = player == null ? null : player.getUUID(); + AnimationData anim = getAnimation(animation); + if(anim == null) return ApiBack.RESOURCE_NOT_FOUND; + + AnimationEvent.Play playEvent = new AnimationEvent.Play( + LogicalSide.CLIENT, + player, + layer, + anim + ); + boolean post = MinecraftForge.EVENT_BUS.post(playEvent); + Event.Result eventResult = playEvent.getResult(); + if(post || eventResult == Event.Result.DENY) return ApiBack.BE_CANCELLED; + ModChannel.sendToServer(new PlayAnimationPacket(anim, layer, animation, uuid)); + return ApiBack.SUCCESS; + }); + } + + /** + * Trigger event and let implementation class handle + * @param player player + * @param layer target layer + * @param animation animation + * @return Api back + */ + default ApiBack playAnimation(@NotNull ServerPlayer player, ResourceLocation layer, AnimationData animation){ + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + AnimationEvent.Play playEvent = new AnimationEvent.Play(LogicalSide.SERVER, player, layer, animation); + boolean post = MinecraftForge.EVENT_BUS.post(playEvent); + Event.Result eventResult = playEvent.getResult(); + if(post || eventResult == Event.Result.DENY) return ApiBack.BE_CANCELLED; + return playAnimationServer(player, layer, animation); + }); + } + ApiBack playAnimationServer(@NotNull ServerPlayer player, ResourceLocation layer, AnimationData animation); + + /** + * Request animation + */ + default ApiBack request(ServerPlayer player, ServerPlayer target, ResourceLocation layer, AnimationData animation, boolean isRide) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(!isAnimationLayerPresent(layer) || !isAnimationPresent(animation.getKey())) + return ApiBack.RESOURCE_NOT_FOUND; + int tickCount = player.server.getTickCount(); + UUID playerUUID = player.getUUID(); + UUID targetUUID = target.getUUID(); + + int origin = ModConfigs.Server.requestValidTime.get() * 20; + int cooldown = ModConfigs.Server.requestCooldown.get() * 20; + AnimationEvent.Send sendEvent = new AnimationEvent.Send(LogicalSide.SERVER, origin, cooldown, AnimationEvent.Type.APPLY); + boolean post = MinecraftForge.EVENT_BUS.post(sendEvent); + if(post) return ApiBack.BE_CANCELLED; + Event.Result eventResult = sendEvent.getResult(); + switch (eventResult) { + case DENY : return ApiBack.BE_CANCELLED; + case DEFAULT : { + //Test if is not in cooldown + int lastTick = lastRequestTickMap.getOrDefault(playerUUID, 0); + if(Math.max(tickCount - sendEvent.getCooldownTick(), 0) >= lastTick) { + lastRequestTickMap.put(playerUUID, tickCount); + } else return ApiBack.COOLDOWN; + } + case ALLOW : { + //Add to cache, done + int expireTick = sendEvent.getValidTick() + tickCount; + requestMap.put(playerUUID, new RequestAnimationRecord(layer, animation, expireTick, targetUUID, isRide)); + return ApiBack.SUCCESS; + } + default: return ApiBack.UNSUPPORTED; + } + }); + } + + /** + * Client request + * @param target target + * @param layer layer + * @param animation animation + * @return Api back + */ + default ApiBack request(AbstractClientPlayer target, ResourceLocation layer, ResourceLocation animation) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(!isAnimationLayerPresent(layer) || !isAnimationPresent(animation)) + return ApiBack.RESOURCE_NOT_FOUND; + KeyframeAnimation keyframeAnimation = PlayerAnimationRegistry.getAnimation(animation); + if(keyframeAnimation == null) return ApiBack.RESOURCE_NOT_FOUND; + D data = getAnimation(animation); + if(data == null) return ApiBack.RESOURCE_NOT_FOUND; + AnimationEvent.Send sendEvent = new AnimationEvent.Send( + LogicalSide.CLIENT, + 0, + 0, + AnimationEvent.Type.REQUEST + ); + boolean post = MinecraftForge.EVENT_BUS.post(sendEvent); + Event.Result eventResult = sendEvent.getResult(); + if(post || eventResult == Event.Result.DENY) return ApiBack.BE_CANCELLED; + ModChannel.sendToServer(new RequestAnimationPacket(data, layer, target.getUUID())); + return ApiBack.SUCCESS; + }); + } + + /** + * Accept Request + */ + default ApiBack acceptRequest(ServerPlayer player, ServerPlayer requestor) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + UUID requestorUUID = requestor.getUUID(); + RequestAnimationRecord record = requestMap.getOrDefault(requestorUUID, null); + if (record == null) return ApiBack.UNSUPPORTED; + UUID uuid = player.getUUID(); + if (!record.target().equals(uuid)) return ApiBack.UNSUPPORTED; + + AnimationEvent.Accept acceptEvent = new AnimationEvent.Accept(AnimationEvent.Type.REQUEST, 0); + boolean post = MinecraftForge.EVENT_BUS.post(acceptEvent); + if(post) return ApiBack.BE_CANCELLED; + Event.Result eventResult = acceptEvent.getResult(); + switch (eventResult) { + case DENY : return ApiBack.BE_CANCELLED; + case DEFAULT : { + //Test if is in valid time + int tickCount = requestor.server.getTickCount(); + if (tickCount >= record.expireTick()) { + requestMap.remove(requestorUUID); + return ApiBack.OPERATION_EXPIRE; + } + } + case ALLOW : { + //done + ApiBack back; + if(record.isRide()) back = playAnimationWithRide(player, record.layer(), record.animation(), false); + else back = playAnimation(player, record.layer(), record.animation()); + if(back == ApiBack.SUCCESS) requestMap.remove(requestorUUID); + return back; + } + default: return ApiBack.UNSUPPORTED; + } + }); + } + + /** + * Send apply to join animation on server side + */ + default ApiBack apply(ServerPlayer player, ServerPlayer target) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + int tickCount = player.server.getTickCount(); + + int origin = ModConfigs.Server.applyValidTime.get() * 20; + int cooldown = ModConfigs.Server.applyCooldown.get() * 20; + AnimationEvent.Send sendEvent = new AnimationEvent.Send( + LogicalSide.SERVER, + origin, + cooldown, + AnimationEvent.Type.APPLY + ); + boolean post = MinecraftForge.EVENT_BUS.post(sendEvent); + if(post) return ApiBack.BE_CANCELLED; + Event.Result eventResult = sendEvent.getResult(); + switch (eventResult) { + case DENY : return ApiBack.BE_CANCELLED; + case DEFAULT : { + //Test if is not in cooldown + int lastTick = lastApplyTickMap.getOrDefault(player.getUUID(), 0); + if(Math.max(tickCount - sendEvent.getCooldownTick(), 0) >= lastTick) { + lastApplyTickMap.put(player.getUUID(), tickCount); + } else return ApiBack.COOLDOWN; + } + case ALLOW : { + //Add to cache, done + int expireTick = sendEvent.getValidTick() + tickCount; + applyMap.put(player.getUUID(), new ApplyAnimationRecord(target.getUUID(), expireTick)); + return ApiBack.SUCCESS; + } + default: return ApiBack.UNSUPPORTED; + } + }); + } + + /** + * Send apply to join animation on client side.
+ * It will send network packet and work on server side. + */ + @OnlyIn(Dist.CLIENT) + default ApiBack apply(AbstractClientPlayer target) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + AnimationEvent.Send sendEvent = new AnimationEvent.Send( + LogicalSide.CLIENT, + 0, + 0, + AnimationEvent.Type.APPLY + ); + boolean post = MinecraftForge.EVENT_BUS.post(sendEvent); + Event.Result eventResult = sendEvent.getResult(); + if(post || eventResult == Event.Result.DENY) return ApiBack.BE_CANCELLED; + ModChannel.sendToServer(new ApplyAnimationPacket(target.getUUID())); + return ApiBack.SUCCESS; + }); + } + + /** + * Player accept join apply. + * @param player Acceptor + * @param applier Applier + * @return If accept and riding success + */ + default ApiBack acceptApply(ServerPlayer player, ServerPlayer applier) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + ApplyAnimationRecord record = applyMap.getOrDefault(applier.getUUID(), null); + if (record == null) return ApiBack.UNSUPPORTED; + UUID uuid = player.getUUID(); + if (!record.target().equals(uuid)) return ApiBack.UNSUPPORTED; + + int maxDistance = ModConfigs.Server.applyValidDistance.get(); + AnimationEvent.Accept acceptEvent = new AnimationEvent.Accept(AnimationEvent.Type.APPLY, maxDistance); + boolean post = MinecraftForge.EVENT_BUS.post(acceptEvent); + if(post) return ApiBack.BE_CANCELLED; + Event.Result eventResult = acceptEvent.getResult(); + switch (eventResult) { + case DENY : return ApiBack.BE_CANCELLED; + case DEFAULT : { + //Test if is in valid distance + int validDistance = acceptEvent.getValidDistance(); + if(player.distanceToSqr(applier) > validDistance * validDistance) { + return ApiBack.OUT_RANGE; + } + int tickCount = applier.server.getTickCount(); + if (tickCount > record.expireTick()) { + applyMap.remove(applier.getUUID()); + return ApiBack.OPERATION_EXPIRE; + } + } + case ALLOW : { + //done + ApiBack back = joinAnimation(applier, player, false); + if(back == ApiBack.SUCCESS) applyMap.remove(applier.getUUID()); + return back; + } + default: return ApiBack.UNSUPPORTED; + } + }); + } + + /** + * Send invite on server side. + * @param player Sender + * @param animation Raw animation info + * @param layer Target layer + * @param targets Be invited players + */ + default ApiBack invite(ServerPlayer player, ResourceLocation layer, AnimationData animation, Collection targets) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(!isAnimationLayerPresent(layer) || !isAnimationPresent(animation.getKey())) + return ApiBack.RESOURCE_NOT_FOUND; + int tickCount = player.server.getTickCount(); + + int origin = ModConfigs.Server.inviteValidTime.get() * 20; + int cooldown = ModConfigs.Server.inviteCooldown.get() * 20; + AnimationEvent.Send sendEvent = new AnimationEvent.Send( + LogicalSide.SERVER, + origin, + cooldown, + AnimationEvent.Type.INVITE + ); + boolean post = MinecraftForge.EVENT_BUS.post(sendEvent); + if(post) return ApiBack.BE_CANCELLED; + Event.Result eventResult = sendEvent.getResult(); + switch (eventResult) { + case DENY : return ApiBack.BE_CANCELLED; + case DEFAULT : { + //Test if is not in cooldown + int lastTick = lastInviteTickMap.getOrDefault(player.getUUID(), 0); + if(Math.max(tickCount - sendEvent.getCooldownTick(), 0) >= lastTick) { + lastInviteTickMap.put(player.getUUID(), tickCount); + } else return ApiBack.COOLDOWN; + } + case ALLOW : { + //Add to cache, done + int expireTick = sendEvent.getValidTick() + tickCount; + inviteMap.put(player.getUUID(), new InviteAnimationRecord(layer, animation, expireTick, new ArrayList<>(targets))); + return ApiBack.SUCCESS; + } + default: return ApiBack.UNSUPPORTED; + } + }); + } + + /** + * Send invite on client side.
+ * It will send network packet and work on server side. + * @param animation Raw animation info + * @param layer Target layer + * @param targets Be invited players + * @return If send to server successfully. + */ + @OnlyIn(Dist.CLIENT) + default ApiBack invite(ResourceLocation layer, ResourceLocation animation, AbstractClientPlayer ... targets){ + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(!isAnimationLayerPresent(layer) || !isAnimationPresent(animation)) + return ApiBack.RESOURCE_NOT_FOUND; + KeyframeAnimation keyframeAnimation = PlayerAnimationRegistry.getAnimation(animation); + if(keyframeAnimation == null) return ApiBack.RESOURCE_NOT_FOUND; + Set list = Arrays.stream(targets).map(AbstractClientPlayer::getUUID).collect(Collectors.toSet()); + D data = getAnimation(animation); + if(data == null) return ApiBack.RESOURCE_NOT_FOUND; + AnimationEvent.Send sendEvent = new AnimationEvent.Send( + LogicalSide.CLIENT, + 0, + 0, + AnimationEvent.Type.INVITE + ); + boolean post = MinecraftForge.EVENT_BUS.post(sendEvent); + Event.Result eventResult = sendEvent.getResult(); + if(post || eventResult == Event.Result.DENY) return ApiBack.BE_CANCELLED; + ModChannel.sendToServer(new InviteAnimationPacket(data, layer, list)); + return ApiBack.SUCCESS; + }); + } + + /** + * Player accept invite + * @param player Acceptor + * @param inviter Inviter + * @return If accept and riding success. + */ + default ApiBack acceptInvite(ServerPlayer player, ServerPlayer inviter) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + InviteAnimationRecord record = inviteMap.getOrDefault(inviter.getUUID(), null); + if(record == null) return ApiBack.UNSUPPORTED; + UUID uuid = player.getUUID(); + if (!record.targets().contains(uuid)) return ApiBack.UNSUPPORTED; + + int maxDistance = ModConfigs.Server.inviteValidDistance.get(); + AnimationEvent.Accept acceptEvent = new AnimationEvent.Accept(AnimationEvent.Type.INVITE, maxDistance); + boolean post = MinecraftForge.EVENT_BUS.post(acceptEvent); + if(post) return ApiBack.BE_CANCELLED; + Event.Result eventResult = acceptEvent.getResult(); + switch (eventResult) { + case DENY : return ApiBack.BE_CANCELLED; + case DEFAULT : { + //Test if is in valid distance + int validDistance = acceptEvent.getValidDistance(); + if(player.distanceToSqr(inviter) > validDistance * validDistance) { + return ApiBack.OUT_RANGE; + } + int tickCount = inviter.server.getTickCount(); + if(tickCount >= record.expireTick()) { + inviteMap.remove(inviter.getUUID()); + return ApiBack.OPERATION_EXPIRE; + } + } + case ALLOW : { + //done + playAnimationWithRide(inviter, record.layer(), record.animation(), false); + if(record.targets().isEmpty()) inviteMap.remove(inviter.getUUID()); + ApiBack back = joinAnimation(player, inviter, false); + if(back == ApiBack.SUCCESS) record.targets().remove(uuid); + return back; + } + default: return ApiBack.UNSUPPORTED; + } + }); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/helper/IHelperGetter.java b/src/main/java/com/linearpast/sccore/animation/helper/IHelperGetter.java new file mode 100644 index 0000000..cc5f487 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/helper/IHelperGetter.java @@ -0,0 +1,31 @@ +package com.linearpast.sccore.animation.helper; + +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashSet; +import java.util.Set; + +public interface IHelperGetter { + /** + * Get helper + */ + Set> HELPERS = new LinkedHashSet<>(){{ + add(AnimationHelper.INSTANCE); + add(RawAnimationHelper.INSTANCE); + }}; + + /** + * @see IHelperGetter#filter + */ + @Nullable + default IAnimationHelper getHelper() { + for (IAnimationHelper helper : HELPERS) { + if (filter(helper)) { + return helper; + } + } + return null; + } + + boolean filter(IAnimationHelper helper); +} diff --git a/src/main/java/com/linearpast/sccore/animation/helper/JsonHelper.java b/src/main/java/com/linearpast/sccore/animation/helper/JsonHelper.java new file mode 100644 index 0000000..cc14b87 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/helper/JsonHelper.java @@ -0,0 +1,93 @@ +package com.linearpast.sccore.animation.helper; + +import com.linearpast.sccore.animation.data.GenericAnimationData; +import com.linearpast.sccore.animation.data.util.AnimJson; +import com.linearpast.sccore.animation.data.util.AnimLayerJson; +import com.linearpast.sccore.animation.register.AnimationRegistry; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.storage.LevelResource; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; + +public class JsonHelper { + private final MinecraftServer server; + JsonHelper(MinecraftServer server) { + this.server = server; + } + + public static JsonHelper getHelper(MinecraftServer server) { + return new JsonHelper(server); + } + + /** + * Get animation path + * @return path + */ + private Path getAnimationPath() { + Path dataPackPath = server.getWorldPath(LevelResource.DATAPACK_DIR); + return dataPackPath.resolve("animation"); + } + + /** + * Delete directories + * @throws IOException Exception + */ + public void clearPath() throws IOException { + Path animationPath = getAnimationPath(); + if (!Files.exists(animationPath)) return; + try (var pathStream = Files.walk(animationPath)) { + pathStream.sorted(Comparator.reverseOrder()).forEach(path -> { + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (RuntimeException ignored) {} + } + + /** + * Generate all json from server animation + * @param isLayer If layer + * @param isReset If reset + * @return Generate path + */ + @Nullable + public Path generateJson(boolean isLayer, boolean isReset) { + try { + Path animationPath = getAnimationPath(); + if (!Files.exists(animationPath)) { + try {Files.createDirectories(animationPath);} + catch (IOException e) { throw new RuntimeException(e); } + } + if(isReset) clearPath(); + + if(isLayer) { + return AnimLayerJson.Writer.syntaxImmediately(animationPath); + } else { + for (GenericAnimationData value : AnimationRegistry.getAnimations().values()) { + AnimJson.Writer.stream(animationPath, value).syntax(); + } + } + return animationPath; + } catch (Exception ignored){} + return null; + } + + /** + * Generate example json + * @return Example json path + */ + @Nullable + public Path generateExample() { + try { + Path animationPath = getAnimationPath(); + return AnimJson.Writer.syntaxExample(animationPath); + } catch (Exception ignored) {} + return null; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/helper/RawAnimationHelper.java b/src/main/java/com/linearpast/sccore/animation/helper/RawAnimationHelper.java new file mode 100644 index 0000000..af297ed --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/helper/RawAnimationHelper.java @@ -0,0 +1,169 @@ +package com.linearpast.sccore.animation.helper; + +import com.linearpast.sccore.animation.capability.RawAnimationDataCapability; +import com.linearpast.sccore.animation.data.AnimationData; +import com.linearpast.sccore.animation.data.RawAnimationData; +import com.linearpast.sccore.animation.entity.RawAnimationRideEntity; +import com.linearpast.sccore.animation.event.create.AnimationRegisterEvent; +import com.linearpast.sccore.animation.register.RawAnimationRegistry; +import com.linearpast.sccore.animation.utils.AnimationUtils; +import com.linearpast.sccore.animation.utils.ApiBack; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.fml.loading.FMLEnvironment; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public class RawAnimationHelper implements IAnimationHelper { + public static final RawAnimationHelper INSTANCE = new RawAnimationHelper(); + + /** + * Trigger raw animation registry.
+ * It will clear all have been registered raw animation, then trigger register event.
+ * If you need dynamic register, see {@link RawAnimationRegistry#register}, but it will reset when registry call register event.
+ * If you need static register, you can add listener to {@link AnimationRegisterEvent.RawAnimation} + */ + @OnlyIn(Dist.CLIENT) + public void triggerRegistry() { + ANIMATION_RUNNER.testLoadedAndRun(RawAnimationRegistry::triggerRegistry); + } + + @Override + public @Nullable RawAnimationData getAnimation(ResourceLocation location) { + if(FMLEnvironment.dist == Dist.CLIENT) { + return RawAnimationRegistry.getAnimations().getOrDefault(location, null); + } else { + return null; + } + } + + @Override + public @Nullable RawAnimationData getAnimation(CompoundTag tag) { + return new RawAnimationData(){{deserializeNBT(tag);}}; + } + + @Override + public @Nullable RawAnimationDataCapability getCapability(Player player) { + return RawAnimationDataCapability.getCapability(player).orElse(null); + } + + @Override + public void clearAnimations(ServerPlayer serverPlayer) { + ANIMATION_RUNNER.testLoadedAndRun(() -> { + Optional.ofNullable(getCapability(serverPlayer)).ifPresent(RawAnimationDataCapability::clearAnimations); + detachAnimation(serverPlayer); + }); + } + + @Override + public boolean isAnimationPresent(ResourceLocation location) { + return true; + } + + @Override + public ApiBack detachAnimation(ServerPlayer player) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(player.getVehicle() instanceof RawAnimationRideEntity) { + player.stopRiding(); + return ApiBack.SUCCESS; + } + return ApiBack.UNSUPPORTED; + }); + } + + @Override + public ApiBack joinAnimationServer(ServerPlayer player, ServerPlayer target, boolean force) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + Entity vehicle = target.getVehicle(); + if(vehicle instanceof RawAnimationRideEntity) { + boolean result = player.startRiding(vehicle, force); + return result ? ApiBack.SUCCESS : ApiBack.FAIL; + } + return ApiBack.UNSUPPORTED; + }); + } + + @Override + @OnlyIn(Dist.CLIENT) + public void refreshAnimation(AbstractClientPlayer clientPlayer) { + ANIMATION_RUNNER.testLoadedAndRun(() -> { + RawAnimationDataCapability data = getCapability(clientPlayer); + if(data == null) return; + Set oldLayers = new HashSet<>(data.getAnimations().keySet()); + for (ResourceLocation layer : Set.copyOf(oldLayers)) { + if (AnimationUtils.isClientAnimationStop(clientPlayer, layer)) { + removeAnimation(clientPlayer, layer); + } + } + }); + } + + @Override + public @Nullable ResourceLocation getAnimationPlaying(Player player, @Nullable ResourceLocation layer) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + RawAnimationDataCapability data = getCapability(player); + if(data == null) return null; + if(layer == null){ + for (ResourceLocation value : data.getAnimations().values()) { + if(value != null) return value; + } + } else if (isAnimationLayerPresent(layer)) { + if(data.isAnimationPresent(layer)){ + return data.getAnimation(layer); + } + } + return null; + }); + } + + @Override + public ApiBack removeAnimation(@NotNull ServerPlayer serverPlayer, ResourceLocation layer) { + boolean result = ANIMATION_RUNNER.testLoadedAndCall(() -> Optional.ofNullable(getCapability(serverPlayer)) + .map(data -> data.removeAnimation(layer)).orElse(false)); + return result ? ApiBack.SUCCESS : ApiBack.FAIL; + } + + @Override + public ApiBack playAnimationWithRide(@NotNull ServerPlayer player, ResourceLocation layer, AnimationData animation, boolean force) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + if(!isAnimationLayerPresent(layer)) + return ApiBack.RESOURCE_NOT_FOUND; + if(animation.getRide() == null) + return ApiBack.RESOURCE_NOT_FOUND; + if(player instanceof FakePlayer) + return ApiBack.UNSUPPORTED; + boolean flag = player.getVehicle() != null; + if(flag && force) player.unRide(); + else if(flag) return ApiBack.UNSUPPORTED; + if(!(animation instanceof RawAnimationData data)) + return ApiBack.UNSUPPORTED; + RawAnimationRideEntity rawAnimationRideEntity = RawAnimationRideEntity.create(player, layer, data); + boolean result = rawAnimationRideEntity != null; + return result ? ApiBack.SUCCESS : ApiBack.FAIL; + }); + } + + @Override + public ApiBack playAnimationServer(@NotNull ServerPlayer player, ResourceLocation layer, AnimationData animation) { + return ANIMATION_RUNNER.testLoadedAndCall(() -> { + ResourceLocation key = animation.getKey(); + if(!isAnimationLayerPresent(layer) || !isAnimationPresent(key)) + return ApiBack.RESOURCE_NOT_FOUND; + if(player instanceof FakePlayer) return ApiBack.UNSUPPORTED; + Boolean flag = Optional.ofNullable(getCapability(player)).map(data -> + data.mergeAnimation(layer, key)).orElse(false); + return flag ? ApiBack.SUCCESS : ApiBack.FAIL; + }); + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/HelperGetterPacket.java b/src/main/java/com/linearpast/sccore/animation/network/HelperGetterPacket.java new file mode 100644 index 0000000..0e4619e --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/HelperGetterPacket.java @@ -0,0 +1,47 @@ +package com.linearpast.sccore.animation.network; + +import com.linearpast.sccore.animation.helper.AnimationHelper; +import com.linearpast.sccore.animation.helper.IAnimationHelper; +import com.linearpast.sccore.animation.helper.IHelperGetter; +import com.linearpast.sccore.animation.helper.RawAnimationHelper; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +public abstract class HelperGetterPacket implements IHelperGetter { + /** + * Override it to filter helper in network packet + * @param helper helper + * @return Is right helper + */ + public boolean filter(IAnimationHelper helper) { + if(helper instanceof AnimationHelper animationHelper) { + ResourceLocation animation = getAnimation(); + if(animation != null) return animationHelper.isAnimationPresent(animation); + } else if (helper instanceof RawAnimationHelper rawHelper) { + CompoundTag tag = getAnimationTag(); + if(tag != null) return rawHelper.getAnimation(tag) != null; + } + return false; + } + + /** + * Selectable to override it + * @return Animation loacation + * @see HelperGetterPacket#filter + */ + @Nullable + protected ResourceLocation getAnimation() { + return null; + } + + /** + * Selectable to override it + * @return Animation data + * @see HelperGetterPacket#filter + */ + @Nullable + protected CompoundTag getAnimationTag() { + return null; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationCapabilityPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationCapabilityPacket.java index 77fe2f2..19feec8 100644 --- a/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationCapabilityPacket.java +++ b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationCapabilityPacket.java @@ -1,8 +1,9 @@ package com.linearpast.sccore.animation.network.toclient; -import com.linearpast.sccore.animation.AnimationPlayer; import com.linearpast.sccore.animation.capability.AnimationDataCapability; import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; +import com.linearpast.sccore.animation.utils.AnimationUtils; +import com.linearpast.sccore.capability.data.ICapabilitySync; import com.linearpast.sccore.capability.data.player.SimplePlayerCapabilitySync; import com.linearpast.sccore.capability.network.SimpleCapabilityPacket; import net.minecraft.client.Minecraft; @@ -13,15 +14,14 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; -import org.jetbrains.annotations.Nullable; import java.util.HashSet; import java.util.Objects; import java.util.Set; public class AnimationCapabilityPacket extends SimpleCapabilityPacket { - public AnimationCapabilityPacket(CompoundTag data) { - super(data); + public AnimationCapabilityPacket(ICapabilitySync packet) { + super(packet); } public AnimationCapabilityPacket(FriendlyByteBuf buf) { @@ -36,18 +36,14 @@ public class AnimationCapabilityPacket extends SimpleCapabilityPacket { if (level == null) return; CompoundTag nbt = getData(); Player player = level.getPlayerByUUID(nbt.getUUID(SimplePlayerCapabilitySync.OwnerUUID)); + if(player == null) return; try { - IAnimationCapability data = getCapability(player); + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); testPlayAnimations((AbstractClientPlayer) player, nbt, data); syncData(nbt, data); }catch (Exception ignored) {} } - @Override - public @Nullable IAnimationCapability getCapability(Player player) { - return AnimationDataCapability.getCapability(player).orElse(null); - } - private void testPlayAnimations(AbstractClientPlayer player, CompoundTag tag, IAnimationCapability data) { if(data == null) return; ResourceLocation oldRiderAnimLayer = data.getRiderAnimLayer(); @@ -56,8 +52,8 @@ public class AnimationCapabilityPacket extends SimpleCapabilityPacket { if(!Objects.equals(oldRiderAnimLayer, newRiderAnimLayer)) { String riderAnimationString = tag.getString(AnimationDataCapability.RideAnimation); ResourceLocation newRiderAnimation = riderAnimationString.isEmpty() ? null : new ResourceLocation(riderAnimationString); - if(oldRiderAnimLayer != null) AnimationPlayer.playAnimation(player, oldRiderAnimLayer, null); - if(newRiderAnimLayer != null) AnimationPlayer.playAnimation(player, newRiderAnimLayer, newRiderAnimation); + if(oldRiderAnimLayer != null) AnimationUtils.playAnimation(player, oldRiderAnimLayer, null); + if(newRiderAnimLayer != null) AnimationUtils.playAnimation(player, newRiderAnimLayer, newRiderAnimation); } Set oldLayerSet = new HashSet<>(data.getAnimations().keySet()); @@ -68,15 +64,12 @@ public class AnimationCapabilityPacket extends SimpleCapabilityPacket { ResourceLocation newAnimLocation = newAnimString.isEmpty() ? null : new ResourceLocation(newAnimString); ResourceLocation oldAnimLocation = data.getAnimation(newLayerLocation); if (!Objects.equals(newAnimLocation, oldAnimLocation)) { - AnimationPlayer.playAnimation(player, newLayerLocation, newAnimLocation); + AnimationUtils.playAnimation(player, newLayerLocation, newAnimLocation); } oldLayerSet.remove(newLayerLocation); } for (ResourceLocation oldLayerLocation : oldLayerSet) { - AnimationPlayer.playAnimation(player, oldLayerLocation, null); + AnimationUtils.playAnimation(player, oldLayerLocation, null); } - - - } } diff --git a/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationJsonPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationJsonPacket.java index 5759adb..45dbdfe 100644 --- a/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationJsonPacket.java +++ b/src/main/java/com/linearpast/sccore/animation/network/toclient/AnimationJsonPacket.java @@ -2,7 +2,7 @@ package com.linearpast.sccore.animation.network.toclient; import com.google.gson.JsonElement; import com.google.gson.JsonParser; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.GenericAnimationData; import com.linearpast.sccore.animation.data.util.AnimJson; import com.linearpast.sccore.animation.data.util.AnimLayerJson; import com.linearpast.sccore.animation.register.AnimationRegistry; @@ -31,7 +31,7 @@ public record AnimationJsonPacket(String json, boolean isLayer) { Map parse = AnimLayerJson.Reader.stream(element).parse(); parse.forEach(AnimationRegistry.ClientCache::cacheAddAnimationLayer); } else { - Animation animation = AnimJson.Reader.stream(element).parse(); + GenericAnimationData animation = AnimJson.Reader.stream(element).parse(); AnimationRegistry.ClientCache.cacheAddAnimation(animation.getKey(), animation); } }); diff --git a/src/main/java/com/linearpast/sccore/animation/network/toclient/RawAnimationCapabilityPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toclient/RawAnimationCapabilityPacket.java new file mode 100644 index 0000000..0a75e1f --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toclient/RawAnimationCapabilityPacket.java @@ -0,0 +1,74 @@ +package com.linearpast.sccore.animation.network.toclient; + +import com.linearpast.sccore.animation.capability.RawAnimationDataCapability; +import com.linearpast.sccore.animation.utils.AnimationUtils; +import com.linearpast.sccore.capability.data.ICapabilitySync; +import com.linearpast.sccore.capability.data.player.SimplePlayerCapabilitySync; +import com.linearpast.sccore.capability.network.SimpleCapabilityPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.network.NetworkEvent; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class RawAnimationCapabilityPacket extends SimpleCapabilityPacket { + public RawAnimationCapabilityPacket(ICapabilitySync packet) { + super(packet); + } + + public RawAnimationCapabilityPacket(FriendlyByteBuf buf) { + super(buf); + } + + @Override + public void handler(NetworkEvent.Context context) { + context.setPacketHandled(true); + Minecraft instance = Minecraft.getInstance(); + ClientLevel level = instance.level; + if (level == null) return; + CompoundTag nbt = getData(); + Player player = level.getPlayerByUUID(nbt.getUUID(SimplePlayerCapabilitySync.OwnerUUID)); + if(player == null) return; + try { + RawAnimationDataCapability data = RawAnimationDataCapability.getCapability(player).orElse(null); + testPlayAnimations((AbstractClientPlayer) player, nbt, data); + syncData(nbt, data); + }catch (Exception ignored) {} + } + + private void testPlayAnimations(AbstractClientPlayer player, CompoundTag tag, RawAnimationDataCapability data) { + if(data == null) return; + ResourceLocation oldRiderAnimLayer = data.getRiderAnimLayer(); + String riderAnimLayerString = tag.getString(RawAnimationDataCapability.RideAnimLayer); + ResourceLocation newRiderAnimLayer = riderAnimLayerString.isEmpty() ? null : new ResourceLocation(riderAnimLayerString); + if(!Objects.equals(oldRiderAnimLayer, newRiderAnimLayer)) { + String riderAnimationString = tag.getString(RawAnimationDataCapability.RideAnimation); + ResourceLocation newRiderAnimation = riderAnimationString.isEmpty() ? null : new ResourceLocation(riderAnimationString); + if(oldRiderAnimLayer != null) AnimationUtils.playAnimation(player, oldRiderAnimLayer, null); + if(newRiderAnimLayer != null) AnimationUtils.playAnimation(player, newRiderAnimLayer, newRiderAnimation); + } + + Set oldLayerSet = new HashSet<>(data.getAnimations().keySet()); + CompoundTag animMap = tag.getCompound(RawAnimationDataCapability.AnimMap); + for (String newLayerString : animMap.getAllKeys()) { + ResourceLocation newLayerLocation = new ResourceLocation(newLayerString); + String newAnimString = animMap.getString(newLayerString); + ResourceLocation newAnimLocation = newAnimString.isEmpty() ? null : new ResourceLocation(newAnimString); + ResourceLocation oldAnimLocation = data.getAnimation(newLayerLocation); + if (!Objects.equals(newAnimLocation, oldAnimLocation)) { + AnimationUtils.playAnimation(player, newLayerLocation, newAnimLocation); + } + oldLayerSet.remove(newLayerLocation); + } + for (ResourceLocation oldLayerLocation : oldLayerSet) { + AnimationUtils.playAnimation(player, oldLayerLocation, null); + } + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toclient/SyncAnimationPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toclient/SyncAnimationPacket.java index 5245a0d..7370ba8 100644 --- a/src/main/java/com/linearpast/sccore/animation/network/toclient/SyncAnimationPacket.java +++ b/src/main/java/com/linearpast/sccore/animation/network/toclient/SyncAnimationPacket.java @@ -1,7 +1,7 @@ package com.linearpast.sccore.animation.network.toclient; -import com.linearpast.sccore.animation.AnimationPlayer; -import com.linearpast.sccore.animation.event.client.ClientPlayerTick; +import com.linearpast.sccore.animation.event.client.ClientPlayerEvent; +import com.linearpast.sccore.animation.utils.AnimationUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.player.AbstractClientPlayer; @@ -39,8 +39,8 @@ public class SyncAnimationPacket { if(level == null) return; AbstractClientPlayer player = (AbstractClientPlayer) level.getPlayerByUUID(playerUUID); AbstractClientPlayer target = (AbstractClientPlayer) level.getPlayerByUUID(targetUUID); - ClientPlayerTick.runs.put( - () -> AnimationPlayer.syncAnimation(player, target), + ClientPlayerEvent.runs.put( + () -> AnimationUtils.syncAnimation(player, target), new AbstractMap.SimpleEntry<>(5, 0) ); diff --git a/src/main/java/com/linearpast/sccore/animation/network/toserver/ApplyAnimationPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toserver/ApplyAnimationPacket.java new file mode 100644 index 0000000..135b2c9 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toserver/ApplyAnimationPacket.java @@ -0,0 +1,48 @@ +package com.linearpast.sccore.animation.network.toserver; + +import com.linearpast.sccore.animation.helper.IAnimationHelper; +import com.linearpast.sccore.animation.network.HelperGetterPacket; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.UUID; +import java.util.function.Supplier; + +public class ApplyAnimationPacket extends HelperGetterPacket { + private final UUID targetUUID; + + public ApplyAnimationPacket(UUID targetUUID) { + this.targetUUID = targetUUID; + } + + public ApplyAnimationPacket(FriendlyByteBuf buf) { + this.targetUUID = buf.readUUID(); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeUUID(targetUUID); + } + + public void handle(Supplier supplier) { + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + context.setPacketHandled(true); + ServerPlayer target; + ServerPlayer sender = context.getSender(); + if(sender == null || sender.getServer() == null) return; + if(this.targetUUID == null) target = sender; + else target = sender.getServer().getPlayerList().getPlayer(this.targetUUID); + if(target == null) return; + IAnimationHelper helper = getHelper(); + if(helper == null) return; + if(target == sender) return; + helper.apply(sender, target); + }); + } + + @Override + public boolean filter(IAnimationHelper helper) { + return true; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toserver/InviteAnimationPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toserver/InviteAnimationPacket.java new file mode 100644 index 0000000..2de2070 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toserver/InviteAnimationPacket.java @@ -0,0 +1,68 @@ +package com.linearpast.sccore.animation.network.toserver; + +import com.linearpast.sccore.animation.data.AnimationData; +import com.linearpast.sccore.animation.helper.IAnimationHelper; +import com.linearpast.sccore.animation.network.HelperGetterPacket; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.UUID; +import java.util.function.Supplier; + +public class InviteAnimationPacket extends HelperGetterPacket { + private final @Nullable CompoundTag animationTag; + private final ResourceLocation layer; + private final ResourceLocation animation; + private final Collection targets; + + public InviteAnimationPacket(AnimationData data, ResourceLocation layer, Collection targets) { + this.animationTag = data.serializeNBT(); + this.animation = data.getKey(); + this.layer = layer; + this.targets = targets; + } + + public InviteAnimationPacket(FriendlyByteBuf buf) { + this.animationTag = buf.readNullable(FriendlyByteBuf::readAnySizeNbt); + this.layer = buf.readResourceLocation(); + this.animation = buf.readResourceLocation(); + this.targets = buf.readList(FriendlyByteBuf::readUUID); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeNullable(animationTag, FriendlyByteBuf::writeNbt); + buf.writeResourceLocation(layer); + buf.writeResourceLocation(animation); + buf.writeCollection(targets, FriendlyByteBuf::writeUUID); + } + + public void handle(Supplier supplier) { + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + context.setPacketHandled(true); + ServerPlayer sender = context.getSender(); + if(sender == null) return; + IAnimationHelper helper = getHelper(); + if(helper == null) return; + AnimationData data = helper.getAnimation(animationTag); + if(data == null) return; + if(!targets.isEmpty()) helper.invite(sender, layer, data, targets); + else helper.playAnimationWithRide(sender, layer, data, false); + }); + } + + @Override + public @Nullable CompoundTag getAnimationTag() { + return animationTag; + } + + @Override + public @Nullable ResourceLocation getAnimation() { + return animation; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationPacket.java new file mode 100644 index 0000000..34a432a --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationPacket.java @@ -0,0 +1,71 @@ +package com.linearpast.sccore.animation.network.toserver; + +import com.linearpast.sccore.animation.data.AnimationData; +import com.linearpast.sccore.animation.helper.IAnimationHelper; +import com.linearpast.sccore.animation.network.HelperGetterPacket; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; +import java.util.function.Supplier; + +public class PlayAnimationPacket extends HelperGetterPacket { + private final @Nullable CompoundTag animationTag; + private final ResourceLocation layer; + private final ResourceLocation animation; + private final @Nullable UUID uuid; + + public PlayAnimationPacket(AnimationData data, ResourceLocation layer, ResourceLocation animation, @Nullable UUID uuid) { + this.animationTag = data.serializeNBT(); + this.layer = layer; + this.animation = animation; + this.uuid = uuid; + } + + public PlayAnimationPacket(FriendlyByteBuf buf){ + this.animationTag = buf.readNullable(FriendlyByteBuf::readAnySizeNbt); + this.layer = buf.readResourceLocation(); + this.animation = buf.readResourceLocation(); + this.uuid = buf.readNullable(FriendlyByteBuf::readUUID); + } + + public void encode(FriendlyByteBuf buf){ + buf.writeNullable(animationTag, FriendlyByteBuf::writeNbt); + buf.writeResourceLocation(layer); + buf.writeResourceLocation(animation); + buf.writeNullable(uuid, FriendlyByteBuf::writeUUID); + } + + public void handle(Supplier supplier){ + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + context.setPacketHandled(true); + ServerPlayer target; + ServerPlayer sender = context.getSender(); + if(sender == null || sender.getServer() == null) return; + if(this.uuid == null) target = sender; + else target = sender.getServer().getPlayerList().getPlayer(this.uuid); + if(target == null) return; + IAnimationHelper helper = getHelper(); + if(helper == null) return; + AnimationData data = helper.getAnimation(animationTag); + if(data == null) return; + if(target == sender) helper.playAnimation(target, layer, data); + else helper.request(sender, target, layer, data, false); + }); + } + + @Override + public @Nullable CompoundTag getAnimationTag() { + return animationTag; + } + + @Override + public @Nullable ResourceLocation getAnimation() { + return animation; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRequestPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRequestPacket.java deleted file mode 100644 index ad66db3..0000000 --- a/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRequestPacket.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.linearpast.sccore.animation.network.toserver; - -import com.linearpast.sccore.animation.AnimationPlayer; -import com.linearpast.sccore.animation.register.AnimationRegistry; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.network.NetworkEvent; -import org.jetbrains.annotations.Nullable; - -import java.util.UUID; -import java.util.function.Supplier; - -public class PlayAnimationRequestPacket { - private final ResourceLocation layer; - private @Nullable ResourceLocation animation; - private @Nullable UUID uuid; - public PlayAnimationRequestPacket(@Nullable UUID uuid, ResourceLocation layer, @Nullable ResourceLocation animation) { - this.layer = layer; - this.animation = animation; - this.uuid = uuid; - } - public PlayAnimationRequestPacket(FriendlyByteBuf buf){ - this.layer = buf.readResourceLocation(); - try { - this.animation = buf.readResourceLocation(); - this.uuid = buf.readUUID(); - } catch (Exception e) { - this.animation = null; - this.uuid = null; - } - } - public void encode(FriendlyByteBuf buf){ - buf.writeResourceLocation(layer); - if(animation != null) buf.writeResourceLocation(animation); - if(uuid != null) buf.writeUUID(uuid); - } - - public void handle(Supplier supplier){ - NetworkEvent.Context context = supplier.get(); - context.enqueueWork(() -> { - context.setPacketHandled(true); - if (AnimationRegistry.getLayers().containsKey(layer)) { - ServerPlayer target; - ServerPlayer sender = context.getSender(); - if(sender == null || sender.getServer() == null) return; - if(this.uuid == null) target = sender; - else target = sender.getServer().getPlayerList().getPlayer(this.uuid); - AnimationPlayer.serverPlayAnimation(target, layer, animation); - } - }); - } -} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRidePacket.java b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRidePacket.java index c3fd8c4..4a900fb 100644 --- a/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRidePacket.java +++ b/src/main/java/com/linearpast/sccore/animation/network/toserver/PlayAnimationRidePacket.java @@ -1,54 +1,75 @@ package com.linearpast.sccore.animation.network.toserver; -import com.linearpast.sccore.animation.AnimationPlayer; -import com.linearpast.sccore.animation.register.AnimationRegistry; +import com.linearpast.sccore.animation.data.AnimationData; +import com.linearpast.sccore.animation.helper.IAnimationHelper; +import com.linearpast.sccore.animation.network.HelperGetterPacket; +import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkEvent; import org.jetbrains.annotations.Nullable; +import java.util.UUID; import java.util.function.Supplier; -public class PlayAnimationRidePacket { +public class PlayAnimationRidePacket extends HelperGetterPacket { + private final @Nullable CompoundTag animationTag; private final ResourceLocation layer; - private @Nullable ResourceLocation animation; + private final ResourceLocation animation; + private final @Nullable UUID uuid; private final boolean force; - public PlayAnimationRidePacket(ResourceLocation layer, @Nullable ResourceLocation animation, boolean force) { + + public PlayAnimationRidePacket(AnimationData data, ResourceLocation layer, ResourceLocation animation, @Nullable UUID uuid, boolean force) { + this.animationTag = data.serializeNBT(); this.layer = layer; this.animation = animation; + this.uuid = uuid; this.force = force; } public PlayAnimationRidePacket(FriendlyByteBuf buf) { + this.animationTag = buf.readNullable(FriendlyByteBuf::readAnySizeNbt); this.layer = buf.readResourceLocation(); + this.animation = buf.readResourceLocation(); + this.uuid = buf.readNullable(FriendlyByteBuf::readUUID); this.force = buf.readBoolean(); - try { - this.animation = buf.readResourceLocation(); - } catch (Exception e) { - this.animation = null; - } - } public void encode(FriendlyByteBuf buf) { + buf.writeNullable(animationTag, FriendlyByteBuf::writeNbt); buf.writeResourceLocation(layer); + buf.writeResourceLocation(animation); + buf.writeNullable(uuid, FriendlyByteBuf::writeUUID); buf.writeBoolean(force); - if(animation != null) { - buf.writeResourceLocation(animation); - } } public void handle(Supplier supplier) { NetworkEvent.Context context = supplier.get(); context.enqueueWork(() -> { context.setPacketHandled(true); - if (AnimationRegistry.getLayers().containsKey(layer)) { - ServerPlayer sender = context.getSender(); - if(sender == null) return; - AnimationPlayer.playAnimationWithRide(sender, layer, animation, force); - } + ServerPlayer target; + ServerPlayer sender = context.getSender(); + if(sender == null || sender.getServer() == null) return; + if(this.uuid == null) target = sender; + else target = sender.getServer().getPlayerList().getPlayer(this.uuid); + if(target == null) return; + IAnimationHelper helper = getHelper(); + if(helper == null) return; + AnimationData data = helper.getAnimation(animationTag); + if(data == null) return; + if(target == sender) helper.playAnimationWithRide(target, layer, data, force); + else helper.request(sender, target, layer, data, true); }); } + @Override + public @Nullable CompoundTag getAnimationTag() { + return animationTag; + } + + @Override + public @Nullable ResourceLocation getAnimation() { + return animation; + } } diff --git a/src/main/java/com/linearpast/sccore/animation/network/toserver/RefreshAnimationPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toserver/RefreshAnimationPacket.java deleted file mode 100644 index ca7811d..0000000 --- a/src/main/java/com/linearpast/sccore/animation/network/toserver/RefreshAnimationPacket.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.linearpast.sccore.animation.network.toserver; - -import com.linearpast.sccore.animation.capability.AnimationDataCapability; -import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.network.NetworkEvent; - -import java.util.HashSet; -import java.util.Set; -import java.util.function.Supplier; - -public class RefreshAnimationPacket { - private final Set needRemoves; - public RefreshAnimationPacket(Set needRemoves) { - this.needRemoves = needRemoves; - } - public RefreshAnimationPacket(FriendlyByteBuf buf) { - int i = buf.readInt(); - needRemoves = new HashSet<>(); - for (int j = 0; j < i; ++j) { - needRemoves.add(buf.readResourceLocation()); - } - } - - public void encode(FriendlyByteBuf buf) { - buf.writeInt(needRemoves.size()); - for (ResourceLocation needRemove : needRemoves) { - buf.writeResourceLocation(needRemove); - } - } - - public void handle(Supplier supplier) { - NetworkEvent.Context context = supplier.get(); - context.enqueueWork(() -> { - context.setPacketHandled(true); - ServerPlayer sender = context.getSender(); - if(sender == null) return; - IAnimationCapability data = AnimationDataCapability.getCapability(sender).orElse(null); - if(data == null) return; - needRemoves.forEach(data::removeAnimation); - }); - } -} diff --git a/src/main/java/com/linearpast/sccore/animation/network/toserver/RequestAnimationPacket.java b/src/main/java/com/linearpast/sccore/animation/network/toserver/RequestAnimationPacket.java new file mode 100644 index 0000000..c9d6dfc --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/network/toserver/RequestAnimationPacket.java @@ -0,0 +1,71 @@ +package com.linearpast.sccore.animation.network.toserver; + +import com.linearpast.sccore.animation.data.AnimationData; +import com.linearpast.sccore.animation.helper.IAnimationHelper; +import com.linearpast.sccore.animation.network.HelperGetterPacket; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; +import java.util.function.Supplier; + +public class RequestAnimationPacket extends HelperGetterPacket { + private final @Nullable CompoundTag animationTag; + private final ResourceLocation layer; + private final ResourceLocation animation; + private final UUID targetUUID; + + public RequestAnimationPacket(AnimationData data, ResourceLocation layer, UUID targetUUID) { + this.animationTag = data.serializeNBT(); + this.animation = data.getKey(); + this.layer = layer; + this.targetUUID = targetUUID; + } + + public RequestAnimationPacket(FriendlyByteBuf buf) { + this.animationTag = buf.readNullable(FriendlyByteBuf::readAnySizeNbt); + this.layer = buf.readResourceLocation(); + this.animation = buf.readResourceLocation(); + this.targetUUID = buf.readUUID(); + } + + public void encode(FriendlyByteBuf buf) { + buf.writeNullable(animationTag, FriendlyByteBuf::writeNbt); + buf.writeResourceLocation(layer); + buf.writeResourceLocation(animation); + buf.writeUUID(targetUUID); + } + + public void handle(Supplier supplier) { + NetworkEvent.Context context = supplier.get(); + context.enqueueWork(() -> { + context.setPacketHandled(true); + ServerPlayer target; + ServerPlayer sender = context.getSender(); + if(sender == null || sender.getServer() == null) return; + if(this.targetUUID == null) target = sender; + else target = sender.getServer().getPlayerList().getPlayer(this.targetUUID); + if(target == null) return; + IAnimationHelper helper = getHelper(); + if(helper == null) return; + AnimationData data = helper.getAnimation(animationTag); + if(data == null) return; + if(target == sender) return; + helper.request(sender, target, layer, data, false); + }); + } + + @Override + public @Nullable CompoundTag getAnimationTag() { + return animationTag; + } + + @Override + public @Nullable ResourceLocation getAnimation() { + return animation; + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/register/AnimationCapabilities.java b/src/main/java/com/linearpast/sccore/animation/register/AnimationCapabilities.java index 77ea2fb..1f1ba40 100644 --- a/src/main/java/com/linearpast/sccore/animation/register/AnimationCapabilities.java +++ b/src/main/java/com/linearpast/sccore/animation/register/AnimationCapabilities.java @@ -1,8 +1,10 @@ package com.linearpast.sccore.animation.register; import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.RawAnimationDataCapability; import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; import com.linearpast.sccore.animation.network.toclient.AnimationCapabilityPacket; +import com.linearpast.sccore.animation.network.toclient.RawAnimationCapabilityPacket; import com.linearpast.sccore.capability.CapabilityUtils; import com.linearpast.sccore.capability.data.player.PlayerCapabilityRegistry; import com.linearpast.sccore.capability.network.CapabilityChannel; @@ -27,5 +29,19 @@ public class AnimationCapabilities { AnimationCapabilityPacket::encode, AnimationCapabilityPacket::handle ); + CapabilityUtils.registerPlayerCapabilityWithNetwork( + RawAnimationDataCapability.key, + new PlayerCapabilityRegistry.CapabilityRecord<>( + RawAnimationDataCapability.class, + CapabilityManager.get(new CapabilityToken<>() {}), + RawAnimationDataCapability.class + ), + channel, + ModChannel.getAndAddCid(), + RawAnimationCapabilityPacket.class, + RawAnimationCapabilityPacket::new, + RawAnimationCapabilityPacket::encode, + RawAnimationCapabilityPacket::handle + ); } } diff --git a/src/main/java/com/linearpast/sccore/animation/register/AnimationChannels.java b/src/main/java/com/linearpast/sccore/animation/register/AnimationChannels.java index 4af2853..0d85138 100644 --- a/src/main/java/com/linearpast/sccore/animation/register/AnimationChannels.java +++ b/src/main/java/com/linearpast/sccore/animation/register/AnimationChannels.java @@ -3,44 +3,57 @@ package com.linearpast.sccore.animation.register; import com.linearpast.sccore.animation.network.toclient.AnimationClientStatusPacket; import com.linearpast.sccore.animation.network.toclient.AnimationJsonPacket; import com.linearpast.sccore.animation.network.toclient.SyncAnimationPacket; -import com.linearpast.sccore.animation.network.toserver.PlayAnimationRequestPacket; -import com.linearpast.sccore.animation.network.toserver.PlayAnimationRidePacket; -import com.linearpast.sccore.animation.network.toserver.RefreshAnimationPacket; +import com.linearpast.sccore.animation.network.toserver.*; import com.linearpast.sccore.core.ModChannel; import net.minecraftforge.network.NetworkDirection; public class AnimationChannels { public static void registerChannel() { - ModChannel.INSTANCE.messageBuilder(SyncAnimationPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_CLIENT) + ModChannel.INSTANCE.messageBuilder(SyncAnimationPacket.class, cid(), NetworkDirection.PLAY_TO_CLIENT) .decoder(SyncAnimationPacket::new) .encoder(SyncAnimationPacket::encode) .consumerMainThread(SyncAnimationPacket::handle) .add(); - ModChannel.INSTANCE.messageBuilder(AnimationJsonPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_CLIENT) + ModChannel.INSTANCE.messageBuilder(AnimationJsonPacket.class, cid(), NetworkDirection.PLAY_TO_CLIENT) .decoder(AnimationJsonPacket::new) .encoder(AnimationJsonPacket::encode) .consumerMainThread(AnimationJsonPacket::handle) .add(); - ModChannel.INSTANCE.messageBuilder(AnimationClientStatusPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_CLIENT) + ModChannel.INSTANCE.messageBuilder(AnimationClientStatusPacket.class, cid(), NetworkDirection.PLAY_TO_CLIENT) .decoder(AnimationClientStatusPacket::new) .encoder(AnimationClientStatusPacket::encode) .consumerMainThread(AnimationClientStatusPacket::handle) .add(); - ModChannel.INSTANCE.messageBuilder(PlayAnimationRequestPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_SERVER) - .decoder(PlayAnimationRequestPacket::new) - .encoder(PlayAnimationRequestPacket::encode) - .consumerMainThread(PlayAnimationRequestPacket::handle) + //To server + ModChannel.INSTANCE.messageBuilder(PlayAnimationPacket.class, cid(), NetworkDirection.PLAY_TO_SERVER) + .decoder(PlayAnimationPacket::new) + .encoder(PlayAnimationPacket::encode) + .consumerMainThread(PlayAnimationPacket::handle) .add(); - ModChannel.INSTANCE.messageBuilder(PlayAnimationRidePacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_SERVER) + ModChannel.INSTANCE.messageBuilder(PlayAnimationRidePacket.class, cid(), NetworkDirection.PLAY_TO_SERVER) .decoder(PlayAnimationRidePacket::new) .encoder(PlayAnimationRidePacket::encode) .consumerMainThread(PlayAnimationRidePacket::handle) .add(); - ModChannel.INSTANCE.messageBuilder(RefreshAnimationPacket.class, ModChannel.getAndAddCid(), NetworkDirection.PLAY_TO_SERVER) - .decoder(RefreshAnimationPacket::new) - .encoder(RefreshAnimationPacket::encode) - .consumerMainThread(RefreshAnimationPacket::handle) + ModChannel.INSTANCE.messageBuilder(ApplyAnimationPacket.class, cid(), NetworkDirection.PLAY_TO_SERVER) + .decoder(ApplyAnimationPacket::new) + .encoder(ApplyAnimationPacket::encode) + .consumerMainThread(ApplyAnimationPacket::handle) + .add(); + ModChannel.INSTANCE.messageBuilder(InviteAnimationPacket.class, cid(), NetworkDirection.PLAY_TO_SERVER) + .decoder(InviteAnimationPacket::new) + .encoder(InviteAnimationPacket::encode) + .consumerMainThread(InviteAnimationPacket::handle) + .add(); + ModChannel.INSTANCE.messageBuilder(RequestAnimationPacket.class, cid(), NetworkDirection.PLAY_TO_SERVER) + .decoder(RequestAnimationPacket::new) + .encoder(RequestAnimationPacket::encode) + .consumerMainThread(RequestAnimationPacket::handle) .add(); } + + private static int cid() { + return ModChannel.getAndAddCid(); + } } diff --git a/src/main/java/com/linearpast/sccore/animation/register/AnimationCommands.java b/src/main/java/com/linearpast/sccore/animation/register/AnimationCommands.java index d12e649..6ddbfcc 100644 --- a/src/main/java/com/linearpast/sccore/animation/register/AnimationCommands.java +++ b/src/main/java/com/linearpast/sccore/animation/register/AnimationCommands.java @@ -1,11 +1,11 @@ package com.linearpast.sccore.animation.register; -import com.linearpast.sccore.animation.AnimationUtils; import com.linearpast.sccore.animation.command.*; import com.linearpast.sccore.animation.command.argument.AnimationArgument; import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument; -import com.linearpast.sccore.animation.command.client.GenerateJsonClientCommand; -import com.linearpast.sccore.animation.command.client.RefreshAnimCommand; +import com.linearpast.sccore.animation.command.client.ListClientCommand; +import com.linearpast.sccore.animation.command.client.RefreshCommand; +import com.linearpast.sccore.animation.helper.IAnimationHelper; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.synchronization.ArgumentTypeInfo; @@ -17,34 +17,35 @@ import static net.minecraft.commands.Commands.literal; public class AnimationCommands { public static void commonCommandRegister(LiteralArgumentBuilder builder) { - if(AnimationUtils.ANIMATION_RUNNER.isModLoaded()){ + if(IAnimationHelper.ANIMATION_RUNNER.isModLoaded()){ LiteralArgumentBuilder anim = literal("anim"); - PlayAnimCommand.register(anim); - RequestAnimCommand.register(anim); - CombineAnimCommand.register(anim); - GenerateJsonCommand.register(anim); - ApplyJoinAnimCommand.register(anim); + ApplyCommand.register(anim); + InviteCommand.register(anim); + JsonCommand.register(anim); + ListServerCommand.register(anim); + PlayCommand.register(anim); + RequestCommand.register(anim); builder.then(anim); } } public static void clientCommandRegister(LiteralArgumentBuilder builder) { - if(AnimationUtils.ANIMATION_RUNNER.isModLoaded()) { + if(IAnimationHelper.ANIMATION_RUNNER.isModLoaded()) { LiteralArgumentBuilder anim = literal("anim"); - RefreshAnimCommand.register(anim); - GenerateJsonClientCommand.register(anim); + ListClientCommand.register(anim); + RefreshCommand.register(anim); builder.then(anim); } } public static void registerArguments(DeferredRegister> register) { - register.register("animation", + register.register("animations", () -> ArgumentTypeInfos.registerByClass( AnimationArgument.class, SingletonArgumentInfo.contextFree(AnimationArgument::animation) ) ); - register.register("layer", + register.register("layers", () -> ArgumentTypeInfos.registerByClass( AnimationLayerArgument.class, SingletonArgumentInfo.contextFree(AnimationLayerArgument::layer) diff --git a/src/main/java/com/linearpast/sccore/animation/register/AnimationRegistry.java b/src/main/java/com/linearpast/sccore/animation/register/AnimationRegistry.java index 0e0b2ef..c7c54e5 100644 --- a/src/main/java/com/linearpast/sccore/animation/register/AnimationRegistry.java +++ b/src/main/java/com/linearpast/sccore/animation/register/AnimationRegistry.java @@ -3,21 +3,26 @@ package com.linearpast.sccore.animation.register; import com.google.gson.JsonElement; import com.linearpast.sccore.SnowyCrescentCore; import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.RawAnimationDataCapability; import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.GenericAnimationData; +import com.linearpast.sccore.animation.data.RawAnimationData; import com.linearpast.sccore.animation.data.util.AnimJson; import com.linearpast.sccore.animation.data.util.AnimLayerJson; -import com.linearpast.sccore.animation.event.create.AnimationLayerRegisterEvent; import com.linearpast.sccore.animation.event.create.AnimationRegisterEvent; +import com.linearpast.sccore.animation.helper.RawAnimationHelper; import com.linearpast.sccore.animation.mixin.IMixinPlayerAnimationFactoryHolder; import com.linearpast.sccore.animation.network.toclient.AnimationClientStatusPacket; import com.linearpast.sccore.animation.network.toclient.AnimationJsonPacket; import com.linearpast.sccore.core.ModChannel; +import com.linearpast.sccore.utils.ModuleAccess; import dev.kosmx.playerAnim.api.layered.AnimationStack; import dev.kosmx.playerAnim.api.layered.IAnimation; import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer; import dev.kosmx.playerAnim.api.layered.ModifierLayer; +import dev.kosmx.playerAnim.api.layered.modifier.AbstractFadeModifier; import dev.kosmx.playerAnim.core.data.KeyframeAnimation; +import dev.kosmx.playerAnim.core.util.Ease; import dev.kosmx.playerAnim.core.util.Pair; import dev.kosmx.playerAnim.impl.animation.AnimationApplier; import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; @@ -35,6 +40,7 @@ import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; import java.io.IOException; import java.io.InputStream; @@ -53,10 +59,10 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class AnimationRegistry { - private static final Map animations = new HashMap<>(); + private static final Map animations = new HashMap<>(); private static final Map layers = new HashMap<>(); - public static Map getAnimations() { + public static Map getAnimations() { return Map.copyOf(animations); } @@ -65,7 +71,7 @@ public class AnimationRegistry { } @OnlyIn(Dist.CLIENT) - public static void registerAnimations(Map animationMap) { + public static void registerAnimations(Map animationMap) { animations.clear(); animations.putAll(animationMap); } @@ -76,6 +82,7 @@ public class AnimationRegistry { layers.putAll(layerMap); } + @SubscribeEvent public static void onServerStarted(ServerStartedEvent event) { Path dataPackPath = event.getServer().getWorldPath(LevelResource.DATAPACK_DIR); Path animationPath = dataPackPath.resolve("animation"); @@ -109,12 +116,12 @@ public class AnimationRegistry { dataPackPath.resolve("animation"), path -> path.getFileName().toString().equals("animation.layer.json") ); - Set animationsSet = new HashSet<>(); + Set animationsSet = new HashSet<>(); Map layersMap = new HashMap<>(); for (Path path : animPaths) { try { AnimJson.Reader reader = AnimJson.Reader.stream(path); - Animation anim = reader.parse(); + GenericAnimationData anim = reader.parse(); animationsSet.add(anim); } catch (Exception ignored) { SnowyCrescentCore.log.error("Failed to parse animation JSON: {}", path.toString()); @@ -131,20 +138,21 @@ public class AnimationRegistry { } animations.clear(); - AnimationRegisterEvent animationRegisterEvent = new AnimationRegisterEvent(); + AnimationRegisterEvent.Animation animationRegisterEvent = new AnimationRegisterEvent.Animation(); MinecraftForge.EVENT_BUS.post(animationRegisterEvent); - Map animationMap = animationRegisterEvent.getAnimations(); + Map animationMap = animationRegisterEvent.getAnimations(); animations.putAll(animationMap); - animations.putAll(animationsSet.stream().collect(Collectors.toMap(Animation::getKey, animation -> animation))); + animations.putAll(animationsSet.stream().collect(Collectors.toMap(GenericAnimationData::getKey, animation -> animation))); layers.clear(); - AnimationLayerRegisterEvent layerRegisterEvent = new AnimationLayerRegisterEvent(); + AnimationRegisterEvent.Layer layerRegisterEvent = new AnimationRegisterEvent.Layer(); MinecraftForge.EVENT_BUS.post(layerRegisterEvent); Map layerMap = layerRegisterEvent.getLayers(); layers.putAll(layerMap); layers.putAll(layersMap); } + @SubscribeEvent public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { if (event.getEntity() instanceof ServerPlayer serverPlayer) { MinecraftServer server = serverPlayer.getServer(); @@ -156,7 +164,7 @@ public class AnimationRegistry { catch (IOException e) { return; } } ModChannel.sendToPlayer(new AnimationClientStatusPacket(AnimationClientStatusPacket.Status.ANIM_CACHE_CLEAR), serverPlayer); - for (Animation value : animations.values()) { + for (GenericAnimationData value : animations.values()) { JsonElement json = AnimJson.Writer.stream(value).toJson(); String string = json.toString(); ModChannel.sendToPlayer(new AnimationJsonPacket(string, false), serverPlayer); @@ -213,10 +221,11 @@ public class AnimationRegistry { @OnlyIn(Dist.CLIENT) public static class ClientCache { - private static final Map animationsCache = new HashMap<>(); + public static boolean isAnimationRegistered = false; + private static final Map animationsCache = new HashMap<>(); private static final Map layersCache = new HashMap<>(); - public static void cacheAddAnimation(ResourceLocation location, Animation animation) { + public static void cacheAddAnimation(ResourceLocation location, GenericAnimationData animation) { animationsCache.put(location, animation); } @@ -233,7 +242,11 @@ public class AnimationRegistry { .sccore$clearAnimations(); layersCache.clear(); } - case ANIM_REGISTER -> registerAnimations(animationsCache); + case ANIM_REGISTER -> { + isAnimationRegistered = true; + registerAnimations(animationsCache); + RawAnimationRegistry.triggerRegistry(); + } case LAYER_REGISTER -> { registerLayers(layersCache); layersCache.forEach((key, value) -> @@ -246,47 +259,71 @@ public class AnimationRegistry { SnowyCrescentCore.log.error("{} : Level is null", ClientCache.class.getName()); return; } - for (AbstractClientPlayer player : level.players()) { - try { - if (player == null) throw new Exception("player is null"); - Class playerClass = Player.class; - Field animationStackField = playerClass.getDeclaredField("animationStack"); - animationStackField.setAccessible(true); - Method createAnimationStack = playerClass.getDeclaredMethod("createAnimationStack"); - createAnimationStack.setAccessible(true); - AnimationStack newAnimationStack = (AnimationStack) createAnimationStack.invoke(player); - AnimationStack oldAnimationStack = (AnimationStack) animationStackField.get(player); - Field layersField = AnimationStack.class.getDeclaredField("layers"); - layersField.setAccessible(true); - ArrayList> oldArrayList = (ArrayList>) layersField.get(oldAnimationStack); - ArrayList> newArrayList = (ArrayList>) layersField.get(newAnimationStack); - ArrayList> result = new ArrayList<>(); - result.addAll(oldArrayList); - result.addAll(newArrayList); - layersField.set(newAnimationStack, result); - animationStackField.set(player, newAnimationStack); - Field animationApplierField = playerClass.getDeclaredField("animationApplier"); - animationApplierField.setAccessible(true); - animationApplierField.set(player, new AnimationApplier(newAnimationStack)); - IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); - if(data == null) return; - Map dataAnimations = data.getAnimations(); - ResourceLocation riderAnimLayer = data.getRiderAnimLayer(); - if(riderAnimLayer != null) { - dataAnimations.put(riderAnimLayer, data.getRiderAnimation()); + try { + ModuleAccess.open( + Player.class.getModule(), + Player.class.getPackageName(), + AnimationRegistry.class.getModule() + ); + for (AbstractClientPlayer player : level.players()) { + try { + Class playerClass = Player.class; + Field animationStackField = playerClass.getDeclaredField("animationStack"); + animationStackField.setAccessible(true); + Method createAnimationStack = playerClass.getDeclaredMethod("createAnimationStack"); + createAnimationStack.setAccessible(true); + AnimationStack newAnimationStack = (AnimationStack) createAnimationStack.invoke(player); + AnimationStack oldAnimationStack = (AnimationStack) animationStackField.get(player); + Field layersField = AnimationStack.class.getDeclaredField("layers"); + layersField.setAccessible(true); + ArrayList> oldArrayList = (ArrayList>) layersField.get(oldAnimationStack); + ArrayList> newArrayList = (ArrayList>) layersField.get(newAnimationStack); + ArrayList> result = new ArrayList<>(); + result.addAll(oldArrayList); + result.addAll(newArrayList); + layersField.set(newAnimationStack, result); + animationStackField.set(player, newAnimationStack); + Field animationApplierField = playerClass.getDeclaredField("animationApplier"); + animationApplierField.setAccessible(true); + animationApplierField.set(player, new AnimationApplier(newAnimationStack)); + IAnimationCapability data = AnimationDataCapability.getCapability(player).orElse(null); + if(data == null) continue; + RawAnimationDataCapability rawData = RawAnimationDataCapability.getCapability(player).orElse(null); + if(rawData == null) continue; + Map dataAnimations = new HashMap<>(); + dataAnimations.putAll(data.getAnimations()); + dataAnimations.putAll(rawData.getAnimations()); + ResourceLocation riderAnimLayer = data.getRiderAnimLayer(); + if(riderAnimLayer != null) { + dataAnimations.put(riderAnimLayer, data.getRiderAnimation()); + } + ResourceLocation rawRiderAnimLayer = rawData.getRiderAnimLayer(); + if(rawRiderAnimLayer != null) { + dataAnimations.put(rawRiderAnimLayer, rawData.getRiderAnimation()); + } + for (ResourceLocation location : dataAnimations.keySet()) { + ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(player).get(location); + if(modifierLayer == null) continue; + KeyframeAnimation keyframeAnimation; + ResourceLocation animationLocation = dataAnimations.get(location); + GenericAnimationData anim = animations.get(animationLocation); + if(anim == null) { + RawAnimationData rawAnim = RawAnimationHelper.INSTANCE.getAnimation(animationLocation); + if(rawAnim == null) return; + keyframeAnimation = rawAnim.getAnimation(); + } else keyframeAnimation = anim.getAnimation(); + if(keyframeAnimation == null) continue; + modifierLayer.replaceAnimationWithFade( + AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE), + new KeyframeAnimationPlayer(keyframeAnimation) + ); + } + }catch (Exception e){ + SnowyCrescentCore.log.error("Failed to register on {} animation layer: {}", player, e.getMessage(), e); } - for (ResourceLocation location : dataAnimations.keySet()) { - ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess - .getPlayerAssociatedData(player).get(location); - if(modifierLayer == null) continue; - KeyframeAnimation animation = animations.get(dataAnimations.get(location)).getAnimation(); - if(animation == null) continue; - modifierLayer.setAnimation(new KeyframeAnimationPlayer(animation)); - } - }catch (Exception e){ - SnowyCrescentCore.log.error("Failed to register on {} animation layer: {}", player, e.getMessage(), e); } - } + } catch (Exception ignored) {} } } } diff --git a/src/main/java/com/linearpast/sccore/animation/register/RawAnimationRegistry.java b/src/main/java/com/linearpast/sccore/animation/register/RawAnimationRegistry.java new file mode 100644 index 0000000..6204d73 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/register/RawAnimationRegistry.java @@ -0,0 +1,64 @@ +package com.linearpast.sccore.animation.register; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.command.argument.AnimationArgument; +import com.linearpast.sccore.animation.data.RawAnimationData; +import com.linearpast.sccore.animation.event.create.AnimationRegisterEvent; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.MinecraftForge; + +import java.util.HashMap; +import java.util.Map; + +@OnlyIn(Dist.CLIENT) +public class RawAnimationRegistry { + private static final Map animations = new HashMap<>(); + + public static Map getAnimations() { + return Map.copyOf(animations); + } + + private static void registerAnimations(Map animationMap) { + animations.clear(); + Map.copyOf(animationMap).keySet().forEach(location -> { + if(!validateLocation(location)) animations.remove(location); + }); + animations.putAll(animationMap); + AnimationArgument.resetAnimationNames(); + } + + public static boolean register(ResourceLocation location, RawAnimationData rawAnimation) { + if (validateLocation(location)) { + animations.put(location, rawAnimation); + return true; + } + return false; + } + + public static void triggerRegistry() { + resetAnimations(); + AnimationRegisterEvent.RawAnimation event = new AnimationRegisterEvent.RawAnimation(); + MinecraftForge.EVENT_BUS.post(event); + registerAnimations(event.getAnimations()); + } + + private static void resetAnimations() { + animations.clear(); + } + + public static boolean validateLocation(ResourceLocation location) { + try { + if(!AnimationRegistry.ClientCache.isAnimationRegistered) + throw new RuntimeException("Server animation is not registered!"); + if(AnimationRegistry.getAnimations().containsKey(location)) + throw new RuntimeException("Duplicated animation on server: " + location); + return true; + } catch (RuntimeException e) { + SnowyCrescentCore.log.error(e.getMessage(), e); + return false; + } + } + +} diff --git a/src/main/java/com/linearpast/sccore/animation/utils/AnimationUtils.java b/src/main/java/com/linearpast/sccore/animation/utils/AnimationUtils.java new file mode 100644 index 0000000..4a8b8e4 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/utils/AnimationUtils.java @@ -0,0 +1,196 @@ +package com.linearpast.sccore.animation.utils; + +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.animation.capability.AnimationDataCapability; +import com.linearpast.sccore.animation.capability.RawAnimationDataCapability; +import com.linearpast.sccore.animation.data.GenericAnimationData; +import com.linearpast.sccore.animation.data.RawAnimationData; +import com.linearpast.sccore.animation.helper.AnimationHelper; +import com.linearpast.sccore.animation.helper.IAnimationHelper; +import com.linearpast.sccore.animation.helper.RawAnimationHelper; +import com.linearpast.sccore.animation.mixin.IMixinKeyframeAnimationPlayer; +import com.linearpast.sccore.animation.register.AnimationRegistry; +import com.linearpast.sccore.core.datagen.ModLang; +import dev.kosmx.playerAnim.api.layered.IAnimation; +import dev.kosmx.playerAnim.api.layered.KeyframeAnimationPlayer; +import dev.kosmx.playerAnim.api.layered.ModifierLayer; +import dev.kosmx.playerAnim.api.layered.modifier.AbstractFadeModifier; +import dev.kosmx.playerAnim.core.data.KeyframeAnimation; +import dev.kosmx.playerAnim.core.util.Ease; +import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationAccess; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +public class AnimationUtils { + /** + * Test if layer exist animation which is not end.
+ * Only in dist client + * @param player Target player + * @param layer Target layer + * @return True when animation is loop, or currentTick not larger than endTick + */ + @SuppressWarnings("unchecked") + @OnlyIn(Dist.CLIENT) + public static boolean isClientAnimationNotEnd(AbstractClientPlayer player, @Nullable ResourceLocation layer) { + return IAnimationHelper.ANIMATION_RUNNER.testLoadedAndCall(() -> { + try { + Set resourceLocations = new HashSet<>(); + if(layer == null) resourceLocations.addAll(AnimationRegistry.getLayers().keySet()); + else resourceLocations.add(layer); + for (ResourceLocation location : resourceLocations) { + ModifierLayer animationModifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(player).get(location); + if(animationModifierLayer == null) continue; + KeyframeAnimationPlayer animation = (KeyframeAnimationPlayer) animationModifierLayer.getAnimation(); + if(animation == null) return false; + int currentTick = animation.getCurrentTick(); + boolean isLoop = animation.getData().isInfinite; + int endTick = animation.getData().endTick; + return isLoop || currentTick <= endTick; + } + } catch (Exception ignored) {} + return false; + }); + } + + /** + * Test if layer exist animation which is not stop.
+ * Only in dist client + * @param player Target player + * @param layer Target layer + * @return True when the currentTick not larger than stopTick + */ + @SuppressWarnings("unchecked") + @OnlyIn(Dist.CLIENT) + public static boolean isClientAnimationStop(AbstractClientPlayer player, @Nullable ResourceLocation layer) { + return IAnimationHelper.ANIMATION_RUNNER.testLoadedAndCall(() -> { + try { + Set resourceLocations = new HashSet<>(); + if(layer == null) resourceLocations.addAll(AnimationRegistry.getLayers().keySet()); + else resourceLocations.add(layer); + for (ResourceLocation location : resourceLocations) { + ModifierLayer animationModifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(player).get(location); + if(animationModifierLayer == null) continue; + KeyframeAnimationPlayer animation = (KeyframeAnimationPlayer) animationModifierLayer.getAnimation(); + if(animation == null) return false; + int currentTick = animation.getCurrentTick(); + int stopTick = animation.getStopTick(); + return currentTick > stopTick; + } + } catch (Exception ignored) {} + return true; + }); + } + + /** + * Client sync animation + * @param clientPlayer player + * @param target target + */ + @SuppressWarnings("unchecked") + @OnlyIn(Dist.CLIENT) + public static void syncAnimation(AbstractClientPlayer clientPlayer, AbstractClientPlayer target) { + AtomicReference clientPlayerLayer = new AtomicReference<>(); + AtomicReference targetLayer = new AtomicReference<>(); + clientPlayerLayer.set(null); + targetLayer.set(null); + AnimationDataCapability.getCapability(clientPlayer).ifPresent(clientPlayerData -> + AnimationDataCapability.getCapability(target).ifPresent(targetData -> { + clientPlayerLayer.set(clientPlayerData.getRiderAnimLayer()); + targetLayer.set(targetData.getRiderAnimLayer()); + }) + ); + if(clientPlayerLayer.get() == null || targetLayer.get() == null) { + RawAnimationDataCapability.getCapability(clientPlayer).ifPresent(clientPlayerData -> + RawAnimationDataCapability.getCapability(target).ifPresent(targetData -> { + clientPlayerLayer.set(clientPlayerData.getRiderAnimLayer()); + targetLayer.set(targetData.getRiderAnimLayer()); + }) + ); + } + try { + if(clientPlayerLayer.get() == null || targetLayer.get() == null) return; + ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(clientPlayer).get(clientPlayerLayer.get()); + ModifierLayer targetModifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(target).get(targetLayer.get()); + if(modifierLayer == null || targetModifierLayer == null) return; + IMixinKeyframeAnimationPlayer animation = (IMixinKeyframeAnimationPlayer) modifierLayer.getAnimation(); + KeyframeAnimationPlayer targetAnimation = (KeyframeAnimationPlayer) targetModifierLayer.getAnimation(); + if(animation == null || targetAnimation == null) return; + int currentTick = targetAnimation.getCurrentTick(); + animation.sccore$setCurrentTick(currentTick); + } catch (Exception ignored) {} + } + + /** + * client remove animation + * @param clientPlayer player + * @param layer layer + */ + @OnlyIn(Dist.CLIENT) + public static void removeAnimation(@Nullable AbstractClientPlayer clientPlayer, ResourceLocation layer) { + playAnimation(clientPlayer, layer, null); + } + + /** + * Client play animation + * @param clientPlayer player + * @param layer layer + * @param animation animation + */ + @SuppressWarnings("unchecked") + @OnlyIn(Dist.CLIENT) + public static void playAnimation(@Nullable AbstractClientPlayer clientPlayer, ResourceLocation layer, @Nullable ResourceLocation animation) { + try { + LocalPlayer localPlayer = Minecraft.getInstance().player; + if(clientPlayer == null) clientPlayer = localPlayer; + if(clientPlayer == null) return; + ModifierLayer modifierLayer = (ModifierLayer) PlayerAnimationAccess + .getPlayerAssociatedData(clientPlayer).get(layer); + if(animation == null) { + if(modifierLayer != null) { + modifierLayer.replaceAnimationWithFade( + AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE), + null + ); + } + return; + } + if(modifierLayer == null) return; + KeyframeAnimation keyframeAnimation; + GenericAnimationData anim = AnimationHelper.INSTANCE.getAnimation(animation); + if(anim == null) { + RawAnimationData rawAnim = RawAnimationHelper.INSTANCE.getAnimation(animation); + if(rawAnim == null) return; + keyframeAnimation = rawAnim.getAnimation(); + } else keyframeAnimation = anim.getAnimation(); + if(keyframeAnimation == null) { + if(localPlayer == null) return; + localPlayer.sendSystemMessage(Component.translatable( + ModLang.TranslatableMessage.ANIMATION_RESOURCE_NOT_FOUND.getKey(), + animation.toString() + ).withStyle(ChatFormatting.RED)); + return; + } + modifierLayer.replaceAnimationWithFade( + AbstractFadeModifier.standardFadeIn(3, Ease.INOUTSINE), + new KeyframeAnimationPlayer(keyframeAnimation) + ); + }catch (Exception e) { + SnowyCrescentCore.log.error("Failed to play animation : {}", animation, e); + } + } +} diff --git a/src/main/java/com/linearpast/sccore/animation/utils/ApiBack.java b/src/main/java/com/linearpast/sccore/animation/utils/ApiBack.java new file mode 100644 index 0000000..1d491f9 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/animation/utils/ApiBack.java @@ -0,0 +1,34 @@ +package com.linearpast.sccore.animation.utils; + +import org.jetbrains.annotations.Nullable; + +public enum ApiBack { + FAIL(0), + SUCCESS(1), + COOLDOWN(2), + RESOURCE_NOT_FOUND(3), + OUT_RANGE(4), + OPERATION_EXPIRE(5), + UNSUPPORTED(6), + BE_CANCELLED(7), + ; + public final int value; + ApiBack(int value) { + this.value = value; + } + + public boolean isValueOf(int value) { + return this.value == value; + } + + @Nullable + public static ApiBack valueOf(int value) { + ApiBack[] values = ApiBack.values(); + for (ApiBack v : values) { + if (v.isValueOf(value)) { + return v; + } + } + return null; + } +} diff --git a/src/main/java/com/linearpast/sccore/capability/data/ICapabilitySync.java b/src/main/java/com/linearpast/sccore/capability/data/ICapabilitySync.java index e822143..af80f66 100644 --- a/src/main/java/com/linearpast/sccore/capability/data/ICapabilitySync.java +++ b/src/main/java/com/linearpast/sccore/capability/data/ICapabilitySync.java @@ -3,6 +3,7 @@ package com.linearpast.sccore.capability.data; import com.linearpast.sccore.capability.network.SimpleCapabilityPacket; import com.linearpast.sccore.core.ModChannel; import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraftforge.common.util.INBTSerializable; @@ -16,6 +17,8 @@ public interface ICapabilitySync extends INBTSerializable { */ void handler(NetworkEvent.Context context); - /** - * Retrieve the corresponding capability from the network packet, usually executed after {@link ICapabilityPacket#syncData} - * @param entity Target - * @return capability - */ - @Nullable ICapabilitySync getCapability(T entity); /** * Get tag diff --git a/src/main/java/com/linearpast/sccore/capability/network/SimpleCapabilityPacket.java b/src/main/java/com/linearpast/sccore/capability/network/SimpleCapabilityPacket.java index 435915c..54d1589 100644 --- a/src/main/java/com/linearpast/sccore/capability/network/SimpleCapabilityPacket.java +++ b/src/main/java/com/linearpast/sccore/capability/network/SimpleCapabilityPacket.java @@ -1,5 +1,7 @@ package com.linearpast.sccore.capability.network; +import com.linearpast.sccore.SnowyCrescentCore; +import com.linearpast.sccore.capability.CapabilityUtils; import com.linearpast.sccore.capability.data.ICapabilitySync; import com.linearpast.sccore.capability.data.entity.SimpleEntityCapabilitySync; import com.linearpast.sccore.capability.data.player.SimplePlayerCapabilitySync; @@ -7,25 +9,34 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; import net.minecraftforge.network.NetworkEvent; -public abstract class SimpleCapabilityPacket implements ICapabilityPacket { +public class SimpleCapabilityPacket implements ICapabilityPacket { + private final ResourceLocation key; private final CompoundTag data; /** * Constructor * @param data data tag */ - public SimpleCapabilityPacket(CompoundTag data) { + public SimpleCapabilityPacket(ResourceLocation key, CompoundTag data) { + this.key = key; this.data = data; } + public SimpleCapabilityPacket(ICapabilitySync packet) { + this.key = packet.getKey(); + this.data = packet.serializeNBT(); + } + /** * decoder * @param buf buf */ public SimpleCapabilityPacket(FriendlyByteBuf buf) { + this.key = buf.readResourceLocation(); this.data = buf.readNbt(); } @@ -35,6 +46,7 @@ public abstract class SimpleCapabilityPacket implements ICapab */ @Override public void encode(FriendlyByteBuf buf) { + buf.writeResourceLocation(key); buf.writeNbt(data); } @@ -42,7 +54,6 @@ public abstract class SimpleCapabilityPacket implements ICapab * Default network packet handle, generally sufficient for use * @param context NetworkEvent.Context */ - @SuppressWarnings("unchecked") @Override public void handler(NetworkEvent.Context context) { context.setPacketHandled(true); @@ -59,7 +70,11 @@ public abstract class SimpleCapabilityPacket implements ICapab } if(entity == null) return; try { - ICapabilitySync data = getCapability((T) entity); + ICapabilitySync data = CapabilityUtils.getCapability(entity, key); + if(data == null) { + SnowyCrescentCore.log.error("key {} not found when sync capability on entity {}", key, entity); + return; + } syncData(nbt, data); }catch (Exception ignored) {} } diff --git a/src/main/java/com/linearpast/sccore/core/ModLazyRun.java b/src/main/java/com/linearpast/sccore/core/ModLazyRun.java index 8522125..9821c1f 100644 --- a/src/main/java/com/linearpast/sccore/core/ModLazyRun.java +++ b/src/main/java/com/linearpast/sccore/core/ModLazyRun.java @@ -30,7 +30,7 @@ public abstract class ModLazyRun { try { if(isModLoaded()) return callable.call(); else return elseCall.call(); - }catch(Exception e) { + }catch(Exception ignored) { return null; } } diff --git a/src/main/java/com/linearpast/sccore/core/configs/ModConfigs.java b/src/main/java/com/linearpast/sccore/core/configs/ModConfigs.java index fb49236..32273c8 100644 --- a/src/main/java/com/linearpast/sccore/core/configs/ModConfigs.java +++ b/src/main/java/com/linearpast/sccore/core/configs/ModConfigs.java @@ -4,14 +4,16 @@ import net.minecraftforge.common.ForgeConfigSpec; public class ModConfigs { public enum ConfigName { - inviteDuration("inviteDuration"), - inviteDistance("inviteDistance"), + inviteValidTime("inviteValidTime"), + inviteValidDistance("inviteValidDistance"), inviteCooldown("inviteCooldown"), - requestDuration("requestDuration"), - requestCooldown("requestCooldown"), - applyDistance("applyDistance"), - applyDuration("applyDuration"), + + applyValidTime("applyValidTime"), + applyValidDistance("applyValidDistance"), applyCooldown("applyCooldown"), + + requestValidTime("requestValidTime"), + requestCooldown("requestCooldown"), ; private final String name; @@ -27,33 +29,42 @@ public class ModConfigs { public static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); public static final ForgeConfigSpec SPEC; - public static final ForgeConfigSpec.ConfigValue inviteDuration; - public static final ForgeConfigSpec.ConfigValue inviteDistance; + //invite + public static final ForgeConfigSpec.ConfigValue inviteValidTime; + public static final ForgeConfigSpec.ConfigValue inviteValidDistance; public static final ForgeConfigSpec.ConfigValue inviteCooldown; - public static final ForgeConfigSpec.ConfigValue requestDuration; - public static final ForgeConfigSpec.ConfigValue requestCooldown; - public static final ForgeConfigSpec.ConfigValue applyDistance; - public static final ForgeConfigSpec.ConfigValue applyDuration; + //apply + public static final ForgeConfigSpec.ConfigValue applyValidTime; + public static final ForgeConfigSpec.ConfigValue applyValidDistance; public static final ForgeConfigSpec.ConfigValue applyCooldown; + //request + public static final ForgeConfigSpec.ConfigValue requestValidTime; + public static final ForgeConfigSpec.ConfigValue requestCooldown; static { BUILDER.push("Animation"); - inviteDuration = BUILDER.comment("Animation invite duration. Ignore when zero. (seconds)") - .defineInRange(ConfigName.inviteDuration.name, 120, 0, Integer.MAX_VALUE); - inviteDistance = BUILDER.comment("Animation invite max distance. Ignore when zero. (blocks)") - .defineInRange(ConfigName.inviteDistance.name, 6, 0, Integer.MAX_VALUE); + //invite + inviteValidTime = BUILDER.comment("Animation invite valid time. Ignore when zero. (seconds)") + .defineInRange(ConfigName.inviteValidTime.name, 120, 0, Integer.MAX_VALUE); + inviteValidDistance = BUILDER.comment("Animation invite max distance. Ignore when zero. (blocks)") + .defineInRange(ConfigName.inviteValidDistance.name, 6, 0, Integer.MAX_VALUE); inviteCooldown = BUILDER.comment("Animation invite cooldown. (seconds)") .defineInRange(ConfigName.inviteCooldown.name, 60, 0, Integer.MAX_VALUE); - requestDuration = BUILDER.comment("Animation request duration. Ignore when zero (seconds)") - .defineInRange(ConfigName.requestDuration.name, 120, 0, Integer.MAX_VALUE); - requestCooldown = BUILDER.comment("Animation request cooldown. (seconds)") - .defineInRange(ConfigName.requestCooldown.name, 60, 0, Integer.MAX_VALUE); - applyDuration = BUILDER.comment("Animation apply duration. Ignore when zero. (seconds)") - .defineInRange(ConfigName.applyDuration.name, 120, 0, Integer.MAX_VALUE); - applyDistance = BUILDER.comment("Animation apply max distance. Ignore when zero. (blocks)") - .defineInRange(ConfigName.applyDistance.name, 6, 0, Integer.MAX_VALUE); + + //apply + applyValidTime = BUILDER.comment("Animation apply valid time. Ignore when zero. (seconds)") + .defineInRange(ConfigName.applyValidTime.name, 120, 0, Integer.MAX_VALUE); + applyValidDistance = BUILDER.comment("Animation apply max distance. Ignore when zero. (blocks)") + .defineInRange(ConfigName.applyValidDistance.name, 6, 0, Integer.MAX_VALUE); applyCooldown = BUILDER.comment("Animation apply cooldown. (seconds)") .defineInRange(ConfigName.applyCooldown.name, 60, 0, Integer.MAX_VALUE); + + //request + requestValidTime = BUILDER.comment("Animation request valid time. Ignore when zero (seconds)") + .defineInRange(ConfigName.requestValidTime.name, 120, 0, Integer.MAX_VALUE); + requestCooldown = BUILDER.comment("Animation request cooldown. (seconds)") + .defineInRange(ConfigName.requestCooldown.name, 60, 0, Integer.MAX_VALUE); + BUILDER.pop(); SPEC = BUILDER.build(); } diff --git a/src/main/java/com/linearpast/sccore/core/datagen/ModLang.java b/src/main/java/com/linearpast/sccore/core/datagen/ModLang.java index 0bd8207..d40b0ed 100644 --- a/src/main/java/com/linearpast/sccore/core/datagen/ModLang.java +++ b/src/main/java/com/linearpast/sccore/core/datagen/ModLang.java @@ -23,16 +23,6 @@ public class ModLang { "命令执行成功。", "Command run success." )), - ANIMATION_NOT_PRESENT(new LangEntity<>( - translationString + command + animation + ".animation_not_present", - "动画不存在。", - "Animation is not present." - )), - ANIMATION_LAYER_NOT_PRESENT(new LangEntity<>( - translationString + command + animation + ".animation_layer_not_present", - "动画层不存在。", - "Animation layer is not present." - )), PLAY_ANIMATION_FAIL(new LangEntity<>( translationString + command + animation + ".play_animation_fail", "在这些玩家上播放动画失败:%s", @@ -78,26 +68,6 @@ public class ModLang { "%s§c§l 邀请§r你进行动画:%s。", "%s§c§l invites§r you to animation: %s. " )), - ACCEPT_INVITE_EXPIRED(new LangEntity<>( - translationString + command + animation + ".accept_invite_expired", - "邀请已超时。(%s分钟)", - "Invite expired.(%s minute(s))" - )), - INVITE_EXPIRED(new LangEntity<>( - translationString + command + animation + ".invite_expired", - "%s 接受了你的动画邀请,但是邀请超时了。(%s分钟)", - "%s has accepted your animation invitation but the invitation has expired. (%s minute(s))" - )), - ACCEPT_INVITE_TOO_FAR(new LangEntity<>( - translationString + command + animation + ".accept_invite_too_far", - "你们距离太远了。(%s格)", - "You are too far apart. (%s block(s))" - )), - INVITE_TOO_FAR(new LangEntity<>( - translationString + command + animation + ".invite_too_far", - "%s 接受了你的动画邀请,但你们距离太远了。(%s格)", - "%s has accepted your animation invitation but you are too far apart. (%s block(s))" - )), ACCEPT_INVITE_SUCCESS(new LangEntity<>( translationString + command + animation + ".accept_invite_success", "已接受邀请。", @@ -118,16 +88,6 @@ public class ModLang { "%s§d§l 请求§r你进行动画:%s。", "%s§d§l requests§r you to animation: %s. " )), - ACCEPT_REQUEST_EXPIRED(new LangEntity<>( - translationString + command + animation + ".accept_request_expired", - "请求已超时。(%s分钟)", - "Request expired.(%s minute(s))" - )), - REQUEST_EXPIRED(new LangEntity<>( - translationString + command + animation + ".request_expired", - "%s 接受了你的动画请求,但是请求超时了。(%s分钟)", - "%s has accepted your animation request but the request has expired. (%s minute(s))" - )), ACCEPT_REQUEST_SUCCESS(new LangEntity<>( translationString + command + animation + ".accept_request_success", "已接受请求。", @@ -148,26 +108,6 @@ public class ModLang { "%s§b§l 申请§r加入动画。", "%S§b§l Apply for §r to join your animation. " )), - ACCEPT_APPLY_EXPIRED(new LangEntity<>( - translationString + command + animation + ".accept_apply_expired", - "申请已超时。(%s分钟)", - "Application expired.(%s minute(s))" - )), - APPLY_EXPIRED(new LangEntity<>( - translationString + command + animation + ".apply_expired", - "%s 接受了你的动画申请,但是申请超时了。(%s分钟)", - "%s has accepted your animation application but the application has expired. (%s minute(s))" - )), - ACCEPT_APPLY_TOO_FAR(new LangEntity<>( - translationString + command + animation + ".accept_apply_too_far", - "你们距离太远了。(%s格)", - "You are too far apart. (%s block(s))" - )), - APPLY_TOO_FAR(new LangEntity<>( - translationString + command + animation + ".apply_too_far", - "%s 接受了你的动画申请,但你们距离太远了。(%s格)", - "%s has accepted your animation application but you are too far apart. (%s block(s))" - )), ACCEPT_APPLY_SUCCESS(new LangEntity<>( translationString + command + animation + ".accept_apply_success", "%s 接受了 %s 的申请。", @@ -178,16 +118,6 @@ public class ModLang { "%s 接受了你的动画申请。", "%s has accepted your animation application." )), - WITHOUT_ANIMATION_RIDE_ENTITY(new LangEntity<>( - translationString + animation + ".without_animation_ride_entity", - "命令执行错误,已满人或不支持的动画。", - "Command run fail, full or unsupported animations." - )), - COMMAND_COOLDOWN(new LangEntity<>( - translationString + animation + ".command_cooldown", - "你不能执行该指令,冷却中:%s 秒。", - "You cannot execute this command, cooling down: %s seconds." - )), ANIMATION_TO_JSON(new LangEntity<>( translationString + command + animation + ".animation_to_json", "动画%s已经存储到%s路径:", @@ -198,15 +128,40 @@ public class ModLang { "%s", "%s" )), - UNKNOWN_ANIMATION(new LangEntity<>( - translationString + animation + ".unknown_animation", - "未知的动画%s,请检查你的资源包是否完整。", - "Unknown animation %s, please check if your resource packs is complete." + LIST_ANIMATION_RESOURCE(new LangEntity<>( + translationString + command + animation + ".list_animation_resource", + "%s侧的%s有:%s", + "The %2$s on %1$s has : %s" )), - UNSAFE_FILE_DIRECTORY(new LangEntity<>( - translationString + command + animation + ".unsafe_file_directory", - "你选择的文件路径并不安全", - "%s" + ANIMATION_EXPIRE(new LangEntity<>( + translationString + command + animation + ".animation_expire", + "你不能执行该操作: 已过期。", + "You cannot perform this operation: It has expired." + )), + ANIMATION_OUT_RANGE(new LangEntity<>( + translationString + command + animation + ".animation_out_range", + "你不能执行该操作: 距离不在%s格以内。", + "You cannot perform this operation: The distance is not within %s blocks." + )), + ANIMATION_OPERATION_UNSUPPORTED(new LangEntity<>( + translationString + command + animation + ".animation_operation_unsupported", + "错误: 不支持这样做。", + "Error: Unsupported operation." + )), + ANIMATION_COOLDOWN(new LangEntity<>( + translationString + command + animation + ".animation_cooldown", + "你不能执行该操作: 冷却中(%s秒)。", + "You cannot perform this operation: Cooling down (%s second(s))." + )), + ANIMATION_RESOURCE_NOT_FOUND(new LangEntity<>( + translationString + command + animation + ".animation_resource_not_found", + "错误: 资源未找到,请检查资源或操作是否有误。", + "Error: Resource not found, please check if there are any errors in the resource or operation." + )), + ANIMATION_OPERATION_CANCELLED(new LangEntity<>( + translationString + command + animation + ".animation_operation_cancelled", + "异常: 操作被取消。", + "Exception: Operation cancelled." )), ; diff --git a/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java b/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java index 79e36a2..54e4a84 100644 --- a/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java +++ b/src/main/java/com/linearpast/sccore/example/animation/ModAnimation.java @@ -1,12 +1,10 @@ package com.linearpast.sccore.example.animation; import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.AnimationUtils; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.RawAnimationData; import com.linearpast.sccore.animation.data.Ride; -import com.linearpast.sccore.animation.event.create.AnimationLayerRegisterEvent; import com.linearpast.sccore.animation.event.create.AnimationRegisterEvent; -import com.linearpast.sccore.example.animation.event.ExampleCommandEvent; +import com.linearpast.sccore.animation.helper.AnimationHelper; import com.linearpast.sccore.example.animation.event.ExamplePlayerAttackEvent; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; @@ -14,7 +12,7 @@ import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.fml.loading.FMLEnvironment; /** - * @see AnimationUtils + * @see AnimationHelper */ public class ModAnimation { /** @@ -38,7 +36,7 @@ public class ModAnimation { * See wiki (If I'm done.) * @param event event */ - public static void onLayerRegister(AnimationLayerRegisterEvent event) { + public static void onLayerRegister(AnimationRegisterEvent.Layer event) { event.registerLayer(normalLayers, 42); } @@ -47,23 +45,34 @@ public class ModAnimation { * See wiki (If I'm done.) * @param event event */ - public static void onAnimationRegister(AnimationRegisterEvent event) { + public static void onAnimationRegister(AnimationRegisterEvent.Animation event) { //You must define corresponding Animation to invite - Animation amLTRL = Animation.create(AmLyingToRightLying) - .withLyingType(Animation.LyingType.RIGHT) - .withName("Lying-to-Right-Lying"); - Animation amSTL = Animation.create(AmStandToLying) - .withName("Stand-to-Lying") - .withLyingType(Animation.LyingType.FRONT); - Animation waltzGentleman = Animation.create(WaltzGentleman) - .withName("Waltz-Gentleman") - .withRide(Ride.create().addComponentAnimation(WaltzLady)); - Animation waltzLady = Animation.create(WaltzLady) - .withName("Waltz-Lady") - .withCamYaw(180) - .withRide(Ride.create().addComponentAnimation(WaltzGentleman)); +// Animation amLTRL = Animation.create(AmLyingToRightLying) +// .withLyingType(Animation.LyingType.RIGHT) +// .withName("Lying-to-Right-Lying"); +// Animation amSTL = Animation.create(AmStandToLying) +// .withName("Stand-to-Lying") +// .withLyingType(Animation.LyingType.FRONT); +// Animation waltzGentleman = Animation.create(WaltzGentleman) +// .withName("Waltz-Gentleman") +// .withRide(Ride.create().addComponentAnimation(WaltzLady)); +// Animation waltzLady = Animation.create(WaltzLady) +// .withName("Waltz-Lady") +// .withCamYaw(180) +// .withRide(Ride.create().addComponentAnimation(WaltzGentleman)); +// +// //You can use it to invite an Animation +// event.registerAnimation(AmLyingToRightLying, amLTRL); +// event.registerAnimation(AmStandToLying, amSTL); +// event.registerAnimation(WaltzGentleman, waltzGentleman); +// event.registerAnimation(WaltzLady, waltzLady); + } - //You can use it to invite an Animation + public static void onRawAnimationRegister(AnimationRegisterEvent.RawAnimation event) { + RawAnimationData amSTL = RawAnimationData.create(AmStandToLying).withRide(Ride.create().withExistTick(100)); + RawAnimationData amLTRL = RawAnimationData.create(AmLyingToRightLying).withRide(Ride.create().withExistTick(100)); + RawAnimationData waltzGentleman = RawAnimationData.create(WaltzGentleman).withRide(Ride.create().withExistTick(100).addComponentAnimation(WaltzLady)); + RawAnimationData waltzLady = RawAnimationData.create(WaltzLady).withRide(Ride.create().withExistTick(100).addComponentAnimation(WaltzGentleman)); event.registerAnimation(AmLyingToRightLying, amLTRL); event.registerAnimation(AmStandToLying, amSTL); event.registerAnimation(WaltzGentleman, waltzGentleman); @@ -77,10 +86,11 @@ public class ModAnimation { forgeBus.addListener(ModAnimation::onAnimationRegister); //Try to play animation - forgeBus.addListener(ExamplePlayerAttackEvent::onPlayerAttack); - forgeBus.addListener(ExampleCommandEvent::inviteDance); +// forgeBus.addListener(ExamplePlayerAttackEvent::onPlayerAttack); + forgeBus.addListener(ExamplePlayerAttackEvent::rawAnimationAttack); if(FMLEnvironment.dist == Dist.CLIENT){ - forgeBus.addListener(ExamplePlayerAttackEvent::onInputEvent); +// forgeBus.addListener(ExamplePlayerAttackEvent::onInputEvent); + forgeBus.addListener(ModAnimation::onRawAnimationRegister); } } } diff --git a/src/main/java/com/linearpast/sccore/example/animation/event/ExampleCommandEvent.java b/src/main/java/com/linearpast/sccore/example/animation/event/ExampleCommandEvent.java deleted file mode 100644 index d408cb4..0000000 --- a/src/main/java/com/linearpast/sccore/example/animation/event/ExampleCommandEvent.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.linearpast.sccore.example.animation.event; - -import com.linearpast.sccore.animation.AnimationUtils; -import com.linearpast.sccore.animation.command.argument.AnimationArgument; -import com.linearpast.sccore.animation.command.argument.AnimationLayerArgument; -import com.mojang.brigadier.arguments.BoolArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import net.minecraft.ChatFormatting; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.arguments.EntityArgument; -import net.minecraft.network.chat.ClickEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.event.RegisterCommandsEvent; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static net.minecraft.commands.Commands.argument; -import static net.minecraft.commands.Commands.literal; - -@Deprecated -public class ExampleCommandEvent { - record InviteRecord(long time, ResourceLocation layer, ResourceLocation animation, boolean isForce){} - private static final Map> invites = new HashMap<>(); - public static void inviteDance(RegisterCommandsEvent event) { - LiteralArgumentBuilder builder = literal("dance").then(literal("invite") - .then(argument("player", EntityArgument.player()) - .then(argument("layer", AnimationLayerArgument.layer()) - .then(argument("anim", AnimationArgument.animation()) - .executes(ExampleCommandEvent::inviteDance) - .then(argument("force", BoolArgumentType.bool()) - .executes(ExampleCommandEvent::inviteDance) - ) - ) - ) - ) - .then(literal("accept") - .then(argument("player", EntityArgument.player()) - .executes(ExampleCommandEvent::acceptInvite) - ) - ) - ); - event.getDispatcher().register(builder); - } - - private static int inviteDance(CommandContext context) { - CommandSourceStack source = context.getSource(); - try { - //get info - boolean force = false; - try { - force = BoolArgumentType.getBool(context, "force"); - } catch (Exception ignored) {} - ServerPlayer player = source.getPlayerOrException(); - ServerPlayer target = EntityArgument.getPlayer(context, "player"); - String layerString = AnimationLayerArgument.getLayer(context, "layer"); - String animString = AnimationArgument.getAnimation(context, "anim"); - ResourceLocation layer = new ResourceLocation(layerString); - ResourceLocation anim = new ResourceLocation(animString); - boolean finalForce = force; - - //test info present - boolean animationPresent = AnimationUtils.isAnimationPresent(anim); - boolean animationLayerPresent = AnimationUtils.isAnimationLayerPresent(layer); - if(!animationLayerPresent || !animationPresent) throw new Exception(); - - //update static cache - Map inviteRecordMap = invites.getOrDefault(player.getUUID(), new HashMap<>()); - inviteRecordMap.put(target.getUUID(), new InviteRecord(System.currentTimeMillis(), layer, anim, finalForce)); - invites.put(player.getUUID(), inviteRecordMap); - - //send message - Component name = player.getName(); - Style pStyle = Style.EMPTY.withBold(true).withColor(ChatFormatting.GREEN).withClickEvent( - new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/dance invite accept " + player.getName().getString()) - ); - target.sendSystemMessage(name.copy().append("邀请你跳一支舞。").append(Component.literal("单击此处同意.").setStyle(pStyle))); - source.sendSuccess(() -> Component.literal("命令执行成功. 已发送邀请").withStyle(ChatFormatting.GREEN), true); - } catch (Exception e) { - source.sendFailure(Component.literal("Command run fail.").withStyle(ChatFormatting.RED)); - return 0; - } - return 1; - } - - private static int acceptInvite(CommandContext context) { - CommandSourceStack source = context.getSource(); - try { - ServerPlayer target = source.getPlayerOrException(); - ServerPlayer player = EntityArgument.getPlayer(context, "player"); - - Map inviteRecordMap = invites.getOrDefault(player.getUUID(), null); - if(inviteRecordMap == null) throw new Exception(); - InviteRecord inviteRecord = inviteRecordMap.getOrDefault(target.getUUID(), null); - if(inviteRecord == null) throw new Exception(); - long now = System.currentTimeMillis(); - if(now - inviteRecord.time > 120000) { - source.sendFailure(Component.literal("邀请已超时(2分钟).").withStyle(ChatFormatting.RED)); - player.sendSystemMessage(target.getName().copy().append("接受了你的舞蹈邀请. 但是邀请超时了(2分钟).").withStyle(ChatFormatting.RED)); - return 0; - } - if(player.position().distanceToSqr(target.position()) > 64) { - source.sendFailure(Component.literal("你们距离太远了(8格).").withStyle(ChatFormatting.RED)); - player.sendSystemMessage(target.getName().copy().append("接受了你的舞蹈邀请. 但你们距离太远了(8格).").withStyle(ChatFormatting.RED)); - return 0; - } - inviteRecordMap.remove(target.getUUID()); - invites.put(player.getUUID(), inviteRecordMap); - AnimationUtils.startAnimationTogether(player, inviteRecord.layer, inviteRecord.animation, inviteRecord.isForce, target); - source.sendSuccess(() -> Component.literal("已接受邀请.").withStyle(ChatFormatting.GREEN), true); - player.sendSystemMessage(target.getName().copy().append("已接受你的舞蹈邀请.").withStyle(ChatFormatting.GREEN)); - } catch (Exception e) { - source.sendFailure(Component.literal("Command run fail.").withStyle(ChatFormatting.RED)); - return 0; - } - return 1; - } -} diff --git a/src/main/java/com/linearpast/sccore/example/animation/event/ExamplePlayerAttackEvent.java b/src/main/java/com/linearpast/sccore/example/animation/event/ExamplePlayerAttackEvent.java index bcae68b..9df60f9 100644 --- a/src/main/java/com/linearpast/sccore/example/animation/event/ExamplePlayerAttackEvent.java +++ b/src/main/java/com/linearpast/sccore/example/animation/event/ExamplePlayerAttackEvent.java @@ -1,8 +1,10 @@ package com.linearpast.sccore.example.animation.event; -import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.helper.AnimationHelper; +import com.linearpast.sccore.animation.helper.RawAnimationHelper; import com.linearpast.sccore.example.animation.ModAnimation; import net.minecraft.client.Minecraft; +import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.player.LocalPlayer; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; @@ -24,16 +26,33 @@ public class ExamplePlayerAttackEvent { Player entity = event.getEntity(); if(entity instanceof ServerPlayer player) { if(target instanceof Sheep){ - ResourceLocation playing = AnimationUtils.getAnimationPlaying(player, ModAnimation.normalLayers); + ResourceLocation playing = AnimationHelper.INSTANCE.getAnimationPlaying(player, ModAnimation.normalLayers); if(playing == null) { - AnimationUtils.playAnimation(player, ModAnimation.normalLayers, ModAnimation.AmStandToLying); + AnimationHelper.INSTANCE.playAnimation(player, ModAnimation.normalLayers, ModAnimation.AmStandToLying); } else { - AnimationUtils.playAnimation(player, ModAnimation.normalLayers, null); + AnimationHelper.INSTANCE.removeAnimation(player, ModAnimation.normalLayers); } } } } + public static void rawAnimationAttack(AttackEntityEvent event) { + Entity target = event.getTarget(); + Player entity = event.getEntity(); + if(entity instanceof AbstractClientPlayer player && target instanceof AbstractClientPlayer targetPlayer) { + if(player == Minecraft.getInstance().player){ + RawAnimationHelper.INSTANCE.invite( + ModAnimation.normalLayers, + ModAnimation.WaltzGentleman, + targetPlayer + ); + } + } + if(entity instanceof ServerPlayer player && target instanceof ServerPlayer targetPlayer) { + RawAnimationHelper.INSTANCE.acceptInvite(player, targetPlayer); + } + } + /** * when press "/", this will run * @param event event @@ -44,9 +63,9 @@ public class ExamplePlayerAttackEvent { LocalPlayer player = instance.player; if (player == null) return; if(instance.options.keyCommand.isDown()) { - ResourceLocation playing = AnimationUtils.getAnimationPlaying(player, ModAnimation.normalLayers); + ResourceLocation playing = AnimationHelper.INSTANCE.getAnimationPlaying(player, ModAnimation.normalLayers); if(playing == null) { - AnimationUtils.playAnimationWithRide(null, ModAnimation.normalLayers, ModAnimation.AmLyingToRightLying, true); + AnimationHelper.INSTANCE.playAnimationWithRide((AbstractClientPlayer) null, ModAnimation.normalLayers, ModAnimation.AmLyingToRightLying, true); } } } diff --git a/src/main/java/com/linearpast/sccore/example/capability/data/SheepDataCapability.java b/src/main/java/com/linearpast/sccore/example/capability/data/SheepDataCapability.java index 92535fc..23ce494 100644 --- a/src/main/java/com/linearpast/sccore/example/capability/data/SheepDataCapability.java +++ b/src/main/java/com/linearpast/sccore/example/capability/data/SheepDataCapability.java @@ -10,7 +10,6 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.animal.Sheep; -import org.jetbrains.annotations.Nullable; import java.util.Optional; @@ -76,20 +75,32 @@ public class SheepDataCapability extends SimpleEntityCapabilitySync imple * @see SimpleCapabilityPacket */ public static class SheepCapabilityPacket extends SimpleCapabilityPacket { - public SheepCapabilityPacket(CompoundTag data) { - super(data); - } - + /** + * You must override the constructor of FriendlyByteBuf + * @param buf FriendlyByteBuf + */ public SheepCapabilityPacket(FriendlyByteBuf buf) { super(buf); } - @Override - public @Nullable SheepDataCapability getCapability(Sheep entity) { - return SheepDataCapability.getCapability(entity).orElse(null); + /** + * More, you must override a constructor to help you create an instance + * @param data Cap data + */ + public SheepCapabilityPacket(SheepDataCapability data) { + super(data); } } + /** + * Return the capability key + * @return The capability key + */ + @Override + public ResourceLocation getKey() { + return new ResourceLocation(SnowyCrescentCore.MODID, "sheep_data"); + } + /** * Get the default network packet, which will be called when sendToClient sends it * @return network packet @@ -97,7 +108,7 @@ public class SheepDataCapability extends SimpleEntityCapabilitySync imple */ @Override public SimpleCapabilityPacket getDefaultPacket() { - return new SheepCapabilityPacket(serializeNBT()); + return new SheepCapabilityPacket(this); } /** diff --git a/src/main/java/com/linearpast/sccore/mixin/SCCoreMixinPlugin.java b/src/main/java/com/linearpast/sccore/mixin/SCCoreMixinPlugin.java index 88443d1..759c9d6 100644 --- a/src/main/java/com/linearpast/sccore/mixin/SCCoreMixinPlugin.java +++ b/src/main/java/com/linearpast/sccore/mixin/SCCoreMixinPlugin.java @@ -1,7 +1,7 @@ package com.linearpast.sccore.mixin; import com.linearpast.sccore.SnowyCrescentCore; -import com.linearpast.sccore.animation.AnimationUtils; +import com.linearpast.sccore.animation.helper.AnimationHelper; import net.minecraftforge.fml.ModList; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; @@ -27,7 +27,7 @@ public class SCCoreMixinPlugin implements IMixinConfigPlugin { return "runData".equals(System.getProperty("gradle.task")); } if (mixinClassName.startsWith("com\\.linearpast\\." + SnowyCrescentCore.MODID + "\\.mixin\\.animation")) { - return ModList.get().isLoaded(AnimationUtils.AnimModId); + return ModList.get().isLoaded(AnimationHelper.AnimModId); } return true; } diff --git a/src/main/java/com/linearpast/sccore/mixin/animation/MixinEntity.java b/src/main/java/com/linearpast/sccore/mixin/animation/MixinEntity.java index 66005d0..05a56a3 100644 --- a/src/main/java/com/linearpast/sccore/mixin/animation/MixinEntity.java +++ b/src/main/java/com/linearpast/sccore/mixin/animation/MixinEntity.java @@ -1,9 +1,9 @@ package com.linearpast.sccore.mixin.animation; -import com.linearpast.sccore.animation.AnimationUtils; import com.linearpast.sccore.animation.capability.AnimationDataCapability; import com.linearpast.sccore.animation.capability.inter.IAnimationCapability; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.GenericAnimationData; +import com.linearpast.sccore.animation.helper.AnimationHelper; import com.llamalad7.mixinextras.injector.ModifyReturnValue; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; @@ -36,7 +36,7 @@ public abstract class MixinEntity { if(data == null) return; Float camYModifier = null; for (ResourceLocation value : data.getAnimations().values()) { - Animation animation = AnimationUtils.getAnimation(value); + GenericAnimationData animation = AnimationHelper.INSTANCE.getAnimation(value); if(animation == null) continue; float animationCamY = animation.getCamY(); if(camYModifier == null) camYModifier = animationCamY; @@ -56,7 +56,7 @@ public abstract class MixinEntity { private void redefinedBoundingBox(CallbackInfoReturnable cir){ Entity self = Entity.class.cast(this); if(self instanceof Player player){ - float heightModifier = AnimationUtils.getHeightModifier(player); + float heightModifier = AnimationHelper.INSTANCE.getHeightModifier(player); if(heightModifier == 1.0f) return; double modifyHeight = 1.8f * heightModifier; cir.setReturnValue(this.bb.setMaxY(modifyHeight + this.bb.minY)); @@ -70,7 +70,7 @@ public abstract class MixinEntity { private float redefinedBbHeight(float original){ Entity self = Entity.class.cast(this); if(self instanceof Player player){ - float heightModifier = AnimationUtils.getHeightModifier(player); + float heightModifier = AnimationHelper.INSTANCE.getHeightModifier(player); if(heightModifier == 1.0f) return original; return original * heightModifier; } @@ -87,7 +87,7 @@ public abstract class MixinEntity { private void redefinedPose(CallbackInfoReturnable cir){ Entity self = Entity.class.cast(this); if(self instanceof Player player){ - float heightModifier = AnimationUtils.getHeightModifier(player); + float heightModifier = AnimationHelper.INSTANCE.getHeightModifier(player); if(heightModifier == 1.0f) return; setPose(Pose.STANDING); cir.setReturnValue(Pose.STANDING); diff --git a/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinEntity.java b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinEntity.java index 5db9656..713da24 100644 --- a/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinEntity.java +++ b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinEntity.java @@ -1,7 +1,7 @@ package com.linearpast.sccore.mixin.animation.client; -import com.linearpast.sccore.animation.AnimationUtils; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.GenericAnimationData; +import com.linearpast.sccore.animation.helper.AnimationHelper; import net.minecraft.client.Minecraft; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; @@ -36,7 +36,7 @@ public abstract class MixinEntity { private void turnPosePlayer(double pYRot, double pXRot, CallbackInfo ci) { Entity self = Entity.class.cast(this); if(self instanceof Player player){ - Animation.LyingType lyingType = AnimationUtils.getSideView(player); + GenericAnimationData.LyingType lyingType = AnimationHelper.INSTANCE.getSideView(player); if(lyingType != null && Minecraft.getInstance().options.getCameraType().isFirstPerson()) { float f = (float)pXRot * 0.15F; float f1 = (float)pYRot * 0.15F; diff --git a/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinHumanoidModel.java b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinHumanoidModel.java index e95d084..7820f7c 100644 --- a/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinHumanoidModel.java +++ b/src/main/java/com/linearpast/sccore/mixin/animation/client/MixinHumanoidModel.java @@ -1,7 +1,7 @@ package com.linearpast.sccore.mixin.animation.client; -import com.linearpast.sccore.animation.AnimationUtils; -import com.linearpast.sccore.animation.data.Animation; +import com.linearpast.sccore.animation.data.GenericAnimationData; +import com.linearpast.sccore.animation.helper.AnimationHelper; import net.minecraft.client.model.AgeableListModel; import net.minecraft.client.model.ArmedModel; import net.minecraft.client.model.HeadedModel; @@ -26,7 +26,7 @@ public abstract class MixinHumanoidModel extends Ageable ) private void modifyHeadRot(T pEntity, float pLimbSwing, float pLimbSwingAmount, float pAgeInTicks, float pNetHeadYaw, float pHeadPitch, CallbackInfo ci){ if(pEntity instanceof Player player){ - Animation.LyingType lyingType = AnimationUtils.getSideView(player); + GenericAnimationData.LyingType lyingType = AnimationHelper.INSTANCE.getSideView(player); if(lyingType != null) { float pitch = pHeadPitch - 90.0f; float yaw = pNetHeadYaw * -1.0f; diff --git a/src/main/java/com/linearpast/sccore/utils/ModuleAccess.java b/src/main/java/com/linearpast/sccore/utils/ModuleAccess.java new file mode 100644 index 0000000..5f9a702 --- /dev/null +++ b/src/main/java/com/linearpast/sccore/utils/ModuleAccess.java @@ -0,0 +1,355 @@ +package com.linearpast.sccore.utils; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.lang.reflect.Proxy; +import java.util.UUID; + +/** + * This field is a static method in a bridge class.
+ * The bridge class is generated by static{} statement.
+ * This method invokes JavaLangAccess::addOpens(Module,String,Module) + * to open a package to another module.
+ * It looks like: + *
+ * public static void export(
+ *     Object module,
+ *     Object packageName,
+ *     Object target)
+ * {
+ *     SharedSecrets.getJavaLangAccess().addExports(
+ *         (Module)module,
+ *         (String)packageName,
+ *         (Module)target
+ *     );
+ * }
+ * This used {@link Proxy} to force access + * JavaLangAccess and SharedSecrets.
+ * This field is null in Java 8. + */ +public class ModuleAccess extends ClassLoader +{ + + public static final MethodHandle MODULE; + public static final MethodHandle EXPORT; + public static final MethodHandle OPEN; + public static final MethodHandle READ; + public static final MethodHandles.Lookup LOOKUP; + + public ModuleAccess() + { + super(ModuleAccess.class.getClassLoader()); + } + + public Class loading(byte[] code) + { + Class clazz = this.defineClass(null, code, 0, code.length); + try + { + Class.forName(clazz.getName(), true, this); + } + catch (ClassNotFoundException e) + { + e.printStackTrace(System.out); + } + return clazz; + } + + public static Object module(Class clazz) + { + if (ModuleAccess.MODULE != null) + { + try + { + return ModuleAccess.MODULE.invoke(clazz); + } + catch (Throwable t) + { + exception(t); + } + } + return null; + } + + public static void export(Object module, Object packageName, Object target) + { + if (ModuleAccess.EXPORT != null) + { + try + { + ModuleAccess.EXPORT.invokeExact(module, packageName, target); + } + catch (Throwable t) + { + exception(t); + } + } + } + + /** + * A wrapper method of {@link ModuleAccess#OPEN} without any exception. + * @param module The module of the class you want to access. + * @param packageName The package of the class you want to access. + * @param target The module of your class + */ + public static void open(Object module, Object packageName, Object target) + { + if (ModuleAccess.OPEN != null) + { + try + { + ModuleAccess.OPEN.invokeExact(module, packageName, target); + } + catch (Throwable t) + { + exception(t); + } + } + } + + public static void read(Object module, Object target) + { + if (ModuleAccess.READ != null) + { + try + { + ModuleAccess.READ.invokeExact(module, target); + } + catch (Throwable t) + { + exception(t); + } + } + } + + @SuppressWarnings("unchecked") + public static void exception(Throwable t) throws T + { + throw (T) t; + } + + static + { + try + { + ModuleAccess access = new ModuleAccess(); + /* + * JavaLangAccess class and SharedSecrets class. + * In Java 9 and 10, they are in jdk.internal.misc package. + * And in higher version, they ar in jdk.internal.access package + */ + Class JLA; + Class shared; + try + { + JLA = Class.forName("jdk.internal.access.JavaLangAccess"); + shared = Class.forName("jdk.internal.access.SharedSecrets"); + } + catch(ClassNotFoundException ignored) + { + JLA = Class.forName("jdk.internal.misc.JavaLangAccess"); + shared = Class.forName("jdk.internal.misc.SharedSecrets"); + } + // Use proxy to force access the package of JavaLangAccess in the module of the proxy object. + Object proxy = Proxy.newProxyInstance(access, new Class[]{JLA}, (_1, _2, _3) -> null); + String packageName = proxy.getClass().getPackage().getName(); + // The name of the bridge class is a random UUID. + String uuid = 'Z' + UUID.randomUUID().toString().toUpperCase().replaceAll("-", ""); + // In the same package, this class can also access JLA and SS + String className = packageName.replace('.', '/') + "/" + uuid; + /* + * This bridge class looks like: + * + * package com.sun.proxy.jdk.proxy1; + * + * import jdk.internal.access.JavaLangAccess; + * import jdk.internal.access.SharedSecrets; + * + * public class Bridge + * { + * public static void export(Object module, Object packageName, Object open) + * { + * SharedSecrets.getJavaLangAccess().addExports((Module)module, (String)packageName, (Module)open); + * } + * + * public static void open(Object module, Object packageName, Object open) + * { + * SharedSecrets.getJavaLangAccess().addOpens((Module)module, (String)packageName, (Module)open); + * } + * + * public static void read(Object module, Object packageName, Object open) + * { + * SharedSecrets.getJavaLangAccess().addReads((Module)module, (String)packageName, (Module)open); + * } + * + * static + * { + * Bridge.class.getModule().addExports( + * "com.sun.proxy.jdk.proxy1", + * Class.forName("org.mve.invoke.ModuleAccess").getModule() + * ); + * } + * } + */ + ClassWriter visitor = new ClassWriter(0); + visitor.visit(52, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, className, null, "java/lang/Object", null); + MethodVisitor mv; + + mv = visitor.visitMethod( + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "open", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", + null, null + ); + mv.visitCode(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + shared.getTypeName().replace('.', '/'), + "getJavaLangAccess", + MethodType.methodType(JLA).toMethodDescriptorString(), + false + ); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Module"); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/String"); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Module"); + mv.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + JLA.getTypeName().replace('.', '/'), + "addOpens", + "(Ljava/lang/Module;Ljava/lang/String;Ljava/lang/Module;)V", + true + ); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(4, 3); + mv.visitEnd(); + + mv = visitor.visitMethod( + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "export", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", + null, null + ); + mv.visitCode(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + shared.getTypeName().replace('.', '/'), + "getJavaLangAccess", + MethodType.methodType(JLA).toMethodDescriptorString(), + false + ); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Module"); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/String"); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Module"); + mv.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + JLA.getTypeName().replace('.', '/'), + "addExports", + "(Ljava/lang/Module;Ljava/lang/String;Ljava/lang/Module;)V", + true + ); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(4, 3); + mv.visitEnd(); + + mv = visitor.visitMethod( + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "read", + "(Ljava/lang/Object;Ljava/lang/Object;)V", + null, null + ); + mv.visitCode(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + shared.getTypeName().replace('.', '/'), + "getJavaLangAccess", + MethodType.methodType(JLA).toMethodDescriptorString(), + false + ); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Module"); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Module"); + mv.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + JLA.getTypeName().replace('.', '/'), + "addReads", + "(Ljava/lang/Module;Ljava/lang/Module;)V", + true + ); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(3, 2); + mv.visitEnd(); + + mv = visitor.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null); + mv.visitCode(); + mv.visitLdcInsn(Type.getType("L" + className + ";")); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getModule", "()Ljava/lang/Module;", false); + mv.visitLdcInsn(packageName); + mv.visitLdcInsn(ModuleAccess.class.getTypeName()); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getModule", "()Ljava/lang/Module;", false); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Module", "addExports", "(Ljava/lang/String;Ljava/lang/Module;)Ljava/lang/Module;", false); + mv.visitInsn(Opcodes.POP); + mv.visitLdcInsn(Type.getType("L" + className + ";")); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getModule", "()Ljava/lang/Module;", false); + mv.visitLdcInsn(packageName); + mv.visitLdcInsn(ModuleAccess.class.getTypeName()); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getModule", "()Ljava/lang/Module;", false); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Module", "addOpens", "(Ljava/lang/String;Ljava/lang/Module;)Ljava/lang/Module;", false); + mv.visitInsn(Opcodes.POP); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(3, 0); + mv.visitEnd(); + + visitor.visitEnd(); + byte[] code = visitor.toByteArray(); + + Class clazz = access.loading(code); + ModuleAccess.class.getModule().addReads(clazz.getModule()); + // Use MethodHandle to invoke methods. + MethodHandles.Lookup lookup = MethodHandles.lookup(); + Class moduleClass = Class.forName("java.lang.Module"); + MODULE = lookup.findVirtual(Class.class, "getModule", MethodType.methodType(moduleClass)); + EXPORT = lookup.findStatic( + clazz, + "export", + MethodType.methodType(void.class, Object.class, Object.class, Object.class) + ); + OPEN = lookup.findStatic( + clazz, + "open", + MethodType.methodType(void.class, Object.class, Object.class, Object.class) + ); + READ = lookup.findStatic( + clazz, + "read", + MethodType.methodType(void.class, Object.class, Object.class) + ); + + ModuleAccess.open(MethodHandles.Lookup.class.getModule(), MethodHandles.Lookup.class.getPackageName(), ModuleAccess.class.getModule()); + Field implLookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); + implLookupField.setAccessible(true); + LOOKUP = (MethodHandles.Lookup) implLookupField.get(null); + } + catch (Throwable t) + { + exception(t); + throw new RuntimeException(t); + } + } +} +