Load Emotecraft emotes into a registry and add a whole-player bone #5
@@ -5,11 +5,15 @@ A player emote library for Saturn Client, with limb bending.
|
||||
## Layout
|
||||
|
||||
- `core/`: Minecraft-independent animation model (`Bone`, `BoneTransform`, `Pose`, `Animation`,
|
||||
`AnimationPlayer`). Plain Java 21 with JUnit tests. Published as `org.saturnclient:emotes-core`.
|
||||
`AnimationPlayer`), keyframes and easings, the Emotecraft JSON loader and the `EmoteRegistry`.
|
||||
Plain Java 21 plus Jackson, with JUnit tests. Published as `org.saturnclient:emotes-core`.
|
||||
- `src/main/`: the Fabric mod that puts poses on player models. It's built once per Minecraft
|
||||
version with [Stonecutter](https://stonecutter.kikugie.dev/) and published as
|
||||
`org.saturnclient:emotes-fabric:<version>+<mc>`, with `core` bundled inside the jar.
|
||||
- `src/testmod/`: a dev-only tester mod that is never published. It adds `/emotes play <name>`,
|
||||
`org.saturnclient:emotes-fabric:<version>+<mc>`, with `core` and Jackson 2.17.0 (the version Saturn
|
||||
Client ships) bundled inside the jar.
|
||||
- `src/testmod/`: a dev-only tester mod that is never published. It loads Saturn's emotes into the
|
||||
library's registry and keeps its own hand-written test animations in a separate registry.
|
||||
`/emotes play <id>` plays a registered emote, `/emotes test <name>` a test animation, and there's
|
||||
`/emotes stop` and `/emotes info`. Press F5 to see yourself. It also has a Fabric client gametest
|
||||
that creates a world and screenshots each animation from the front and side, into
|
||||
`versions/<mc>/build/run/clientGameTest/screenshots/`.
|
||||
@@ -17,6 +21,33 @@ A player emote library for Saturn Client, with limb bending.
|
||||
Supported versions: 1.21.4 to 1.21.11, the same range as Saturn Client. Each version's Fabric API
|
||||
version is set in `stonecutter.properties.toml`. The code uses Mojang mappings.
|
||||
|
||||
## Using it
|
||||
|
||||
```java
|
||||
// At startup: load emotes (Emotecraft / player-animation-lib JSON) into the registry.
|
||||
try (InputStream in = MyMod.class.getResourceAsStream("/assets/mymod/player_animations/facepalm.json")) {
|
||||
Emotes.registry().loadEmotecraft("facepalm", in);
|
||||
}
|
||||
|
||||
Emotes.play(player.getUUID(), "facepalm"); // false if there's no such emote
|
||||
Emotes.play(player.getUUID(), animation); // or any Animation, e.g. written in code
|
||||
Emotes.stop(player.getUUID());
|
||||
Emotes.playing(player.getUUID()); // Optional<AnimationPlayer>
|
||||
```
|
||||
|
||||
Emotes work for any player you can see, run on game ticks, and a non-looping one stops by itself.
|
||||
`Emotes.registry().all()` lists the emotes with their name, description and author, for an emote wheel.
|
||||
|
||||
An `Animation` returns a `Pose` for a time in ticks: a `BoneTransform` (offset in model pixels,
|
||||
pitch/yaw/roll in radians, bend and bend axis) for any of `ROOT` (the whole player), `HEAD`, `BODY`,
|
||||
`RIGHT_ARM`, `LEFT_ARM`, `RIGHT_LEG` and `LEFT_LEG`. Bones it doesn't set keep their vanilla pose.
|
||||
`KeyframeAnimation` builds one from keyframes and `Easing`s.
|
||||
|
||||
`EmotecraftLoader` follows player-animation-lib's reader: absolute part pivots become offsets,
|
||||
`body` (and `torso` before format version 3) is the whole player in blocks, angles are degrees unless
|
||||
`"degrees": false`, `turn` adds whole turns, and loops jump back to `returnTick` after `endTick`. Scale
|
||||
channels, `easingArg` and blending with the vanilla pose aren't supported yet (issue #4).
|
||||
|
||||
## How it hooks in
|
||||
|
||||
1. `LivingEntityRendererMixin` samples the entity's current pose into its render state (players only).
|
||||
|
||||
+5
-1
@@ -70,9 +70,13 @@ dependencies {
|
||||
modImplementation("net.fabricmc:fabric-loader:${property("deps.fabric_loader")}")
|
||||
fapi("fabric-api-base", "fabric-lifecycle-events-v1")
|
||||
|
||||
// The core is Minecraft-independent; ship it inside the mod jar.
|
||||
// The core is Minecraft-independent; ship it inside the mod jar, with the Jackson it reads emotes
|
||||
// with (the version Saturn Client ships too, so the game only loads one).
|
||||
api(project(":core"))
|
||||
include(project(":core"))
|
||||
for (module in listOf("jackson-databind", "jackson-core", "jackson-annotations")) {
|
||||
include("com.fasterxml.jackson.core:$module:2.17.0")
|
||||
}
|
||||
|
||||
for (module in listOf("fabric-command-api-v2", "fabric-client-gametest-api-v1")) {
|
||||
"modTestmodImplementation"(fabricApi.module(module, sc.properties["deps.fabric_api"]))
|
||||
|
||||
@@ -11,6 +11,9 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Same version Saturn Client ships, so only one copy ends up in the game.
|
||||
implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0")
|
||||
|
||||
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
|
||||
@@ -14,4 +14,12 @@ public interface Animation {
|
||||
default boolean loops() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where a looping animation starts again after reaching {@link #length()}, in ticks. The part
|
||||
* before it plays once, like an intro.
|
||||
*/
|
||||
default float loopStart() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,18 @@ public final class AnimationPlayer {
|
||||
public float time(long tick, float partialTick) {
|
||||
float elapsed = Math.max(0, tick - startTick + partialTick);
|
||||
float length = animation.length();
|
||||
if (animation.loops() && length > 0) {
|
||||
return elapsed % length;
|
||||
if (!animation.loops()) {
|
||||
return Math.min(elapsed, length);
|
||||
}
|
||||
return Math.min(elapsed, length);
|
||||
if (elapsed < length) {
|
||||
return elapsed;
|
||||
}
|
||||
float loopStart = animation.loopStart();
|
||||
float loopLength = length - loopStart;
|
||||
if (loopLength <= 0) {
|
||||
return length;
|
||||
}
|
||||
return loopStart + (elapsed - length) % loopLength;
|
||||
}
|
||||
|
||||
public boolean isFinished(long tick) {
|
||||
|
||||
@@ -2,7 +2,14 @@ package org.saturnclient.emotes.core;
|
||||
|
||||
/** The parts of the player model an animation can move. */
|
||||
public enum Bone {
|
||||
/**
|
||||
* The whole player. Offsets are in model pixels (Y down, like the other bones), and rotations turn
|
||||
* the whole model around its middle, 12.8 pixels below the neck (0.7 blocks above the feet). Used
|
||||
* for flips, spins and jumps.
|
||||
*/
|
||||
ROOT,
|
||||
HEAD,
|
||||
/** The torso. It bends from the hips; see {@code LimbBend#anchoredAtEnd}. */
|
||||
BODY,
|
||||
RIGHT_ARM,
|
||||
LEFT_ARM,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.saturnclient.emotes.core;
|
||||
|
||||
/**
|
||||
* An animation with the details a player sees, like in an emote wheel.
|
||||
*
|
||||
* @param id identifies the emote, for example in network messages ("facepalm")
|
||||
*/
|
||||
public record Emote(String id, String name, String description, String author, Animation animation) {
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.saturnclient.emotes.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.saturnclient.emotes.core.format.EmotecraftLoader;
|
||||
|
||||
/** Emotes by ID, in the order they were added. Safe to use from any thread. */
|
||||
public final class EmoteRegistry {
|
||||
private final Map<String, Emote> emotes = new LinkedHashMap<>();
|
||||
|
||||
/** Adds the emote, replacing any with the same ID. */
|
||||
public synchronized Emote register(Emote emote) {
|
||||
emotes.put(emote.id(), emote);
|
||||
return emote;
|
||||
}
|
||||
|
||||
/** Reads an emote in Emotecraft's JSON format and registers it under {@code id}. */
|
||||
public Emote loadEmotecraft(String id, InputStream json) throws IOException {
|
||||
return register(EmotecraftLoader.load(id, json));
|
||||
}
|
||||
|
||||
public synchronized Optional<Emote> get(String id) {
|
||||
return Optional.ofNullable(emotes.get(id));
|
||||
}
|
||||
|
||||
public synchronized List<Emote> all() {
|
||||
return List.copyOf(emotes.values());
|
||||
}
|
||||
|
||||
public synchronized List<String> ids() {
|
||||
return new ArrayList<>(emotes.keySet());
|
||||
}
|
||||
|
||||
public synchronized boolean remove(String id) {
|
||||
return emotes.remove(id) != null;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
emotes.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.saturnclient.emotes.core.format;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Easing curves between keyframes (see easings.net). Each maps progress through a segment, from 0
|
||||
* to 1, to how far the value has moved.
|
||||
*/
|
||||
public enum Easing {
|
||||
LINEAR,
|
||||
/** Holds the earlier keyframe's value until the next one. */
|
||||
CONSTANT,
|
||||
INSINE, OUTSINE, INOUTSINE,
|
||||
INQUAD, OUTQUAD, INOUTQUAD,
|
||||
INCUBIC, OUTCUBIC, INOUTCUBIC,
|
||||
INQUART, OUTQUART, INOUTQUART,
|
||||
INQUINT, OUTQUINT, INOUTQUINT,
|
||||
INEXPO, OUTEXPO, INOUTEXPO,
|
||||
INCIRC, OUTCIRC, INOUTCIRC,
|
||||
INBACK, OUTBACK, INOUTBACK,
|
||||
INELASTIC, OUTELASTIC, INOUTELASTIC,
|
||||
INBOUNCE, OUTBOUNCE, INOUTBOUNCE;
|
||||
|
||||
private static final double BACK = 1.70158;
|
||||
private static final double BACK_INOUT = BACK * 1.525;
|
||||
|
||||
/**
|
||||
* Reads an Emotecraft easing name. Case and underscores don't matter, and the optional "EASE"
|
||||
* prefix is dropped ("EASEINOUTQUAD" and "inOutQuad" both work). Unknown names are linear.
|
||||
*/
|
||||
public static Easing fromName(String name) {
|
||||
if (name == null) {
|
||||
return LINEAR;
|
||||
}
|
||||
String key = name.toUpperCase(Locale.ROOT).replace("_", "");
|
||||
if (key.startsWith("EASE")) {
|
||||
key = key.substring(4);
|
||||
}
|
||||
try {
|
||||
return valueOf(key);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return LINEAR;
|
||||
}
|
||||
}
|
||||
|
||||
public float apply(float progress) {
|
||||
double t = Math.max(0, Math.min(1, progress));
|
||||
return (float) switch (this) {
|
||||
case LINEAR -> t;
|
||||
case CONSTANT -> t < 1 ? 0 : 1;
|
||||
case INSINE -> 1 - Math.cos(t * Math.PI / 2);
|
||||
case OUTSINE -> Math.sin(t * Math.PI / 2);
|
||||
case INOUTSINE -> -(Math.cos(Math.PI * t) - 1) / 2;
|
||||
case INQUAD -> in(t, 2);
|
||||
case OUTQUAD -> out(t, 2);
|
||||
case INOUTQUAD -> inOut(t, 2);
|
||||
case INCUBIC -> in(t, 3);
|
||||
case OUTCUBIC -> out(t, 3);
|
||||
case INOUTCUBIC -> inOut(t, 3);
|
||||
case INQUART -> in(t, 4);
|
||||
case OUTQUART -> out(t, 4);
|
||||
case INOUTQUART -> inOut(t, 4);
|
||||
case INQUINT -> in(t, 5);
|
||||
case OUTQUINT -> out(t, 5);
|
||||
case INOUTQUINT -> inOut(t, 5);
|
||||
case INEXPO -> t == 0 ? 0 : Math.pow(2, 10 * t - 10);
|
||||
case OUTEXPO -> t == 1 ? 1 : 1 - Math.pow(2, -10 * t);
|
||||
case INOUTEXPO -> t == 0 || t == 1 ? t
|
||||
: t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;
|
||||
case INCIRC -> 1 - Math.sqrt(1 - t * t);
|
||||
case OUTCIRC -> Math.sqrt(1 - (t - 1) * (t - 1));
|
||||
case INOUTCIRC -> t < 0.5 ? (1 - Math.sqrt(1 - 4 * t * t)) / 2
|
||||
: (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
|
||||
case INBACK -> (BACK + 1) * t * t * t - BACK * t * t;
|
||||
case OUTBACK -> 1 + (BACK + 1) * Math.pow(t - 1, 3) + BACK * Math.pow(t - 1, 2);
|
||||
case INOUTBACK -> t < 0.5
|
||||
? Math.pow(2 * t, 2) * ((BACK_INOUT + 1) * 2 * t - BACK_INOUT) / 2
|
||||
: (Math.pow(2 * t - 2, 2) * ((BACK_INOUT + 1) * (t * 2 - 2) + BACK_INOUT) + 2) / 2;
|
||||
case INELASTIC -> t == 0 || t == 1 ? t
|
||||
: -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * (2 * Math.PI / 3));
|
||||
case OUTELASTIC -> t == 0 || t == 1 ? t
|
||||
: Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * (2 * Math.PI / 3)) + 1;
|
||||
case INOUTELASTIC -> t == 0 || t == 1 ? t
|
||||
: t < 0.5 ? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * (2 * Math.PI / 4.5))) / 2
|
||||
: Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * (2 * Math.PI / 4.5)) / 2 + 1;
|
||||
case INBOUNCE -> 1 - bounce(1 - t);
|
||||
case OUTBOUNCE -> bounce(t);
|
||||
case INOUTBOUNCE -> t < 0.5 ? (1 - bounce(1 - 2 * t)) / 2 : (1 + bounce(2 * t - 1)) / 2;
|
||||
};
|
||||
}
|
||||
|
||||
private static double in(double t, int power) {
|
||||
return Math.pow(t, power);
|
||||
}
|
||||
|
||||
private static double out(double t, int power) {
|
||||
return 1 - Math.pow(1 - t, power);
|
||||
}
|
||||
|
||||
private static double inOut(double t, int power) {
|
||||
return t < 0.5 ? Math.pow(2, power - 1) * Math.pow(t, power) : 1 - Math.pow(-2 * t + 2, power) / 2;
|
||||
}
|
||||
|
||||
private static double bounce(double t) {
|
||||
double n = 7.5625;
|
||||
double d = 2.75;
|
||||
if (t < 1 / d) {
|
||||
return n * t * t;
|
||||
} else if (t < 2 / d) {
|
||||
return n * (t -= 1.5 / d) * t + 0.75;
|
||||
} else if (t < 2.5 / d) {
|
||||
return n * (t -= 2.25 / d) * t + 0.9375;
|
||||
}
|
||||
return n * (t -= 2.625 / d) * t + 0.984375;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package org.saturnclient.emotes.core.format;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import org.saturnclient.emotes.core.Bone;
|
||||
import org.saturnclient.emotes.core.Emote;
|
||||
import org.saturnclient.emotes.core.format.KeyframeAnimation.Channel;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Reads emotes in Emotecraft's JSON format, following player-animation-lib's reader:
|
||||
*
|
||||
* <pre>
|
||||
* { "version": 1, "name": ..., "description": ..., "author": ...,
|
||||
* "emote": { "isLoop": ..., "returnTick": ..., "endTick": ..., "stopTick": ..., "degrees": ...,
|
||||
* "moves": [ { "tick": 10, "easing": "INOUTQUAD", "turn": 0,
|
||||
* "rightArm": { "pitch": -1.9, "bend": -0.9 } }, ... ] } }
|
||||
* </pre>
|
||||
*
|
||||
* <p>Parts are {@code head}, {@code torso}, {@code rightArm}, {@code leftArm}, {@code rightLeg},
|
||||
* {@code leftLeg} and {@code body} (the whole player, {@link Bone#ROOT}). Before format version 3,
|
||||
* {@code torso} also means the whole player. Axes are {@code x}, {@code y}, {@code z}, {@code pitch},
|
||||
* {@code yaw}, {@code roll}, {@code bend} and {@code axis}. Values are converted to this library's
|
||||
* conventions:
|
||||
* <ul>
|
||||
* <li>Part positions are absolute pivots (the right arm rests at x = -5, y = 2), so the rest pivot is
|
||||
* subtracted to get an offset.</li>
|
||||
* <li>Whole-player positions are in blocks with Y up, and its rotations are around the world axes
|
||||
* before Minecraft flips the model, so X, Y, pitch and yaw change sign.</li>
|
||||
* <li>Emotecraft bends the other way, so {@code bend} changes sign.</li>
|
||||
* <li>Angles are in degrees unless {@code "degrees": false}.</li>
|
||||
* <li>{@code turn} adds whole turns to the move's angles, for spins and flips.</li>
|
||||
* <li>Looping emotes jump back to {@code returnTick} after {@code endTick}, and only loop when they
|
||||
* have a {@code returnTick}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Not supported: {@code scaleX}/{@code scaleY}/{@code scaleZ}, {@code easingArg}, and the blend with
|
||||
* the vanilla pose before the first keyframe and between {@code endTick} and {@code stopTick} (the
|
||||
* first and last keyframes are held instead).
|
||||
*/
|
||||
public final class EmotecraftLoader {
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private static final Map<String, Bone> PARTS = Map.of(
|
||||
"body", Bone.ROOT,
|
||||
"head", Bone.HEAD,
|
||||
"torso", Bone.BODY, // Bone.ROOT before version 3
|
||||
|
||||
"rightArm", Bone.RIGHT_ARM,
|
||||
"leftArm", Bone.LEFT_ARM,
|
||||
"rightLeg", Bone.RIGHT_LEG,
|
||||
"leftLeg", Bone.LEFT_LEG);
|
||||
|
||||
private static final Map<String, Channel> AXES = Map.of(
|
||||
"x", Channel.X,
|
||||
"y", Channel.Y,
|
||||
"z", Channel.Z,
|
||||
"pitch", Channel.PITCH,
|
||||
"yaw", Channel.YAW,
|
||||
"roll", Channel.ROLL,
|
||||
"bend", Channel.BEND,
|
||||
"axis", Channel.BEND_AXIS);
|
||||
|
||||
/** Emotecraft's rest pivots, {x, y, z} in model pixels. */
|
||||
private static final Map<Bone, float[]> REST_PIVOTS = Map.of(
|
||||
Bone.RIGHT_ARM, new float[] { -5, 2, 0 },
|
||||
Bone.LEFT_ARM, new float[] { 5, 2, 0 },
|
||||
Bone.RIGHT_LEG, new float[] { -1.9f, 12, 0.1f },
|
||||
Bone.LEFT_LEG, new float[] { 1.9f, 12, 0.1f });
|
||||
|
||||
private static final float FULL_TURN = (float) (2 * Math.PI);
|
||||
|
||||
private EmotecraftLoader() {
|
||||
}
|
||||
|
||||
public static Emote load(String id, InputStream json) throws IOException {
|
||||
return read(id, MAPPER.readTree(json));
|
||||
}
|
||||
|
||||
public static Emote load(String id, String json) throws IOException {
|
||||
return read(id, MAPPER.readTree(json));
|
||||
}
|
||||
|
||||
private static Emote read(String id, JsonNode root) throws IOException {
|
||||
JsonNode emote = root.path("emote");
|
||||
JsonNode moves = emote.path("moves");
|
||||
if (!moves.isArray()) {
|
||||
throw new IOException("Emote " + id + " has no \"emote.moves\" array");
|
||||
}
|
||||
int version = root.path("version").asInt(1);
|
||||
boolean degrees = bool(emote.path("degrees"), true);
|
||||
|
||||
KeyframeAnimation.Builder animation = KeyframeAnimation.builder()
|
||||
.easeBefore(bool(emote.path("easeBeforeKeyframe"), false));
|
||||
float lastTick = 0;
|
||||
for (JsonNode move : moves) {
|
||||
float tick = (float) move.path("tick").asDouble();
|
||||
lastTick = Math.max(lastTick, tick);
|
||||
Easing easing = Easing.fromName(move.path("easing").asText(null));
|
||||
float turn = (float) move.path("turn").asDouble(0) * FULL_TURN;
|
||||
|
||||
for (Map.Entry<String, Bone> part : PARTS.entrySet()) {
|
||||
JsonNode axes = move.get(part.getKey());
|
||||
if (axes == null || !axes.isObject()) {
|
||||
continue;
|
||||
}
|
||||
Bone bone = part.getKey().equals("torso") && version < 3 ? Bone.ROOT : part.getValue();
|
||||
for (Map.Entry<String, Channel> axis : AXES.entrySet()) {
|
||||
JsonNode value = axes.get(axis.getKey());
|
||||
if (value != null && value.isNumber()) {
|
||||
float converted = convert(bone, axis.getValue(), value.floatValue(), degrees, turn);
|
||||
animation.keyframe(bone, axis.getValue(), tick, converted, easing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float endTick = (float) emote.path("endTick").asDouble(lastTick);
|
||||
if (bool(emote.path("isLoop"), false) && emote.has("returnTick")) {
|
||||
// endTick itself plays before jumping back.
|
||||
float returnTick = (float) emote.path("returnTick").asDouble();
|
||||
animation.length(endTick + 1).loop(returnTick <= endTick ? returnTick : 0);
|
||||
} else {
|
||||
animation.length((float) emote.path("stopTick").asDouble(endTick));
|
||||
}
|
||||
|
||||
return new Emote(id, text(root.path("name"), id), text(root.path("description"), ""),
|
||||
text(root.path("author"), ""), animation.build());
|
||||
}
|
||||
|
||||
private static float convert(Bone bone, Channel channel, float value, boolean degrees, float turn) {
|
||||
return switch (channel) {
|
||||
case X, Y, Z -> {
|
||||
if (bone == Bone.ROOT) {
|
||||
// Blocks to pixels, and into model space, where X and Y point the other way.
|
||||
yield channel == Channel.Z ? value * 16 : -value * 16;
|
||||
}
|
||||
float[] rest = REST_PIVOTS.get(bone);
|
||||
yield rest == null ? value : value - rest[channel.ordinal()];
|
||||
}
|
||||
case PITCH, YAW, ROLL -> {
|
||||
float radians = angle(value, degrees, turn);
|
||||
yield bone == Bone.ROOT && channel != Channel.ROLL ? -radians : radians;
|
||||
}
|
||||
case BEND -> -angle(value, degrees, turn);
|
||||
case BEND_AXIS -> angle(value, degrees, turn);
|
||||
};
|
||||
}
|
||||
|
||||
private static float angle(float value, boolean degrees, float turn) {
|
||||
return (degrees ? (float) Math.toRadians(value) : value) + turn;
|
||||
}
|
||||
|
||||
/** Emotecraft writes booleans both as JSON booleans and as "true"/"false" strings. */
|
||||
private static boolean bool(JsonNode node, boolean fallback) {
|
||||
if (node.isBoolean()) {
|
||||
return node.booleanValue();
|
||||
}
|
||||
if (node.isTextual()) {
|
||||
return Boolean.parseBoolean(node.textValue());
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private static String text(JsonNode node, String fallback) {
|
||||
return node.isTextual() ? node.textValue() : fallback;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package org.saturnclient.emotes.core.format;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.saturnclient.emotes.core.Animation;
|
||||
import org.saturnclient.emotes.core.Bone;
|
||||
import org.saturnclient.emotes.core.BoneTransform;
|
||||
import org.saturnclient.emotes.core.Pose;
|
||||
|
||||
/**
|
||||
* An animation made of keyframes: for each bone, each {@link Channel} has its own list of values at
|
||||
* given ticks, and values in between follow the segment's {@link Easing}.
|
||||
*
|
||||
* <p>A bone with any keyframes gets a full {@link BoneTransform} each frame. Its channels without
|
||||
* keyframes are 0. Bones without keyframes keep their vanilla pose. Before a channel's first keyframe
|
||||
* and after its last one, it holds that keyframe's value.
|
||||
*/
|
||||
public final class KeyframeAnimation implements Animation {
|
||||
/** The fields of a {@link BoneTransform}, in the same units. */
|
||||
public enum Channel {
|
||||
X, Y, Z, PITCH, YAW, ROLL, BEND, BEND_AXIS
|
||||
}
|
||||
|
||||
public record Keyframe(float tick, float value, Easing easing) {
|
||||
}
|
||||
|
||||
private final Map<Bone, Map<Channel, Keyframe[]>> tracks;
|
||||
private final float length;
|
||||
private final boolean loops;
|
||||
private final float loopStart;
|
||||
private final boolean easeBefore;
|
||||
|
||||
private KeyframeAnimation(Builder builder) {
|
||||
this.tracks = new EnumMap<>(Bone.class);
|
||||
builder.keyframes.forEach((bone, channels) -> {
|
||||
Map<Channel, Keyframe[]> sorted = new EnumMap<>(Channel.class);
|
||||
channels.forEach((channel, keyframes) -> sorted.put(channel, sortedUnique(keyframes)));
|
||||
tracks.put(bone, sorted);
|
||||
});
|
||||
this.length = builder.length;
|
||||
this.loops = builder.loops;
|
||||
this.loopStart = builder.loopStart;
|
||||
this.easeBefore = builder.easeBefore;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean loops() {
|
||||
return loops;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float loopStart() {
|
||||
return loopStart;
|
||||
}
|
||||
|
||||
/** The keyframes of one channel, sorted by tick, or an empty array. */
|
||||
public Keyframe[] keyframes(Bone bone, Channel channel) {
|
||||
Map<Channel, Keyframe[]> channels = tracks.get(bone);
|
||||
Keyframe[] keyframes = channels == null ? null : channels.get(channel);
|
||||
return keyframes == null ? new Keyframe[0] : keyframes.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pose sample(float tick) {
|
||||
Pose.Builder pose = Pose.builder();
|
||||
float[] values = new float[Channel.values().length];
|
||||
for (Map.Entry<Bone, Map<Channel, Keyframe[]>> track : tracks.entrySet()) {
|
||||
Arrays.fill(values, 0);
|
||||
for (Map.Entry<Channel, Keyframe[]> channel : track.getValue().entrySet()) {
|
||||
values[channel.getKey().ordinal()] = valueAt(channel.getValue(), tick);
|
||||
}
|
||||
pose.set(track.getKey(), new BoneTransform(values[0], values[1], values[2], values[3], values[4],
|
||||
values[5], values[6], values[7]));
|
||||
}
|
||||
return pose.build();
|
||||
}
|
||||
|
||||
private float valueAt(Keyframe[] keyframes, float tick) {
|
||||
if (tick <= keyframes[0].tick()) {
|
||||
return keyframes[0].value();
|
||||
}
|
||||
for (int i = 0; i + 1 < keyframes.length; i++) {
|
||||
Keyframe from = keyframes[i];
|
||||
Keyframe to = keyframes[i + 1];
|
||||
if (tick < to.tick()) {
|
||||
float progress = (tick - from.tick()) / (to.tick() - from.tick());
|
||||
Easing easing = easeBefore ? to.easing() : from.easing();
|
||||
return from.value() + (to.value() - from.value()) * easing.apply(progress);
|
||||
}
|
||||
}
|
||||
return keyframes[keyframes.length - 1].value();
|
||||
}
|
||||
|
||||
/** Sorts by tick. Of several keyframes on the same tick, the last one added wins. */
|
||||
private static Keyframe[] sortedUnique(List<Keyframe> keyframes) {
|
||||
List<Keyframe> sorted = new ArrayList<>(keyframes);
|
||||
sorted.sort(Comparator.comparingDouble(Keyframe::tick)); // Stable, so the added order is kept
|
||||
List<Keyframe> unique = new ArrayList<>(sorted.size());
|
||||
for (Keyframe keyframe : sorted) {
|
||||
if (!unique.isEmpty() && unique.get(unique.size() - 1).tick() == keyframe.tick()) {
|
||||
unique.set(unique.size() - 1, keyframe);
|
||||
} else {
|
||||
unique.add(keyframe);
|
||||
}
|
||||
}
|
||||
return unique.toArray(Keyframe[]::new);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final Map<Bone, Map<Channel, List<Keyframe>>> keyframes = new EnumMap<>(Bone.class);
|
||||
private float length;
|
||||
private boolean loops;
|
||||
private float loopStart;
|
||||
private boolean easeBefore;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder keyframe(Bone bone, Channel channel, float tick, float value, Easing easing) {
|
||||
keyframes.computeIfAbsent(bone, b -> new EnumMap<>(Channel.class))
|
||||
.computeIfAbsent(channel, c -> new ArrayList<>())
|
||||
.add(new Keyframe(tick, value, easing));
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Length in ticks. A looping animation repeats from {@code loopStart} after this. */
|
||||
public Builder length(float length) {
|
||||
this.length = length;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder loop(float loopStart) {
|
||||
this.loops = true;
|
||||
this.loopStart = loopStart;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a segment uses the easing of the keyframe it moves towards. By default it uses the
|
||||
* easing of the keyframe it starts from, like Emotecraft's original format.
|
||||
*/
|
||||
public Builder easeBefore(boolean easeBefore) {
|
||||
this.easeBefore = easeBefore;
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyframeAnimation build() {
|
||||
return new KeyframeAnimation(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,4 +57,34 @@ class AnimationPlayerTest {
|
||||
AnimationPlayer player = new AnimationPlayer(headSpin(20, false), 100);
|
||||
assertEquals(0f, player.time(90, 0), 1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loopsRestartFromLoopStart() {
|
||||
Animation withIntro = new Animation() {
|
||||
@Override
|
||||
public Pose sample(float tick) {
|
||||
return Pose.builder().set(Bone.HEAD, BoneTransform.rotation(0, tick, 0)).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float length() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean loops() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float loopStart() {
|
||||
return 5;
|
||||
}
|
||||
};
|
||||
AnimationPlayer player = new AnimationPlayer(withIntro, 0);
|
||||
assertEquals(3, player.time(3, 0), 1e-6); // intro plays once
|
||||
assertEquals(19, player.time(19, 0), 1e-6);
|
||||
assertEquals(5, player.time(20, 0), 1e-6); // then 5..20 repeats
|
||||
assertEquals(7, player.time(37, 0), 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.saturnclient.emotes.core;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class EmoteRegistryTest {
|
||||
private static Emote emote(String id) {
|
||||
return new Emote(id, id, "", "", new Animation() {
|
||||
@Override
|
||||
public Pose sample(float tick) {
|
||||
return Pose.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float length() {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void findsEmotesById() {
|
||||
EmoteRegistry registry = new EmoteRegistry();
|
||||
Emote wave = registry.register(emote("wave"));
|
||||
assertSame(wave, registry.get("wave").orElseThrow());
|
||||
assertTrue(registry.get("dab").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsTheOrderEmotesWereAdded() {
|
||||
EmoteRegistry registry = new EmoteRegistry();
|
||||
registry.register(emote("b"));
|
||||
registry.register(emote("a"));
|
||||
registry.register(emote("c"));
|
||||
assertEquals(List.of("b", "a", "c"), registry.ids());
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacesEmotesWithTheSameId() {
|
||||
EmoteRegistry registry = new EmoteRegistry();
|
||||
registry.register(emote("wave"));
|
||||
Emote replacement = registry.register(emote("wave"));
|
||||
assertEquals(1, registry.all().size());
|
||||
assertSame(replacement, registry.get("wave").orElseThrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
void removesEmotes() {
|
||||
EmoteRegistry registry = new EmoteRegistry();
|
||||
registry.register(emote("wave"));
|
||||
assertTrue(registry.remove("wave"));
|
||||
assertFalse(registry.remove("wave"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadsEmotecraftFiles() throws IOException {
|
||||
EmoteRegistry registry = new EmoteRegistry();
|
||||
try (InputStream in = getClass().getResourceAsStream("/emotes/cry.json")) {
|
||||
registry.loadEmotecraft("cry", in);
|
||||
}
|
||||
assertEquals("T_T", registry.get("cry").orElseThrow().description());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.saturnclient.emotes.core.format;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class EasingTest {
|
||||
@Test
|
||||
void everyEasingStartsAtZeroAndEndsAtOne() {
|
||||
for (Easing easing : Easing.values()) {
|
||||
assertEquals(0, easing.apply(0), 1e-5, easing.name());
|
||||
assertEquals(1, easing.apply(1), 1e-5, easing.name());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void symmetricEasingsAreHalfwayAtTheMiddle() {
|
||||
for (Easing easing : new Easing[] { Easing.LINEAR, Easing.INOUTSINE, Easing.INOUTQUAD, Easing.INOUTCUBIC,
|
||||
Easing.INOUTQUART, Easing.INOUTQUINT, Easing.INOUTEXPO, Easing.INOUTCIRC }) {
|
||||
assertEquals(0.5, easing.apply(0.5f), 1e-5, easing.name());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void inIsSlowAtFirstAndOutIsFast() {
|
||||
assertEquals(0.25, Easing.INQUAD.apply(0.5f), 1e-6);
|
||||
assertEquals(0.75, Easing.OUTQUAD.apply(0.5f), 1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void constantHoldsUntilTheEnd() {
|
||||
assertEquals(0, Easing.CONSTANT.apply(0.99f), 1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsEmotecraftNames() {
|
||||
assertEquals(Easing.INOUTQUAD, Easing.fromName("INOUTQUAD"));
|
||||
assertEquals(Easing.INOUTQUAD, Easing.fromName("EASEINOUTQUAD"));
|
||||
assertEquals(Easing.OUTSINE, Easing.fromName("easeOutSine"));
|
||||
assertEquals(Easing.INBOUNCE, Easing.fromName("in_bounce"));
|
||||
assertEquals(Easing.LINEAR, Easing.fromName("wobbly"));
|
||||
assertEquals(Easing.LINEAR, Easing.fromName(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.saturnclient.emotes.core.format;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.saturnclient.emotes.core.Bone;
|
||||
import org.saturnclient.emotes.core.BoneTransform;
|
||||
import org.saturnclient.emotes.core.Emote;
|
||||
|
||||
/** Loads the emotes Saturn Client ships (copied into test resources). */
|
||||
class EmotecraftLoaderTest {
|
||||
private static Emote load(String id) throws IOException {
|
||||
try (InputStream in = EmotecraftLoaderTest.class.getResourceAsStream("/emotes/" + id + ".json")) {
|
||||
return EmotecraftLoader.load(id, in);
|
||||
}
|
||||
}
|
||||
|
||||
private static Emote inline(String moves, String emoteFields) throws IOException {
|
||||
return EmotecraftLoader.load("test", "{\"emote\": {" + emoteFields + "\"moves\": [" + moves + "]}}");
|
||||
}
|
||||
|
||||
private static BoneTransform bone(Emote emote, Bone bone, float tick) {
|
||||
return emote.animation().sample(tick).get(bone).orElseThrow();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadsEveryShippedEmote() throws IOException {
|
||||
for (String id : new String[] { "back_flip", "cry", "facepalm", "front_flip", "giga_chad", "tpose" }) {
|
||||
Emote emote = load(id);
|
||||
assertEquals(id, emote.id());
|
||||
assertFalse(emote.animation().sample(emote.animation().length() / 2).isEmpty(), id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsDetails() throws IOException {
|
||||
Emote emote = load("facepalm");
|
||||
assertEquals("facepalm", emote.name());
|
||||
assertEquals("just facepalm", emote.description());
|
||||
assertEquals("dsty", emote.author());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsTiming() throws IOException {
|
||||
// facepalm: plays once until stopTick 61.
|
||||
Emote facepalm = load("facepalm");
|
||||
assertFalse(facepalm.animation().loops());
|
||||
assertEquals(61, facepalm.animation().length(), 1e-6);
|
||||
|
||||
// cry: loops back to returnTick 5 after playing endTick 60.
|
||||
Emote cry = load("cry");
|
||||
assertTrue(cry.animation().loops());
|
||||
assertEquals(61, cry.animation().length(), 1e-6);
|
||||
assertEquals(5, cry.animation().loopStart(), 1e-6);
|
||||
|
||||
// back_flip writes isLoop as the string "false".
|
||||
assertFalse(load("back_flip").animation().loops());
|
||||
}
|
||||
|
||||
@Test
|
||||
void turnsPivotsIntoOffsets() throws IOException {
|
||||
// tpose's arms start at their rest pivots (x = ±5, y = 2), which is no offset.
|
||||
BoneTransform arm = bone(load("tpose"), Bone.RIGHT_ARM, 0);
|
||||
assertEquals(0, arm.x(), 1e-5);
|
||||
assertEquals(0, arm.y(), 1e-5);
|
||||
assertEquals(0, arm.z(), 1e-5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void flipsTheBendDirection() throws IOException {
|
||||
// cry bends the elbows by -0.96 in Emotecraft terms: towards the face.
|
||||
assertEquals(0.96f, bone(load("cry"), Bone.RIGHT_ARM, 30).bend(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapsTheWholeBodyToRoot() throws IOException {
|
||||
// front_flip turns "body" pitch to -2π by tick 15, which is a forward flip: +2π around model X.
|
||||
Emote flip = load("front_flip");
|
||||
assertEquals(2 * Math.PI, bone(flip, Bone.ROOT, 15).pitch(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
void treatsTheTorsoAsTheWholeBodyBeforeVersion3() throws IOException {
|
||||
// back_flip has no version (so 1) and flips with "torso".
|
||||
assertEquals(-2 * Math.PI, bone(load("back_flip"), Bone.ROOT, 70).pitch(), 0.01);
|
||||
assertTrue(load("back_flip").animation().sample(40).get(Bone.BODY).isEmpty());
|
||||
|
||||
String move = "{\"tick\": 0, \"torso\": {\"pitch\": 1}}";
|
||||
Emote version3 = EmotecraftLoader.load("v3",
|
||||
"{\"version\": 3, \"emote\": {\"degrees\": false, \"moves\": [" + move + "]}}");
|
||||
assertEquals(1, bone(version3, Bone.BODY, 0).pitch(), 1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertsRootOffsetsFromBlocks() throws IOException {
|
||||
Emote emote = inline("{\"tick\": 0, \"body\": {\"x\": 1, \"y\": 0.5, \"z\": 0.25}}", "");
|
||||
BoneTransform root = bone(emote, Bone.ROOT, 0);
|
||||
assertEquals(-16, root.x(), 1e-5);
|
||||
assertEquals(-8, root.y(), 1e-5);
|
||||
assertEquals(4, root.z(), 1e-5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void anglesAreInDegreesUnlessSaidOtherwise() throws IOException {
|
||||
Emote emote = inline("{\"tick\": 0, \"head\": {\"pitch\": 90}}", "");
|
||||
assertEquals(Math.PI / 2, bone(emote, Bone.HEAD, 0).pitch(), 1e-5);
|
||||
Emote radians = inline("{\"tick\": 0, \"head\": {\"pitch\": 1.5}}", "\"degrees\": false,");
|
||||
assertEquals(1.5, bone(radians, Bone.HEAD, 0).pitch(), 1e-5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyLoopsWithAReturnTick() throws IOException {
|
||||
assertFalse(inline("{\"tick\": 0, \"head\": {\"yaw\": 1}}", "\"isLoop\": true, \"endTick\": 10,")
|
||||
.animation().loops());
|
||||
}
|
||||
|
||||
@Test
|
||||
void addsTurns() throws IOException {
|
||||
Emote emote = inline("{\"tick\": 0, \"turn\": 1, \"head\": {\"yaw\": 0.5}}", "\"degrees\": false,");
|
||||
assertEquals(0.5 + 2 * Math.PI, bone(emote, Bone.HEAD, 0).yaw(), 1e-5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsFilesWithoutMoves() {
|
||||
assertThrows(IOException.class, () -> EmotecraftLoader.load("broken", "{\"emote\": {}}"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.saturnclient.emotes.core.format;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.saturnclient.emotes.core.Bone;
|
||||
import org.saturnclient.emotes.core.BoneTransform;
|
||||
import org.saturnclient.emotes.core.format.KeyframeAnimation.Channel;
|
||||
|
||||
class KeyframeAnimationTest {
|
||||
private static BoneTransform head(KeyframeAnimation animation, float tick) {
|
||||
return animation.sample(tick).get(Bone.HEAD).orElseThrow();
|
||||
}
|
||||
|
||||
@Test
|
||||
void interpolatesBetweenKeyframes() {
|
||||
KeyframeAnimation animation = KeyframeAnimation.builder()
|
||||
.keyframe(Bone.HEAD, Channel.PITCH, 0, 0, Easing.LINEAR)
|
||||
.keyframe(Bone.HEAD, Channel.PITCH, 10, 1, Easing.LINEAR)
|
||||
.length(10).build();
|
||||
assertEquals(0.25f, head(animation, 2.5f).pitch(), 1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void segmentsUseTheEasingOfTheKeyframeTheyStartFrom() {
|
||||
KeyframeAnimation animation = KeyframeAnimation.builder()
|
||||
.keyframe(Bone.HEAD, Channel.PITCH, 0, 0, Easing.INQUAD)
|
||||
.keyframe(Bone.HEAD, Channel.PITCH, 10, 1, Easing.LINEAR)
|
||||
.length(10).build();
|
||||
assertEquals(0.25f, head(animation, 5).pitch(), 1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void easeBeforeUsesTheEasingOfTheNextKeyframe() {
|
||||
KeyframeAnimation animation = KeyframeAnimation.builder()
|
||||
.keyframe(Bone.HEAD, Channel.PITCH, 0, 0, Easing.INQUAD)
|
||||
.keyframe(Bone.HEAD, Channel.PITCH, 10, 1, Easing.LINEAR)
|
||||
.easeBefore(true).length(10).build();
|
||||
assertEquals(0.5f, head(animation, 5).pitch(), 1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void holdsTheFirstAndLastValues() {
|
||||
KeyframeAnimation animation = KeyframeAnimation.builder()
|
||||
.keyframe(Bone.HEAD, Channel.YAW, 5, 1, Easing.LINEAR)
|
||||
.keyframe(Bone.HEAD, Channel.YAW, 10, 2, Easing.LINEAR)
|
||||
.length(20).build();
|
||||
assertEquals(1, head(animation, 0).yaw(), 1e-6);
|
||||
assertEquals(2, head(animation, 15).yaw(), 1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void channelsWithoutKeyframesAreZeroAndBonesWithoutKeyframesAreLeftAlone() {
|
||||
KeyframeAnimation animation = KeyframeAnimation.builder()
|
||||
.keyframe(Bone.HEAD, Channel.YAW, 0, 1, Easing.LINEAR)
|
||||
.length(10).build();
|
||||
assertEquals(0, head(animation, 0).pitch(), 1e-6);
|
||||
assertTrue(animation.sample(0).get(Bone.BODY).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void laterKeyframesOnTheSameTickWin() {
|
||||
KeyframeAnimation animation = KeyframeAnimation.builder()
|
||||
.keyframe(Bone.HEAD, Channel.ROLL, 5, 1, Easing.LINEAR)
|
||||
.keyframe(Bone.HEAD, Channel.ROLL, 5, 3, Easing.LINEAR)
|
||||
.length(10).build();
|
||||
assertEquals(1, animation.keyframes(Bone.HEAD, Channel.ROLL).length);
|
||||
assertEquals(3, head(animation, 5).roll(), 1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void keyframesCanBeAddedOutOfOrder() {
|
||||
KeyframeAnimation animation = KeyframeAnimation.builder()
|
||||
.keyframe(Bone.HEAD, Channel.PITCH, 10, 1, Easing.LINEAR)
|
||||
.keyframe(Bone.HEAD, Channel.PITCH, 0, 0, Easing.LINEAR)
|
||||
.length(10).build();
|
||||
assertEquals(0.5f, head(animation, 5).pitch(), 1e-6);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,305 @@
|
||||
{
|
||||
"version": 1,
|
||||
"valid": true,
|
||||
"name": "cry",
|
||||
"description": "T_T",
|
||||
"author": "dsty",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 60,
|
||||
"stopTick": 61,
|
||||
"isLoop": true,
|
||||
"returnTick": 5,
|
||||
"nsfw": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.34134695
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.22808763
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 25,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.21269174
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 35,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.32248768
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 45,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.0874397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 55,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.17582592
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.21476103
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -6.320644E-4
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 25,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": 0.029434478
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 35,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.039604172
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 45,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": 0.037607383
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 55,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": 0.050604735
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": -0.00930274
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 25,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0010032838
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 35,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": -0.009209918
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 45,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": -0.012478679
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 55,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.031160546
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -1.8700758
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.4349194
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.4970688
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.9622451
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": -1.9203311
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": 0.6307226
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": -0.2971223
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"bend": -0.8864816
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
{
|
||||
"version": 1,
|
||||
"valid": true,
|
||||
"name": "facepalm",
|
||||
"description": "just facepalm",
|
||||
"author": "dsty",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 60,
|
||||
"stopTick": 61,
|
||||
"isLoop": false,
|
||||
"returnTick": 2,
|
||||
"nsfw": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.067995355
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.14453278
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.11871816
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.19018666
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.20064461
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": 0.060454715
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.086299464
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.087619215
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.083825104
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": 0.036273066
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0041144555
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.12942825
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": -0.050149817
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.08701207
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.08676299
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -3.8523226
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -3.8523226
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.0664942
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.074402
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.074402
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.02932
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.02932
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.47555572
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.5093512
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.5093512
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.38563538
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.38563538
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.20147677
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.20521602
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.3274244
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.30787703
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.40478125
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.49199954
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.54730254
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.52019864
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,697 @@
|
||||
{
|
||||
"version": 1,
|
||||
"valid": true,
|
||||
"name": "front_flip",
|
||||
"description": "Parkour!!!!1",
|
||||
"author": "DKinchik",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 17,
|
||||
"stopTick": 18,
|
||||
"isLoop": false,
|
||||
"returnTick": 1,
|
||||
"nsfw": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.7020477
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.7020477
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"x": -1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"x": -1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"y": 11.169379
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"z": -0.39214998
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"z": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"z": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -1.9707682
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -1.9707682
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -1.3665518
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -1.3665518
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": -1.0793308
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": -1.0793308
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"y": 10.861001
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"z": -0.5082285
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"z": -0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"z": -0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -1.9850811
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -1.9850811
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"x": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"x": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"y": -0.061333913
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 7,
|
||||
"easing": "OUTQUAD",
|
||||
"body": {
|
||||
"y": 0.48852682
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "CONSTANT",
|
||||
"body": {
|
||||
"y": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"y": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 7,
|
||||
"easing": "OUTQUAD",
|
||||
"body": {
|
||||
"pitch": -2.8362873
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "CONSTANT",
|
||||
"body": {
|
||||
"pitch": -6.2831855
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"yaw": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"yaw": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"body": {
|
||||
"bend": -0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,354 @@
|
||||
{
|
||||
"version": 1,
|
||||
"valid": true,
|
||||
"name": "tpose",
|
||||
"description": "MENACING",
|
||||
"author": "stephanieisdumb",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 80,
|
||||
"stopTick": 81,
|
||||
"isLoop": true,
|
||||
"returnTick": 20,
|
||||
"nsfw": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -6.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -6.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 3.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 3.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -1.0704756
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": 0.059108086
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.61077195
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.008776078
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.6060502
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 1.5707964
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 1.5765934
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 1.5707964
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -1.3806202
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 6.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 6.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 3.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 3.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": -1.0045553
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.010133168
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": 0.44322997
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": 0.038214765
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": -0.5571526
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": -1.5707964
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": -1.5692185
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": -1.5707964
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"bend": -1.3810014
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.saturnclient.emotes.core.Animation;
|
||||
import org.saturnclient.emotes.core.AnimationPlayer;
|
||||
import org.saturnclient.emotes.core.Emote;
|
||||
import org.saturnclient.emotes.core.EmoteRegistry;
|
||||
import org.saturnclient.emotes.core.Pose;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -20,6 +22,7 @@ public final class Emotes implements ClientModInitializer {
|
||||
public static final String VERSION = /*$ mod_version*/ "0.1.0";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
|
||||
private static final EmoteRegistry REGISTRY = new EmoteRegistry();
|
||||
private static final Map<UUID, AnimationPlayer> PLAYING = new ConcurrentHashMap<>();
|
||||
private static long tick;
|
||||
|
||||
@@ -31,11 +34,27 @@ public final class Emotes implements ClientModInitializer {
|
||||
});
|
||||
}
|
||||
|
||||
/** The emotes {@link #play(UUID, String)} looks up. Load or register emotes into it at startup. */
|
||||
public static EmoteRegistry registry() {
|
||||
return REGISTRY;
|
||||
}
|
||||
|
||||
/** Starts {@code animation} on the player, replacing whatever they were playing. */
|
||||
public static void play(UUID player, Animation animation) {
|
||||
PLAYING.put(player, new AnimationPlayer(animation, tick));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the registered emote {@code id} on the player.
|
||||
*
|
||||
* @return false if no emote has that ID, in which case nothing changes
|
||||
*/
|
||||
public static boolean play(UUID player, String id) {
|
||||
Optional<Emote> emote = REGISTRY.get(id);
|
||||
emote.ifPresent(e -> play(player, e.animation()));
|
||||
return emote.isPresent();
|
||||
}
|
||||
|
||||
public static void stop(UUID player) {
|
||||
PLAYING.remove(player);
|
||||
}
|
||||
|
||||
@@ -23,9 +23,17 @@ public final class PoseApplier {
|
||||
private PoseApplier() {
|
||||
}
|
||||
|
||||
/** Where {@link Bone#ROOT} turns around, in model pixels below the neck (0.7 blocks above the feet). */
|
||||
private static final float ROOT_PIVOT_Y = 12.8f;
|
||||
|
||||
public static void apply(HumanoidModel<?> model, Pose pose) {
|
||||
pose.get(Bone.ROOT).ifPresent(transform -> applyRoot(model.root(), transform));
|
||||
|
||||
LimbBend bodyBend = null;
|
||||
for (Bone bone : Bone.values()) {
|
||||
if (bone == Bone.ROOT) {
|
||||
continue;
|
||||
}
|
||||
ModelPart part = part(model, bone);
|
||||
BoneTransform transform = pose.get(bone).orElse(null);
|
||||
if (transform != null) {
|
||||
@@ -49,6 +57,7 @@ public final class PoseApplier {
|
||||
|
||||
private static ModelPart part(HumanoidModel<?> model, Bone bone) {
|
||||
return switch (bone) {
|
||||
case ROOT -> model.root();
|
||||
case HEAD -> model.head;
|
||||
case BODY -> model.body;
|
||||
case RIGHT_ARM -> model.rightArm;
|
||||
@@ -68,6 +77,21 @@ public final class PoseApplier {
|
||||
part.zRot = transform.roll();
|
||||
}
|
||||
|
||||
/**
|
||||
* The root part turns around its own pivot, which is at the neck. Move it so the model turns around
|
||||
* its middle instead: a point p ends up at offset + pivot + R (p - pivot).
|
||||
*/
|
||||
private static void applyRoot(ModelPart root, BoneTransform transform) {
|
||||
root.xRot = transform.pitch();
|
||||
root.yRot = transform.yaw();
|
||||
root.zRot = transform.roll();
|
||||
Vector3f pivot = new Vector3f(0, ROOT_PIVOT_Y, 0);
|
||||
Vector3f turned = rotation(root).transform(new Vector3f(pivot));
|
||||
root.x += transform.x() + pivot.x - turned.x;
|
||||
root.y += transform.y() + pivot.y - turned.y;
|
||||
root.z += transform.z() + pivot.z - turned.z;
|
||||
}
|
||||
|
||||
private static LimbBend bend(Bone bone, BoneTransform transform) {
|
||||
if (transform == null || transform.bend() == 0) {
|
||||
return null;
|
||||
@@ -78,7 +102,7 @@ public final class PoseApplier {
|
||||
float bend = transform.bend();
|
||||
float axis = transform.bendAxis();
|
||||
return switch (bone) {
|
||||
case HEAD -> null;
|
||||
case ROOT, HEAD -> null;
|
||||
case RIGHT_ARM, LEFT_ARM -> new LimbBend(-2, 10, bend, axis, SHARPNESS, 12);
|
||||
case RIGHT_LEG, LEFT_LEG -> new LimbBend(0, 12, bend, axis, SHARPNESS, 12);
|
||||
// The hips stay on the legs and the chest leans.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.saturnclient.emotes.testmod;
|
||||
|
||||
import org.saturnclient.emotes.core.EmoteRegistry;
|
||||
import org.saturnclient.emotes.fabric.Emotes;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
|
||||
@@ -15,19 +16,35 @@ import net.minecraft.client.CameraType;
|
||||
public final class EmotesGameTest implements FabricClientGameTest {
|
||||
private static final String MINECRAFT = /*$ minecraft*/ "1.21.11";
|
||||
|
||||
/** Animation name, the tick to screenshot it at, and the angles to look from (0 is the front). */
|
||||
/** What to play, the tick to screenshot it at, and the angles to look from (0 is the front). */
|
||||
private static final Shot[] SHOTS = {
|
||||
new Shot("tpose", 10, 0, 90),
|
||||
new Shot("bend", 20, 0, 90),
|
||||
new Shot("flap", 15, 0, 90),
|
||||
new Shot("squat", 20, 0, 90),
|
||||
new Shot("bow", 20, 0, 90),
|
||||
Shot.test("tpose", 10, 0, 90),
|
||||
Shot.test("bend", 20, 0, 90),
|
||||
Shot.test("flap", 15, 0, 90),
|
||||
Shot.test("squat", 20, 0, 90),
|
||||
Shot.test("bow", 20, 0, 90),
|
||||
// Top and bottom of the bounce.
|
||||
new Shot("twerk", 16, 0, 90, 150),
|
||||
new Shot("twerk", 4, 0, 90, 150),
|
||||
Shot.test("twerk", 16, 0, 90, 150),
|
||||
Shot.test("twerk", 4, 0, 90, 150),
|
||||
// Saturn's emotes, loaded from Emotecraft JSON into the library's registry.
|
||||
Shot.emote("facepalm", 40, 0, 90),
|
||||
Shot.emote("cry", 30, 0, 90),
|
||||
Shot.emote("tpose", 30, 0),
|
||||
Shot.emote("front_flip", 9, 90),
|
||||
Shot.emote("back_flip", 30, 90),
|
||||
Shot.emote("back_flip", 50, 90),
|
||||
Shot.emote("giga_chad", 60, 0, 90),
|
||||
};
|
||||
|
||||
private record Shot(String animation, int tick, float... angles) {
|
||||
/** A test animation or a registered emote, named apart in screenshots since IDs can repeat. */
|
||||
private record Shot(EmoteRegistry registry, String prefix, String id, int tick, float... angles) {
|
||||
static Shot test(String id, int tick, float... angles) {
|
||||
return new Shot(TestAnimations.REGISTRY, "test-", id, tick, angles);
|
||||
}
|
||||
|
||||
static Shot emote(String id, int tick, float... angles) {
|
||||
return new Shot(Emotes.registry(), "", id, tick, angles);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,17 +59,18 @@ public final class EmotesGameTest implements FabricClientGameTest {
|
||||
|
||||
for (Shot shot : SHOTS) {
|
||||
context.runOnClient(client -> Emotes.play(client.player.getUUID(),
|
||||
TestAnimations.ALL.get(shot.animation())));
|
||||
shot.registry().get(shot.id()).orElseThrow().animation()));
|
||||
context.waitTicks(shot.tick());
|
||||
for (float angle : shot.angles()) {
|
||||
screenshot(context, shot.animation() + "-t" + shot.tick() + "-" + (int) angle, angle);
|
||||
screenshot(context, shot.prefix() + shot.id() + "-t" + shot.tick() + "-" + (int) angle, angle);
|
||||
}
|
||||
context.runOnClient(client -> Emotes.stop(client.player.getUUID()));
|
||||
}
|
||||
|
||||
// A held item should come out of the fist on the bent forearm.
|
||||
singleplayer.getServer().runCommand("give @a diamond_sword");
|
||||
context.runOnClient(client -> Emotes.play(client.player.getUUID(), TestAnimations.ALL.get("hold")));
|
||||
context.runOnClient(client -> Emotes.play(client.player.getUUID(),
|
||||
TestAnimations.REGISTRY.get("hold").orElseThrow().animation()));
|
||||
context.waitTicks(10);
|
||||
screenshot(context, "hold-0", 0);
|
||||
screenshot(context, "hold-90", 90);
|
||||
|
||||
@@ -3,9 +3,13 @@ package org.saturnclient.emotes.testmod;
|
||||
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
|
||||
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
|
||||
|
||||
import java.util.TreeSet;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.saturnclient.emotes.core.Animation;
|
||||
import org.saturnclient.emotes.core.Emote;
|
||||
import org.saturnclient.emotes.core.EmoteRegistry;
|
||||
import org.saturnclient.emotes.fabric.Emotes;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
|
||||
@@ -18,46 +22,70 @@ import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
/**
|
||||
* Dev-only tester. In game: {@code /emotes play <name>}, {@code /emotes stop}, {@code /emotes info}.
|
||||
* Dev-only tester. In game: {@code /emotes play <id>} plays an emote from the library's registry
|
||||
* (Saturn's emotes, loaded here), {@code /emotes test <name>} plays one of the testmod's own
|
||||
* {@link TestAnimations}, and there's {@code /emotes stop} and {@code /emotes info}.
|
||||
* Switch to third person (F5) to see yourself.
|
||||
*/
|
||||
public final class EmotesTestmod implements ClientModInitializer {
|
||||
private static final String MINECRAFT = /*$ minecraft*/ "1.21.11";
|
||||
/** Saturn Client's emotes, in Emotecraft's format, from assets/saturn_emotes_testmod/player_animations. */
|
||||
static final List<String> EMOTECRAFT_EMOTES = List.of("back_flip", "cry", "facepalm", "front_flip",
|
||||
"giga_chad", "tpose");
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
// Applies every mixin now, so a broken target crashes at startup instead of in a world.
|
||||
MixinEnvironment.getCurrentEnvironment().audit();
|
||||
Emotes.LOGGER.info("Mixin audit passed on Minecraft {}", MINECRAFT);
|
||||
loadEmotes();
|
||||
|
||||
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(
|
||||
literal("emotes")
|
||||
.then(literal("play").then(argument("name", StringArgumentType.word())
|
||||
.then(literal("play").then(argument("id", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> {
|
||||
TestAnimations.ALL.keySet().forEach(builder::suggest);
|
||||
Emotes.registry().ids().forEach(builder::suggest);
|
||||
return builder.buildFuture();
|
||||
})
|
||||
.executes(EmotesTestmod::play)))
|
||||
.executes(ctx -> play(ctx, Emotes.registry(), "id"))))
|
||||
.then(literal("test").then(argument("name", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> {
|
||||
TestAnimations.REGISTRY.ids().forEach(builder::suggest);
|
||||
return builder.buildFuture();
|
||||
})
|
||||
.executes(ctx -> play(ctx, TestAnimations.REGISTRY, "name"))))
|
||||
.then(literal("stop").executes(ctx -> {
|
||||
Emotes.stop(ctx.getSource().getPlayer().getUUID());
|
||||
return 1;
|
||||
}))
|
||||
.then(literal("info").executes(ctx -> {
|
||||
ctx.getSource().sendFeedback(Component.literal("Saturn Emotes " + Emotes.VERSION
|
||||
+ " on Minecraft " + MINECRAFT + ". Animations: "
|
||||
+ String.join(", ", new TreeSet<>(TestAnimations.ALL.keySet()))));
|
||||
+ " on Minecraft " + MINECRAFT + ". Emotes: "
|
||||
+ String.join(", ", Emotes.registry().ids()) + ". Test animations: "
|
||||
+ String.join(", ", TestAnimations.REGISTRY.ids())));
|
||||
return 1;
|
||||
}))));
|
||||
}
|
||||
|
||||
private static int play(CommandContext<FabricClientCommandSource> ctx) {
|
||||
String name = StringArgumentType.getString(ctx, "name");
|
||||
Animation animation = TestAnimations.ALL.get(name);
|
||||
if (animation == null) {
|
||||
ctx.getSource().sendError(Component.literal("Unknown animation: " + name));
|
||||
private static void loadEmotes() {
|
||||
for (String id : EMOTECRAFT_EMOTES) {
|
||||
String path = "/assets/saturn_emotes_testmod/player_animations/" + id + ".json";
|
||||
try (InputStream in = EmotesTestmod.class.getResourceAsStream(path)) {
|
||||
Emotes.registry().loadEmotecraft(id, in);
|
||||
} catch (IOException | NullPointerException e) {
|
||||
Emotes.LOGGER.error("Couldn't load emote {}", id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int play(CommandContext<FabricClientCommandSource> ctx, EmoteRegistry registry, String argument) {
|
||||
String id = StringArgumentType.getString(ctx, argument);
|
||||
Optional<Emote> emote = registry.get(id);
|
||||
if (emote.isEmpty()) {
|
||||
ctx.getSource().sendError(Component.literal("Unknown: " + id));
|
||||
return 0;
|
||||
}
|
||||
Emotes.play(ctx.getSource().getPlayer().getUUID(), animation);
|
||||
Emotes.play(ctx.getSource().getPlayer().getUUID(), emote.get().animation());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
package org.saturnclient.emotes.testmod;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.saturnclient.emotes.core.Animation;
|
||||
import org.saturnclient.emotes.core.Bone;
|
||||
import org.saturnclient.emotes.core.Emote;
|
||||
import org.saturnclient.emotes.core.EmoteRegistry;
|
||||
import org.saturnclient.emotes.core.BoneTransform;
|
||||
import org.saturnclient.emotes.core.Pose;
|
||||
|
||||
/** Hand-written animations for checking that poses reach the model on every version. */
|
||||
/**
|
||||
* Hand-written animations for checking that poses reach the model on every version. They live in the
|
||||
* testmod's own registry, separate from the library's {@code Emotes.registry()}.
|
||||
*/
|
||||
final class TestAnimations {
|
||||
private static final float HALF_PI = (float) (Math.PI / 2);
|
||||
|
||||
static final Map<String, Animation> ALL = Map.of(
|
||||
private static final Map<String, Animation> ALL = Map.of(
|
||||
// Right arm raised, waving side to side. Tests rotation.
|
||||
"wave", of(40, true, t -> Pose.builder()
|
||||
.set(Bone.RIGHT_ARM, BoneTransform.rotation((float) Math.PI, 0,
|
||||
@@ -75,6 +81,13 @@ final class TestAnimations {
|
||||
private TestAnimations() {
|
||||
}
|
||||
|
||||
static final EmoteRegistry REGISTRY = new EmoteRegistry();
|
||||
|
||||
static {
|
||||
new TreeMap<>(ALL).forEach((id, animation) ->
|
||||
REGISTRY.register(new Emote(id, id, "Test animation", "Saturn Emotes testmod", animation)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Squatting with knees out and hips pushed back, bouncing the hips about 2.5 times a second. The
|
||||
* torso tips forward around the hips and bends backwards, so the lower back arches in (not a
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,305 @@
|
||||
{
|
||||
"version": 1,
|
||||
"valid": true,
|
||||
"name": "cry",
|
||||
"description": "T_T",
|
||||
"author": "dsty",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 60,
|
||||
"stopTick": 61,
|
||||
"isLoop": true,
|
||||
"returnTick": 5,
|
||||
"nsfw": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.34134695
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.22808763
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 25,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.21269174
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 35,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.32248768
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 45,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.0874397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 55,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.17582592
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.21476103
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -6.320644E-4
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 25,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": 0.029434478
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 35,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.039604172
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 45,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": 0.037607383
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 55,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": 0.050604735
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": -0.00930274
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 25,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0010032838
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 35,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": -0.009209918
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 45,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": -0.012478679
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 55,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.031160546
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -1.8700758
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.4349194
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.4970688
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.9622451
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": -1.9203311
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": 0.6307226
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": -0.2971223
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"bend": -0.8864816
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
{
|
||||
"version": 1,
|
||||
"valid": true,
|
||||
"name": "facepalm",
|
||||
"description": "just facepalm",
|
||||
"author": "dsty",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 60,
|
||||
"stopTick": 61,
|
||||
"isLoop": false,
|
||||
"returnTick": 2,
|
||||
"nsfw": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.067995355
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.14453278
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.11871816
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.19018666
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.20064461
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": 0.060454715
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.086299464
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.087619215
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.083825104
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": 0.036273066
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0041144555
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.12942825
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": -0.050149817
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.08701207
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.08676299
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -3.8523226
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -3.8523226
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.0664942
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.074402
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.074402
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.02932
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.02932
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.47555572
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.5093512
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.5093512
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.38563538
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.38563538
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.20147677
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.20521602
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.3274244
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.30787703
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.40478125
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.49199954
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.54730254
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 40,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.52019864
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,697 @@
|
||||
{
|
||||
"version": 1,
|
||||
"valid": true,
|
||||
"name": "front_flip",
|
||||
"description": "Parkour!!!!1",
|
||||
"author": "DKinchik",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 17,
|
||||
"stopTick": 18,
|
||||
"isLoop": false,
|
||||
"returnTick": 1,
|
||||
"nsfw": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.7020477
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.7020477
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"x": -1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"x": -1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"y": 11.169379
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"z": -0.39214998
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"z": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"z": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -1.9707682
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -1.9707682
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -1.3665518
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -1.3665518
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": -1.0793308
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": -1.0793308
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"y": 10.861001
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"z": -0.5082285
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"z": -0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"z": -0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -1.9850811
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -1.9850811
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"x": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"x": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"y": -0.061333913
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 7,
|
||||
"easing": "OUTQUAD",
|
||||
"body": {
|
||||
"y": 0.48852682
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "CONSTANT",
|
||||
"body": {
|
||||
"y": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"y": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 7,
|
||||
"easing": "OUTQUAD",
|
||||
"body": {
|
||||
"pitch": -2.8362873
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "CONSTANT",
|
||||
"body": {
|
||||
"pitch": -6.2831855
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"yaw": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"yaw": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"body": {
|
||||
"bend": -0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,354 @@
|
||||
{
|
||||
"version": 1,
|
||||
"valid": true,
|
||||
"name": "tpose",
|
||||
"description": "MENACING",
|
||||
"author": "stephanieisdumb",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 80,
|
||||
"stopTick": 81,
|
||||
"isLoop": true,
|
||||
"returnTick": 20,
|
||||
"nsfw": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -6.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -6.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 3.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 3.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -1.0704756
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": 0.059108086
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.61077195
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.008776078
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 0.6060502
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 1.5707964
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 1.5765934
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": 1.5707964
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -1.3806202
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 6.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 6.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 3.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 3.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": -1.0045553
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.010133168
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": 0.44322997
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": 0.038214765
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": -0.5571526
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": -1.5707964
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": -1.5692185
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": -1.5707964
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"bend": -1.3810014
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user