├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
└── main
├── java
└── io
│ └── github
│ └── foundationgames
│ └── animatica
│ ├── Animatica.java
│ ├── animation
│ ├── AnimatedTexture.java
│ ├── AnimationLoader.java
│ └── AnimationMeta.java
│ ├── config
│ └── AnimaticaConfig.java
│ ├── mixin
│ ├── IdentifierMixin.java
│ ├── RenderSystemMixin.java
│ └── VideoOptionsScreenMixin.java
│ └── util
│ ├── Flags.java
│ ├── PropertyUtil.java
│ ├── TextureUtil.java
│ ├── Utilities.java
│ └── exception
│ ├── InvalidPropertyException.java
│ ├── MissingPropertyException.java
│ └── PropertyParseException.java
└── resources
├── animatica.mixins.json
├── assets
└── animatica
│ ├── icon.png
│ └── lang
│ ├── en_us.json
│ └── fr_fr.json
└── fabric.mod.json
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | on: [pull_request, push]
3 |
4 | jobs:
5 | build:
6 | strategy:
7 | matrix:
8 | java: [17]
9 | os: [ubuntu-20.04, windows-latest]
10 | runs-on: ${{ matrix.os }}
11 | steps:
12 | - name: checkout repository
13 | uses: actions/checkout@v2
14 | - name: validate gradle wrapper
15 | uses: gradle/wrapper-validation-action@v1
16 | - name: setup jdk ${{ matrix.java }}
17 | uses: actions/setup-java@v1
18 | with:
19 | java-version: ${{ matrix.java }}
20 | - name: make gradle wrapper executable
21 | if: ${{ runner.os != 'Windows' }}
22 | run: chmod +x ./gradlew
23 | - name: build
24 | run: ./gradlew build
25 | - name: capture build artifacts
26 | if: ${{ runner.os == 'Linux' && matrix.java == '17' }}
27 | uses: actions/upload-artifact@v2
28 | with:
29 | name: Artifacts
30 | path: build/libs/
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # gradle
2 |
3 | .gradle/
4 | build/
5 | out/
6 | classes/
7 |
8 | # eclipse
9 |
10 | *.launch
11 |
12 | # idea
13 |
14 | .idea/
15 | *.iml
16 | *.ipr
17 | *.iws
18 |
19 | # vscode
20 |
21 | .settings/
22 | .vscode/
23 | bin/
24 | .classpath
25 | .project
26 |
27 | # macos
28 |
29 | *.DS_Store
30 |
31 | # fabric
32 |
33 | run/
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
167 | Copyright 2021 FoundationGames, LGPLv3
168 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Animatica
2 | A Minecraft mod for the Fabric mod loader intended to load the MCPatcher/OptiFine [animated texture format](https://github.com/sp614x/optifine/blob/master/OptiFineDoc/doc/custom_animations.txt).
3 | ## What resource packs does this support?
4 | Animatica adds support for resource packs that use **custom animated textures**
5 | This includes:
6 | - Animated GUI textures
7 | - Animated entity textures
8 | - Animated block entity textures
9 | - Animated armor textures
10 | - Other animated textures (Animated blocks and items are a vanilla feature)
11 | ## Animatica DOES NOT support:
12 | - Fully Custom GUI ([OptiGUI for Fabric](https://www.curseforge.com/minecraft/mc-mods/optigui))
13 | - Custom entity models ([CEM for Fabric](https://www.curseforge.com/minecraft/mc-mods/custom-entity-models-cem))
14 | - Custom entity model part animations in 3D ([CEM for Fabric](https://www.curseforge.com/minecraft/mc-mods/custom-entity-models-cem))
15 | - Any other MCPatcher/OptiFine resource pack feature such as connected textures, etc
16 |
17 | **These features WILL NEVER be implemented in Animatica.**
18 | ### Animatica is ONLY for ANIMATED TEXTURES.
19 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'fabric-loom' version '1.2-SNAPSHOT'
3 | }
4 |
5 | sourceCompatibility = JavaVersion.VERSION_17
6 | targetCompatibility = JavaVersion.VERSION_17
7 |
8 | archivesBaseName = project.archives_base_name
9 | version = project.mod_version
10 | group = project.maven_group
11 |
12 | repositories {
13 | }
14 |
15 | dependencies {
16 | // To change the versions see the gradle.properties file
17 | minecraft "com.mojang:minecraft:${project.minecraft_version}"
18 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
19 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
20 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
21 | }
22 |
23 | processResources {
24 | inputs.property "version", project.version
25 |
26 | filesMatching("fabric.mod.json") {
27 | expand "version": project.version
28 | }
29 | }
30 |
31 | tasks.withType(JavaCompile).configureEach {
32 | it.options.encoding = "UTF-8"
33 | it.options.release = 17
34 | }
35 |
36 | java {
37 | withSourcesJar()
38 | }
39 |
40 | jar {
41 | from("LICENSE") {
42 | rename { "${it}_${project.archivesBaseName}"}
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1G
2 |
3 | minecraft_version=1.20.1
4 | yarn_mappings=1.20.1+build.3
5 | loader_version=0.14.21
6 |
7 | #Fabric api
8 | fabric_version=0.83.1+1.20.1
9 |
10 | mod_version = 0.6+1.20
11 | maven_group = io.github.foundationgames
12 | archives_base_name = animatica
13 |
14 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FoundationGames/Animatica/2d08028eb0ae350f096c32f324a852770a4bc710/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MSYS* | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | maven {
4 | name = 'Fabric'
5 | url = 'https://maven.fabricmc.net/'
6 | }
7 | gradlePluginPortal()
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/Animatica.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica;
2 |
3 | import io.github.foundationgames.animatica.animation.AnimationLoader;
4 | import io.github.foundationgames.animatica.config.AnimaticaConfig;
5 | import net.fabricmc.api.ClientModInitializer;
6 | import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
7 | import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
8 | import net.minecraft.resource.ResourceType;
9 | import net.minecraft.util.Identifier;
10 | import org.apache.logging.log4j.LogManager;
11 | import org.apache.logging.log4j.Logger;
12 |
13 | public class Animatica implements ClientModInitializer {
14 | public static final Logger LOG = LogManager.getLogger("Animatica");
15 | public static final String NAMESPACE = "animatica";
16 |
17 | public static final AnimaticaConfig CONFIG = new AnimaticaConfig();
18 |
19 | @Override
20 | public void onInitializeClient() {
21 | ClientTickEvents.START_CLIENT_TICK.register(client -> AnimationLoader.INSTANCE.tickTextures());
22 |
23 | ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(AnimationLoader.INSTANCE);
24 | }
25 |
26 | public static Identifier id(String path) {
27 | return new Identifier(NAMESPACE, path);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/animation/AnimatedTexture.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.animation;
2 |
3 | import com.google.common.collect.ImmutableList;
4 | import io.github.foundationgames.animatica.Animatica;
5 | import io.github.foundationgames.animatica.util.TextureUtil;
6 | import net.minecraft.client.texture.NativeImage;
7 | import net.minecraft.client.texture.NativeImageBackedTexture;
8 | import net.minecraft.resource.ResourceManager;
9 | import net.minecraft.util.Identifier;
10 | import net.minecraft.util.math.MathHelper;
11 |
12 | import java.io.IOException;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.Optional;
16 |
17 | public class AnimatedTexture extends NativeImageBackedTexture {
18 | public final Animation[] anims;
19 | private final NativeImage original;
20 | private int frame = 0;
21 |
22 | public static Optional tryCreate(ResourceManager resources, Identifier targetTexId, List anims) {
23 | try (var targetTexResource = resources.getResourceOrThrow(targetTexId).getInputStream()) {
24 | return Optional.of(new AnimatedTexture(resources, anims, NativeImage.read(targetTexResource)));
25 | } catch (IOException e) { Animatica.LOG.error(e); }
26 |
27 | return Optional.empty();
28 | }
29 |
30 | public AnimatedTexture(ResourceManager resources, List metas, NativeImage image) throws IOException {
31 | super(new NativeImage(image.getFormat(), image.getWidth(), image.getHeight(), true));
32 |
33 | this.anims = new Animation[metas.size()];
34 | for (int i = 0; i < metas.size(); i++) {
35 | this.anims[i] = new Animation(metas.get(i), resources);
36 | }
37 | this.original = image;
38 |
39 | updateAndDraw(this.getImage(), true);
40 | this.upload();
41 | }
42 |
43 | public boolean canLoop() {
44 | for (var anim : anims) {
45 | if (!anim.isOnFrameZero()) {
46 | return false;
47 | }
48 | }
49 | // All animations for this texture are at zero again, so the frame counter can be reset
50 | return true;
51 | }
52 |
53 | public boolean updateAndDraw(NativeImage image, boolean force) {
54 | boolean changed = false;
55 |
56 | if (canLoop()) {
57 | if (frame > 0) {
58 | frame = 0;
59 | }
60 | } else if (frame <= 0) {
61 | changed = true;
62 | }
63 |
64 | for (var anim : anims) {
65 | if (anim.isChanged()) {
66 | changed = true;
67 | break;
68 | }
69 | }
70 |
71 | if (changed || force) {
72 | image.copyFrom(this.original);
73 |
74 | Phase phase;
75 | for (var anim : anims) {
76 | phase = anim.getCurrentPhase();
77 | if (phase instanceof InterpolatedPhase iPhase) {
78 | TextureUtil.blendCopy(anim.sourceTexture, 0, iPhase.prevV, 0, iPhase.v, anim.width, anim.height, image, anim.targetX, anim.targetY, iPhase.blend.getBlend(anim.getPhaseFrame()));
79 | } else {
80 | TextureUtil.copy(anim.sourceTexture, 0, phase.v, anim.width, anim.height, image, anim.targetX, anim.targetY);
81 | }
82 | }
83 | }
84 |
85 | for (var anim : anims) {
86 | anim.advance();
87 | }
88 | frame++;
89 |
90 | return changed;
91 | }
92 |
93 | public void tick() {
94 | if (this.updateAndDraw(this.getImage(), false)) {
95 | this.upload();
96 | }
97 | }
98 |
99 | @Override
100 | public void close() {
101 | for (var anim : anims) {
102 | anim.close();
103 | }
104 |
105 | this.original.close();
106 | super.close();
107 | }
108 |
109 | // Represents an active animation from an animation meta file; progresses through phases while being drawn
110 | public static class Animation implements AutoCloseable {
111 | private final List phases;
112 | public final NativeImage sourceTexture;
113 | public final int targetX;
114 | public final int targetY;
115 | public final int width;
116 | public final int height;
117 | private final int duration;
118 |
119 | private int frame = 0;
120 | private Phase currentPhase = null;
121 | private int phaseFrame = 0;
122 | private boolean changed = true;
123 |
124 | // Assembles all animation phases for one texture animation being baked
125 | public Animation(AnimationMeta meta, ResourceManager resources) throws IOException {
126 | this.targetX = meta.targetX();
127 | this.targetY = meta.targetY();
128 | this.width = meta.width();
129 | this.height = meta.height();
130 |
131 | try (var source = resources.getResourceOrThrow(meta.source()).getInputStream()) {
132 | this.sourceTexture = NativeImage.read(source);
133 | }
134 |
135 | var phases = ImmutableList.builder();
136 | int duration = 0;
137 |
138 | final int textureFrameCount = (int)Math.floor((float) sourceTexture.getHeight() / meta.height());
139 | final int animFrameCount = Math.max(textureFrameCount, meta.getGreatestUsedFrame() + 1);
140 |
141 | // The int array stored for each frame must contain the frame mapping and duration
142 | List frames = new ArrayList<>();
143 | for (int f = 0; f < animFrameCount; f++) {
144 | if (f >= textureFrameCount && !meta.frameMapping().containsKey(f)) {
145 | continue;
146 | }
147 |
148 | frames.add(new int[] {
149 | meta.frameMapping().getOrDefault(f, f),
150 | meta.frameDurations().getOrDefault(f, meta.defaultFrameDuration())
151 | });
152 | }
153 |
154 | for (int i = 0; i < frames.size(); i++) {
155 | int[] frame = frames.get(i);
156 |
157 | int fMap = frame[0];
158 | int fDuration = frame[1];
159 |
160 | int v = getVForFrame(fMap, textureFrameCount);
161 | int nextV = getVForFrame(frames.get(Math.floorMod(i + 1, frames.size()))[0], textureFrameCount);
162 |
163 | if (meta.interpolate()) {
164 | if (meta.interpolationDelay() > 0) {
165 | // Adds a static version of the current phase as a "delay" before the next interpolated phase (if specified in animation)
166 | phases.add(new Phase(meta.interpolationDelay(), v));
167 | duration += meta.interpolationDelay();
168 | }
169 |
170 | // Add interpolated animation phase
171 | final int interpolatedDuration = fDuration - meta.interpolationDelay();
172 | phases.add(new InterpolatedPhase(interpolatedDuration, v, nextV, (phaseFrame) -> ((float) phaseFrame / interpolatedDuration)));
173 | duration += interpolatedDuration;
174 | } else {
175 | phases.add(new Phase(fDuration, v));
176 | duration += fDuration;
177 | }
178 | }
179 |
180 | this.duration = duration;
181 | this.phases = phases.build();
182 |
183 | updateCurrentPhase();
184 | }
185 |
186 | public void updateCurrentPhase() {
187 | changed = false;
188 | int progress = frame;
189 |
190 | for (var phase : phases) {
191 | progress -= phase.duration; // Take away as much progress as each phase is long, until progress is below zero
192 | if (progress < 0) {
193 | if (currentPhase != phase) {
194 | // Marks baking anim as changed should it be in a new, unique phase
195 | changed = true;
196 | }
197 | if (phase instanceof InterpolatedPhase iPhase) changed = iPhase.hasChangingV(); // Marks baking anim as changed should its current phase be changing
198 |
199 | this.currentPhase = phase;
200 | this.phaseFrame = phase.duration + progress; // Adding progress to the phase duration results in how far it is into the phase
201 |
202 | return;
203 | }
204 | }
205 | }
206 |
207 | public Phase getCurrentPhase() {
208 | return currentPhase;
209 | }
210 |
211 | public int getPhaseFrame() {
212 | return phaseFrame;
213 | }
214 |
215 | public boolean isOnFrameZero() {
216 | return frame <= 0;
217 | }
218 |
219 | public boolean isChanged() {
220 | return changed;
221 | }
222 |
223 | public void advance() {
224 | frame++;
225 | if (frame >= duration) {
226 | frame = 0;
227 | }
228 | updateCurrentPhase();
229 | }
230 |
231 | @Override
232 | public void close() {
233 | this.sourceTexture.close();
234 | }
235 |
236 | private int getVForFrame(int frame, int textureFrameCount) {
237 | return MathHelper.clamp(frame * this.height, 0, (textureFrameCount - 1) * this.height);
238 | }
239 | }
240 |
241 | // Represents a phase that an animation is in (loosely defined by the animation file's "tile"s)
242 | // Base class represents the simplest possible type of animation phase, which only needs to generate
243 | // one texture to be used over a period of time
244 | public static class Phase {
245 | public final int duration;
246 | public final int v;
247 |
248 | public Phase(int duration, int v) {
249 | this.duration = duration;
250 | this.v = v;
251 | }
252 |
253 | @Override
254 | public String toString() {
255 | return "Animation Bakery Phase { v: "+this.v+" }";
256 | }
257 | }
258 |
259 | // A phase that blends between its previous phase and itself, requiring the generation of many
260 | // more textures to construct the blend animation
261 | public static class InterpolatedPhase extends Phase {
262 | public final int prevV;
263 | public final BlendInterpolator blend;
264 |
265 | public InterpolatedPhase(int duration, int v1, int v2, BlendInterpolator blend) {
266 | super(duration, v2);
267 | this.prevV = v1;
268 | this.blend = blend;
269 | }
270 |
271 | public boolean hasChangingV() {
272 | return this.prevV != this.v;
273 | }
274 | }
275 |
276 | @FunctionalInterface
277 | public interface BlendInterpolator {
278 | float getBlend(int phaseFrame);
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/animation/AnimationLoader.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.animation;
2 |
3 | import com.mojang.blaze3d.systems.RenderSystem;
4 | import io.github.foundationgames.animatica.Animatica;
5 | import io.github.foundationgames.animatica.util.Flags;
6 | import io.github.foundationgames.animatica.util.exception.PropertyParseException;
7 | import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener;
8 | import net.minecraft.client.MinecraftClient;
9 | import net.minecraft.resource.Resource;
10 | import net.minecraft.resource.ResourceManager;
11 | import net.minecraft.util.Identifier;
12 | import org.jetbrains.annotations.Nullable;
13 |
14 | import java.io.IOException;
15 | import java.util.ArrayList;
16 | import java.util.HashMap;
17 | import java.util.HashSet;
18 | import java.util.List;
19 | import java.util.Map;
20 | import java.util.Properties;
21 | import java.util.Set;
22 | import java.util.function.BiConsumer;
23 |
24 | public final class AnimationLoader implements SimpleSynchronousResourceReloadListener {
25 | public static final String[] ANIM_PATHS = {
26 | "animatica/anim",
27 | "mcpatcher/anim",
28 | "optifine/anim"
29 | };
30 | private static final Identifier ID = Animatica.id("animation_storage");
31 |
32 | public static final AnimationLoader INSTANCE = new AnimationLoader();
33 |
34 | private final Map animationIds = new HashMap<>();
35 | private final Set animatedTextures = new HashSet<>();
36 |
37 | private AnimationLoader() {
38 | }
39 |
40 | private static void findAllMCPAnimations(ResourceManager manager, BiConsumer action) {
41 | for (var path : ANIM_PATHS) {
42 | manager.findResources(path, p -> p.getPath().endsWith(".properties")).forEach(action);
43 | }
44 | }
45 |
46 | public @Nullable Identifier getAnimationId(Identifier id) {
47 | return animationIds.get(id);
48 | }
49 |
50 | public void tickTextures() {
51 | if (!RenderSystem.isOnRenderThread()) {
52 | RenderSystem.recordRenderCall(this::tickTextures);
53 | } else {
54 | for (var texture : animatedTextures) {
55 | texture.tick();
56 | }
57 | }
58 | }
59 |
60 | @Override
61 | public Identifier getFabricId() {
62 | return ID;
63 | }
64 |
65 | @Override
66 | public void reload(ResourceManager manager) {
67 | this.animatedTextures.clear();
68 | this.animationIds.clear();
69 |
70 | if (!Animatica.CONFIG.animatedTextures) {
71 | return;
72 | }
73 |
74 | Flags.ALLOW_INVALID_ID_CHARS = true;
75 |
76 | var animations = new HashMap>();
77 |
78 | findAllMCPAnimations(manager, (id, resource) -> {
79 | try {
80 | try (var resourceInputStream = resource.getInputStream()) {
81 | var ppt = new Properties();
82 | ppt.load(resourceInputStream);
83 |
84 | var anim = AnimationMeta.of(id, ppt);
85 |
86 | var targetId = anim.target();
87 | if (!animations.containsKey(targetId)) animations.put(targetId, new ArrayList<>());
88 | animations.get(targetId).add(anim);
89 | }
90 | } catch (IOException | PropertyParseException e) {
91 | Animatica.LOG.error(e.getMessage());
92 | }
93 | });
94 |
95 | for (var targetId : animations.keySet()) {
96 | AnimatedTexture.tryCreate(manager, targetId, animations.get(targetId))
97 | .ifPresent(tex -> {
98 | var animId = new Identifier(targetId.getNamespace(), targetId.getPath() + "-anim");
99 | this.animationIds.put(targetId, animId);
100 | this.animatedTextures.add(tex);
101 | tex.registerTexture(MinecraftClient.getInstance().getTextureManager(), manager, animId, MinecraftClient.getInstance());
102 | });
103 | }
104 |
105 | Flags.ALLOW_INVALID_ID_CHARS = false;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/animation/AnimationMeta.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.animation;
2 |
3 | import io.github.foundationgames.animatica.util.PropertyUtil;
4 | import io.github.foundationgames.animatica.util.Utilities;
5 | import io.github.foundationgames.animatica.util.exception.InvalidPropertyException;
6 | import io.github.foundationgames.animatica.util.exception.PropertyParseException;
7 | import net.minecraft.util.Identifier;
8 | import net.minecraft.util.InvalidIdentifierException;
9 |
10 | import java.util.HashSet;
11 | import java.util.Map;
12 | import java.util.Properties;
13 | import java.util.Set;
14 |
15 | public record AnimationMeta(
16 | Identifier source, Identifier target, int targetX,
17 | int targetY, int width, int height, int defaultFrameDuration, boolean interpolate,
18 | int interpolationDelay, Map frameMapping,
19 | Map frameDurations
20 | ) {
21 | public static AnimationMeta of(Identifier file, Properties properties) throws PropertyParseException {
22 | Identifier source;
23 | Identifier target;
24 | try {
25 | source = Utilities.processPath(file, new Identifier(PropertyUtil.get(file, properties, "from")));
26 | } catch (InvalidIdentifierException ex) { throw new InvalidPropertyException(file, "from", "resource location"); }
27 | try {
28 | target = Utilities.processPath(file, new Identifier(PropertyUtil.get(file, properties, "to")));
29 | } catch (InvalidIdentifierException ex) { throw new InvalidPropertyException(file, "to", "resource location"); }
30 | return new AnimationMeta(
31 | source,
32 | target,
33 | PropertyUtil.getInt(file, properties, "x"),
34 | PropertyUtil.getInt(file, properties, "y"),
35 | PropertyUtil.getInt(file, properties, "w"),
36 | PropertyUtil.getInt(file, properties, "h"),
37 | PropertyUtil.getIntOr(file, properties, "duration", 1),
38 | PropertyUtil.getBoolOr(file, properties, "interpolate", false),
39 | PropertyUtil.getIntOr(file, properties, "skip", 0),
40 | PropertyUtil.intToIntMap(PropertyUtil.getSubProperties(properties, "tile")),
41 | PropertyUtil.intToIntMap(PropertyUtil.getSubProperties(properties, "duration"))
42 | );
43 | }
44 |
45 | public int getGreatestUsedFrame() {
46 | Set frames = new HashSet<>(frameMapping.keySet());
47 | frames.addAll(frameDurations.keySet());
48 |
49 | int greatestFrame = 0;
50 | for (int frame : frames) {
51 | greatestFrame = Math.max(frame, greatestFrame);
52 | }
53 |
54 | return greatestFrame;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/config/AnimaticaConfig.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.config;
2 |
3 | import io.github.foundationgames.animatica.Animatica;
4 | import net.fabricmc.loader.api.FabricLoader;
5 | import net.minecraft.client.MinecraftClient;
6 | import net.minecraft.client.option.SimpleOption;
7 |
8 | import java.io.IOException;
9 | import java.nio.file.Files;
10 | import java.nio.file.Path;
11 | import java.util.Properties;
12 |
13 | public class AnimaticaConfig {
14 | public static String ANIMATED_TEXTURES_KEY = "animated_textures";
15 |
16 | public static final String FILE_NAME = "animatica.properties";
17 |
18 | private final SimpleOption animatedTexturesOption;
19 | public boolean animatedTextures;
20 |
21 | public AnimaticaConfig() {
22 | try {
23 | load();
24 | } catch (IOException e) {
25 | Animatica.LOG.error("Error loading config during initialization!", e);
26 | }
27 |
28 | this.animatedTexturesOption = SimpleOption.ofBoolean(
29 | "option.animatica.animated_textures",
30 | this.animatedTextures,
31 | value -> {
32 | this.animatedTextures = value;
33 | try {
34 | this.save();
35 | } catch (IOException e) { Animatica.LOG.error("Error saving config while changing in game!", e); }
36 | MinecraftClient.getInstance().reloadResources();
37 | }
38 | );
39 | }
40 |
41 | public void writeTo(Properties properties) {
42 | properties.put(ANIMATED_TEXTURES_KEY, Boolean.toString(animatedTextures));
43 | }
44 |
45 | public void readFrom(Properties properties) {
46 | this.animatedTextures = boolFrom(properties.getProperty(ANIMATED_TEXTURES_KEY), true);
47 | }
48 |
49 | public Path getFile() throws IOException {
50 | var file = FabricLoader.getInstance().getConfigDir().resolve(FILE_NAME);
51 | if (!Files.exists(file)) {
52 | Files.createFile(file);
53 | }
54 |
55 | return file;
56 | }
57 |
58 | public SimpleOption getAnimatedTexturesOption() {
59 | return animatedTexturesOption;
60 | }
61 |
62 | public void save() throws IOException {
63 | var file = getFile();
64 | var properties = new Properties();
65 |
66 | writeTo(properties);
67 |
68 | try (var out = Files.newOutputStream(file)) {
69 | properties.store(out, "Configuration file for Animatica");
70 | }
71 | }
72 |
73 | public void load() throws IOException {
74 | var file = getFile();
75 | var properties = new Properties();
76 |
77 | try (var in = Files.newInputStream(file)) {
78 | properties.load(in);
79 | }
80 |
81 | readFrom(properties);
82 | }
83 |
84 | private static boolean boolFrom(String s, boolean defaultVal) {
85 | return s == null ? defaultVal : "true".equals(s);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/mixin/IdentifierMixin.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.mixin;
2 |
3 | import io.github.foundationgames.animatica.Animatica;
4 | import io.github.foundationgames.animatica.util.Flags;
5 | import net.minecraft.util.Identifier;
6 | import org.spongepowered.asm.mixin.Mixin;
7 | import org.spongepowered.asm.mixin.injection.At;
8 | import org.spongepowered.asm.mixin.injection.Inject;
9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
11 |
12 | // Allows invalid characters in paths to support packs with extremely outdated formatting (because OptiFine does too)
13 | @Mixin(Identifier.class)
14 | public class IdentifierMixin {
15 | @Inject(method = "([Ljava/lang/String;)V", at = @At("TAIL"))
16 | private void animatica$reportInvalidIdentifierCharacters(String[] id, CallbackInfo ci) {
17 | if (Flags.ALLOW_INVALID_ID_CHARS && !animatica$isPathAllowed(id[1]) && !id[1].startsWith("~/")) {
18 | Animatica.LOG.warn("Legacy resource pack is using an invalid namespaced identifier '{}:{}'! DO NOT use non [a-z0-9_.-] characters for resource pack files and file names!", id[0], id[1]);
19 | }
20 | }
21 |
22 | @Inject(method = "isPathCharacterValid", at = @At("RETURN"), cancellable = true)
23 | private static void animatica$allowInvalidCharacters(char character, CallbackInfoReturnable cir) {
24 | if (Flags.ALLOW_INVALID_ID_CHARS) {
25 | cir.setReturnValue(true);
26 | }
27 | }
28 |
29 | private static boolean animatica$isPathAllowed(String path) {
30 | if (path == null) return true;
31 | for (char c : path.toCharArray()) {
32 | if (!(c == '_' || c == '-' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '/' || c == '.')) {
33 | return false;
34 | }
35 | }
36 | return true;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/mixin/RenderSystemMixin.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.mixin;
2 |
3 | import com.mojang.blaze3d.systems.RenderSystem;
4 | import io.github.foundationgames.animatica.Animatica;
5 | import io.github.foundationgames.animatica.animation.AnimationLoader;
6 | import net.minecraft.util.Identifier;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.injection.At;
9 | import org.spongepowered.asm.mixin.injection.ModifyVariable;
10 |
11 | @Mixin(RenderSystem.class)
12 | public class RenderSystemMixin {
13 | @ModifyVariable(method = "_setShaderTexture(ILnet/minecraft/util/Identifier;)V", at = @At("HEAD"), index = 1)
14 | private static Identifier animatica$replaceWithAnimatedTexture(Identifier old) {
15 | if (Animatica.CONFIG.animatedTextures) {
16 | var anim = AnimationLoader.INSTANCE.getAnimationId(old);
17 | if (anim != null) {
18 | return anim;
19 | }
20 | }
21 | return old;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/mixin/VideoOptionsScreenMixin.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.mixin;
2 |
3 | import io.github.foundationgames.animatica.Animatica;
4 | import net.minecraft.client.gui.screen.Screen;
5 | import net.minecraft.client.gui.screen.option.VideoOptionsScreen;
6 | import net.minecraft.client.option.SimpleOption;
7 | import net.minecraft.text.Text;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.injection.At;
10 | import org.spongepowered.asm.mixin.injection.ModifyArg;
11 |
12 | @Mixin(VideoOptionsScreen.class)
13 | public abstract class VideoOptionsScreenMixin extends Screen {
14 | protected VideoOptionsScreenMixin(Text title) {
15 | super(title);
16 | }
17 |
18 | @ModifyArg(
19 | method = "init",
20 | at = @At(
21 | value = "INVOKE",
22 | target = "Lnet/minecraft/client/gui/widget/OptionListWidget;addAll([Lnet/minecraft/client/option/SimpleOption;)V"
23 | ),
24 | index = 0
25 | )
26 | private SimpleOption>[] animatica$addTextureAnimationOptionButton(SimpleOption>[] old) {
27 | var options = new SimpleOption>[old.length + 1];
28 | System.arraycopy(old, 0, options, 0, old.length);
29 | options[options.length - 1] = Animatica.CONFIG.getAnimatedTexturesOption();
30 | return options;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/util/Flags.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.util;
2 |
3 | public enum Flags {;
4 | public static volatile boolean ALLOW_INVALID_ID_CHARS = false;
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/util/PropertyUtil.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.util;
2 |
3 | import com.google.common.collect.ImmutableMap;
4 | import io.github.foundationgames.animatica.util.exception.InvalidPropertyException;
5 | import io.github.foundationgames.animatica.util.exception.MissingPropertyException;
6 | import io.github.foundationgames.animatica.util.exception.PropertyParseException;
7 | import net.minecraft.util.Identifier;
8 |
9 | import java.util.Map;
10 | import java.util.Properties;
11 |
12 | public enum PropertyUtil {;
13 | public static String get(Identifier file, Properties properties, String key) throws PropertyParseException {
14 | var p = properties.getProperty(key);
15 | if (p == null) {
16 | throw new MissingPropertyException(file, key);
17 | }
18 | return p;
19 | }
20 |
21 | public static Properties getSubProperties(Properties properties, String key) {
22 | var p = new Properties();
23 | final var prefix = key + ".";
24 | for (String k : properties.stringPropertyNames()) {
25 | if (k.startsWith(prefix)) {
26 | var newKey = k.replaceFirst(prefix, "");
27 | p.setProperty(newKey, properties.getProperty(k));
28 | }
29 | }
30 | return p;
31 | }
32 |
33 | public static int getInt(Identifier file, Properties properties, String key) throws PropertyParseException {
34 | int r;
35 | try {
36 | r = Integer.parseInt(get(file, properties, key));
37 | } catch (NumberFormatException ignored) {
38 | throw new InvalidPropertyException(file, key, "integer (whole number)");
39 | }
40 | return r;
41 | }
42 |
43 | public static int getIntOr(Identifier file, Properties properties, String key, int defaultVal) throws PropertyParseException {
44 | var p = properties.getProperty(key);
45 | if (p == null) {
46 | return defaultVal;
47 | }
48 | int r;
49 | try {
50 | r = Integer.parseInt(p);
51 | } catch (NumberFormatException ignored) {
52 | throw new InvalidPropertyException(file, key, "integer");
53 | }
54 | return r;
55 | }
56 |
57 | public static boolean getBoolOr(Identifier file, Properties properties, String key, boolean defaultVal) throws PropertyParseException {
58 | var p = properties.getProperty(key);
59 | if (p == null) {
60 | return defaultVal;
61 | }
62 | if ("false".equals(p) || "true".equals(p)) {
63 | return "true".equals(p);
64 | }
65 | throw new InvalidPropertyException(file, key, "boolean (false/true)");
66 | }
67 |
68 | public static Map intToIntMap(Properties in) {
69 | var builder = ImmutableMap.builder();
70 | for (String k : in.stringPropertyNames()) {
71 | try {
72 | builder.put(Integer.parseInt(k), Integer.parseInt(in.getProperty(k)));
73 | } catch (NumberFormatException ignored) {}
74 | }
75 | return builder.build();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/util/TextureUtil.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.util;
2 |
3 | import net.minecraft.client.texture.NativeImage;
4 | import net.minecraft.util.math.MathHelper;
5 |
6 | public enum TextureUtil {;
7 |
8 | /**
9 | * Copy a section of an image into another image
10 | *
11 | * @param src The source image to copy from
12 | * @param u The u coordinate on the source image to start the selection from
13 | * @param v The v coordinate on the source image to start the selection from
14 | * @param w The width of the selection area
15 | * @param h The height of the selection area
16 | * @param dest The destination image to copy to
17 | * @param du The u coordinate on the destination image to place the selection at
18 | * @param dv The v coordinate on the destination image to place the selection at
19 | */
20 | public static void copy(NativeImage src, int u, int v, int w, int h, NativeImage dest, int du, int dv) {
21 | // iterate through the entire section of the image to be copied over
22 | for (int rx = 0; rx < w; rx++) {
23 | for (int ry = 0; ry < h; ry++) {
24 | // the current x/y coordinates in the source image
25 | int srcX = u + rx;
26 | int srcY = v + ry;
27 | // the corresponding target x/y coordinates in the target image
28 | int trgX = du + rx;
29 | int trgY = dv + ry;
30 |
31 | // set the color of the target pixel on the destination image
32 | // to the color from the corresponding pixel on the source image
33 | dest.setColor(trgX, trgY, src.getColor(srcX, srcY));
34 | }
35 | }
36 | }
37 |
38 | /**
39 | * Copy a blend between 2 sections on a source image to a destination image
40 | *
41 | * @param src The source image to copy from
42 | * @param u0 The u coordinate on the source image to start the first selection from
43 | * @param v0 The v coordinate on the source image to start the first selection from
44 | * @param u1 The u coordinate on the source image to start the second selection from
45 | * @param v1 The v coordinate on the source image to start the second selection from
46 | * @param w The width of the selection area
47 | * @param h The height of the selection area
48 | * @param dest The destination image to copy to
49 | * @param du The u coordinate on the destination image to place the selection at
50 | * @param dv The v coordinate on the destination image to place the selection at
51 | * @param blend The blend between the first selection from the source and the
52 | * second (0 = solid first image, 1 = solid second image)
53 | */
54 | public static void blendCopy(NativeImage src, int u0, int v0, int u1, int v1, int w, int h, NativeImage dest, int du, int dv, float blend) {
55 | // iterate through the entire section of the image to be copied over
56 | for (int rx = 0; rx < w; rx++) {
57 | for (int ry = 0; ry < h; ry++) {
58 | // the first set of x/y coordinates in the source image
59 | int srcX0 = u0 + rx;
60 | int srcY0 = v0 + ry;
61 | // the second set of x/y coordinates in the source image
62 | int srcX1 = u1 + rx;
63 | int srcY1 = v1 + ry;
64 | // the corresponding target x/y coordinates in the target image
65 | int trgX = du + rx;
66 | int trgY = dv + ry;
67 |
68 | // set the color of the target pixel on the destination image to a blend
69 | // of the colors from the corresponding pixels on the source image
70 | dest.setColor(trgX, trgY, lerpColor(src.getFormat(), src.getColor(srcX0, srcY0), src.getColor(srcX1, srcY1), blend));
71 | }
72 | }
73 | }
74 |
75 | public static int lerpColor(NativeImage.Format format, int c1, int c2, float delta) {
76 | int a1 = (c1 >> format.getAlphaOffset()) & 0xFF;
77 | int r1 = (c1 >> format.getRedOffset()) & 0xFF;
78 | int g1 = (c1 >> format.getGreenOffset()) & 0xFF;
79 | int b1 = (c1 >> format.getBlueOffset()) & 0xFF;
80 |
81 | int a2 = (c2 >> format.getAlphaOffset()) & 0xFF;
82 | int r2 = (c2 >> format.getRedOffset()) & 0xFF;
83 | int g2 = (c2 >> format.getGreenOffset()) & 0xFF;
84 | int b2 = (c2 >> format.getBlueOffset()) & 0xFF;
85 |
86 | // If the first or second color is transparent,
87 | // don't lerp any leftover rgb values and instead
88 | // only use those of the non-transparent color
89 | if (a1 <= 0) {
90 | r1 = r2;
91 | g1 = g2;
92 | b1 = b2;
93 | } else if (a2 <= 0) {
94 | r2 = r1;
95 | g2 = g1;
96 | b2 = b1;
97 | }
98 |
99 | int oa = MathHelper.lerp(delta, a1, a2);
100 | int or = MathHelper.lerp(delta, r1, r2);
101 | int og = MathHelper.lerp(delta, g1, g2);
102 | int ob = MathHelper.lerp(delta, b1, b2);
103 |
104 | return (oa << format.getAlphaOffset()) | (or << format.getRedOffset()) | (og << format.getGreenOffset()) | (ob << format.getBlueOffset());
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/util/Utilities.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.util;
2 |
3 | import net.minecraft.util.Identifier;
4 |
5 | public enum Utilities {;
6 | public static Identifier processPath(Identifier fileRelativeTo, Identifier path) {
7 | if (path.getPath().startsWith("./")) {
8 | int lInd = fileRelativeTo.getPath().lastIndexOf("/");
9 | if (lInd > 0) {
10 | var builder = new StringBuilder(fileRelativeTo.getPath());
11 | builder.replace(lInd, builder.length(), path.getPath().replaceFirst("\\./", "/"));
12 | return new Identifier(fileRelativeTo.getNamespace(), builder.toString());
13 | }
14 | } else if (path.getPath().startsWith("~/")) {
15 | return new Identifier(path.getNamespace(), path.getPath().replaceFirst("~/", "optifine/"));
16 | }
17 | return path;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/util/exception/InvalidPropertyException.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.util.exception;
2 |
3 | import net.minecraft.util.Identifier;
4 |
5 | public class InvalidPropertyException extends PropertyParseException {
6 | public InvalidPropertyException(Identifier file, String key, String expectedType) {
7 | super(String.format("Property '%s' in file '%s' expected to be of type: %s", key, file, expectedType));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/util/exception/MissingPropertyException.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.util.exception;
2 |
3 | import net.minecraft.util.Identifier;
4 |
5 | public class MissingPropertyException extends PropertyParseException {
6 | public MissingPropertyException(Identifier file, String key) {
7 | super(String.format("Expected property '%s' in file '%s'", key, file));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/io/github/foundationgames/animatica/util/exception/PropertyParseException.java:
--------------------------------------------------------------------------------
1 | package io.github.foundationgames.animatica.util.exception;
2 |
3 | public abstract class PropertyParseException extends Exception {
4 | public PropertyParseException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/resources/animatica.mixins.json:
--------------------------------------------------------------------------------
1 | {
2 | "required": true,
3 | "minVersion": "0.8",
4 | "package": "io.github.foundationgames.animatica.mixin",
5 | "compatibilityLevel": "JAVA_16",
6 | "client": [
7 | "RenderSystemMixin",
8 | "IdentifierMixin",
9 | "VideoOptionsScreenMixin"
10 | ],
11 | "injectors": {
12 | "defaultRequire": 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/resources/assets/animatica/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FoundationGames/Animatica/2d08028eb0ae350f096c32f324a852770a4bc710/src/main/resources/assets/animatica/icon.png
--------------------------------------------------------------------------------
/src/main/resources/assets/animatica/lang/en_us.json:
--------------------------------------------------------------------------------
1 | {
2 | "option.animatica.animated_textures": "Custom Animations"
3 | }
--------------------------------------------------------------------------------
/src/main/resources/assets/animatica/lang/fr_fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "option.animatica.animated_textures": "Animations Personalisées"
3 | }
4 |
--------------------------------------------------------------------------------
/src/main/resources/fabric.mod.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": 1,
3 | "id": "animatica",
4 | "version": "${version}",
5 |
6 | "name": "Animatica",
7 | "description": "Allow for animating more game textures using the MCPatcher/OptiFine animation format",
8 | "authors": [
9 | "FoundationGames"
10 | ],
11 | "contact": {
12 | "homepage": "https://github.com/FoundationGames/Animatica",
13 | "sources": "https://github.com/FoundationGames/Animatica"
14 | },
15 |
16 | "license": "LGPLv3",
17 | "icon": "assets/animatica/icon.png",
18 |
19 | "environment": "client",
20 | "entrypoints": {
21 | "client": [
22 | "io.github.foundationgames.animatica.Animatica"
23 | ]
24 | },
25 | "mixins": [
26 | "animatica.mixins.json"
27 | ],
28 | "depends": {
29 | "fabricloader": ">=0.11.3",
30 | "fabric": "*",
31 | "minecraft": ["1.19.4", "1.20.x"],
32 | "java": ">=16"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------