WIP: initial epic fight core extraction (Phase 0)

83 files forkés d'Epic Fight (~18k LOC). Base non-compilable en l'état.

Contenu extrait :
- math/ — OpenMatrix4f, Vec3f/4f/2f, QuaternionUtils, MathUtils, ...
- armature/ — Armature, Joint, JointTransform, HumanoidArmature
- anim/ — Animator, ServerAnimator, ClientAnimator, LivingMotion, ...
- anim/types/ — StaticAnimation, DynamicAnimation, MovementAnimation, LinkAnimation,
                ConcurrentLinkAnimation, LayerOffAnimation, EntityState
- anim/client/ — Layer, ClientAnimator, JointMask
- mesh/ — SkinnedMesh, SingleGroupVertexBuilder, Mesh, HumanoidMesh, ...
- cloth/ — AbstractSimulator, ClothSimulator (dépendance transitive de StaticMesh)
- asset/ — JsonAssetLoader, AssetAccessor
- patch/ — EntityPatch, LivingEntityPatch, PlayerPatch, ClientPlayerPatch
- util/ — ParseUtil, TypeFlexibleHashMap
- exception/ — AssetLoadingException
- event/ — PatchedRenderersEvent, PrepareModelEvent, RegisterResourceLayersEvent
- render/ — TiedUpRenderTypes

Headers GPLv3 + attribution injectés sur tous les .java.
Package declarations fixées sur Armature.java et TiedUpRenderTypes.java.

115 imports résiduels à résoudre manuellement :
- yesman.epicfight.main (EpicFightMod, EpicFightSharedConstants) — 30
- yesman.epicfight.gameasset (Animations, Armatures, EpicFightSounds) — 12
- yesman.epicfight.api.physics + physics.ik (combat physics) — 16
- yesman.epicfight.network.* (combat packets) — 13
- yesman.epicfight.world.* (combat entity logic) — 10
- yesman.epicfight.config.ClientConfig — 3
- yesman.epicfight.skill, .client.gui, .particle, .collider — divers combat/UI

Stratégie fix (2-3 sem manuel) : strip usage combat, stubs pour refs
core (EpicFightMod → TiedUpMod, SharedConstants → TiedUpRigConstants,
ClientConfig → TiedUpAnimationConfig).
This commit is contained in:
notevil
2026-04-22 00:26:29 +02:00
parent b141e137e7
commit cbf61906e0
83 changed files with 18109 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.mutable.MutableInt;
import net.minecraft.util.Mth;
public class AnimationClip {
public static final AnimationClip EMPTY_CLIP = new AnimationClip();
protected Map<String, TransformSheet> jointTransforms = new HashMap<> ();
protected float clipTime;
protected float[] bakedTimes;
/// To modify existing keyframes in runtime and keep the baked state, call [#setBaked] again
/// after finishing clip modification. (Frequent calls of this method will cause a performance issue)
public void addJointTransform(String jointName, TransformSheet sheet) {
this.jointTransforms.put(jointName, sheet);
this.bakedTimes = null;
}
public boolean hasJointTransform(String jointName) {
return this.jointTransforms.containsKey(jointName);
}
/// Bakes all keyframes to optimize calculating current pose,
public void bakeKeyframes() {
Set<Float> timestamps = new HashSet<> ();
this.jointTransforms.values().forEach(transformSheet -> {
transformSheet.forEach((i, keyframe) -> {
timestamps.add(keyframe.time());
});
});
float[] bakedTimestamps = new float[timestamps.size()];
MutableInt mi = new MutableInt(0);
timestamps.stream().sorted().toList().forEach(f -> {
bakedTimestamps[mi.getAndAdd(1)] = f;
});
Map<String, TransformSheet> bakedJointTransforms = new HashMap<> ();
this.jointTransforms.forEach((jointName, transformSheet) -> {
bakedJointTransforms.put(jointName, transformSheet.createInterpolated(bakedTimestamps));
});
this.jointTransforms = bakedJointTransforms;
this.bakedTimes = bakedTimestamps;
}
/// Bake keyframes supposing all keyframes are aligned (mainly used when creating link animations)
public void setBaked() {
TransformSheet transformSheet = this.jointTransforms.get("Root");
if (transformSheet != null) {
this.bakedTimes = new float[transformSheet.getKeyframes().length];
for (int i = 0; i < transformSheet.getKeyframes().length; i++) {
this.bakedTimes[i] = transformSheet.getKeyframes()[i].time();
}
}
}
public TransformSheet getJointTransform(String jointName) {
return this.jointTransforms.get(jointName);
}
public final Pose getPoseInTime(float time) {
Pose pose = new Pose();
if (time < 0.0F) {
time = this.clipTime + time;
}
if (this.bakedTimes != null && this.bakedTimes.length > 0) {
// Binary search
int begin = 0, end = this.bakedTimes.length - 1;
while (end - begin > 1) {
int i = begin + (end - begin) / 2;
if (this.bakedTimes[i] <= time && this.bakedTimes[i+1] > time) {
begin = i;
end = i+1;
break;
} else {
if (this.bakedTimes[i] > time) {
end = i;
} else if (this.bakedTimes[i+1] <= time) {
begin = i;
}
}
}
float delta = Mth.clamp((time - this.bakedTimes[begin]) / (this.bakedTimes[end] - this.bakedTimes[begin]), 0.0F, 1.0F);
TransformSheet.InterpolationInfo iInfo = new TransformSheet.InterpolationInfo(begin, end, delta);
for (String jointName : this.jointTransforms.keySet()) {
pose.putJointData(jointName, this.jointTransforms.get(jointName).getInterpolatedTransform(iInfo));
}
} else {
for (String jointName : this.jointTransforms.keySet()) {
pose.putJointData(jointName, this.jointTransforms.get(jointName).getInterpolatedTransform(time));
}
}
return pose;
}
/// @return returns protected keyframes of each joint to keep the baked state of keyframes.
public Map<String, TransformSheet> getJointTransforms() {
return Collections.unmodifiableMap(this.jointTransforms);
}
public void reset() {
this.jointTransforms.clear();
this.bakedTimes = null;
}
public void setClipTime(float clipTime) {
this.clipTime = clipTime;
}
public float getClipTime() {
return this.clipTime;
}
}

View File

@@ -0,0 +1,514 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.logging.log4j.Logger;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.FileToIdConverter;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import com.tiedup.remake.rig.anim.property.AnimationProperty;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.asset.JsonAssetLoader;
import com.tiedup.remake.rig.anim.client.AnimationSubFileReader;
import yesman.epicfight.api.data.reloader.SkillManager;
import com.tiedup.remake.rig.exception.AssetLoadingException;
import com.tiedup.remake.rig.util.InstantiateInvoker;
import com.tiedup.remake.rig.util.MutableBoolean;
import yesman.epicfight.gameasset.Animations;
import yesman.epicfight.gameasset.Armatures;
import yesman.epicfight.main.EpicFightMod;
import yesman.epicfight.main.EpicFightSharedConstants;
import yesman.epicfight.network.EpicFightNetworkManager;
import yesman.epicfight.network.client.CPCheckAnimationRegistryMatches;
import yesman.epicfight.network.server.SPDatapackSync;
@SuppressWarnings("unchecked")
public class AnimationManager extends SimplePreparableReloadListener<List<ResourceLocation>> {
private static final AnimationManager INSTANCE = new AnimationManager();
private static ResourceManager serverResourceManager = null;
private static final Gson GSON = new GsonBuilder().create();
private static final String DIRECTORY = "animmodels/animations";
public static AnimationManager getInstance() {
return INSTANCE;
}
private final Map<Integer, AnimationAccessor<? extends StaticAnimation>> animationById = Maps.newHashMap();
private final Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>> animationByName = Maps.newHashMap();
private final Map<AnimationAccessor<? extends StaticAnimation>, StaticAnimation> animations = Maps.newHashMap();
private final Map<AnimationAccessor<? extends StaticAnimation>, String> resourcepackAnimationCommands = Maps.newHashMap();
public static boolean checkNull(AssetAccessor<? extends StaticAnimation> animation) {
if (animation == null || animation.isEmpty()) {
if (animation != null) {
EpicFightMod.stacktraceIfDevSide("Empty animation accessor: " + animation.registryName(), NoSuchElementException::new);
} else {
EpicFightMod.stacktraceIfDevSide("Null animation accessor", NoSuchElementException::new);
}
return true;
}
return false;
}
public static <T extends StaticAnimation> AnimationAccessor<T> byKey(String registryName) {
return byKey(ResourceLocation.parse(registryName));
}
public static <T extends StaticAnimation> AnimationAccessor<T> byKey(ResourceLocation registryName) {
return (AnimationAccessor<T>)getInstance().animationByName.get(registryName);
}
public static <T extends StaticAnimation> AnimationAccessor<T> byId(int animationId) {
return (AnimationAccessor<T>)getInstance().animationById.get(animationId);
}
public Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>> getAnimations(Predicate<AssetAccessor<? extends StaticAnimation>> filter) {
Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>> filteredItems =
this.animationByName.entrySet().stream()
.filter(entry -> {
return filter.test(entry.getValue());
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return ImmutableMap.copyOf(filteredItems);
}
public AnimationClip loadAnimationClip(StaticAnimation animation, BiFunction<JsonAssetLoader, StaticAnimation, AnimationClip> clipLoader) {
try {
if (getAnimationResourceManager() == null) {
return null;
}
JsonAssetLoader modelLoader = new JsonAssetLoader(getAnimationResourceManager(), animation.getLocation());
AnimationClip loadedClip = clipLoader.apply(modelLoader, animation);
return loadedClip;
} catch (AssetLoadingException e) {
throw new AssetLoadingException("Failed to load animation clip from: " + animation, e);
}
}
public static void readAnimationProperties(StaticAnimation animation) {
ResourceLocation dataLocation = getSubAnimationFileLocation(animation.getLocation(), AnimationSubFileReader.SUBFILE_CLIENT_PROPERTY);
ResourceLocation povLocation = getSubAnimationFileLocation(animation.getLocation(), AnimationSubFileReader.SUBFILE_POV_ANIMATION);
getAnimationResourceManager().getResource(dataLocation).ifPresent((rs) -> {
AnimationSubFileReader.readAndApply(animation, rs, AnimationSubFileReader.SUBFILE_CLIENT_PROPERTY);
});
getAnimationResourceManager().getResource(povLocation).ifPresent((rs) -> {
AnimationSubFileReader.readAndApply(animation, rs, AnimationSubFileReader.SUBFILE_POV_ANIMATION);
});
}
@Override
protected List<ResourceLocation> prepare(ResourceManager resourceManager, ProfilerFiller profilerIn) {
if (!EpicFightSharedConstants.isPhysicalClient() && serverResourceManager == null) {
serverResourceManager = resourceManager;
}
this.animations.clear();
this.animationById.entrySet().removeIf(entry -> !entry.getValue().inRegistry());
this.animationByName.entrySet().removeIf(entry -> !entry.getValue().inRegistry());
this.resourcepackAnimationCommands.clear();
List<ResourceLocation> directories = new ArrayList<> ();
scanDirectoryNames(resourceManager, directories);
return directories;
}
private static void scanDirectoryNames(ResourceManager resourceManager, List<ResourceLocation> output) {
FileToIdConverter filetoidconverter = FileToIdConverter.json(DIRECTORY);
filetoidconverter.listMatchingResources(resourceManager).keySet().stream().map(AnimationManager::pathToId).forEach(output::add);
}
@Override
protected void apply(List<ResourceLocation> objects, ResourceManager resourceManager, ProfilerFiller profilerIn) {
Armatures.reload(resourceManager);
Set<ResourceLocation> registeredAnimation =
this.animationById.values().stream()
.reduce(
new HashSet<> (),
(set, accessor) -> {
set.add(accessor.registryName());
for (AssetAccessor<? extends StaticAnimation> subAnimAccessor : accessor.get().getSubAnimations()) {
set.add(subAnimAccessor.registryName());
}
return set;
},
(set1, set2) -> {
set1.addAll(set2);
return set1;
}
);
// Load animations that are not registered by AnimationRegistryEvent
// Reads from /assets folder in physical client, /datapack in physical server.
objects.stream()
.filter(animId -> !registeredAnimation.contains(animId) && !animId.getPath().contains("/data/") && !animId.getPath().contains("/pov/"))
.sorted(Comparator.comparing(ResourceLocation::toString))
.forEach(animId -> {
Optional<Resource> resource = resourceManager.getResource(idToPath(animId));
try (Reader reader = resource.orElseThrow().openAsReader()) {
JsonElement jsonelement = GsonHelper.fromJson(GSON, reader, JsonElement.class);
this.readResourcepackAnimation(animId, jsonelement.getAsJsonObject());
} catch (IOException | JsonParseException | IllegalArgumentException resourceReadException) {
EpicFightMod.LOGGER.error("Couldn't parse animation data from {}", animId, resourceReadException);
} catch (Exception e) {
EpicFightMod.LOGGER.error("Failed at constructing {}", animId, e);
}
});
SkillManager.reloadAllSkillsAnimations();
this.animations.entrySet().stream()
.reduce(
new ArrayList<AssetAccessor<? extends StaticAnimation>>(),
(list, entry) -> {
MutableBoolean init = new MutableBoolean(true);
if (entry.getValue() == null || entry.getValue().getAccessor() == null) {
EpicFightMod.logAndStacktraceIfDevSide(Logger::error, "Invalid animation implementation: " + entry.getKey(), AssetLoadingException::new);
init.set(false);
}
entry.getValue().getSubAnimations().forEach((subAnimation) -> {
if (subAnimation == null || subAnimation.get() == null) {
EpicFightMod.logAndStacktraceIfDevSide(Logger::error, "Invalid sub animation implementation: " + entry.getKey(), AssetLoadingException::new);
init.set(false);
}
});
if (init.value()) {
list.add(entry.getValue().getAccessor());
list.addAll(entry.getValue().getSubAnimations());
}
return list;
},
(list1, list2) -> {
list1.addAll(list2);
return list1;
}
)
.forEach(accessor -> {
accessor.doOrThrow(StaticAnimation::postInit);
if (EpicFightSharedConstants.isPhysicalClient()) {
AnimationManager.readAnimationProperties(accessor.get());
}
});
}
public static ResourceLocation getSubAnimationFileLocation(ResourceLocation location, AnimationSubFileReader.SubFileType<?> subFileType) {
int splitIdx = location.getPath().lastIndexOf('/');
if (splitIdx < 0) {
splitIdx = 0;
}
return ResourceLocation.fromNamespaceAndPath(location.getNamespace(), String.format("%s/" + subFileType.getDirectory() + "%s", location.getPath().substring(0, splitIdx), location.getPath().substring(splitIdx)));
}
/// Converts animation id, acquired by [StaticAnimation#getRegistryName], to animation resource path acquired by [StaticAnimation#getLocation]
public static ResourceLocation idToPath(ResourceLocation rl) {
return rl.getPath().matches(DIRECTORY + "/.*\\.json") ? rl : ResourceLocation.fromNamespaceAndPath(rl.getNamespace(), DIRECTORY + "/" + rl.getPath() + ".json");
}
/// Converts animation resource path, acquired by [StaticAnimation#getLocation], to animation id acquired by [StaticAnimation#getRegistryName]
public static ResourceLocation pathToId(ResourceLocation rl) {
return ResourceLocation.fromNamespaceAndPath(rl.getNamespace(), rl.getPath().replace(DIRECTORY + "/", "").replace(".json", ""));
}
public static void setServerResourceManager(ResourceManager pResourceManager) {
serverResourceManager = pResourceManager;
}
public static ResourceManager getAnimationResourceManager() {
return EpicFightSharedConstants.isPhysicalClient() ? Minecraft.getInstance().getResourceManager() : serverResourceManager;
}
public int getResourcepackAnimationCount() {
return this.resourcepackAnimationCommands.size();
}
public Stream<CompoundTag> getResourcepackAnimationStream() {
return this.resourcepackAnimationCommands.entrySet().stream().map((entry) -> {
CompoundTag compTag = new CompoundTag();
compTag.putString("registry_name", entry.getKey().registryName().toString());
compTag.putInt("id", entry.getKey().id());
compTag.putString("invoke_command", entry.getValue());
return compTag;
});
}
/**
* @param mandatoryPack : creates dummy animations for animations from the server without animation clips when the server has mandatory resource pack.
* custom weapon types & mob capabilities won't be created because they won't be able to find the animations from the server
* dummy animations will be automatically removed right after reloading resourced as the server forces using resource pack
*/
@OnlyIn(Dist.CLIENT)
public void processServerPacket(SPDatapackSync packet, boolean mandatoryPack) {
if (mandatoryPack) {
for (CompoundTag tag : packet.getTags()) {
String invocationCommand = tag.getString("invoke_command");
ResourceLocation registryName = ResourceLocation.parse(tag.getString("registry_name"));
int id = tag.getInt("id");
if (this.animationByName.containsKey(registryName)) {
continue;
}
AnimationAccessor<? extends StaticAnimation> accessor = AnimationAccessorImpl.create(registryName, getResourcepackAnimationCount(), false, (accessor$2) -> {
try {
return InstantiateInvoker.invoke(invocationCommand, StaticAnimation.class).getResult();
} catch (Exception e) {
EpicFightMod.LOGGER.warn("Failed at creating animation from server resource pack");
e.printStackTrace();
return Animations.EMPTY_ANIMATION;
}
});
this.animationById.put(id, accessor);
this.animationByName.put(registryName, accessor);
}
}
int animationCount = this.animations.size();
String[] registryNames = new String[animationCount];
for (int i = 0; i < animationCount; i++) {
String registryName = this.animationById.get(i + 1).registryName().toString();
registryNames[i] = registryName;
}
CPCheckAnimationRegistryMatches registrySyncPacket = new CPCheckAnimationRegistryMatches(animationCount, registryNames);
EpicFightNetworkManager.sendToServer(registrySyncPacket);
}
public void validateClientAnimationRegistry(CPCheckAnimationRegistryMatches msg, ServerGamePacketListenerImpl connection) {
StringBuilder messageBuilder = new StringBuilder();
int count = 0;
Set<String> clientAnimationRegistry = new HashSet<> (Set.of(msg.registryNames));
for (String registryName : this.animations.keySet().stream().map((rl) -> rl.toString()).toList()) {
if (!clientAnimationRegistry.contains(registryName)) {
// Animations that don't exist in client
if (count < 10) {
messageBuilder.append(registryName);
messageBuilder.append("\n");
}
count++;
} else {
clientAnimationRegistry.remove(registryName);
}
}
// Animations that don't exist in server
for (String registryName : clientAnimationRegistry) {
if (registryName.equals("empty")) {
continue;
}
if (count < 10) {
messageBuilder.append(registryName);
messageBuilder.append("\n");
}
count++;
}
if (count >= 10) {
messageBuilder.append(Component.translatable("gui.epicfight.warn.animation_unsync.etc", (count - 9)).getString());
messageBuilder.append("\n");
}
if (!messageBuilder.isEmpty()) {
connection.disconnect(Component.translatable("gui.epicfight.warn.animation_unsync", messageBuilder.toString()));
}
}
private static final Set<String> NO_WARNING_MODID = Sets.newHashSet();
public static void addNoWarningModId(String modid) {
NO_WARNING_MODID.add(modid);
}
/**************************************************
* User-animation loader
**************************************************/
@SuppressWarnings({ "deprecation" })
private void readResourcepackAnimation(ResourceLocation rl, JsonObject json) throws Exception {
JsonElement constructorElement = json.get("constructor");
if (constructorElement == null) {
if (NO_WARNING_MODID.contains(rl.getNamespace())) {
return;
} else {
EpicFightMod.logAndStacktraceIfDevSide(
Logger::error
, "Datapack animation reading failed: No constructor information has provided: " + rl
, IllegalStateException::new
, "No constructor information has provided in User animation, " + rl + "\nPlease remove this resource if it's unnecessary to optimize your project."
);
return;
}
}
JsonObject constructorObject = constructorElement.getAsJsonObject();
String invocationCommand = constructorObject.get("invocation_command").getAsString();
StaticAnimation animation = InstantiateInvoker.invoke(invocationCommand, StaticAnimation.class).getResult();
JsonElement propertiesElement = json.getAsJsonObject().get("properties");
if (propertiesElement != null) {
JsonObject propertiesObject = propertiesElement.getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : propertiesObject.entrySet()) {
AnimationProperty<?> propertyKey = AnimationProperty.getSerializableProperty(entry.getKey());
Object value = propertyKey.parseFrom(entry.getValue());
animation.addPropertyUnsafe(propertyKey, value);
}
}
AnimationAccessor<StaticAnimation> accessor = AnimationAccessorImpl.create(rl, this.animations.size() + 1, false, null);
animation.setAccessor(accessor);
this.resourcepackAnimationCommands.put(accessor, invocationCommand);
this.animationById.put(accessor.id(), accessor);
this.animationByName.put(accessor.registryName(), accessor);
this.animations.put(accessor, animation);
}
public interface AnimationAccessor<A extends DynamicAnimation> extends AssetAccessor<A> {
int id();
default boolean idBetween(AnimationAccessor<? extends StaticAnimation> a1, AnimationAccessor<? extends StaticAnimation> a2) {
return a1.id() <= this.id() && a2.id() >= this.id();
}
}
public static record AnimationAccessorImpl<A extends StaticAnimation> (ResourceLocation registryName, int id, boolean inRegistry, Function<AnimationAccessor<A>, A> onLoad) implements AnimationAccessor<A> {
private static <A extends StaticAnimation> AnimationAccessor<A> create(ResourceLocation registryName, int id, boolean inRegistry, Function<AnimationAccessor<A>, A> onLoad) {
return new AnimationAccessorImpl<A> (registryName, id, inRegistry, onLoad);
}
@Override
public A get() {
if (!INSTANCE.animations.containsKey(this)) {
INSTANCE.animations.put(this, this.onLoad.apply(this));
}
return (A)INSTANCE.animations.get(this);
}
public String toString() {
return this.registryName.toString();
}
public int hashCode() {
return this.registryName.hashCode();
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof AnimationAccessor armatureAccessor) {
return this.registryName.equals(armatureAccessor.registryName());
} else if (obj instanceof ResourceLocation rl) {
return this.registryName.equals(rl);
} else if (obj instanceof String name) {
return this.registryName.toString().equals(name);
} else {
return false;
}
}
}
public static class AnimationRegistryEvent extends Event implements IModBusEvent {
private List<AnimationBuilder> builders = Lists.newArrayList();
private Set<String> namespaces = Sets.newHashSet();
public void newBuilder(String namespace, Consumer<AnimationBuilder> build) {
if (this.namespaces.contains(namespace)) {
throw new IllegalArgumentException("Animation builder namespace '" + namespace + "' already exists!");
}
this.namespaces.add(namespace);
this.builders.add(new AnimationBuilder(namespace, build));
}
public List<AnimationBuilder> getBuilders() {
return this.builders;
}
}
public static record AnimationBuilder(String namespace, Consumer<AnimationBuilder> task) {
public <T extends StaticAnimation> AnimationManager.AnimationAccessor<T> nextAccessor(String id, Function<AnimationManager.AnimationAccessor<T>, T> onLoad) {
AnimationAccessor<T> accessor = AnimationAccessorImpl.create(ResourceLocation.fromNamespaceAndPath(this.namespace, id), INSTANCE.animations.size() + 1, true, onLoad);
INSTANCE.animationById.put(accessor.id(), accessor);
INSTANCE.animationByName.put(accessor.registryName(), accessor);
INSTANCE.animations.put(accessor, null);
return accessor;
}
}
}

View File

@@ -0,0 +1,162 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import com.mojang.datafixers.util.Pair;
import com.tiedup.remake.rig.anim.property.AnimationProperty.PlaybackSpeedModifier;
import com.tiedup.remake.rig.anim.property.AnimationProperty.PlaybackTimeModifier;
import com.tiedup.remake.rig.anim.property.AnimationProperty.StaticAnimationProperty;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import yesman.epicfight.gameasset.Animations;
import yesman.epicfight.main.EpicFightSharedConstants;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public class AnimationPlayer {
protected float elapsedTime;
protected float prevElapsedTime;
protected boolean isEnd;
protected boolean doNotResetTime;
protected boolean reversed;
protected AssetAccessor<? extends DynamicAnimation> play;
public AnimationPlayer() {
this.setPlayAnimation(Animations.EMPTY_ANIMATION);
}
public void tick(LivingEntityPatch<?> entitypatch) {
DynamicAnimation currentPlay = this.getAnimation().get();
DynamicAnimation currentPlayStatic = currentPlay.getRealAnimation().get();
this.prevElapsedTime = this.elapsedTime;
float playbackSpeed = currentPlay.getPlaySpeed(entitypatch, currentPlay);
PlaybackSpeedModifier playSpeedModifier = currentPlayStatic.getProperty(StaticAnimationProperty.PLAY_SPEED_MODIFIER).orElse(null);
if (playSpeedModifier != null) {
playbackSpeed = playSpeedModifier.modify(currentPlay, entitypatch, playbackSpeed, this.prevElapsedTime, this.elapsedTime);
}
this.elapsedTime += EpicFightSharedConstants.A_TICK * playbackSpeed * (this.isReversed() && currentPlay.canBePlayedReverse() ? -1.0F : 1.0F);
PlaybackTimeModifier playTimeModifier = currentPlayStatic.getProperty(StaticAnimationProperty.ELAPSED_TIME_MODIFIER).orElse(null);
if (playTimeModifier != null) {
Pair<Float, Float> time = playTimeModifier.modify(currentPlay, entitypatch, playbackSpeed, this.prevElapsedTime, this.elapsedTime);
this.prevElapsedTime = time.getFirst();
this.elapsedTime = time.getSecond();
}
if (this.elapsedTime > currentPlay.getTotalTime()) {
if (currentPlay.isRepeat()) {
this.prevElapsedTime = this.prevElapsedTime - currentPlay.getTotalTime();
this.elapsedTime %= currentPlay.getTotalTime();
} else {
this.elapsedTime = currentPlay.getTotalTime();
currentPlay.end(entitypatch, null, true);
this.isEnd = true;
}
} else if (this.elapsedTime < 0) {
if (currentPlay.isRepeat()) {
this.prevElapsedTime = currentPlay.getTotalTime() - this.elapsedTime;
this.elapsedTime = currentPlay.getTotalTime() + this.elapsedTime;
} else {
this.elapsedTime = 0.0F;
currentPlay.end(entitypatch, null, true);
this.isEnd = true;
}
}
}
public void reset() {
this.elapsedTime = 0;
this.prevElapsedTime = 0;
this.isEnd = false;
}
public void setPlayAnimation(AssetAccessor<? extends DynamicAnimation> animation) {
if (this.doNotResetTime) {
this.doNotResetTime = false;
this.isEnd = false;
} else {
this.reset();
}
this.play = animation;
}
public Pose getCurrentPose(LivingEntityPatch<?> entitypatch, float partialTicks) {
return this.play.get().getPoseByTime(entitypatch, this.prevElapsedTime + (this.elapsedTime - this.prevElapsedTime) * partialTicks, partialTicks);
}
public float getElapsedTime() {
return this.elapsedTime;
}
public float getPrevElapsedTime() {
return this.prevElapsedTime;
}
public void setElapsedTimeCurrent(float elapsedTime) {
this.elapsedTime = elapsedTime;
this.isEnd = false;
}
public void setElapsedTime(float elapsedTime) {
this.elapsedTime = elapsedTime;
this.prevElapsedTime = elapsedTime;
this.isEnd = false;
}
public void setElapsedTime(float prevElapsedTime, float elapsedTime) {
this.elapsedTime = elapsedTime;
this.prevElapsedTime = prevElapsedTime;
this.isEnd = false;
}
public void begin(AssetAccessor<? extends DynamicAnimation> animation, LivingEntityPatch<?> entitypatch) {
animation.get().tick(entitypatch);
}
public AssetAccessor<? extends DynamicAnimation> getAnimation() {
return this.play;
}
public AssetAccessor<? extends StaticAnimation> getRealAnimation() {
return this.play.get().getRealAnimation();
}
public void markDoNotResetTime() {
this.doNotResetTime = true;
}
public boolean isEnd() {
return this.isEnd;
}
public void terminate(LivingEntityPatch<?> entitypatch) {
this.play.get().end(entitypatch, this.play, true);
this.isEnd = true;
}
public boolean isReversed() {
return this.reversed;
}
public void setReversed(boolean reversed) {
this.reversed = reversed;
}
public boolean isEmpty() {
return this.play == Animations.EMPTY_ANIMATION;
}
@Override
public String toString() {
return this.getAnimation() + " " + this.prevElapsedTime + " " + this.elapsedTime;
}
}

View File

@@ -0,0 +1,267 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import org.checkerframework.checker.nullness.qual.NonNull;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.util.ParseUtil;
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap.TypeKey;
import yesman.epicfight.gameasset.Animations;
import yesman.epicfight.network.common.AnimationVariablePacket;
public class AnimationVariables {
protected final Animator animator;
protected final TypeFlexibleHashMap<AnimationVariableKey<?>> animationVariables = new TypeFlexibleHashMap<> (false);
public AnimationVariables(Animator animator) {
this.animator = animator;
}
public <T> Optional<T> getSharedVariable(SharedAnimationVariableKey<T> key) {
return Optional.ofNullable(this.animationVariables.get(key));
}
@SuppressWarnings("unchecked")
public <T> T getOrDefaultSharedVariable(SharedAnimationVariableKey<T> key) {
return ParseUtil.orElse((T)this.animationVariables.get(key), () -> key.defaultValue(this.animator));
}
@SuppressWarnings("unchecked")
public <T> Optional<T> get(IndependentAnimationVariableKey<T> key, AssetAccessor<? extends StaticAnimation> animation) {
if (animation == null) {
return Optional.empty();
}
Map<ResourceLocation, Object> subMap = this.animationVariables.get(key);
if (subMap == null) {
return Optional.empty();
} else {
return Optional.ofNullable((T)subMap.get(animation.registryName()));
}
}
@SuppressWarnings("unchecked")
public <T> T getOrDefault(IndependentAnimationVariableKey<T> key, AssetAccessor<? extends StaticAnimation> animation) {
if (animation == null) {
return Objects.requireNonNull(key.defaultValue(this.animator), "Null value returned by default provider.");
}
Map<ResourceLocation, Object> subMap = this.animationVariables.get(key);
if (subMap == null) {
return Objects.requireNonNull(key.defaultValue(this.animator), "Null value returned by default provider.");
} else {
return ParseUtil.orElse((T)subMap.get(animation.registryName()), () -> key.defaultValue(this.animator));
}
}
public <T> void putDefaultSharedVariable(SharedAnimationVariableKey<T> key) {
T value = key.defaultValue(this.animator);
Objects.requireNonNull(value, "Null value returned by default provider.");
this.putSharedVariable(key, value);
}
public <T> void putSharedVariable(SharedAnimationVariableKey<T> key, T value) {
this.putSharedVariable(key, value, true);
}
@SuppressWarnings("unchecked")
@Deprecated // Avoid direct use
public <T> void putSharedVariable(SharedAnimationVariableKey<T> key, T value, boolean synchronize) {
if (this.animationVariables.containsKey(key) && !key.mutable()) {
throw new UnsupportedOperationException("Can't modify a const variable");
}
this.animationVariables.put((AnimationVariableKey<?>)key, value);
if (synchronize && key instanceof SynchedAnimationVariableKey) {
SynchedAnimationVariableKey<T> synchedanimationvariablekey = (SynchedAnimationVariableKey<T>)key;
synchedanimationvariablekey.sync(this.animator.entitypatch, (AssetAccessor<? extends StaticAnimation>)null, value, AnimationVariablePacket.Action.PUT);
}
}
public <T> void putDefaultValue(IndependentAnimationVariableKey<T> key, AssetAccessor<? extends StaticAnimation> animation) {
T value = key.defaultValue(this.animator);
Objects.requireNonNull(value, "Null value returned by default provider.");
this.put(key, animation, value);
}
public <T> void put(IndependentAnimationVariableKey<T> key, AssetAccessor<? extends StaticAnimation> animation, T value) {
this.put(key, animation, value, true);
}
@SuppressWarnings("unchecked")
@Deprecated // Avoid direct use
public <T> void put(IndependentAnimationVariableKey<T> key, AssetAccessor<? extends StaticAnimation> animation, T value, boolean synchronize) {
if (animation == Animations.EMPTY_ANIMATION) {
return;
}
this.animationVariables.computeIfPresent(key, (k, v) -> {
Map<ResourceLocation, Object> variablesByAnimations = ((Map<ResourceLocation, Object>)v);
if (!key.mutable() && variablesByAnimations.containsKey(animation.registryName())) {
throw new UnsupportedOperationException("Can't modify a const variable");
}
variablesByAnimations.put(animation.registryName(), value);
return v;
});
this.animationVariables.computeIfAbsent(key, (k) -> {
return new HashMap<> (Map.of(animation.registryName(), value));
});
if (synchronize && key instanceof SynchedAnimationVariableKey) {
SynchedAnimationVariableKey<T> synchedanimationvariablekey = (SynchedAnimationVariableKey<T>)key;
synchedanimationvariablekey.sync(this.animator.entitypatch, animation, value, AnimationVariablePacket.Action.PUT);
}
}
public <T> T removeSharedVariable(SharedAnimationVariableKey<T> key) {
return this.removeSharedVariable(key, true);
}
@SuppressWarnings("unchecked")
@Deprecated // Avoid direct use
public <T> T removeSharedVariable(SharedAnimationVariableKey<T> key, boolean synchronize) {
if (!key.mutable()) {
throw new UnsupportedOperationException("Can't remove a const variable");
}
if (synchronize && key instanceof SynchedAnimationVariableKey) {
SynchedAnimationVariableKey<T> synchedanimationvariablekey = (SynchedAnimationVariableKey<T>)key;
synchedanimationvariablekey.sync(this.animator.entitypatch, null, null, AnimationVariablePacket.Action.REMOVE);
}
return (T)this.animationVariables.remove(key);
}
@SuppressWarnings("unchecked")
public void removeAll(AnimationAccessor<? extends StaticAnimation> animation) {
if (animation == Animations.EMPTY_ANIMATION) {
return;
}
for (Map.Entry<AnimationVariableKey<?>, Object> entry : this.animationVariables.entrySet()) {
if (entry.getKey().isSharedKey()) {
continue;
}
Map<ResourceLocation, Object> map = (Map<ResourceLocation, Object>)entry.getValue();
if (map != null) {
map.remove(animation.registryName());
}
}
}
public void remove(IndependentAnimationVariableKey<?> key, AssetAccessor<? extends StaticAnimation> animation) {
this.remove(key, animation, true);
}
@SuppressWarnings("unchecked")
@Deprecated // Avoid direct use
public void remove(IndependentAnimationVariableKey<?> key, AssetAccessor<? extends StaticAnimation> animation, boolean synchronize) {
if (animation == Animations.EMPTY_ANIMATION) {
return;
}
Map<ResourceLocation, Object> map = (Map<ResourceLocation, Object>)this.animationVariables.get(key);
if (map != null) {
map.remove(animation.registryName());
}
if (synchronize && key instanceof SynchedAnimationVariableKey) {
SynchedAnimationVariableKey<?> synchedanimationvariablekey = (SynchedAnimationVariableKey<?>)key;
synchedanimationvariablekey.sync(this.animator.entitypatch, null, null, AnimationVariablePacket.Action.REMOVE);
}
}
public static <T> SharedAnimationVariableKey<T> shared(Function<Animator, T> defaultValueSupplier, boolean mutable) {
return new SharedAnimationVariableKey<> (defaultValueSupplier, mutable);
}
public static <T> IndependentAnimationVariableKey<T> independent(Function<Animator, T> defaultValueSupplier, boolean mutable) {
return new IndependentAnimationVariableKey<> (defaultValueSupplier, mutable);
}
protected abstract static class AnimationVariableKey<T> implements TypeKey<T> {
protected final Function<Animator, T> defaultValueSupplier;
protected final boolean mutable;
protected AnimationVariableKey(Function<Animator, T> defaultValueSupplier, boolean mutable) {
this.defaultValueSupplier = defaultValueSupplier;
this.mutable = mutable;
}
@NonNull
public T defaultValue(Animator animator) {
return this.defaultValueSupplier.apply(animator);
}
public boolean mutable() {
return this.mutable;
}
@Override
public T defaultValue() {
throw new UnsupportedOperationException("Use defaultValue(Animator animator) to get default value of animation variable key");
}
public abstract boolean isSharedKey();
public abstract boolean isSynched();
}
public static class SharedAnimationVariableKey<T> extends AnimationVariableKey<T> {
protected SharedAnimationVariableKey(Function<Animator, T> initValueSupplier, boolean mutable) {
super(initValueSupplier, mutable);
}
@Override
public boolean isSharedKey() {
return true;
}
@Override
public boolean isSynched() {
return false;
}
}
public static class IndependentAnimationVariableKey<T> extends AnimationVariableKey<T> {
protected IndependentAnimationVariableKey(Function<Animator, T> initValueSupplier, boolean mutable) {
super(initValueSupplier, mutable);
}
@Override
public boolean isSharedKey() {
return false;
}
@Override
public boolean isSynched() {
return false;
}
}
}

View File

@@ -0,0 +1,145 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.mojang.datafixers.util.Pair;
import net.minecraftforge.common.MinecraftForge;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.anim.types.EntityState;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.event.InitAnimatorEvent;
import yesman.epicfight.gameasset.Animations;
import yesman.epicfight.main.EpicFightMod;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public abstract class Animator {
protected final Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> livingAnimations = Maps.newHashMap();
protected final AnimationVariables animationVariables = new AnimationVariables(this);
protected final LivingEntityPatch<?> entitypatch;
public Animator(LivingEntityPatch<?> entitypatch) {
this.entitypatch = entitypatch;
}
/**
* Play an animation
*
* @param nextAnimation the animation that is meant to be played.
* @param transitionTimeModifier extends the transition time if positive value provided, or starts in time as an amount of time (e.g. -0.1F starts in 0.1F frame time)
*/
public abstract void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, float transitionTimeModifier);
public final void playAnimation(int id, float transitionTimeModifier) {
this.playAnimation(AnimationManager.byId(id), transitionTimeModifier);
}
/**
* Play a given animation without transition animation.
* @param nextAnimation
*/
public abstract void playAnimationInstantly(AssetAccessor<? extends StaticAnimation> nextAnimation);
public final void playAnimationInstantly(int id) {
this.playAnimationInstantly(AnimationManager.byId(id));
}
/**
* Reserve a given animation until the current animation ends.
* If the given animation has a higher priority than current animation, it terminates the current animation by force and play the next animation
* @param nextAnimation
*/
public abstract void reserveAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation);
public final void reserveAnimation(int id) {
this.reserveAnimation(AnimationManager.byId(id));
}
/**
* Stop playing given animation if exist
* @param targetAnimation
* @return true when found and successfully stop the target animation
*/
public abstract boolean stopPlaying(AssetAccessor<? extends StaticAnimation> targetAnimation);
/**
* Play an shooting animation to end aiming pose
*/
public abstract void playShootingAnimation();
public final boolean stopPlaying(int id) {
return this.stopPlaying(AnimationManager.byId(id));
}
public abstract void setSoftPause(boolean paused);
public abstract void setHardPause(boolean paused);
public abstract void tick();
public abstract EntityState getEntityState();
/**
* Searches an animation player playing the given animation parameter or return base layer if it's null
* Secure non-null but returned animation player won't match with a given animation
*/
@Nullable
public abstract AnimationPlayer getPlayerFor(@Nullable AssetAccessor<? extends DynamicAnimation> playingAnimation);
/**
* Searches an animation player playing the given animation parameter
*/
@Nullable
public abstract Optional<AnimationPlayer> getPlayer(AssetAccessor<? extends DynamicAnimation> playingAnimation);
public abstract <T> Pair<AnimationPlayer, T> findFor(Class<T> animationType);
public abstract Pose getPose(float partialTicks);
public void postInit() {
InitAnimatorEvent initAnimatorEvent = new InitAnimatorEvent(this.entitypatch, this);
MinecraftForge.EVENT_BUS.post(initAnimatorEvent);
}
public void playDeathAnimation() {
this.playAnimation(this.livingAnimations.getOrDefault(LivingMotions.DEATH, Animations.BIPED_DEATH), 0);
}
public void addLivingAnimation(LivingMotion livingMotion, AssetAccessor<? extends StaticAnimation> animation) {
if (AnimationManager.checkNull(animation)) {
EpicFightMod.LOGGER.warn("Unable to put an empty animation for " + livingMotion);
return;
}
this.livingAnimations.put(livingMotion, animation);
}
public AssetAccessor<? extends StaticAnimation> getLivingAnimation(LivingMotion livingMotion, AssetAccessor<? extends StaticAnimation> defaultGetter) {
return this.livingAnimations.getOrDefault(livingMotion, defaultGetter);
}
public Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> getLivingAnimations() {
return ImmutableMap.copyOf(this.livingAnimations);
}
public AnimationVariables getVariables() {
return this.animationVariables;
}
public LivingEntityPatch<?> getEntityPatch() {
return this.entitypatch;
}
public void resetLivingAnimations() {
this.livingAnimations.clear();
}
}

View File

@@ -0,0 +1,47 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
public class Keyframe {
private float timeStamp;
private final JointTransform transform;
public Keyframe(float timeStamp, JointTransform trasnform) {
this.timeStamp = timeStamp;
this.transform = trasnform;
}
public Keyframe(Keyframe original) {
this.transform = JointTransform.empty();
this.copyFrom(original);
}
public void copyFrom(Keyframe target) {
this.timeStamp = target.timeStamp;
this.transform.copyFrom(target.transform);
}
public float time() {
return this.timeStamp;
}
public void setTime(float time) {
this.timeStamp = time;
}
public JointTransform transform() {
return this.transform;
}
public String toString() {
return "Keyframe[Time: " + this.timeStamp + ", " + (this.transform == null ? "null" : this.transform.toString()) + "]";
}
public static Keyframe empty() {
return new Keyframe(0.0F, JointTransform.empty());
}
}

View File

@@ -0,0 +1,24 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import com.tiedup.remake.rig.util.ExtendableEnum;
import com.tiedup.remake.rig.util.ExtendableEnumManager;
public interface LivingMotion extends ExtendableEnum {
ExtendableEnumManager<LivingMotion> ENUM_MANAGER = new ExtendableEnumManager<> ("living_motion");
default boolean isSame(LivingMotion livingMotion) {
if (this == LivingMotions.IDLE && livingMotion == LivingMotions.INACTION) {
return true;
} else if (this == LivingMotions.INACTION && livingMotion == LivingMotions.IDLE) {
return true;
}
return this == livingMotion;
}
}

View File

@@ -0,0 +1,23 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
public enum LivingMotions implements LivingMotion {
ALL, // Datapack edit option
INACTION, IDLE, CONFRONT, ANGRY, FLOAT, WALK, RUN, SWIM, FLY, SNEAK, KNEEL, FALL, SIT, MOUNT, DEATH, CHASE, SPELLCAST, JUMP, CELEBRATE, LANDING_RECOVERY, CREATIVE_FLY, CREATIVE_IDLE, SLEEP, // Base
DIGGING, ADMIRE, CLIMB, DRINK, EAT, NONE, AIM, BLOCK, BLOCK_SHIELD, RELOAD, SHOT, SPECTATE; // Mix
final int id;
LivingMotions() {
this.id = LivingMotion.ENUM_MANAGER.assign(this);
}
public int universalOrdinal() {
return this.id;
}
}

View File

@@ -0,0 +1,113 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
public class Pose {
public static final Pose EMPTY_POSE = new Pose();
public static Pose interpolatePose(Pose pose1, Pose pose2, float pregression) {
Pose pose = new Pose();
Set<String> mergedSet = new HashSet<>(pose1.jointTransformData.keySet());
mergedSet.addAll(pose2.jointTransformData.keySet());
for (String jointName : mergedSet) {
pose.putJointData(jointName, JointTransform.interpolate(pose1.orElseEmpty(jointName), pose2.orElseEmpty(jointName), pregression));
}
return pose;
}
protected final Map<String, JointTransform> jointTransformData;
public Pose() {
this(Maps.newHashMap());
}
public Pose(Map<String, JointTransform> jointTransforms) {
this.jointTransformData = jointTransforms;
}
public void putJointData(String name, JointTransform transform) {
this.jointTransformData.put(name, transform);
}
public Map<String, JointTransform> getJointTransformData() {
return this.jointTransformData;
}
public void disableJoint(Predicate<? super Map.Entry<String, JointTransform>> predicate) {
this.jointTransformData.entrySet().removeIf(predicate);
}
public void disableAllJoints() {
this.jointTransformData.clear();
}
public boolean hasTransform(String jointName) {
return this.jointTransformData.containsKey(jointName);
}
public JointTransform get(String jointName) {
return this.jointTransformData.get(jointName);
}
public JointTransform orElseEmpty(String jointName) {
return this.jointTransformData.getOrDefault(jointName, JointTransform.empty());
}
public JointTransform orElse(String jointName, JointTransform orElse) {
return this.jointTransformData.getOrDefault(jointName, orElse);
}
public void forEachEnabledTransforms(BiConsumer<String, JointTransform> task) {
this.jointTransformData.forEach(task);
}
public void load(Pose pose, LoadOperation operation) {
switch (operation) {
case SET -> {
this.disableAllJoints();
pose.forEachEnabledTransforms(this::putJointData);
}
case OVERWRITE -> {
pose.forEachEnabledTransforms(this::putJointData);
}
case APPEND_ABSENT -> {
pose.forEachEnabledTransforms((name, transform) -> {
if (!this.hasTransform(name)) {
this.putJointData(name, transform);
}
});
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Pose: ");
for (Map.Entry<String, JointTransform> entry : this.jointTransformData.entrySet()) {
sb.append(String.format("%s{%s, %s}, ", entry.getKey(), entry.getValue().translation().toString(), entry.getValue().rotation().toString()) + "\n");
}
return sb.toString();
}
public enum LoadOperation {
SET, OVERWRITE, APPEND_ABSENT
}
}

View File

@@ -0,0 +1,160 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import java.util.Optional;
import com.mojang.datafixers.util.Pair;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.anim.types.EntityState;
import com.tiedup.remake.rig.anim.types.LinkAnimation;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import yesman.epicfight.gameasset.Animations;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public class ServerAnimator extends Animator {
public static Animator getAnimator(LivingEntityPatch<?> entitypatch) {
return new ServerAnimator(entitypatch);
}
private final LinkAnimation linkAnimation;
public final AnimationPlayer animationPlayer;
protected AssetAccessor<? extends DynamicAnimation> nextAnimation;
public boolean hardPaused = false;
public boolean softPaused = false;
public ServerAnimator(LivingEntityPatch<?> entitypatch) {
super(entitypatch);
this.linkAnimation = new LinkAnimation();
this.animationPlayer = new AnimationPlayer();
}
/** Play an animation by animation instance **/
@Override
public void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, float transitionTimeModifier) {
this.softPaused = false;
Pose lastPose = this.animationPlayer.getAnimation().get().getPoseByTime(this.entitypatch, 0.0F, 0.0F);
if (!this.animationPlayer.isEnd()) {
this.animationPlayer.getAnimation().get().end(this.entitypatch, nextAnimation, false);
}
nextAnimation.get().begin(this.entitypatch);
if (!nextAnimation.get().isMetaAnimation()) {
nextAnimation.get().setLinkAnimation(this.animationPlayer.getAnimation(), lastPose, true, transitionTimeModifier, this.entitypatch, this.linkAnimation);
this.linkAnimation.getAnimationClip().setBaked();
this.linkAnimation.putOnPlayer(this.animationPlayer, this.entitypatch);
this.entitypatch.updateEntityState();
this.nextAnimation = nextAnimation;
}
}
@Override
public void playAnimationInstantly(AssetAccessor<? extends StaticAnimation> nextAnimation) {
this.softPaused = false;
if (!this.animationPlayer.isEnd()) {
this.animationPlayer.getAnimation().get().end(this.entitypatch, nextAnimation, false);
}
nextAnimation.get().begin(this.entitypatch);
nextAnimation.get().putOnPlayer(this.animationPlayer, this.entitypatch);
this.entitypatch.updateEntityState();
}
@Override
public void reserveAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation) {
this.softPaused = false;
this.nextAnimation = nextAnimation;
}
@Override
public boolean stopPlaying(AssetAccessor<? extends StaticAnimation> targetAnimation) {
if (this.animationPlayer.getRealAnimation() == targetAnimation) {
this.animationPlayer.terminate(this.entitypatch);
return true;
}
return false;
}
@Override
public void playShootingAnimation() {
}
@Override
public void tick() {
if (this.hardPaused || this.softPaused) {
this.entitypatch.updateEntityState();
return;
}
this.animationPlayer.tick(this.entitypatch);
this.entitypatch.updateEntityState();
if (this.animationPlayer.isEnd()) {
if (this.nextAnimation == null) {
Animations.EMPTY_ANIMATION.putOnPlayer(this.animationPlayer, this.entitypatch);
this.softPaused = true;
} else {
if (!this.animationPlayer.getAnimation().get().isLinkAnimation() && !this.nextAnimation.get().isLinkAnimation()) {
this.nextAnimation.get().begin(this.entitypatch);
}
this.nextAnimation.get().putOnPlayer(this.animationPlayer, this.entitypatch);
this.nextAnimation = null;
}
} else {
this.animationPlayer.getAnimation().get().tick(this.entitypatch);
}
}
@Override
public Pose getPose(float partialTicks) {
return this.animationPlayer.getCurrentPose(this.entitypatch, partialTicks);
}
@Override
public AnimationPlayer getPlayerFor(AssetAccessor<? extends DynamicAnimation> playingAnimation) {
return this.animationPlayer;
}
@Override
public Optional<AnimationPlayer> getPlayer(AssetAccessor<? extends DynamicAnimation> playingAnimation) {
if (this.animationPlayer.getRealAnimation() == playingAnimation.get().getRealAnimation()) {
return Optional.of(this.animationPlayer);
} else {
return Optional.empty();
}
}
@Override
@SuppressWarnings("unchecked")
public <T> Pair<AnimationPlayer, T> findFor(Class<T> animationType) {
return animationType.isAssignableFrom(this.animationPlayer.getAnimation().getClass()) ? Pair.of(this.animationPlayer, (T)this.animationPlayer.getAnimation()) : null;
}
@Override
public EntityState getEntityState() {
return this.animationPlayer.getAnimation().get().getState(this.entitypatch, this.animationPlayer.getElapsedTime());
}
@Override
public void setSoftPause(boolean paused) {
this.softPaused = paused;
}
@Override
public void setHardPause(boolean paused) {
this.hardPaused = paused;
}
}

View File

@@ -0,0 +1,130 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import java.util.function.Function;
import javax.annotation.Nullable;
import net.minecraft.core.IdMapper;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.IForgeRegistryInternal;
import net.minecraftforge.registries.RegistryManager;
import com.tiedup.remake.rig.anim.AnimationVariables.IndependentAnimationVariableKey;
import com.tiedup.remake.rig.anim.AnimationVariables.SharedAnimationVariableKey;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.util.PacketBufferCodec;
import com.tiedup.remake.rig.util.datastruct.ClearableIdMapper;
import yesman.epicfight.main.EpicFightMod;
import yesman.epicfight.network.EpicFightNetworkManager;
import yesman.epicfight.network.client.CPAnimationVariablePacket;
import yesman.epicfight.network.common.AnimationVariablePacket;
import yesman.epicfight.network.server.SPAnimationVariablePacket;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public interface SynchedAnimationVariableKey<T> {
public static <T> SynchedSharedAnimationVariableKey<T> shared(Function<Animator, T> defaultValueSupplier, boolean mutable, PacketBufferCodec<T> codec) {
return new SynchedSharedAnimationVariableKey<> (defaultValueSupplier, mutable, codec);
}
public static <T> SynchedIndependentAnimationVariableKey<T> independent(Function<Animator, T> defaultValueSupplier, boolean mutable, PacketBufferCodec<T> codec) {
return new SynchedIndependentAnimationVariableKey<> (defaultValueSupplier, mutable, codec);
}
public static final ResourceLocation BY_ID_REGISTRY = EpicFightMod.identifier("variablekeytoid");
public static class SynchedAnimationVariableKeyCallbacks implements IForgeRegistry.BakeCallback<SynchedAnimationVariableKey<?>>, IForgeRegistry.CreateCallback<SynchedAnimationVariableKey<?>>, IForgeRegistry.ClearCallback<SynchedAnimationVariableKey<?>> {
private static final SynchedAnimationVariableKeyCallbacks INSTANCE = new SynchedAnimationVariableKeyCallbacks();
@Override
@SuppressWarnings("unchecked")
public void onBake(IForgeRegistryInternal<SynchedAnimationVariableKey<?>> owner, RegistryManager stage) {
final ClearableIdMapper<SynchedAnimationVariableKey<?>> synchedanimationvariablekeybyid = owner.getSlaveMap(BY_ID_REGISTRY, ClearableIdMapper.class);
owner.forEach(synchedanimationvariablekeybyid::add);
}
@Override
public void onCreate(IForgeRegistryInternal<SynchedAnimationVariableKey<?>> owner, RegistryManager stage) {
owner.setSlaveMap(BY_ID_REGISTRY, new ClearableIdMapper<SynchedAnimationVariableKey<?>> (owner.getKeys().size()));
}
@Override
public void onClear(IForgeRegistryInternal<SynchedAnimationVariableKey<?>> owner, RegistryManager stage) {
owner.getSlaveMap(BY_ID_REGISTRY, ClearableIdMapper.class).clear();
}
}
public static SynchedAnimationVariableKeyCallbacks getRegistryCallback() {
return SynchedAnimationVariableKeyCallbacks.INSTANCE;
}
@SuppressWarnings("unchecked")
public static IdMapper<SynchedAnimationVariableKey<?>> getIdMap() {
return SynchedAnimationVariableKeys.REGISTRY.get().getSlaveMap(BY_ID_REGISTRY, IdMapper.class);
}
@SuppressWarnings("unchecked")
public static <T> SynchedAnimationVariableKey<T> byId(int id) {
return (SynchedAnimationVariableKey<T>)getIdMap().byId(id);
}
public PacketBufferCodec<T> getPacketBufferCodec();
public boolean isSharedKey();
default int getId() {
return getIdMap().getId(this);
}
default void sync(LivingEntityPatch<?> entitypatch, @Nullable AssetAccessor<? extends StaticAnimation> animation, T value, AnimationVariablePacket.Action action) {
if (entitypatch.isLogicalClient()) {
EpicFightNetworkManager.sendToServer(new CPAnimationVariablePacket<> (this, animation, value, action));
} else {
entitypatch.sendToAllPlayersTrackingMe(new SPAnimationVariablePacket<> (entitypatch, this, animation, value, action));
}
}
public static class SynchedSharedAnimationVariableKey<T> extends SharedAnimationVariableKey<T> implements SynchedAnimationVariableKey<T> {
private final PacketBufferCodec<T> packetBufferCodec;
protected SynchedSharedAnimationVariableKey(Function<Animator, T> defaultValueSupplier, boolean mutable, PacketBufferCodec<T> packetBufferCodec) {
super(defaultValueSupplier, mutable);
this.packetBufferCodec = packetBufferCodec;
}
@Override
public boolean isSynched() {
return true;
}
@Override
public PacketBufferCodec<T> getPacketBufferCodec() {
return this.packetBufferCodec;
}
}
public static class SynchedIndependentAnimationVariableKey<T> extends IndependentAnimationVariableKey<T> implements SynchedAnimationVariableKey<T> {
private final PacketBufferCodec<T> packetBufferCodec;
protected SynchedIndependentAnimationVariableKey(Function<Animator, T> defaultValueSupplier, boolean mutable, PacketBufferCodec<T> packetBufferCodec) {
super(defaultValueSupplier, mutable);
this.packetBufferCodec = packetBufferCodec;
}
@Override
public boolean isSharedKey() {
return false;
}
@Override
public PacketBufferCodec<T> getPacketBufferCodec() {
return this.packetBufferCodec;
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import java.util.function.Supplier;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.RegistryBuilder;
import net.minecraftforge.registries.RegistryObject;
import com.tiedup.remake.rig.anim.SynchedAnimationVariableKey.SynchedIndependentAnimationVariableKey;
import com.tiedup.remake.rig.util.PacketBufferCodec;
import yesman.epicfight.main.EpicFightMod;
public class SynchedAnimationVariableKeys {
private static final Supplier<RegistryBuilder<SynchedAnimationVariableKey<?>>> BUILDER = () -> new RegistryBuilder<SynchedAnimationVariableKey<?>>().addCallback(SynchedAnimationVariableKey.getRegistryCallback());
public static final DeferredRegister<SynchedAnimationVariableKey<?>> SYNCHED_ANIMATION_VARIABLE_KEYS = DeferredRegister.create(EpicFightMod.identifier("synched_animation_variable_keys"), EpicFightMod.MODID);
public static final Supplier<IForgeRegistry<SynchedAnimationVariableKey<?>>> REGISTRY = SYNCHED_ANIMATION_VARIABLE_KEYS.makeRegistry(BUILDER);
public static final RegistryObject<SynchedIndependentAnimationVariableKey<Vec3>> DESTINATION = SYNCHED_ANIMATION_VARIABLE_KEYS.register("destination", () ->
SynchedAnimationVariableKey.independent(animator -> animator.getEntityPatch().getOriginal().position(), true, PacketBufferCodec.VEC3));
public static final RegistryObject<SynchedIndependentAnimationVariableKey<Integer>> TARGET_ENTITY = SYNCHED_ANIMATION_VARIABLE_KEYS.register("target_entity", () ->
SynchedAnimationVariableKey.independent(animator -> (Integer)null, true, PacketBufferCodec.INTEGER));
public static final RegistryObject<SynchedIndependentAnimationVariableKey<Integer>> CHARGING_TICKS = SYNCHED_ANIMATION_VARIABLE_KEYS.register("animation_playing_speed", () ->
SynchedAnimationVariableKey.independent(animator -> 0, true, PacketBufferCodec.INTEGER));
}

View File

@@ -0,0 +1,354 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import org.joml.Quaternionf;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
import com.tiedup.remake.rig.math.MathUtils;
import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.math.Vec3f;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public class TransformSheet {
public static final TransformSheet EMPTY_SHEET = new TransformSheet(List.of(new Keyframe(0.0F, JointTransform.empty()), new Keyframe(Float.MAX_VALUE, JointTransform.empty())));
public static final Function<Vec3, TransformSheet> EMPTY_SHEET_PROVIDER = translation -> {
return new TransformSheet(List.of(new Keyframe(0.0F, JointTransform.translation(new Vec3f(translation))), new Keyframe(Float.MAX_VALUE, JointTransform.empty())));
};
private Keyframe[] keyframes;
public TransformSheet() {
this(new Keyframe[0]);
}
public TransformSheet(int size) {
this(new Keyframe[size]);
}
public TransformSheet(List<Keyframe> keyframeList) {
this(keyframeList.toArray(new Keyframe[0]));
}
public TransformSheet(Keyframe[] keyframes) {
this.keyframes = keyframes;
}
public JointTransform getStartTransform() {
return this.keyframes[0].transform();
}
public Keyframe[] getKeyframes() {
return this.keyframes;
}
public TransformSheet copyAll() {
return this.copy(0, this.keyframes.length);
}
public TransformSheet copy(int start, int end) {
int len = end - start;
Keyframe[] newKeyframes = new Keyframe[len];
for (int i = 0; i < len; i++) {
Keyframe kf = this.keyframes[i + start];
newKeyframes[i] = new Keyframe(kf);
}
return new TransformSheet(newKeyframes);
}
public TransformSheet readFrom(TransformSheet opponent) {
if (opponent.keyframes.length != this.keyframes.length) {
this.keyframes = new Keyframe[opponent.keyframes.length];
for (int i = 0; i < this.keyframes.length; i++) {
this.keyframes[i] = Keyframe.empty();
}
}
for (int i = 0; i < this.keyframes.length; i++) {
this.keyframes[i].copyFrom(opponent.keyframes[i]);
}
return this;
}
public TransformSheet createInterpolated(float[] timestamp) {
TransformSheet interpolationCreated = new TransformSheet(timestamp.length);
for (int i = 0; i < timestamp.length; i++) {
interpolationCreated.keyframes[i] = new Keyframe(timestamp[i], this.getInterpolatedTransform(timestamp[i]));
}
return interpolationCreated;
}
/**
* Transform each joint
*/
public void forEach(BiConsumer<Integer, Keyframe> task) {
this.forEach(task, 0, this.keyframes.length);
}
public void forEach(BiConsumer<Integer, Keyframe> task, int start, int end) {
end = Math.min(end, this.keyframes.length);
for (int i = start; i < end; i++) {
task.accept(i, this.keyframes[i]);
}
}
public Vec3f getInterpolatedTranslation(float currentTime) {
InterpolationInfo interpolInfo = this.getInterpolationInfo(currentTime);
if (interpolInfo == InterpolationInfo.INVALID) {
return new Vec3f();
}
Vec3f vec3f = MathUtils.lerpVector(this.keyframes[interpolInfo.prev].transform().translation(), this.keyframes[interpolInfo.next].transform().translation(), interpolInfo.delta);
return vec3f;
}
public Quaternionf getInterpolatedRotation(float currentTime) {
InterpolationInfo interpolInfo = this.getInterpolationInfo(currentTime);
if (interpolInfo == InterpolationInfo.INVALID) {
return new Quaternionf();
}
Quaternionf quat = MathUtils.lerpQuaternion(this.keyframes[interpolInfo.prev].transform().rotation(), this.keyframes[interpolInfo.next].transform().rotation(), interpolInfo.delta);
return quat;
}
public JointTransform getInterpolatedTransform(float currentTime) {
return this.getInterpolatedTransform(this.getInterpolationInfo(currentTime));
}
public JointTransform getInterpolatedTransform(InterpolationInfo interpolationInfo) {
if (interpolationInfo == InterpolationInfo.INVALID) {
return JointTransform.empty();
}
JointTransform trasnform = JointTransform.interpolate(this.keyframes[interpolationInfo.prev].transform(), this.keyframes[interpolationInfo.next].transform(), interpolationInfo.delta);
return trasnform;
}
public TransformSheet extend(TransformSheet target) {
int newKeyLength = this.keyframes.length + target.keyframes.length;
Keyframe[] newKeyfrmaes = new Keyframe[newKeyLength];
for (int i = 0; i < this.keyframes.length; i++) {
newKeyfrmaes[i] = this.keyframes[i];
}
for (int i = this.keyframes.length; i < newKeyLength; i++) {
newKeyfrmaes[i] = new Keyframe(target.keyframes[i - this.keyframes.length]);
}
this.keyframes = newKeyfrmaes;
return this;
}
public TransformSheet getFirstFrame() {
TransformSheet part = this.copy(0, 2);
Keyframe[] keyframes = part.getKeyframes();
keyframes[1].transform().copyFrom(keyframes[0].transform());
return part;
}
public void correctAnimationByNewPosition(Vec3f startpos, Vec3f startToEnd, Vec3f modifiedStart, Vec3f modifiedStartToEnd) {
Keyframe[] keyframes = this.getKeyframes();
Keyframe startKeyframe = keyframes[0];
Keyframe endKeyframe = keyframes[keyframes.length - 1];
float pitchDeg = (float) Math.toDegrees(Mth.atan2(modifiedStartToEnd.y - startToEnd.y, modifiedStartToEnd.length()));
float yawDeg = (float) MathUtils.getAngleBetween(modifiedStartToEnd.copy().multiply(1.0F, 0.0F, 1.0F), startToEnd.copy().multiply(1.0F, 0.0F, 1.0F));
for (Keyframe kf : keyframes) {
float lerp = (kf.time() - startKeyframe.time()) / (endKeyframe.time() - startKeyframe.time());
Vec3f line = MathUtils.lerpVector(new Vec3f(0F, 0F, 0F), startToEnd, lerp);
Vec3f modifiedLine = MathUtils.lerpVector(new Vec3f(0F, 0F, 0F), modifiedStartToEnd, lerp);
Vec3f keyTransform = kf.transform().translation();
Vec3f startToKeyTransform = keyTransform.copy().sub(startpos).multiply(-1.0F, 1.0F, -1.0F);
Vec3f animOnLine = startToKeyTransform.copy().sub(line);
OpenMatrix4f rotator = OpenMatrix4f.createRotatorDeg(pitchDeg, Vec3f.X_AXIS).mulFront(OpenMatrix4f.createRotatorDeg(yawDeg, Vec3f.Y_AXIS));
Vec3f toNewKeyTransform = modifiedLine.add(OpenMatrix4f.transform3v(rotator, animOnLine, null));
keyTransform.set(modifiedStart.copy().add((toNewKeyTransform)));
}
}
public TransformSheet getCorrectedModelCoord(LivingEntityPatch<?> entitypatch, Vec3 start, Vec3 dest, int startFrame, int endFrame) {
TransformSheet transform = this.copyAll();
float horizontalDistance = (float) dest.subtract(start).horizontalDistance();
float verticalDistance = (float) Math.abs(dest.y - start.y);
JointTransform startJt = transform.getKeyframes()[startFrame].transform();
JointTransform endJt = transform.getKeyframes()[endFrame].transform();
Vec3f jointCoord = new Vec3f(startJt.translation().x, verticalDistance, horizontalDistance);
startJt.translation().set(jointCoord);
for (int i = startFrame + 1; i < endFrame; i++) {
JointTransform middleJt = transform.getKeyframes()[i].transform();
middleJt.translation().set(MathUtils.lerpVector(startJt.translation(), endJt.translation(), transform.getKeyframes()[i].time() / transform.getKeyframes()[endFrame].time()));
}
return transform;
}
public TransformSheet extendsZCoord(float multiplier, int startFrame, int endFrame) {
TransformSheet transform = this.copyAll();
float extend = 0.0F;
for (int i = 0; i < endFrame + 1; i++) {
Keyframe kf = transform.getKeyframes()[i];
float prevZ = kf.transform().translation().z;
kf.transform().translation().multiply(1.0F, 1.0F, multiplier);
float extendedZ = kf.transform().translation().z;
extend = extendedZ - prevZ;
}
for (int i = endFrame + 1; i < transform.getKeyframes().length; i++) {
Keyframe kf = transform.getKeyframes()[i];
kf.transform().translation().add(0.0F, 0.0F, extend);
}
return transform;
}
/**
* Transform the animation coord system to world coord system regarding origin point as @param worldDest
*
* @param entitypatch
* @param worldStart
* @param worldDest
* @param xRot
* @param entityYRot
* @param startFrame
* @param endFrame
* @return
*/
public TransformSheet transformToWorldCoordOriginAsDest(LivingEntityPatch<?> entitypatch, Vec3 startInWorld, Vec3 destInWorld, float entityYRot, float destYRot, int startFrmae, int destFrame) {
TransformSheet byStart = this.copy(0, destFrame + 1);
TransformSheet byDest = this.copy(0, destFrame + 1);
TransformSheet result = new TransformSheet(destFrame + 1);
Vec3 toTargetInWorld = destInWorld.subtract(startInWorld);
double worldMagnitude = toTargetInWorld.horizontalDistance();
double animMagnitude = this.keyframes[0].transform().translation().horizontalDistance();
float scale = (float)(worldMagnitude / animMagnitude);
byStart.forEach((idx, keyframe) -> {
keyframe.transform().translation().sub(this.keyframes[0].transform().translation());
keyframe.transform().translation().multiply(1.0F, 1.0F, scale);
keyframe.transform().translation().rotate(-entityYRot, Vec3f.Y_AXIS);
keyframe.transform().translation().multiply(-1.0F, 1.0F, -1.0F);
keyframe.transform().translation().add(startInWorld);
});
byDest.forEach((idx, keyframe) -> {
keyframe.transform().translation().multiply(1.0F, 1.0F, Mth.lerp((idx / (float)destFrame), scale, 1.0F));
keyframe.transform().translation().rotate(-destYRot, Vec3f.Y_AXIS);
keyframe.transform().translation().multiply(-1.0F, 1.0F, -1.0F);
keyframe.transform().translation().add(destInWorld);
});
for (int i = 0; i < destFrame + 1; i++) {
if (i <= startFrmae) {
result.getKeyframes()[i] = new Keyframe(this.keyframes[i].time(), JointTransform.translation(byStart.getKeyframes()[i].transform().translation()));
} else {
float lerp = this.keyframes[i].time() == 0.0F ? 0.0F : this.keyframes[i].time() / this.keyframes[destFrame].time();
Vec3f lerpTranslation = Vec3f.interpolate(byStart.getKeyframes()[i].transform().translation(), byDest.getKeyframes()[i].transform().translation(), lerp, null);
result.getKeyframes()[i] = new Keyframe(this.keyframes[i].time(), JointTransform.translation(lerpTranslation));
}
}
if (this.keyframes.length > destFrame) {
TransformSheet behindDestination = this.copy(destFrame + 1, this.keyframes.length);
behindDestination.forEach((idx, keyframe) -> {
keyframe.transform().translation().sub(this.keyframes[destFrame].transform().translation());
keyframe.transform().translation().rotate(entityYRot, Vec3f.Y_AXIS);
keyframe.transform().translation().multiply(-1.0F, 1.0F, -1.0F);
keyframe.transform().translation().add(result.getKeyframes()[destFrame].transform().translation());
});
result.extend(behindDestination);
}
return result;
}
public InterpolationInfo getInterpolationInfo(float currentTime) {
if (this.keyframes.length == 0) {
return InterpolationInfo.INVALID;
}
if (currentTime < 0.0F) {
currentTime = this.keyframes[this.keyframes.length - 1].time() + currentTime;
}
// Binary search
int begin = 0, end = this.keyframes.length - 1;
while (end - begin > 1) {
int i = begin + (end - begin) / 2;
if (this.keyframes[i].time() <= currentTime && this.keyframes[i+1].time() > currentTime) {
begin = i;
end = i+1;
break;
} else {
if (this.keyframes[i].time() > currentTime) {
end = i;
} else if (this.keyframes[i+1].time() <= currentTime) {
begin = i;
}
}
}
float progression = Mth.clamp((currentTime - this.keyframes[begin].time()) / (this.keyframes[end].time() - this.keyframes[begin].time()), 0.0F, 1.0F);
return new InterpolationInfo(begin, end, Float.isNaN(progression) ? 1.0F : progression);
}
public float maxFrameTime() {
float maxFrameTime = -1.0F;
for (Keyframe kf : this.keyframes) {
if (kf.time() > maxFrameTime) {
maxFrameTime = kf.time();
}
}
return maxFrameTime;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
int idx = 0;
for (Keyframe kf : this.keyframes) {
sb.append(kf);
if (++idx < this.keyframes.length) {
sb.append("\n");
}
}
return sb.toString();
}
public static record InterpolationInfo(int prev, int next, float delta) {
public static final InterpolationInfo INVALID = new InterpolationInfo(-1, -1, -1.0F);
}
}

View File

@@ -0,0 +1,321 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonReader;
import com.mojang.datafixers.util.Pair;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.util.GsonHelper;
import com.tiedup.remake.rig.anim.AnimationManager;
import com.tiedup.remake.rig.anim.LivingMotion;
import com.tiedup.remake.rig.anim.TransformSheet;
import com.tiedup.remake.rig.anim.property.AnimationProperty.StaticAnimationProperty;
import com.tiedup.remake.rig.anim.types.ActionAnimation;
import com.tiedup.remake.rig.anim.types.DirectStaticAnimation;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.JsonAssetLoader;
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
import com.tiedup.remake.rig.anim.client.property.JointMaskReloadListener;
import com.tiedup.remake.rig.anim.client.property.LayerInfo;
import com.tiedup.remake.rig.anim.client.property.TrailInfo;
import com.tiedup.remake.rig.exception.AssetLoadingException;
import com.tiedup.remake.rig.util.ParseUtil;
import yesman.epicfight.main.EpicFightMod;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public class AnimationSubFileReader {
public static final SubFileType<ClientProperty> SUBFILE_CLIENT_PROPERTY = new ClientPropertyType();
public static final SubFileType<PovSettings> SUBFILE_POV_ANIMATION = new PovAnimationType();
public static void readAndApply(StaticAnimation animation, Resource iresource, SubFileType<?> subFileType) {
InputStream inputstream = null;
try {
inputstream = iresource.open();
} catch (IOException e) {
e.printStackTrace();
}
assert inputstream != null : "Input stream is null";
try {
subFileType.apply(inputstream, animation);
} catch (JsonParseException e) {
EpicFightMod.LOGGER.warn("Can't read sub file " + subFileType.directory + " for " + animation);
e.printStackTrace();
}
}
public static abstract class SubFileType<T> {
private final String directory;
private final AnimationSubFileDeserializer<T> deserializer;
private SubFileType(String directory, AnimationSubFileDeserializer<T> deserializer) {
this.directory = directory;
this.deserializer = deserializer;
}
// Deserialize from input stream
public void apply(InputStream inputstream, StaticAnimation animation) {
Reader reader = new InputStreamReader(inputstream, StandardCharsets.UTF_8);
JsonReader jsonReader = new JsonReader(reader);
jsonReader.setLenient(true);
T deserialized = this.deserializer.deserialize(animation, Streams.parse(jsonReader));
this.applySubFileInfo(deserialized, animation);
}
// Deserialize from json object
public void apply(JsonElement jsonElement, StaticAnimation animation) {
T deserialized = this.deserializer.deserialize(animation, jsonElement);
this.applySubFileInfo(deserialized, animation);
}
protected abstract void applySubFileInfo(T deserialized, StaticAnimation animation);
public String getDirectory() {
return this.directory;
}
}
private record ClientProperty(LayerInfo layerInfo, LayerInfo multilayerInfo, List<TrailInfo> trailInfo) {
}
private static class ClientPropertyType extends SubFileType<ClientProperty> {
private ClientPropertyType() {
super("data", new AnimationSubFileReader.ClientAnimationPropertyDeserializer());
}
@Override
public void applySubFileInfo(ClientProperty deserialized, StaticAnimation animation) {
if (deserialized.layerInfo() != null) {
if (deserialized.layerInfo().jointMaskEntry.isValid()) {
animation.addProperty(ClientAnimationProperties.JOINT_MASK, deserialized.layerInfo().jointMaskEntry);
}
animation.addProperty(ClientAnimationProperties.LAYER_TYPE, deserialized.layerInfo().layerType);
animation.addProperty(ClientAnimationProperties.PRIORITY, deserialized.layerInfo().priority);
}
if (deserialized.multilayerInfo() != null) {
DirectStaticAnimation multilayerAnimation = new DirectStaticAnimation(animation.getLocation(), animation.getTransitionTime(), animation.isRepeat(), animation.getRegistryName().toString() + "_multilayer", animation.getArmature());
if (deserialized.multilayerInfo().jointMaskEntry.isValid()) {
multilayerAnimation.addProperty(ClientAnimationProperties.JOINT_MASK, deserialized.multilayerInfo().jointMaskEntry);
}
multilayerAnimation.addProperty(ClientAnimationProperties.LAYER_TYPE, deserialized.multilayerInfo().layerType);
multilayerAnimation.addProperty(ClientAnimationProperties.PRIORITY, deserialized.multilayerInfo().priority);
multilayerAnimation.addProperty(StaticAnimationProperty.ELAPSED_TIME_MODIFIER, (self, entitypatch, speed, prevElapsedTime, elapsedTime) -> {
Layer baseLayer = entitypatch.getClientAnimator().baseLayer;
if (baseLayer.animationPlayer.getAnimation().get().getRealAnimation().get() != animation) {
return Pair.of(prevElapsedTime, elapsedTime);
}
if (!self.isStaticAnimation() && baseLayer.animationPlayer.getAnimation().get().isStaticAnimation()) {
return Pair.of(prevElapsedTime + speed, elapsedTime + speed);
}
return Pair.of(baseLayer.animationPlayer.getPrevElapsedTime(), baseLayer.animationPlayer.getElapsedTime());
});
animation.addProperty(ClientAnimationProperties.MULTILAYER_ANIMATION, multilayerAnimation);
}
if (deserialized.trailInfo().size() > 0) {
animation.addProperty(ClientAnimationProperties.TRAIL_EFFECT, deserialized.trailInfo());
}
}
}
private static class ClientAnimationPropertyDeserializer implements AnimationSubFileDeserializer<ClientProperty> {
private static LayerInfo deserializeLayerInfo(JsonObject jsonObject) {
return deserializeLayerInfo(jsonObject, null);
}
private static LayerInfo deserializeLayerInfo(JsonObject jsonObject, @Nullable Layer.LayerType defaultLayerType) {
JointMaskEntry.Builder builder = JointMaskEntry.builder();
Layer.Priority priority = jsonObject.has("priority") ? Layer.Priority.valueOf(GsonHelper.getAsString(jsonObject, "priority")) : null;
Layer.LayerType layerType = jsonObject.has("layer") ? Layer.LayerType.valueOf(GsonHelper.getAsString(jsonObject, "layer")) : Layer.LayerType.BASE_LAYER;
if (jsonObject.has("masks")) {
JsonArray maskArray = jsonObject.get("masks").getAsJsonArray();
if (!maskArray.isEmpty()) {
builder.defaultMask(JointMaskReloadListener.getNoneMask());
maskArray.forEach(element -> {
JsonObject jointMaskEntry = element.getAsJsonObject();
String livingMotionName = GsonHelper.getAsString(jointMaskEntry, "livingmotion");
String type = GsonHelper.getAsString(jointMaskEntry, "type");
if (!type.contains(":")) {
type = (new StringBuilder(EpicFightMod.MODID)).append(":").append(type).toString();
}
if (livingMotionName.equals("ALL")) {
builder.defaultMask(JointMaskReloadListener.getJointMaskEntry(type));
} else {
builder.mask((LivingMotion) LivingMotion.ENUM_MANAGER.getOrThrow(livingMotionName), JointMaskReloadListener.getJointMaskEntry(type));
}
});
}
}
return new LayerInfo(builder.create(), priority, (defaultLayerType == null) ? layerType : defaultLayerType);
}
@Override
public ClientProperty deserialize(StaticAnimation animation, JsonElement json) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
LayerInfo layerInfo = null;
LayerInfo multilayerInfo = null;
if (jsonObject.has("multilayer")) {
JsonObject multiplayerJson = jsonObject.get("multilayer").getAsJsonObject();
layerInfo = deserializeLayerInfo(multiplayerJson.get("base").getAsJsonObject());
multilayerInfo = deserializeLayerInfo(multiplayerJson.get("composite").getAsJsonObject(), Layer.LayerType.COMPOSITE_LAYER);
} else {
layerInfo = deserializeLayerInfo(jsonObject);
}
List<TrailInfo> trailInfos = Lists.newArrayList();
if (jsonObject.has("trail_effects")) {
JsonArray trailArray = jsonObject.get("trail_effects").getAsJsonArray();
trailArray.forEach(element -> trailInfos.add(TrailInfo.deserialize(element)));
}
return new ClientProperty(layerInfo, multilayerInfo, trailInfos);
}
}
public static record PovSettings(
@Nullable TransformSheet cameraTransform,
Map<String, Boolean> visibilities,
RootTransformation rootTransformation,
@Nullable ViewLimit viewLimit,
boolean visibilityOthers,
boolean hasUniqueAnimation,
boolean syncFrame
) {
public enum RootTransformation {
CAMERA, WORLD
}
public record ViewLimit(float xRotMin, float xRotMax, float yRotMin, float yRotMax) {
}
}
private static class PovAnimationType extends SubFileType<PovSettings> {
private PovAnimationType() {
super("pov", new AnimationSubFileReader.PovAnimationDeserializer());
}
@Override
public void applySubFileInfo(PovSettings deserialized, StaticAnimation animation) {
ResourceLocation povAnimationLocation = deserialized.hasUniqueAnimation() ? AnimationManager.getSubAnimationFileLocation(animation.getLocation(), SUBFILE_POV_ANIMATION) : animation.getLocation();
DirectStaticAnimation povAnimation = new DirectStaticAnimation(povAnimationLocation, animation.getTransitionTime(), animation.isRepeat(), animation.getRegistryName().toString() + "_pov", animation.getArmature()) {
@Override
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation pAnimation) {
return animation.getPlaySpeed(entitypatch, pAnimation);
}
};
animation.getProperty(StaticAnimationProperty.PLAY_SPEED_MODIFIER).ifPresent(speedModifier -> {
povAnimation.addProperty(StaticAnimationProperty.PLAY_SPEED_MODIFIER, speedModifier);
});
if (deserialized.syncFrame()) {
animation.getProperty(StaticAnimationProperty.ELAPSED_TIME_MODIFIER).ifPresent(elapsedTimeModifier -> {
povAnimation.addProperty(StaticAnimationProperty.ELAPSED_TIME_MODIFIER, elapsedTimeModifier);
});
}
animation.addProperty(ClientAnimationProperties.POV_ANIMATION, povAnimation);
animation.addProperty(ClientAnimationProperties.POV_SETTINGS, deserialized);
}
}
private static class PovAnimationDeserializer implements AnimationSubFileDeserializer<PovSettings> {
@Override
public PovSettings deserialize(StaticAnimation animation, JsonElement json) throws AssetLoadingException, JsonParseException {
JsonObject jObject = json.getAsJsonObject();
TransformSheet cameraTransform = null;
PovSettings.ViewLimit viewLimit = null;
PovSettings.RootTransformation rootTrasnformation = null;
if (jObject.has("root")) {
rootTrasnformation = PovSettings.RootTransformation.valueOf(ParseUtil.toUpperCase(GsonHelper.getAsString(jObject, "root")));
} else {
if (animation instanceof ActionAnimation) {
rootTrasnformation = PovSettings.RootTransformation.WORLD;
} else {
rootTrasnformation = PovSettings.RootTransformation.CAMERA;
}
}
if (jObject.has("camera")) {
JsonObject cameraTransformJObject = jObject.getAsJsonObject("camera");
cameraTransform = JsonAssetLoader.getTransformSheet(cameraTransformJObject, null, false, JsonAssetLoader.TransformFormat.ATTRIBUTES);
}
ImmutableMap.Builder<String, Boolean> visibilitiesBuilder = ImmutableMap.builder();
boolean others = false;
if (jObject.has("visibilities")) {
JsonObject visibilitiesObject = jObject.getAsJsonObject("visibilities");
visibilitiesObject.entrySet().stream().filter((e) -> !"others".equals(e.getKey())).forEach((entry) -> visibilitiesBuilder.put(entry.getKey(), entry.getValue().getAsBoolean()));
others = visibilitiesObject.get("others").getAsBoolean();
} else {
visibilitiesBuilder.put("leftArm", true);
visibilitiesBuilder.put("leftSleeve", true);
visibilitiesBuilder.put("rightArm", true);
visibilitiesBuilder.put("rightSleeve", true);
}
if (jObject.has("limited_view_degrees")) {
JsonObject limitedViewDegrees = jObject.getAsJsonObject("limited_view_degrees");
JsonArray xRot = limitedViewDegrees.get("xRot").getAsJsonArray();
JsonArray yRot = limitedViewDegrees.get("yRot").getAsJsonArray();
float xRotMin = Math.min(xRot.get(0).getAsFloat(), xRot.get(1).getAsFloat());
float xRotMax = Math.max(xRot.get(0).getAsFloat(), xRot.get(1).getAsFloat());
float yRotMin = Math.min(yRot.get(0).getAsFloat(), yRot.get(1).getAsFloat());
float yRotMax = Math.max(yRot.get(0).getAsFloat(), yRot.get(1).getAsFloat());
viewLimit = new PovSettings.ViewLimit(xRotMin, xRotMax, yRotMin, yRotMax);
}
return new PovSettings(cameraTransform, visibilitiesBuilder.build(), rootTrasnformation, viewLimit, others, jObject.has("animation"), GsonHelper.getAsBoolean(jObject, "sync_frame", false));
}
}
public interface AnimationSubFileDeserializer<T> {
public T deserialize(StaticAnimation animation, JsonElement json) throws JsonParseException;
}
}

View File

@@ -0,0 +1,611 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.client;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.jetbrains.annotations.ApiStatus;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.util.Mth;
import com.tiedup.remake.rig.anim.AnimationManager;
import com.tiedup.remake.rig.anim.AnimationPlayer;
import com.tiedup.remake.rig.anim.Animator;
import com.tiedup.remake.rig.armature.Joint;
import com.tiedup.remake.rig.anim.LivingMotion;
import com.tiedup.remake.rig.anim.LivingMotions;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.ServerAnimator;
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.anim.types.EntityState;
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.anim.client.Layer.Priority;
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
import com.tiedup.remake.rig.anim.client.property.JointMask.BindModifier;
import com.tiedup.remake.rig.anim.client.property.JointMask.JointMaskSet;
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
import yesman.epicfight.gameasset.Animations;
import yesman.epicfight.main.EpicFightMod;
import yesman.epicfight.network.common.AnimatorControlPacket;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public class ClientAnimator extends Animator {
public static Animator getAnimator(LivingEntityPatch<?> entitypatch) {
return entitypatch.isLogicalClient() ? new ClientAnimator(entitypatch) : ServerAnimator.getAnimator(entitypatch);
}
private final Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> compositeLivingAnimations;
private final Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> defaultLivingAnimations;
private final Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> defaultCompositeLivingAnimations;
public final Layer.BaseLayer baseLayer;
private LivingMotion currentMotion;
private LivingMotion currentCompositeMotion;
private boolean hardPaused;
public ClientAnimator(LivingEntityPatch<?> entitypatch) {
this(entitypatch, Layer.BaseLayer::new);
}
public ClientAnimator(LivingEntityPatch<?> entitypatch, Supplier<Layer.BaseLayer> layerSupplier) {
super(entitypatch);
this.currentMotion = LivingMotions.IDLE;
this.currentCompositeMotion = LivingMotions.IDLE;
this.compositeLivingAnimations = Maps.newHashMap();
this.defaultLivingAnimations = Maps.newHashMap();
this.defaultCompositeLivingAnimations = Maps.newHashMap();
this.baseLayer = layerSupplier.get();
}
/** Play an animation by animation instance **/
@Override
public void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, float transitionTimeModifier) {
Layer layer = nextAnimation.get().getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(nextAnimation.get().getPriority());
layer.paused = false;
layer.playAnimation(nextAnimation, this.entitypatch, transitionTimeModifier);
}
/** Play an animation with specifying layer and priority **/
@ApiStatus.Internal
public void playAnimationAt(AssetAccessor<? extends StaticAnimation> nextAnimation, float transitionTimeModifier, AnimatorControlPacket.Layer layerType, AnimatorControlPacket.Priority priority) {
Layer layer = layerType == AnimatorControlPacket.Layer.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(AnimatorControlPacket.getPriority(priority));
layer.paused = false;
layer.playAnimation(nextAnimation, this.entitypatch, transitionTimeModifier);
}
@Override
public void playAnimationInstantly(AssetAccessor<? extends StaticAnimation> nextAnimation) {
Layer layer = nextAnimation.get().getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(nextAnimation.get().getPriority());
layer.paused = false;
layer.playAnimationInstantly(nextAnimation, this.entitypatch);
}
@Override
public void reserveAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation) {
Layer layer = nextAnimation.get().getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(nextAnimation.get().getPriority());
if (nextAnimation.get().getPriority().isHigherThan(layer.animationPlayer.getRealAnimation().get().getPriority())) {
if (!layer.animationPlayer.isEnd() && layer.animationPlayer.getAnimation() != null) {
layer.animationPlayer.getAnimation().get().end(this.entitypatch, nextAnimation, false);
}
layer.animationPlayer.terminate(this.entitypatch);
}
layer.nextAnimation = nextAnimation;
layer.paused = false;
}
@Override
public boolean stopPlaying(AssetAccessor<? extends StaticAnimation> targetAnimation) {
Layer layer = targetAnimation.get().getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(targetAnimation.get().getPriority());
if (layer.animationPlayer.getRealAnimation() == targetAnimation) {
layer.animationPlayer.terminate(this.entitypatch);
return true;
}
return false;
}
@Override
public void setSoftPause(boolean paused) {
this.iterAllLayers(layer -> layer.paused = paused);
}
@Override
public void setHardPause(boolean paused) {
this.hardPaused = paused;
}
@Override
public void addLivingAnimation(LivingMotion livingMotion, AssetAccessor<? extends StaticAnimation> animation) {
if (AnimationManager.checkNull(animation)) {
EpicFightMod.LOGGER.warn("Unable to put an empty animation for " + livingMotion);
return;
}
Layer.LayerType layerType = animation.get().getLayerType();
boolean isBaseLayer = (layerType == Layer.LayerType.BASE_LAYER);
Map<LivingMotion, AssetAccessor<? extends StaticAnimation>> storage = layerType == Layer.LayerType.BASE_LAYER ? this.livingAnimations : this.compositeLivingAnimations;
LivingMotion compareMotion = layerType == Layer.LayerType.BASE_LAYER ? this.currentMotion : this.currentCompositeMotion;
Layer layer = layerType == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(animation.get().getPriority());
storage.put(livingMotion, animation);
if (livingMotion == compareMotion) {
EntityState state = this.getEntityState();
if (!state.inaction()) {
layer.playLivingAnimation(animation, this.entitypatch);
}
}
if (isBaseLayer) {
animation.get().getProperty(ClientAnimationProperties.MULTILAYER_ANIMATION).ifPresent(multilayerAnimation -> {
this.compositeLivingAnimations.put(livingMotion, multilayerAnimation);
if (livingMotion == this.currentCompositeMotion) {
EntityState state = this.getEntityState();
if (!state.inaction()) {
layer.playLivingAnimation(multilayerAnimation, this.entitypatch);
}
}
});
}
}
public void setCurrentMotionsAsDefault() {
this.defaultLivingAnimations.putAll(this.livingAnimations);
this.defaultCompositeLivingAnimations.putAll(this.compositeLivingAnimations);
}
@Override
public void resetLivingAnimations() {
super.resetLivingAnimations();
this.compositeLivingAnimations.clear();
this.defaultLivingAnimations.forEach((key, val) -> this.addLivingAnimation(key, val));
this.defaultCompositeLivingAnimations.forEach((key, val) -> this.addLivingAnimation(key, val));
}
public AssetAccessor<? extends StaticAnimation> getLivingMotion(LivingMotion motion) {
return this.livingAnimations.getOrDefault(motion, this.livingAnimations.get(LivingMotions.IDLE));
}
public AssetAccessor<? extends StaticAnimation> getCompositeLivingMotion(LivingMotion motion) {
return this.compositeLivingAnimations.get(motion);
}
@Override
public void postInit() {
super.postInit();
this.setCurrentMotionsAsDefault();
AssetAccessor<? extends StaticAnimation> idleMotion = this.livingAnimations.get(this.currentMotion);
this.baseLayer.playAnimationInstantly(idleMotion, this.entitypatch);
}
@Override
public void tick() {
/**
// Layer debugging
for (Layer layer : this.getAllLayers()) {
System.out.println(layer);
}
System.out.println();
**/
if (this.hardPaused) {
return;
}
this.baseLayer.update(this.entitypatch);
if (this.baseLayer.animationPlayer.isEnd() && this.baseLayer.nextAnimation == null && this.currentMotion != LivingMotions.DEATH) {
this.entitypatch.updateMotion(false);
if (this.compositeLivingAnimations.containsKey(this.entitypatch.currentCompositeMotion)) {
this.playAnimation(this.getCompositeLivingMotion(this.entitypatch.currentCompositeMotion), 0.0F);
}
this.baseLayer.playAnimation(this.getLivingMotion(this.entitypatch.currentLivingMotion), this.entitypatch, 0.0F);
} else {
if (!this.compareCompositeMotion(this.entitypatch.currentCompositeMotion)) {
/* Turns off the multilayer of the base layer */
this.getLivingMotion(this.currentCompositeMotion).get().getProperty(ClientAnimationProperties.MULTILAYER_ANIMATION).ifPresent((multilayerAnimation) -> {
if (!this.compositeLivingAnimations.containsKey(this.entitypatch.currentCompositeMotion)) {
this.getCompositeLayer(multilayerAnimation.get().getPriority()).off(this.entitypatch);
}
});
if (this.compositeLivingAnimations.containsKey(this.currentCompositeMotion)) {
AssetAccessor<? extends StaticAnimation> nextLivingAnimation = this.getCompositeLivingMotion(this.entitypatch.currentCompositeMotion);
if (nextLivingAnimation == null || nextLivingAnimation.get().getPriority() != this.getCompositeLivingMotion(this.currentCompositeMotion).get().getPriority()) {
this.getCompositeLayer(this.getCompositeLivingMotion(this.currentCompositeMotion).get().getPriority()).off(this.entitypatch);
}
}
if (this.compositeLivingAnimations.containsKey(this.entitypatch.currentCompositeMotion)) {
this.playAnimation(this.getCompositeLivingMotion(this.entitypatch.currentCompositeMotion), 0.0F);
}
}
if (!this.compareMotion(this.entitypatch.currentLivingMotion) && this.entitypatch.currentLivingMotion != LivingMotions.DEATH) {
if (this.livingAnimations.containsKey(this.entitypatch.currentLivingMotion)) {
this.baseLayer.playAnimation(this.getLivingMotion(this.entitypatch.currentLivingMotion), this.entitypatch, 0.0F);
}
}
}
this.currentMotion = this.entitypatch.currentLivingMotion;
this.currentCompositeMotion = this.entitypatch.currentCompositeMotion;
}
@Override
public void playDeathAnimation() {
if (!this.getPlayerFor(null).getAnimation().get().getProperty(ActionAnimationProperty.IS_DEATH_ANIMATION).orElse(false)) {
this.playAnimation(this.livingAnimations.getOrDefault(LivingMotions.DEATH, Animations.EMPTY_ANIMATION), 0.0F);
this.currentMotion = LivingMotions.DEATH;
}
}
public AssetAccessor<? extends StaticAnimation> getJumpAnimation() {
return this.livingAnimations.get(LivingMotions.JUMP);
}
public Layer getCompositeLayer(Layer.Priority priority) {
return this.baseLayer.compositeLayers.get(priority);
}
public void renderDebuggingInfoForAllLayers(PoseStack poseStack, MultiBufferSource buffer, float partialTicks) {
this.iterAllLayers((layer) -> {
if (layer.isOff()) {
return;
}
AnimationPlayer animPlayer = layer.animationPlayer;
float playTime = Mth.lerp(partialTicks, animPlayer.getPrevElapsedTime(), animPlayer.getElapsedTime());
animPlayer.getAnimation().get().renderDebugging(poseStack, buffer, entitypatch, playTime, partialTicks);
});
}
public Collection<Layer> getAllLayers() {
List<Layer> layerList = Lists.newArrayList();
layerList.add(this.baseLayer);
layerList.addAll(this.baseLayer.compositeLayers.values());
return layerList;
}
/**
* Iterates all layers
* @param task
*/
public void iterAllLayers(Consumer<Layer> task) {
task.accept(this.baseLayer);
this.baseLayer.compositeLayers.values().forEach(task);
}
/**
* Iterate layers that is visible by priority
* @param task
* @return
*/
public void iterVisibleLayers(Consumer<Layer> task) {
task.accept(this.baseLayer);
this.baseLayer.compositeLayers.values().stream()
.filter(layer -> layer.isDisabled() || layer.animationPlayer.isEmpty() || !layer.priority.isHigherOrEqual(this.baseLayer.baseLayerPriority))
.forEach(task);
}
/**
* Iterates all activated layers from the highest layer
* when base layer = highest, iterates only base layer
* when base layer = middle, iterates base layer and highest composite layer
* when base layer = lowest, iterates base layer and all composite layers
*
* @param task
* @return true if all layers didn't return false by @param task
*/
public boolean iterVisibleLayersUntilFalse(Function<Layer, Boolean> task) {
Layer.Priority[] highers = this.baseLayer.baseLayerPriority.highers();
for (int i = highers.length - 1; i >= 0; i--) {
Layer layer = this.baseLayer.getLayer(highers[i]);
if (layer.isDisabled() || layer.animationPlayer.isEmpty()) {
if (highers[i] == this.baseLayer.baseLayerPriority) {
return task.apply(this.baseLayer);
}
continue;
}
if (!task.apply(layer)) {
return false;
}
if (highers[i] == this.baseLayer.baseLayerPriority) {
return task.apply(this.baseLayer);
}
}
return true;
}
@Override
public Pose getPose(float partialTicks) {
return this.getPose(partialTicks, true);
}
public Pose getPose(float partialTicks, boolean useCurrentMotion) {
Pose composedPose = new Pose();
Pose baseLayerPose = this.baseLayer.getEnabledPose(this.entitypatch, useCurrentMotion, partialTicks);
Map<Layer.Priority, Pair<AssetAccessor<? extends DynamicAnimation>, Pose>> layerPoses = Maps.newLinkedHashMap();
composedPose.load(baseLayerPose, Pose.LoadOperation.OVERWRITE);
for (Layer.Priority priority : this.baseLayer.baseLayerPriority.highers()) {
Layer compositeLayer = this.baseLayer.compositeLayers.get(priority);
if (!compositeLayer.isDisabled() && !compositeLayer.animationPlayer.isEmpty()) {
Pose layerPose = compositeLayer.getEnabledPose(this.entitypatch, useCurrentMotion, partialTicks);
layerPoses.put(priority, Pair.of(compositeLayer.animationPlayer.getAnimation(), layerPose));
composedPose.load(layerPose, Pose.LoadOperation.OVERWRITE);
}
}
Joint rootJoint = this.entitypatch.getArmature().rootJoint;
this.applyBindModifier(baseLayerPose, composedPose, rootJoint, layerPoses, useCurrentMotion);
return composedPose;
}
public Pose getComposedLayerPoseBelow(Layer.Priority priorityLimit, float partialTicks) {
Pose composedPose = this.baseLayer.getEnabledPose(this.entitypatch, true, partialTicks);
Pose baseLayerPose = this.baseLayer.getEnabledPose(this.entitypatch, true, partialTicks);
Map<Layer.Priority, Pair<AssetAccessor<? extends DynamicAnimation>, Pose>> layerPoses = Maps.newLinkedHashMap();
for (Layer.Priority priority : priorityLimit.lowers()) {
Layer compositeLayer = this.baseLayer.compositeLayers.get(priority);
if (!compositeLayer.isDisabled()) {
Pose layerPose = compositeLayer.getEnabledPose(this.entitypatch, true, partialTicks);
layerPoses.put(priority, Pair.of(compositeLayer.animationPlayer.getAnimation(), layerPose));
composedPose.load(layerPose, Pose.LoadOperation.OVERWRITE);
}
}
if (!layerPoses.isEmpty()) {
this.applyBindModifier(baseLayerPose, composedPose, this.entitypatch.getArmature().rootJoint, layerPoses, true);
}
return composedPose;
}
public void applyBindModifier(Pose basePose, Pose result, Joint joint, Map<Layer.Priority, Pair<AssetAccessor<? extends DynamicAnimation>, Pose>> poses, boolean useCurrentMotion) {
List<Priority> list = Lists.newArrayList(poses.keySet());
Collections.reverse(list);
for (Layer.Priority priority : list) {
AssetAccessor<? extends DynamicAnimation> nowPlaying = poses.get(priority).getFirst();
JointMaskEntry jointMaskEntry = nowPlaying.get().getJointMaskEntry(this.entitypatch, useCurrentMotion).orElse(null);
if (jointMaskEntry != null) {
LivingMotion livingMotion = this.getCompositeLayer(priority).getLivingMotion(this.entitypatch, useCurrentMotion);
if (nowPlaying.get().hasTransformFor(joint.getName()) && !jointMaskEntry.isMasked(livingMotion, joint.getName())) {
JointMaskSet jointmaskset = jointMaskEntry.getMask(livingMotion);
BindModifier bindModifier = jointmaskset.getBindModifier(joint.getName());
if (bindModifier != null) {
bindModifier.modify(this.entitypatch, basePose, result, livingMotion, jointMaskEntry, priority, joint, poses);
break;
}
}
}
}
for (Joint subJoints : joint.getSubJoints()) {
this.applyBindModifier(basePose, result, subJoints, poses, useCurrentMotion);
}
}
public boolean compareMotion(LivingMotion motion) {
return this.currentMotion.isSame(motion);
}
public boolean compareCompositeMotion(LivingMotion motion) {
return this.currentCompositeMotion.isSame(motion);
}
public void forceResetBeforeAction(LivingMotion livingMotion, LivingMotion compositeLivingMotion) {
if (!this.currentMotion.equals(livingMotion)) {
if (this.livingAnimations.containsKey(livingMotion)) {
this.baseLayer.playAnimation(this.getLivingMotion(livingMotion), this.entitypatch, 0.0F);
}
}
this.entitypatch.currentLivingMotion = livingMotion;
this.currentMotion = livingMotion;
if (!this.currentCompositeMotion.equals(compositeLivingMotion)) {
if (this.compositeLivingAnimations.containsKey(this.currentCompositeMotion)) {
this.getCompositeLayer(this.getCompositeLivingMotion(this.currentCompositeMotion).get().getPriority()).off(this.entitypatch);
}
if (this.compositeLivingAnimations.containsKey(compositeLivingMotion)) {
this.playAnimation(this.getCompositeLivingMotion(compositeLivingMotion), 0.0F);
}
}
this.currentCompositeMotion = LivingMotions.NONE;
this.entitypatch.currentCompositeMotion = LivingMotions.NONE;
}
public void resetMotion(boolean resetPrevMotion) {
if (resetPrevMotion) this.currentMotion = LivingMotions.IDLE;
this.entitypatch.currentLivingMotion = LivingMotions.IDLE;
}
public void resetCompositeMotion() {
if (this.currentCompositeMotion != LivingMotions.IDLE && this.compositeLivingAnimations.containsKey(this.currentCompositeMotion)) {
AssetAccessor<? extends StaticAnimation> currentPlaying = this.getCompositeLivingMotion(this.currentCompositeMotion);
AssetAccessor<? extends StaticAnimation> resetPlaying = this.getCompositeLivingMotion(LivingMotions.IDLE);
if (resetPlaying != null && currentPlaying != resetPlaying) {
this.playAnimation(resetPlaying, 0.0F);
} else if (currentPlaying != null) {
this.getCompositeLayer(currentPlaying.get().getPriority()).off(this.entitypatch);
}
}
this.currentCompositeMotion = LivingMotions.NONE;
this.entitypatch.currentCompositeMotion = LivingMotions.NONE;
}
public void offAllLayers() {
for (Layer layer : this.baseLayer.compositeLayers.values()) {
layer.off(this.entitypatch);
}
}
public boolean isAiming() {
return this.currentCompositeMotion == LivingMotions.AIM;
}
@Override
public void playShootingAnimation() {
if (this.compositeLivingAnimations.containsKey(LivingMotions.SHOT)) {
this.playAnimation(this.compositeLivingAnimations.get(LivingMotions.SHOT), 0.0F);
this.entitypatch.currentCompositeMotion = LivingMotions.NONE;
this.currentCompositeMotion = LivingMotions.NONE;
}
}
@Override
public AnimationPlayer getPlayerFor(AssetAccessor<? extends DynamicAnimation> playingAnimation) {
if (playingAnimation == null) {
return this.baseLayer.animationPlayer;
}
DynamicAnimation animation = playingAnimation.get();
if (animation instanceof StaticAnimation staticAnimation) {
Layer layer = staticAnimation.getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(staticAnimation.getPriority());
if (layer.animationPlayer.getAnimation() == playingAnimation) return layer.animationPlayer;
}
for (Layer layer : this.baseLayer.compositeLayers.values()) {
if (layer.animationPlayer.getRealAnimation().equals(playingAnimation)) {
return layer.animationPlayer;
}
}
return this.baseLayer.animationPlayer;
}
@Override
public Optional<AnimationPlayer> getPlayer(AssetAccessor<? extends DynamicAnimation> playingAnimation) {
DynamicAnimation animation = playingAnimation.get();
if (animation instanceof StaticAnimation staticAnimation) {
Layer layer = staticAnimation.getLayerType() == Layer.LayerType.BASE_LAYER ? this.baseLayer : this.baseLayer.compositeLayers.get(staticAnimation.getPriority());
if (layer.animationPlayer.getRealAnimation().equals(playingAnimation)) {
return Optional.of(layer.animationPlayer);
}
}
if (this.baseLayer.animationPlayer.getRealAnimation().equals(playingAnimation.get().getRealAnimation())) {
return Optional.of(this.baseLayer.animationPlayer);
}
for (Layer layer : this.baseLayer.compositeLayers.values()) {
if (layer.animationPlayer.getRealAnimation().equals(playingAnimation.get().getRealAnimation())) {
return Optional.of(layer.animationPlayer);
}
}
return Optional.empty();
}
public Layer.Priority getPriorityFor(AssetAccessor<? extends DynamicAnimation> playingAnimation) {
for (Layer layer : this.baseLayer.compositeLayers.values()) {
if (layer.animationPlayer.getRealAnimation().equals(playingAnimation)) {
return layer.priority;
}
}
return this.baseLayer.priority;
}
public LivingMotion currentMotion() {
return this.currentMotion;
}
public LivingMotion currentCompositeMotion() {
return this.currentCompositeMotion;
}
@SuppressWarnings("unchecked")
@Override
public <T> Pair<AnimationPlayer, T> findFor(Class<T> animationType) {
for (Layer layer : this.baseLayer.compositeLayers.values()) {
if (animationType.isAssignableFrom(layer.animationPlayer.getAnimation().getClass())) {
return Pair.of(layer.animationPlayer, (T)layer.animationPlayer.getAnimation());
}
}
return animationType.isAssignableFrom(this.baseLayer.animationPlayer.getAnimation().getClass()) ? Pair.of(this.baseLayer.animationPlayer, (T)this.baseLayer.animationPlayer.getAnimation()) : null;
}
public LivingEntityPatch<?> getOwner() {
return this.entitypatch;
}
@Override
public EntityState getEntityState() {
TypeFlexibleHashMap<StateFactor<?>> stateMap = new TypeFlexibleHashMap<> (false);
for (Layer layer : this.baseLayer.compositeLayers.values()) {
if (this.baseLayer.baseLayerPriority.isHigherThan(layer.priority)) {
continue;
}
if (!layer.isOff()) {
stateMap.putAll(layer.animationPlayer.getAnimation().get().getStatesMap(this.entitypatch, layer.animationPlayer.getElapsedTime()));
}
// put base layer states
if (layer.priority == this.baseLayer.baseLayerPriority) {
stateMap.putAll(this.baseLayer.animationPlayer.getAnimation().get().getStatesMap(this.entitypatch, this.baseLayer.animationPlayer.getElapsedTime()));
}
}
return new EntityState(stateMap);
}
}

View File

@@ -0,0 +1,359 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.client;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Supplier;
import com.google.common.collect.Maps;
import com.tiedup.remake.rig.anim.AnimationPlayer;
import com.tiedup.remake.rig.anim.LivingMotion;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.types.ConcurrentLinkAnimation;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.anim.types.LayerOffAnimation;
import com.tiedup.remake.rig.anim.types.LinkAnimation;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import yesman.epicfight.gameasset.Animations;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public class Layer {
protected AssetAccessor<? extends StaticAnimation> nextAnimation;
protected final LinkAnimation linkAnimation;
protected final ConcurrentLinkAnimation concurrentLinkAnimation;
protected final LayerOffAnimation layerOffAnimation;
protected final Layer.Priority priority;
protected boolean disabled;
protected boolean paused;
public final AnimationPlayer animationPlayer;
public Layer(Priority priority) {
this(priority, AnimationPlayer::new);
}
public Layer(Priority priority, Supplier<AnimationPlayer> animationPlayerProvider) {
this.animationPlayer = animationPlayerProvider.get();
this.linkAnimation = new LinkAnimation();
this.concurrentLinkAnimation = new ConcurrentLinkAnimation();
this.layerOffAnimation = new LayerOffAnimation(priority);
this.priority = priority;
this.disabled = true;
}
public void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, LivingEntityPatch<?> entitypatch, float transitionTimeModifier) {
// Get pose before StaticAnimation#end is called
Pose lastPose = this.getCurrentPose(entitypatch);
if (!this.animationPlayer.isEnd()) {
this.animationPlayer.getAnimation().get().end(entitypatch, nextAnimation, false);
}
this.resume();
nextAnimation.get().begin(entitypatch);
if (!nextAnimation.get().isMetaAnimation()) {
this.setLinkAnimation(nextAnimation, entitypatch, lastPose, transitionTimeModifier);
this.linkAnimation.putOnPlayer(this.animationPlayer, entitypatch);
entitypatch.updateEntityState();
this.nextAnimation = nextAnimation;
}
}
/**
* Plays an animation without a link animation
*/
public void playAnimationInstantly(AssetAccessor<? extends DynamicAnimation> nextAnimation, LivingEntityPatch<?> entitypatch) {
if (!this.animationPlayer.isEnd()) {
this.animationPlayer.getAnimation().get().end(entitypatch, nextAnimation, false);
}
this.resume();
nextAnimation.get().begin(entitypatch);
nextAnimation.get().putOnPlayer(this.animationPlayer, entitypatch);
entitypatch.updateEntityState();
this.nextAnimation = null;
}
protected void playLivingAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, LivingEntityPatch<?> entitypatch) {
if (!this.animationPlayer.isEnd()) {
this.animationPlayer.getAnimation().get().end(entitypatch, nextAnimation, false);
}
this.resume();
nextAnimation.get().begin(entitypatch);
if (!nextAnimation.get().isMetaAnimation()) {
this.concurrentLinkAnimation.acceptFrom(this.animationPlayer.getRealAnimation(), nextAnimation, this.animationPlayer.getElapsedTime());
this.concurrentLinkAnimation.putOnPlayer(this.animationPlayer, entitypatch);
entitypatch.updateEntityState();
this.nextAnimation = nextAnimation;
}
}
protected Pose getCurrentPose(LivingEntityPatch<?> entitypatch) {
return entitypatch.getClientAnimator().getPose(0.0F, false);
}
protected void setLinkAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, LivingEntityPatch<?> entitypatch, Pose lastPose, float transitionTimeModifier) {
AssetAccessor<? extends DynamicAnimation> fromAnimation = this.animationPlayer.isEmpty() ? entitypatch.getClientAnimator().baseLayer.animationPlayer.getAnimation() : this.animationPlayer.getAnimation();
if (fromAnimation.get() instanceof LinkAnimation linkAnimation) {
fromAnimation = linkAnimation.getFromAnimation();
}
nextAnimation.get().setLinkAnimation(fromAnimation, lastPose, !this.animationPlayer.isEmpty(), transitionTimeModifier, entitypatch, this.linkAnimation);
this.linkAnimation.getAnimationClip().setBaked();
}
public void update(LivingEntityPatch<?> entitypatch) {
if (this.paused) {
this.animationPlayer.setElapsedTime(this.animationPlayer.getElapsedTime());
} else {
this.animationPlayer.tick(entitypatch);
}
if (!this.animationPlayer.isEnd()) {
this.animationPlayer.getAnimation().get().tick(entitypatch);
} else if (!this.paused) {
if (this.nextAnimation != null) {
if (!this.animationPlayer.getAnimation().get().isLinkAnimation() && !this.nextAnimation.get().isLinkAnimation()) {
this.nextAnimation.get().begin(entitypatch);
}
this.nextAnimation.get().putOnPlayer(this.animationPlayer, entitypatch);
this.nextAnimation = null;
} else {
if (this.animationPlayer.getAnimation() instanceof LayerOffAnimation) {
this.animationPlayer.getAnimation().get().end(entitypatch, Animations.EMPTY_ANIMATION, true);
} else {
this.off(entitypatch);
}
}
}
if (this.isBaseLayer()) {
entitypatch.updateEntityState();
entitypatch.updateMotion(true);
}
}
public void pause() {
this.paused = true;
}
public void resume() {
this.paused = false;
this.disabled = false;
}
protected boolean isDisabled() {
return this.disabled;
}
public boolean isOff() {
return this.isDisabled() || this.animationPlayer.isEmpty();
}
protected boolean isBaseLayer() {
return false;
}
public void copyLayerTo(Layer layer, float playbackTime) {
AssetAccessor<? extends DynamicAnimation> animation;
if (this.animationPlayer.getAnimation() == this.linkAnimation) {
this.linkAnimation.copyTo(layer.linkAnimation);
animation = layer.linkAnimation;
} else {
animation = this.animationPlayer.getAnimation();
}
layer.animationPlayer.setPlayAnimation(animation);
layer.animationPlayer.setElapsedTime(this.animationPlayer.getPrevElapsedTime() + playbackTime, this.animationPlayer.getElapsedTime() + playbackTime);
layer.nextAnimation = this.nextAnimation;
layer.resume();
}
public LivingMotion getLivingMotion(LivingEntityPatch<?> entitypatch, boolean current) {
return current ? entitypatch.currentLivingMotion : entitypatch.getClientAnimator().currentMotion();
}
public Pose getEnabledPose(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion, float partialTick) {
Pose pose = this.animationPlayer.getCurrentPose(entitypatch, partialTick);
this.animationPlayer.getAnimation().get().getJointMaskEntry(entitypatch, useCurrentMotion).ifPresent((jointEntry) -> pose.disableJoint((entry) -> jointEntry.isMasked(this.getLivingMotion(entitypatch, useCurrentMotion), entry.getKey())));
return pose;
}
public void off(LivingEntityPatch<?> entitypatch) {
if (!this.isDisabled() && !(this.animationPlayer.getAnimation() instanceof LayerOffAnimation)) {
if (this.priority == null) {
this.disableLayer();
} else {
float transitionTimeModifier = entitypatch.getClientAnimator().baseLayer.animationPlayer.getAnimation().get().getTransitionTime();
setLayerOffAnimation(this.animationPlayer.getAnimation(), this.getEnabledPose(entitypatch, false, 1.0F), this.layerOffAnimation, transitionTimeModifier);
this.playAnimationInstantly(this.layerOffAnimation, entitypatch);
}
}
}
public void disableLayer() {
this.disabled = true;
this.animationPlayer.setPlayAnimation(Animations.EMPTY_ANIMATION);
}
public static void setLayerOffAnimation(AssetAccessor<? extends DynamicAnimation> currentAnimation, Pose currentPose, LayerOffAnimation offAnimation, float transitionTimeModifier) {
offAnimation.setLastAnimation(currentAnimation.get().getRealAnimation());
offAnimation.setLastPose(currentPose);
offAnimation.setTotalTime(transitionTimeModifier);
}
public AssetAccessor<? extends DynamicAnimation> getNextAnimation() {
return this.nextAnimation;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.isBaseLayer() ? "Base Layer(" + ((BaseLayer)this).baseLayerPriority + ") : " : " Composite Layer(" + this.priority + ") : ");
sb.append(this.animationPlayer.getAnimation() + " ");
sb.append(", prev elapsed time: " + this.animationPlayer.getPrevElapsedTime() + " ");
sb.append(", elapsed time: " + this.animationPlayer.getElapsedTime() + " ");
sb.append(", total time: " + this.animationPlayer.getAnimation().get().getTotalTime() + " ");
return sb.toString();
}
public static class BaseLayer extends Layer {
protected Map<Layer.Priority, Layer> compositeLayers = Maps.newLinkedHashMap();
protected Layer.Priority baseLayerPriority;
public BaseLayer() {
this(AnimationPlayer::new);
}
public BaseLayer(Supplier<AnimationPlayer> animationPlayerProvider) {
super(null, animationPlayerProvider);
for (Priority priority : Priority.values()) {
this.compositeLayers.computeIfAbsent(priority, Layer::new);
}
this.baseLayerPriority = Priority.LOWEST;
}
@Override
public void playAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, LivingEntityPatch<?> entitypatch, float transitionTimeModifier) {
this.offCompositeLayersLowerThan(entitypatch, nextAnimation);
super.playAnimation(nextAnimation, entitypatch, transitionTimeModifier);
this.baseLayerPriority = nextAnimation.get().getPriority();
}
@Override
protected void playLivingAnimation(AssetAccessor<? extends StaticAnimation> nextAnimation, LivingEntityPatch<?> entitypatch) {
if (!this.animationPlayer.isEnd()) {
this.animationPlayer.getAnimation().get().end(entitypatch, nextAnimation, false);
}
this.resume();
nextAnimation.get().begin(entitypatch);
if (!nextAnimation.get().isMetaAnimation()) {
this.concurrentLinkAnimation.acceptFrom(this.animationPlayer.getRealAnimation(), nextAnimation, this.animationPlayer.getElapsedTime());
this.concurrentLinkAnimation.putOnPlayer(this.animationPlayer, entitypatch);
entitypatch.updateEntityState();
this.nextAnimation = nextAnimation;
}
}
@Override
public void update(LivingEntityPatch<?> entitypatch) {
super.update(entitypatch);
for (Layer layer : this.compositeLayers.values()) {
layer.update(entitypatch);
}
}
public void offCompositeLayersLowerThan(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> nextAnimation) {
Priority[] layersToOff = nextAnimation.get().isMainFrameAnimation() ? nextAnimation.get().getPriority().lowersAndEqual() : nextAnimation.get().getPriority().lowers();
for (Priority p : layersToOff) {
this.compositeLayers.get(p).off(entitypatch);
}
}
public void disableLayer(Priority priority) {
this.compositeLayers.get(priority).disableLayer();
}
public Layer getLayer(Priority priority) {
return this.compositeLayers.get(priority);
}
public Priority getBaseLayerPriority() {
return this.baseLayerPriority;
}
@Override
public void off(LivingEntityPatch<?> entitypatch) {
}
@Override
protected boolean isDisabled() {
return false;
}
@Override
protected boolean isBaseLayer() {
return true;
}
}
public enum LayerType {
BASE_LAYER, COMPOSITE_LAYER
}
public enum Priority {
/**
* The common usage of each layer
*
* LOWEST: Most of living cycle animations. Also a default value for animations doesn't inherit {@link MainFrameAnimation.class}
* LOW: A few {@link ActionAnimation.class} that allows showing living cycle animations. e.g. step
* MIDDLE: Most of composite living cycle animations. e.g. weapon holding animations
* HIGH: A few composite animations that doesn't repeat. e.g. Uchigatana sheathing, Shield hit
* HIGHEST: Most of {@link MainFrameAnimation.class} and a few living cycle animations. e.g. ladder animation
**/
LOWEST, LOW, MIDDLE, HIGH, HIGHEST;
public Priority[] lowers() {
return Arrays.copyOfRange(Priority.values(), 0, this.ordinal());
}
public Priority[] lowersAndEqual() {
return Arrays.copyOfRange(Priority.values(), 0, this.ordinal() + 1);
}
public Priority[] highers() {
return Arrays.copyOfRange(Priority.values(), this.ordinal(), Priority.values().length);
}
public boolean isHigherThan(Priority priority) {
return this.ordinal() > priority.ordinal();
}
public boolean isHigherOrEqual(Priority priority) {
return this.ordinal() >= priority.ordinal();
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.client.property;
import java.util.List;
import com.tiedup.remake.rig.anim.property.AnimationProperty.StaticAnimationProperty;
import com.tiedup.remake.rig.anim.types.DirectStaticAnimation;
import com.tiedup.remake.rig.anim.client.AnimationSubFileReader;
import com.tiedup.remake.rig.anim.client.Layer;
public class ClientAnimationProperties {
/**
* Layer type. (BASE: Living, attack animations, COMPOSITE: Aiming, weapon holding, digging animation)
*/
public static final StaticAnimationProperty<Layer.LayerType> LAYER_TYPE = new StaticAnimationProperty<Layer.LayerType> ();
/**
* Priority of composite layer.
*/
public static final StaticAnimationProperty<Layer.Priority> PRIORITY = new StaticAnimationProperty<Layer.Priority> ();
/**
* Joint mask for composite layer.
*/
public static final StaticAnimationProperty<JointMaskEntry> JOINT_MASK = new StaticAnimationProperty<JointMaskEntry> ();
/**
* Trail particle information
*/
public static final StaticAnimationProperty<List<TrailInfo>> TRAIL_EFFECT = new StaticAnimationProperty<List<TrailInfo>> ();
/**
* An animation clip being played in first person.
*/
public static final StaticAnimationProperty<DirectStaticAnimation> POV_ANIMATION = new StaticAnimationProperty<DirectStaticAnimation> ();
/**
* An animation clip being played in first person.
*/
public static final StaticAnimationProperty<AnimationSubFileReader.PovSettings> POV_SETTINGS = new StaticAnimationProperty<AnimationSubFileReader.PovSettings> ();
/**
* Multilayer for living animations (e.g. Greatsword holding animation should be played simultaneously with jumping animation)
*/
public static final StaticAnimationProperty<DirectStaticAnimation> MULTILAYER_ANIMATION = new StaticAnimationProperty<DirectStaticAnimation> ();
}

View File

@@ -0,0 +1,104 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.client.property;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Maps;
import com.mojang.datafixers.util.Pair;
import com.tiedup.remake.rig.armature.Joint;
import com.tiedup.remake.rig.armature.JointTransform;
import com.tiedup.remake.rig.anim.LivingMotion;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.anim.client.Layer;
import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.math.Vec3f;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public class JointMask {
@FunctionalInterface
public interface BindModifier {
public void modify(LivingEntityPatch<?> entitypatch, Pose baseLayerPose, Pose resultPose, LivingMotion livingMotion, JointMaskEntry wholeEntry, Layer.Priority priority, Joint joint, Map<Layer.Priority, Pair<AssetAccessor<? extends DynamicAnimation>, Pose>> poses);
}
public static final BindModifier KEEP_CHILD_LOCROT = (entitypatch, baseLayerPose, result, livingMotion, wholeEntry, priority, joint, poses) -> {
Pose currentPose = poses.get(priority).getSecond();
JointTransform lowestTransform = baseLayerPose.orElseEmpty(joint.getName());
JointTransform currentTransform = currentPose.orElseEmpty(joint.getName());
result.orElseEmpty(joint.getName()).translation().y = lowestTransform.translation().y;
OpenMatrix4f lowestMatrix = lowestTransform.toMatrix();
OpenMatrix4f currentMatrix = currentTransform.toMatrix();
OpenMatrix4f currentToLowest = OpenMatrix4f.mul(OpenMatrix4f.invert(currentMatrix, null), lowestMatrix, null);
for (Joint subJoint : joint.getSubJoints()) {
if (wholeEntry.isMasked(livingMotion, subJoint.getName())) {
OpenMatrix4f lowestLocalTransform = OpenMatrix4f.mul(joint.getLocalTransform(), lowestMatrix, null);
OpenMatrix4f currentLocalTransform = OpenMatrix4f.mul(joint.getLocalTransform(), currentMatrix, null);
OpenMatrix4f childTransform = OpenMatrix4f.mul(subJoint.getLocalTransform(), result.orElseEmpty(subJoint.getName()).toMatrix(), null);
OpenMatrix4f lowestFinal = OpenMatrix4f.mul(lowestLocalTransform, childTransform, null);
OpenMatrix4f currentFinal = OpenMatrix4f.mul(currentLocalTransform, childTransform, null);
Vec3f vec = new Vec3f((currentFinal.m30 - lowestFinal.m30) * 0.5F, currentFinal.m31 - lowestFinal.m31, currentFinal.m32 - lowestFinal.m32);
JointTransform jt = result.orElseEmpty(subJoint.getName());
jt.parent(JointTransform.translation(vec), OpenMatrix4f::mul);
jt.jointLocal(JointTransform.fromMatrixWithoutScale(currentToLowest), OpenMatrix4f::mul);
}
}
};
public static JointMask of(String jointName, BindModifier bindModifier) {
return new JointMask(jointName, bindModifier);
}
public static JointMask of(String jointName) {
return new JointMask(jointName, null);
}
private final String jointName;
private final BindModifier bindModifier;
private JointMask(String jointName, BindModifier bindModifier) {
this.jointName = jointName;
this.bindModifier = bindModifier;
}
public static class JointMaskSet {
final Map<String, BindModifier> masks = Maps.newHashMap();
public boolean contains(String name) {
return this.masks.containsKey(name);
}
public BindModifier getBindModifier(String jointName) {
return this.masks.get(jointName);
}
public static JointMaskSet of(JointMask... masks) {
JointMaskSet jointMaskSet = new JointMaskSet();
for (JointMask jointMask : masks) {
jointMaskSet.masks.put(jointMask.jointName, jointMask.bindModifier);
}
return jointMaskSet;
}
public static JointMaskSet of(Set<JointMask> jointMasks) {
JointMaskSet jointMaskSet = new JointMaskSet();
for (JointMask jointMask : jointMasks) {
jointMaskSet.masks.put(jointMask.jointName, jointMask.bindModifier);
}
return jointMaskSet;
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.client.property;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.rig.anim.LivingMotion;
import com.tiedup.remake.rig.anim.client.property.JointMask.JointMaskSet;
public class JointMaskEntry {
public static final JointMaskSet BIPED_UPPER_JOINTS_WITH_ROOT = JointMaskSet.of(
JointMask.of("Root", JointMask.KEEP_CHILD_LOCROT), JointMask.of("Torso"),
JointMask.of("Chest"), JointMask.of("Head"),
JointMask.of("Shoulder_R"), JointMask.of("Arm_R"),
JointMask.of("Hand_R"), JointMask.of("Elbow_R"),
JointMask.of("Tool_R"), JointMask.of("Shoulder_L"),
JointMask.of("Arm_L"), JointMask.of("Hand_L"),
JointMask.of("Elbow_L"), JointMask.of("Tool_L")
);
public static final JointMaskEntry BASIC_ATTACK_MASK = JointMaskEntry.builder().defaultMask(JointMaskEntry.BIPED_UPPER_JOINTS_WITH_ROOT).create();
private final Map<LivingMotion, JointMaskSet> masks = Maps.newHashMap();
private final JointMaskSet defaultMask;
public JointMaskEntry(JointMaskSet defaultMask, List<Pair<LivingMotion, JointMaskSet>> masks) {
this.defaultMask = defaultMask;
for (Pair<LivingMotion, JointMaskSet> mask : masks) {
this.masks.put(mask.getLeft(), mask.getRight());
}
}
public JointMaskSet getMask(LivingMotion livingmotion) {
return this.masks.getOrDefault(livingmotion, this.defaultMask);
}
public boolean isMasked(LivingMotion livingmotion, String jointName) {
return !this.masks.getOrDefault(livingmotion, this.defaultMask).contains(jointName);
}
public Set<Map.Entry<LivingMotion, JointMaskSet>> getEntries() {
return this.masks.entrySet();
}
public JointMaskSet getDefaultMask() {
return this.defaultMask;
}
public boolean isValid() {
return this.defaultMask != null;
}
public static JointMaskEntry.Builder builder() {
return new JointMaskEntry.Builder();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (Map.Entry<LivingMotion, JointMaskSet> entry : this.masks.entrySet()) {
builder.append(entry.getKey() + ": ");
builder.append(JointMaskReloadListener.getKey(entry.getValue()) + ", ");
}
ResourceLocation maskKey = JointMaskReloadListener.getKey(this.defaultMask);
if (maskKey == null) {
builder.append("default: custom");
} else {
builder.append("default: ");
builder.append(JointMaskReloadListener.getKey(this.defaultMask));
}
return builder.toString();
}
public static class Builder {
private final List<Pair<LivingMotion, JointMaskSet>> masks = Lists.newArrayList();
private JointMaskSet defaultMask = null;
public JointMaskEntry.Builder mask(LivingMotion motion, JointMaskSet masks) {
this.masks.add(Pair.of(motion, masks));
return this;
}
public JointMaskEntry.Builder defaultMask(JointMaskSet masks) {
this.defaultMask = masks;
return this;
}
public JointMaskEntry create() {
return new JointMaskEntry(this.defaultMask, this.masks);
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.client.property;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;
import com.tiedup.remake.rig.anim.client.property.JointMask.BindModifier;
import com.tiedup.remake.rig.anim.client.property.JointMask.JointMaskSet;
import yesman.epicfight.main.EpicFightMod;
public class JointMaskReloadListener extends SimpleJsonResourceReloadListener {
private static final BiMap<ResourceLocation, JointMaskSet> JOINT_MASKS = HashBiMap.create();
private static final Map<String, JointMask.BindModifier> BIND_MODIFIERS = Maps.newHashMap();
private static final ResourceLocation NONE_MASK = EpicFightMod.identifier("none");
static {
BIND_MODIFIERS.put("keep_child_locrot", JointMask.KEEP_CHILD_LOCROT);
}
public static JointMaskSet getJointMaskEntry(String type) {
ResourceLocation rl = ResourceLocation.parse(type);
return JOINT_MASKS.getOrDefault(rl, JOINT_MASKS.get(NONE_MASK));
}
public static JointMaskSet getNoneMask() {
return JOINT_MASKS.get(NONE_MASK);
}
public static ResourceLocation getKey(JointMaskSet type) {
return JOINT_MASKS.inverse().get(type);
}
public static Set<Map.Entry<ResourceLocation, JointMaskSet>> entries() {
return JOINT_MASKS.entrySet();
}
public JointMaskReloadListener() {
super((new GsonBuilder()).create(), "animmodels/joint_mask");
}
@Override
protected void apply(Map<ResourceLocation, JsonElement> objectIn, ResourceManager resourceManager, ProfilerFiller profileFiller) {
JOINT_MASKS.clear();
for (Map.Entry<ResourceLocation, JsonElement> entry : objectIn.entrySet()) {
Set<JointMask> masks = Sets.newHashSet();
JsonObject object = entry.getValue().getAsJsonObject();
JsonArray joints = object.getAsJsonArray("joints");
JsonObject bindModifiers = object.has("bind_modifiers") ? object.getAsJsonObject("bind_modifiers") : null;
for (JsonElement joint : joints) {
String jointName = joint.getAsString();
BindModifier modifier = null;
if (bindModifiers != null) {
String modifierName = bindModifiers.has(jointName) ? bindModifiers.get(jointName).getAsString() : null;
modifier = BIND_MODIFIERS.get(modifierName);
}
masks.add(JointMask.of(jointName, modifier));
}
String path = entry.getKey().toString();
ResourceLocation key = ResourceLocation.fromNamespaceAndPath(entry.getKey().getNamespace(), path.substring(path.lastIndexOf("/") + 1));
JOINT_MASKS.put(key, JointMaskSet.of(masks));
}
}
}

View File

@@ -0,0 +1,21 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.client.property;
import com.tiedup.remake.rig.anim.client.Layer;
public class LayerInfo {
public final JointMaskEntry jointMaskEntry;
public final Layer.Priority priority;
public final Layer.LayerType layerType;
public LayerInfo(JointMaskEntry jointMaskEntry, Layer.Priority priority, Layer.LayerType layerType) {
this.jointMaskEntry = jointMaskEntry;
this.priority = priority;
this.layerType = layerType;
}
}

View File

@@ -0,0 +1,244 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.property;
import java.util.function.Predicate;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class AnimationEvent<EVENT extends AnimationEvent.Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>, T extends AnimationEvent<EVENT, T>> {
protected final AnimationEvent.Side side;
protected final EVENT event;
protected AnimationParameters params;
private AnimationEvent(AnimationEvent.Side executionSide, EVENT event) {
this.side = executionSide;
this.event = event;
}
protected abstract boolean checkCondition(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed);
public void execute(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed) {
if (this.side.predicate.test(entitypatch.getOriginal()) && this.checkCondition(entitypatch, animation, prevElapsed, elapsed)) {
this.event.fire(entitypatch, animation, this.params);
}
}
public void executeWithNewParams(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed, AnimationParameters parameters) {
if (this.side.predicate.test(entitypatch.getOriginal()) && this.checkCondition(entitypatch, animation, prevElapsed, elapsed)) {
this.event.fire(entitypatch, animation, parameters);
}
}
public static class SimpleEvent<EVENT extends AnimationEvent.Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> extends AnimationEvent<EVENT, SimpleEvent<EVENT>> {
private SimpleEvent(AnimationEvent.Side executionSide, EVENT event) {
super(executionSide, event);
}
@Override
protected boolean checkCondition(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed) {
return true;
}
public static <E extends Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> SimpleEvent<E> create(E event, AnimationEvent.Side isRemote) {
return new SimpleEvent<> (isRemote, event);
}
}
public static class InTimeEvent<EVENT extends AnimationEvent.Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> extends AnimationEvent<EVENT, InTimeEvent<EVENT>> implements Comparable<InTimeEvent<EVENT>> {
final float time;
private InTimeEvent(float time, AnimationEvent.Side executionSide, EVENT event) {
super(executionSide, event);
this.time = time;
}
@Override
public boolean checkCondition(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed) {
return this.time >= prevElapsed && this.time < elapsed;
}
@Override
public int compareTo(InTimeEvent<EVENT> arg0) {
if(this.time == arg0.time) {
return 0;
} else {
return this.time > arg0.time ? 1 : -1;
}
}
public static <E extends Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> InTimeEvent<E> create(float time, E event, AnimationEvent.Side isRemote) {
return new InTimeEvent<> (time, isRemote, event);
}
}
public static class InPeriodEvent<EVENT extends AnimationEvent.Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> extends AnimationEvent<EVENT, InPeriodEvent<EVENT>> implements Comparable<InPeriodEvent<EVENT>> {
final float start;
final float end;
private InPeriodEvent(float start, float end, AnimationEvent.Side executionSide, EVENT event) {
super(executionSide, event);
this.start = start;
this.end = end;
}
@Override
public boolean checkCondition(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, float prevElapsed, float elapsed) {
return this.start <= elapsed && this.end > elapsed;
}
@Override
public int compareTo(InPeriodEvent<EVENT> arg0) {
if (this.start == arg0.start) {
return 0;
} else {
return this.start > arg0.start ? 1 : -1;
}
}
public static <E extends Event<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> InPeriodEvent<E> create(float start, float end, E event, AnimationEvent.Side isRemote) {
return new InPeriodEvent<> (start, end, isRemote, event);
}
}
public enum Side {
CLIENT((entity) -> entity.level().isClientSide),
SERVER((entity) -> !entity.level().isClientSide), BOTH((entity) -> true),
LOCAL_CLIENT((entity) -> {
if (entity instanceof Player player) {
return player.isLocalPlayer();
}
return false;
});
Predicate<Entity> predicate;
Side(Predicate<Entity> predicate) {
this.predicate = predicate;
}
}
public AnimationParameters<?, ?, ?, ?, ?, ?, ?, ?, ?, ?> getParameters() {
return this.params;
}
public <A> T params(A p1) {
this.params = AnimationParameters.of(p1);
return (T)this;
}
public <A, B> T params(A p1, B p2) {
this.params = AnimationParameters.of(p1, p2);
return (T)this;
}
public <A, B, C> T params(A p1, B p2, C p3) {
this.params = AnimationParameters.of(p1, p2, p3);
return (T)this;
}
public <A, B, C, D> T params(A p1, B p2, C p3, D p4) {
this.params = AnimationParameters.of(p1, p2, p3, p4);
return (T)this;
}
public <A, B, C, D, E> T params(A p1, B p2, C p3, D p4, E p5) {
this.params = AnimationParameters.of(p1, p2, p3, p4, p5);
return (T)this;
}
public <A, B, C, D, E, F> T params(A p1, B p2, C p3, D p4, E p5, F p6) {
this.params = AnimationParameters.of(p1, p2, p3, p4, p5, p6);
return (T)this;
}
public <A, B, C, D, E, F, G> T params(A p1, B p2, C p3, D p4, E p5, F p6, G p7) {
this.params = AnimationParameters.of(p1, p2, p3, p4, p5, p6, p7);
return (T)this;
}
public <A, B, C, D, E, F, G, H> T params(A p1, B p2, C p3, D p4, E p5, F p6, G p7, H p8) {
this.params = AnimationParameters.of(p1, p2, p3, p4, p5, p6, p7, p8);
return (T)this;
}
public <A, B, C, D, E, F, G, H, I> T params(A p1, B p2, C p3, D p4, E p5, F p6, G p7, H p8, I p9) {
this.params = AnimationParameters.of(p1, p2, p3, p4, p5, p6, p7, p8, p9);
return (T)this;
}
public <A, B, C, D, E, F, G, H, I, J> T params(A p1, B p2, C p3, D p4, E p5, F p6, G p7, H p8, I p9, J p10) {
this.params = AnimationParameters.of(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);
return (T)this;
}
@FunctionalInterface
public interface Event<A, B, C, D, E, F, G, H, I, J> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, G, H, I, J> params);
}
@FunctionalInterface
public interface E0 extends Event<Void, Void, Void, Void, Void, Void, Void, Void, Void, Void> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<Void, Void, Void, Void, Void, Void, Void, Void, Void, Void> params);
}
@FunctionalInterface
public interface E1<A> extends Event<A, Void, Void, Void, Void, Void, Void, Void, Void, Void> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, Void, Void, Void, Void, Void, Void, Void, Void, Void> params);
}
@FunctionalInterface
public interface E2<A, B> extends Event<A, B, Void, Void, Void, Void, Void, Void, Void, Void> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, Void, Void, Void, Void, Void, Void, Void, Void> params);
}
@FunctionalInterface
public interface E3<A, B, C> extends Event<A, B, C, Void, Void, Void, Void, Void, Void, Void> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, Void, Void, Void, Void, Void, Void, Void> params);
}
@FunctionalInterface
public interface E4<A, B, C, D> extends Event<A, B, C, D, Void, Void, Void, Void, Void, Void> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, Void, Void, Void, Void, Void, Void> params);
}
@FunctionalInterface
public interface E5<A, B, C, D, E> extends Event<A, B, C, D, E, Void, Void, Void, Void, Void> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, Void, Void, Void, Void, Void> params);
}
@FunctionalInterface
public interface E6<A, B, C, D, E, F> extends Event<A, B, C, D, E, F, Void, Void, Void, Void> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, Void, Void, Void, Void> params);
}
@FunctionalInterface
public interface E7<A, B, C, D, E, F, G> extends Event<A, B, C, D, E, F, G, Void, Void, Void> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, G, Void, Void, Void> params);
}
@FunctionalInterface
public interface E8<A, B, C, D, E, F, G, H> extends Event<A, B, C, D, E, F, G, H, Void, Void> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, G, H, Void, Void> params);
}
@FunctionalInterface
public interface E9<A, B, C, D, E, F, G, H, I> extends Event<A, B, C, D, E, F, G, H, I, Void> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, G, H, I, Void> params);
}
@FunctionalInterface
public interface E10<A, B, C, D, E, F, G, H, I, J> extends Event<A, B, C, D, E, F, G, H, I, J> {
void fire(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends StaticAnimation> animation, AnimationParameters<A, B, C, D, E, F, G, H, I, J> params);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.property;
public record AnimationParameters<A, B, C, D, E, F, G, H, I, J> (
A first,
B second,
C third,
D fourth,
E fifth,
F sixth,
G seventh,
H eighth,
I ninth,
J tenth
) {
public static <A> AnimationParameters<A, Void, Void, Void, Void, Void, Void, Void, Void, Void> of(A first) {
return new AnimationParameters<> (first, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null);
}
public static <A, B> AnimationParameters<A, B, Void, Void, Void, Void, Void, Void, Void, Void> of(A first, B second) {
return new AnimationParameters<> (first, second, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null);
}
public static <A, B, C> AnimationParameters<A, B, C, Void, Void, Void, Void, Void, Void, Void> of(A first, B second, C third) {
return new AnimationParameters<> (first, second, third, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null);
}
public static <A, B, C, D> AnimationParameters<A, B, C, D, Void, Void, Void, Void, Void, Void> of(A first, B second, C third, D fourth) {
return new AnimationParameters<> (first, second, third, fourth, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null);
}
public static <A, B, C, D, E> AnimationParameters<A, B, C, D, E, Void, Void, Void, Void, Void> of(A first, B second, C third, D fourth, E fifth) {
return new AnimationParameters<> (first, second, third, fourth, fifth, (Void)null, (Void)null, (Void)null, (Void)null, (Void)null);
}
public static <A, B, C, D, E, F> AnimationParameters<A, B, C, D, E, F, Void, Void, Void, Void> of(A first, B second, C third, D fourth, E fifth, F sixth) {
return new AnimationParameters<> (first, second, third, fourth, fifth, sixth, (Void)null, (Void)null, (Void)null, (Void)null);
}
public static <A, B, C, D, E, F, G> AnimationParameters<A, B, C, D, E, F, G, Void, Void, Void> of(A first, B second, C third, D fourth, E fifth, F sixth, G seventh) {
return new AnimationParameters<> (first, second, third, fourth, fifth, sixth, seventh, (Void)null, (Void)null, (Void)null);
}
public static <A, B, C, D, E, F, G, H> AnimationParameters<A, B, C, D, E, F, G, H, Void, Void> of(A first, B second, C third, D fourth, E fifth, F sixth, G seventh, H eighth) {
return new AnimationParameters<> (first, second, third, fourth, fifth, sixth, seventh, eighth, (Void)null, (Void)null);
}
public static <A, B, C, D, E, F, G, H, I> AnimationParameters<A, B, C, D, E, F, G, H, I, Void> of(A first, B second, C third, D fourth, E fifth, F sixth, G seventh, H eighth, I ninth) {
return new AnimationParameters<> (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, (Void)null);
}
public static <A, B, C, D, E, F, G, H, I, J> AnimationParameters<A, B, C, D, E, F, G, H, I, J> of(A first, B second, C third, D fourth, E fifth, F sixth, G seventh, H eighth, I ninth, J tenth) {
return new AnimationParameters<> (first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth);
}
public static <A, B, C, D, E, F, G, H, I, J, N> AnimationParameters<?, ?, ?, ?, ?, ?, ?, ?, ?, ?> addParameter(AnimationParameters<A, B, C, D, E, F, G, H, I, J> parameters, N newParam) {
if (parameters.first() == null) {
return new AnimationParameters<N, Void, Void, Void, Void, Void, Void, Void, Void, Void> (newParam, null, null, null, null, null, null, null, null, null);
} else if (parameters.second() == null) {
return new AnimationParameters<A, N, Void, Void, Void, Void, Void, Void, Void, Void> (parameters.first(), newParam, null, null, null, null, null, null, null, null);
} else if (parameters.third() == null) {
return new AnimationParameters<A, B, N, Void, Void, Void, Void, Void, Void, Void> (parameters.first(), parameters.second(), newParam, null, null, null, null, null, null, null);
} else if (parameters.fourth() == null) {
return new AnimationParameters<A, B, C, N, Void, Void, Void, Void, Void, Void> (parameters.first(), parameters.second(), parameters.third(), newParam, null, null, null, null, null, null);
} else if (parameters.fifth() == null) {
return new AnimationParameters<A, B, C, D, N, Void, Void, Void, Void, Void> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), newParam, null, null, null, null, null);
} else if (parameters.sixth() == null) {
return new AnimationParameters<A, B, C, D, E, N, Void, Void, Void, Void> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), parameters.fifth(), newParam, null, null, null, null);
} else if (parameters.seventh() == null) {
return new AnimationParameters<A, B, C, D, E, F, N, Void, Void, Void> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), parameters.fifth(), parameters.sixth(), newParam, null, null, null);
} else if (parameters.eighth() == null) {
return new AnimationParameters<A, B, C, D, E, F, G, N, Void, Void> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), parameters.fifth(), parameters.sixth(), parameters.seventh(), newParam, null, null);
} else if (parameters.ninth() == null) {
return new AnimationParameters<A, B, C, D, E, F, G, H, N, Void> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), parameters.fifth(), parameters.sixth(), parameters.seventh(), parameters.eighth(), newParam, null);
} else if (parameters.tenth() == null) {
return new AnimationParameters<A, B, C, D, E, F, G, H, I, N> (parameters.first(), parameters.second(), parameters.third(), parameters.fourth(), parameters.fifth(), parameters.sixth(), parameters.seventh(), parameters.eighth(), parameters.ninth(), newParam);
}
throw new UnsupportedOperationException("Parameters are full!");
}
}

View File

@@ -0,0 +1,381 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.property;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nullable;
import com.google.common.collect.Maps;
import com.google.gson.JsonElement;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.tags.TagKey;
import net.minecraft.world.damagesource.DamageType;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.registries.RegistryObject;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.anim.LivingMotion;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.TransformSheet;
import com.tiedup.remake.rig.anim.property.AnimationEvent.SimpleEvent;
import com.tiedup.remake.rig.anim.property.MoveCoordFunctions.MoveCoordGetter;
import com.tiedup.remake.rig.anim.property.MoveCoordFunctions.MoveCoordSetter;
import com.tiedup.remake.rig.anim.types.ActionAnimation;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.anim.types.LinkAnimation;
import com.tiedup.remake.rig.anim.types.StaticAnimation;
import yesman.epicfight.api.physics.ik.InverseKinematicsSimulator.BakedInverseKinematicsDefinition;
import yesman.epicfight.api.physics.ik.InverseKinematicsSimulator.InverseKinematicsDefinition;
import com.tiedup.remake.rig.util.HitEntityList.Priority;
import com.tiedup.remake.rig.util.TimePairList;
import com.tiedup.remake.rig.math.ValueModifier;
import yesman.epicfight.main.EpicFightMod;
import yesman.epicfight.particle.HitParticleType;
import yesman.epicfight.skill.BasicAttack;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
import com.tiedup.remake.rig.patch.item.CapabilityItem;
import yesman.epicfight.world.damagesource.ExtraDamageInstance;
import yesman.epicfight.world.damagesource.StunType;
public abstract class AnimationProperty<T> {
private static final Map<String, AnimationProperty<?>> SERIALIZABLE_ANIMATION_PROPERTY_KEYS = Maps.newHashMap();
@SuppressWarnings("unchecked")
public static <T> AnimationProperty<T> getSerializableProperty(String name) {
if (!SERIALIZABLE_ANIMATION_PROPERTY_KEYS.containsKey(name)) {
throw new IllegalStateException("No property key named " + name);
}
return (AnimationProperty<T>) SERIALIZABLE_ANIMATION_PROPERTY_KEYS.get(name);
}
private final Codec<T> codecs;
private final String name;
public AnimationProperty(String name, @Nullable Codec<T> codecs) {
this.codecs = codecs;
this.name = name;
if (name != null) {
if (SERIALIZABLE_ANIMATION_PROPERTY_KEYS.containsKey(name)) {
throw new IllegalStateException("Animation property key " + name + " is already registered.");
}
SERIALIZABLE_ANIMATION_PROPERTY_KEYS.put(name, this);
}
}
public AnimationProperty(String name) {
this(name, null);
}
public T parseFrom(JsonElement e) {
return this.codecs.parse(JsonOps.INSTANCE, e).resultOrPartial((errm) -> EpicFightMod.LOGGER.warn("Failed to parse property " + this.name + " because of " + errm)).orElseThrow();
}
public Codec<T> getCodecs() {
return this.codecs;
}
public static class StaticAnimationProperty<T> extends AnimationProperty<T> {
public StaticAnimationProperty(String rl, @Nullable Codec<T> codecs) {
super(rl, codecs);
}
public StaticAnimationProperty() {
this(null, null);
}
/**
* Events that are fired in every tick.
*/
public static final StaticAnimationProperty<List<AnimationEvent<?, ?>>> TICK_EVENTS = new StaticAnimationProperty<List<AnimationEvent<?, ?>>> ();
/**
* Events that are fired when the animation starts.
*/
public static final StaticAnimationProperty<List<SimpleEvent<?>>> ON_BEGIN_EVENTS = new StaticAnimationProperty<List<SimpleEvent<?>>> ();
/**
* Events that are fired when the animation ends.
*/
public static final StaticAnimationProperty<List<SimpleEvent<?>>> ON_END_EVENTS = new StaticAnimationProperty<List<SimpleEvent<?>>> ();
/**
* An event triggered when entity changes an item in hand.
*/
public static final StaticAnimationProperty<SimpleEvent<AnimationEvent.E2<CapabilityItem, CapabilityItem>>> ON_ITEM_CHANGE_EVENT = new StaticAnimationProperty<SimpleEvent<AnimationEvent.E2<CapabilityItem, CapabilityItem>>> ();
/**
* You can modify the playback speed of the animation.
*/
public static final StaticAnimationProperty<PlaybackSpeedModifier> PLAY_SPEED_MODIFIER = new StaticAnimationProperty<PlaybackSpeedModifier> ();
/**
* You can modify the playback speed of the animation.
*/
public static final StaticAnimationProperty<PlaybackTimeModifier> ELAPSED_TIME_MODIFIER = new StaticAnimationProperty<PlaybackTimeModifier> ();
/**
* This property will be called both in client and server when modifying the pose
*/
public static final StaticAnimationProperty<PoseModifier> POSE_MODIFIER = new StaticAnimationProperty<PoseModifier> ();
/**
* Fix the head rotation to the player's body rotation
*/
public static final StaticAnimationProperty<Boolean> FIXED_HEAD_ROTATION = new StaticAnimationProperty<Boolean> ();
/**
* Defines static animations as link animation when the animation is followed by a specific animation
*/
public static final StaticAnimationProperty<Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>>> TRANSITION_ANIMATIONS_FROM = new StaticAnimationProperty<Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>>> ();
/**
* Defines static animations as link animation when the animation is following a specific animation
*/
public static final StaticAnimationProperty<Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>>> TRANSITION_ANIMATIONS_TO = new StaticAnimationProperty<Map<ResourceLocation, AnimationAccessor<? extends StaticAnimation>>> ();
/**
* Disable physics while playing animation
*/
public static final StaticAnimationProperty<Boolean> NO_PHYSICS = new StaticAnimationProperty<Boolean> ("no_physics", Codec.BOOL);
/**
* Inverse kinematics information
*/
public static final StaticAnimationProperty<List<InverseKinematicsDefinition>> IK_DEFINITION = new StaticAnimationProperty<List<InverseKinematicsDefinition>> ();
/**
* This property automatically baked when animation is loaded
*/
public static final StaticAnimationProperty<List<BakedInverseKinematicsDefinition>> BAKED_IK_DEFINITION = new StaticAnimationProperty<List<BakedInverseKinematicsDefinition>> ();
/**
* This property reset the entity's living motion
*/
public static final StaticAnimationProperty<LivingMotion> RESET_LIVING_MOTION = new StaticAnimationProperty<LivingMotion> ();
}
public static class ActionAnimationProperty<T> extends StaticAnimationProperty<T> {
public ActionAnimationProperty(String rl, @Nullable Codec<T> codecs) {
super(rl, codecs);
}
public ActionAnimationProperty() {
this(null, null);
}
/**
* This property will set the entity's delta movement to (0, 0, 0) at the beginning of an animation if true.
*/
public static final ActionAnimationProperty<Boolean> STOP_MOVEMENT = new ActionAnimationProperty<Boolean> ("stop_movements", Codec.BOOL);
/**
* This property will set the entity's delta movement to (0, 0, 0) at the beginning of an animation if true.
*/
public static final ActionAnimationProperty<Boolean> REMOVE_DELTA_MOVEMENT = new ActionAnimationProperty<Boolean> ("revmoe_delta_move", Codec.BOOL);
/**
* This property will move entity's coord also as y axis if true.
* Don't recommend using this property because it's old system. Use the coord joint instead.
*/
public static final ActionAnimationProperty<Boolean> MOVE_VERTICAL = new ActionAnimationProperty<Boolean> ("move_vertically", Codec.BOOL);
/**
* This property determines the time of entity not affected by gravity.
*/
public static final ActionAnimationProperty<TimePairList> NO_GRAVITY_TIME = new ActionAnimationProperty<TimePairList> ();
/**
* Coord of action animation
*/
public static final ActionAnimationProperty<TransformSheet> COORD = new ActionAnimationProperty<TransformSheet> ();
/**
* This property determines whether to move the entity in link animation or not.
*/
public static final ActionAnimationProperty<Boolean> MOVE_ON_LINK = new ActionAnimationProperty<Boolean> ("move_during_link", Codec.BOOL);
/**
* You can specify the coord movement time in action animation. Must be registered in order of time.
*/
public static final ActionAnimationProperty<TimePairList> MOVE_TIME = new ActionAnimationProperty<TimePairList> ();
/**
* Set the dynamic coordinates of {@link ActionAnimation}. Called before creation of {@link LinkAnimation}.
*/
public static final ActionAnimationProperty<MoveCoordSetter> COORD_SET_BEGIN = new ActionAnimationProperty<MoveCoordSetter> ();
/**
* Set the dynamic coordinates of {@link ActionAnimation}.
*/
public static final ActionAnimationProperty<MoveCoordSetter> COORD_SET_TICK = new ActionAnimationProperty<MoveCoordSetter> ();
/**
* Set the coordinates of action animation.
*/
public static final ActionAnimationProperty<MoveCoordGetter> COORD_GET = new ActionAnimationProperty<MoveCoordGetter> ();
/**
* This property determines if the speed effect will increase the move distance.
*/
public static final ActionAnimationProperty<Boolean> AFFECT_SPEED = new ActionAnimationProperty<Boolean> ("move_speed_based_distance", Codec.BOOL);
/**
* This property determines if the movement can be canceled by {@link LivingEntityPatch#shouldBlockMoving()}.
*/
public static final ActionAnimationProperty<Boolean> CANCELABLE_MOVE = new ActionAnimationProperty<Boolean> ("cancellable_movement", Codec.BOOL);
/**
* Death animations won't be played if this value is true
*/
public static final ActionAnimationProperty<Boolean> IS_DEATH_ANIMATION = new ActionAnimationProperty<Boolean> ("is_death", Codec.BOOL);
/**
* This property determines the update time of {@link ActionAnimationProperty#COORD_SET_TICK}. If the current time out of the bound it uses {@link MoveCoordFunctions#RAW_COORD and MoveCoordFunctions#DIFF_FROM_PREV_COORD}}
*/
public static final ActionAnimationProperty<TimePairList> COORD_UPDATE_TIME = new ActionAnimationProperty<TimePairList> ();
/**
* This property determines if it reset the player basic attack combo counter or not {@link BasicAttack}
*/
public static final ActionAnimationProperty<Boolean> RESET_PLAYER_COMBO_COUNTER = new ActionAnimationProperty<Boolean> ("reset_combo_attack_counter", Codec.BOOL);
/**
* Provide destination of action animation {@link MoveCoordFunctions}
*/
public static final ActionAnimationProperty<DestLocationProvider> DEST_LOCATION_PROVIDER = new ActionAnimationProperty<DestLocationProvider> ();
/**
* Provide y rotation of entity {@link MoveCoordFunctions}
*/
public static final ActionAnimationProperty<YRotProvider> ENTITY_YROT_PROVIDER = new ActionAnimationProperty<YRotProvider> ();
/**
* Provide y rotation of tracing coord {@link MoveCoordFunctions}
*/
public static final ActionAnimationProperty<YRotProvider> DEST_COORD_YROT_PROVIDER = new ActionAnimationProperty<YRotProvider> ();
/**
* Decides the index of start key frame for coord transform, See also with {@link MoveCoordFunctions#TRACE_ORIGIN_AS_DESTINATION}
*/
public static final ActionAnimationProperty<Integer> COORD_START_KEYFRAME_INDEX = new ActionAnimationProperty<Integer> ();
/**
* Decides the index of destination key frame for coord transform, See also with {@link MoveCoordFunctions#TRACE_ORIGIN_AS_DESTINATION}
*/
public static final ActionAnimationProperty<Integer> COORD_DEST_KEYFRAME_INDEX = new ActionAnimationProperty<Integer> ();
/**
* Determines if an entity should look where a camera is looking at the beginning of an animation (player only)
*/
public static final ActionAnimationProperty<Boolean> SYNC_CAMERA = new ActionAnimationProperty<Boolean> ("sync_camera", Codec.BOOL);
}
public static class AttackAnimationProperty<T> extends ActionAnimationProperty<T> {
public AttackAnimationProperty(String rl, @Nullable Codec<T> codecs) {
super(rl, codecs);
}
public AttackAnimationProperty() {
this(null, null);
}
/**
* This property determines if the animation has a fixed amount of move distance not depending on the distance between attacker and target entity
*/
public static final AttackAnimationProperty<Boolean> FIXED_MOVE_DISTANCE = new AttackAnimationProperty<Boolean> ("fixed_movement_distance", Codec.BOOL);
/**
* This property determines how much the playback speed will be affected by entity's attack speed.
*/
public static final AttackAnimationProperty<Float> ATTACK_SPEED_FACTOR = new AttackAnimationProperty<Float> ("attack_speed_factor", Codec.FLOAT);
/**
* This property determines the basis of the speed factor. Default basis is the total animation time.
*/
public static final AttackAnimationProperty<Float> BASIS_ATTACK_SPEED = new AttackAnimationProperty<Float> ("basis_attack_speed", Codec.FLOAT);
/**
* This property adds interpolated colliders when detecting colliding entities by using @MultiCollider.
*/
public static final AttackAnimationProperty<Integer> EXTRA_COLLIDERS = new AttackAnimationProperty<Integer> ("extra_colliders", Codec.INT);
/**
* This property determines a minimal distance between attacker and target.
*/
public static final AttackAnimationProperty<Float> REACH = new AttackAnimationProperty<Float> ("reach", Codec.FLOAT);
}
public static class AttackPhaseProperty<T> {
public AttackPhaseProperty(String rl, @Nullable Codec<? extends T> codecs) {
//super(rl, codecs);
}
public AttackPhaseProperty() {
//this(null, null);
}
public static final AttackPhaseProperty<ValueModifier> MAX_STRIKES_MODIFIER = new AttackPhaseProperty<ValueModifier> ("max_strikes", ValueModifier.CODEC);
public static final AttackPhaseProperty<ValueModifier> DAMAGE_MODIFIER = new AttackPhaseProperty<ValueModifier> ("damage", ValueModifier.CODEC);
public static final AttackPhaseProperty<ValueModifier> ARMOR_NEGATION_MODIFIER = new AttackPhaseProperty<ValueModifier> ("armor_negation", ValueModifier.CODEC);
public static final AttackPhaseProperty<ValueModifier> IMPACT_MODIFIER = new AttackPhaseProperty<ValueModifier> ("impact", ValueModifier.CODEC);
public static final AttackPhaseProperty<Set<ExtraDamageInstance>> EXTRA_DAMAGE = new AttackPhaseProperty<Set<ExtraDamageInstance>> ();
public static final AttackPhaseProperty<StunType> STUN_TYPE = new AttackPhaseProperty<StunType> ();
public static final AttackPhaseProperty<SoundEvent> SWING_SOUND = new AttackPhaseProperty<SoundEvent> ();
public static final AttackPhaseProperty<SoundEvent> HIT_SOUND = new AttackPhaseProperty<SoundEvent> ();
public static final AttackPhaseProperty<RegistryObject<HitParticleType>> PARTICLE = new AttackPhaseProperty<RegistryObject<HitParticleType>> ();
public static final AttackPhaseProperty<Priority> HIT_PRIORITY = new AttackPhaseProperty<Priority> ();
public static final AttackPhaseProperty<Set<TagKey<DamageType>>> SOURCE_TAG = new AttackPhaseProperty<Set<TagKey<DamageType>>> ();
public static final AttackPhaseProperty<Function<LivingEntityPatch<?>, Vec3>> SOURCE_LOCATION_PROVIDER = new AttackPhaseProperty<Function<LivingEntityPatch<?>, Vec3>> ();
}
@FunctionalInterface
public interface Registerer<T> {
void register(Map<AnimationProperty<T>, Object> properties, AnimationProperty<T> key, T object);
}
/******************************
* Static Animation Properties
******************************/
/**
* elapsedTime contains partial tick
*/
@FunctionalInterface
public interface PoseModifier {
void modify(DynamicAnimation self, Pose pose, LivingEntityPatch<?> entitypatch, float elapsedTime, float partialTick);
}
@FunctionalInterface
public interface PlaybackSpeedModifier {
float modify(DynamicAnimation self, LivingEntityPatch<?> entitypatch, float speed, float prevElapsedTime, float elapsedTime);
}
@FunctionalInterface
public interface PlaybackTimeModifier {
Pair<Float, Float> modify(DynamicAnimation self, LivingEntityPatch<?> entitypatch, float speed, float prevElapsedTime, float elapsedTime);
}
@FunctionalInterface
public interface DestLocationProvider {
Vec3 get(DynamicAnimation self, LivingEntityPatch<?> entitypatch);
}
@FunctionalInterface
public interface YRotProvider {
float get(DynamicAnimation self, LivingEntityPatch<?> entitypatch);
}
}

View File

@@ -0,0 +1,475 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.property;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import com.tiedup.remake.rig.anim.AnimationPlayer;
import com.tiedup.remake.rig.armature.JointTransform;
import com.tiedup.remake.rig.anim.Keyframe;
import com.tiedup.remake.rig.anim.SynchedAnimationVariableKeys;
import com.tiedup.remake.rig.anim.TransformSheet;
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
import com.tiedup.remake.rig.anim.property.AnimationProperty.AttackAnimationProperty;
import com.tiedup.remake.rig.anim.property.AnimationProperty.DestLocationProvider;
import com.tiedup.remake.rig.anim.property.AnimationProperty.YRotProvider;
import com.tiedup.remake.rig.anim.types.ActionAnimation;
import com.tiedup.remake.rig.anim.types.AttackAnimation;
import com.tiedup.remake.rig.anim.types.AttackAnimation.Phase;
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
import com.tiedup.remake.rig.anim.types.EntityState;
import com.tiedup.remake.rig.anim.types.grappling.GrapplingAttackAnimation;
import com.tiedup.remake.rig.math.MathUtils;
import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.math.Vec3f;
import com.tiedup.remake.rig.math.Vec4f;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
import com.tiedup.remake.rig.patch.MobPatch;
public class MoveCoordFunctions {
/**
* Defines a function that how to interpret given coordinate and return the movement vector from entity's current position
*/
@FunctionalInterface
public interface MoveCoordGetter {
Vec3f get(DynamicAnimation animation, LivingEntityPatch<?> entitypatch, TransformSheet transformSheet, float prevElapsedTime, float elapsedTime);
}
/**
* Defines a function that how to build the coordinate of {@link ActionAnimation}
*/
@FunctionalInterface
public interface MoveCoordSetter {
void set(DynamicAnimation animation, LivingEntityPatch<?> entitypatch, TransformSheet transformSheet);
}
/**
* MODEL_COORD
* - Calculates the coordinate gap between previous and current elapsed time
* - the coordinate doesn't reflect the entity's rotation
*/
public static final MoveCoordGetter MODEL_COORD = (animation, entitypatch, coord, prevElapsedTime, elapsedTime) -> {
LivingEntity livingentity = entitypatch.getOriginal();
JointTransform oJt = coord.getInterpolatedTransform(prevElapsedTime);
JointTransform jt = coord.getInterpolatedTransform(elapsedTime);
Vec4f prevpos = new Vec4f(oJt.translation());
Vec4f currentpos = new Vec4f(jt.translation());
OpenMatrix4f rotationTransform = entitypatch.getModelMatrix(1.0F).removeTranslation().removeScale();
OpenMatrix4f localTransform = entitypatch.getArmature().searchJointByName("Root").getLocalTransform().removeTranslation();
rotationTransform.mulBack(localTransform);
currentpos.transform(rotationTransform);
prevpos.transform(rotationTransform);
boolean hasNoGravity = entitypatch.getOriginal().isNoGravity();
boolean moveVertical = animation.getProperty(ActionAnimationProperty.MOVE_VERTICAL).orElse(false) || animation.getProperty(ActionAnimationProperty.COORD).isPresent();
float dx = prevpos.x - currentpos.x;
float dy = (moveVertical || hasNoGravity) ? currentpos.y - prevpos.y : 0.0F;
float dz = prevpos.z - currentpos.z;
dx = Math.abs(dx) > 0.0001F ? dx : 0.0F;
dz = Math.abs(dz) > 0.0001F ? dz : 0.0F;
BlockPos blockpos = new BlockPos.MutableBlockPos(livingentity.getX(), livingentity.getBoundingBox().minY - 1.0D, livingentity.getZ());
BlockState blockState = livingentity.level().getBlockState(blockpos);
AttributeInstance movementSpeed = livingentity.getAttribute(Attributes.MOVEMENT_SPEED);
boolean soulboost = blockState.is(BlockTags.SOUL_SPEED_BLOCKS) && EnchantmentHelper.getEnchantmentLevel(Enchantments.SOUL_SPEED, livingentity) > 0;
float speedFactor = (float)(soulboost ? 1.0D : livingentity.level().getBlockState(blockpos).getBlock().getSpeedFactor());
float moveMultiplier = (float)(animation.getProperty(ActionAnimationProperty.AFFECT_SPEED).orElse(false) ? (movementSpeed.getValue() / movementSpeed.getBaseValue()) : 1.0F);
return new Vec3f(dx * moveMultiplier * speedFactor, dy, dz * moveMultiplier * speedFactor);
};
/**
* WORLD_COORD
* - Calculates the coordinate of current elapsed time
* - the coordinate is the world position
*/
public static final MoveCoordGetter WORLD_COORD = (animation, entitypatch, coord, prevElapsedTime, elapsedTime) -> {
JointTransform jt = coord.getInterpolatedTransform(elapsedTime);
Vec3 entityPos = entitypatch.getOriginal().position();
return jt.translation().copy().sub(Vec3f.fromDoubleVector(entityPos));
};
/**
* ATTACHED
* Calculates the relative position of a grappling target entity.
* - especially used by {@link GrapplingAttackAnimation}
* - read by {@link MoveCoordFunctions#RAW_COORD}
*/
public static final MoveCoordGetter ATTACHED = (animation, entitypatch, coord, prevElapsedTime, elapsedTime) -> {
LivingEntity target = entitypatch.getGrapplingTarget();
if (target == null) {
return MODEL_COORD.get(animation, entitypatch, coord, prevElapsedTime, elapsedTime);
}
TransformSheet rootCoord = animation.getCoord();
LivingEntity livingentity = entitypatch.getOriginal();
Vec3f model = rootCoord.getInterpolatedTransform(elapsedTime).translation();
Vec3f world = OpenMatrix4f.transform3v(OpenMatrix4f.createRotatorDeg(-target.getYRot(), Vec3f.Y_AXIS), model, null);
Vec3f dst = Vec3f.fromDoubleVector(target.position()).add(world);
entitypatch.setYRot(Mth.wrapDegrees(target.getYRot() + 180.0F));
return dst.sub(Vec3f.fromDoubleVector(livingentity.position()));
};
/******************************************************
* Action animation properties
******************************************************/
/**
* No destination
*/
public static final DestLocationProvider NO_DEST = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
return null;
};
/**
* Location of the current attack target
*/
public static final DestLocationProvider ATTACK_TARGET_LOCATION = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
return entitypatch.getTarget() == null ? null : entitypatch.getTarget().position();
};
/**
* Location set by Animation Variable
*/
public static final DestLocationProvider SYNCHED_DEST_VARIABLE = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
return entitypatch.getAnimator().getVariables().getOrDefault(SynchedAnimationVariableKeys.DESTINATION.get(), self.getRealAnimation());
};
/**
* Location of current attack target that is provided by animation variable
*/
public static final DestLocationProvider SYNCHED_TARGET_ENTITY_LOCATION_VARIABLE = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
Optional<Integer> targetEntityId = entitypatch.getAnimator().getVariables().get(SynchedAnimationVariableKeys.TARGET_ENTITY.get(), self.getRealAnimation());
if (targetEntityId.isPresent()) {
Entity entity = entitypatch.getOriginal().level().getEntity(targetEntityId.get());
if (entity != null) {
return entity.position();
}
}
return entitypatch.getOriginal().position();
};
/**
* Looking direction from an action beginning location to a destination location
*/
public static final YRotProvider LOOK_DEST = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
Vec3 destLocation = self.getRealAnimation().get().getProperty(ActionAnimationProperty.DEST_LOCATION_PROVIDER).orElse(NO_DEST).get(self, entitypatch);
if (destLocation != null) {
Vec3 startInWorld = entitypatch.getAnimator().getVariables().getOrDefault(ActionAnimation.BEGINNING_LOCATION, self.getRealAnimation());
if (startInWorld == null) {
startInWorld = entitypatch.getOriginal().position();
}
Vec3 toDestWorld = destLocation.subtract(startInWorld);
float yRot = (float)Mth.wrapDegrees(MathUtils.getYRotOfVector(toDestWorld));
float entityYRot = MathUtils.rotlerp(entitypatch.getYRot(), yRot, entitypatch.getYRotLimit());
return entityYRot;
} else {
return entitypatch.getYRot();
}
};
/**
* Rotate an entity toward target for attack animations
*/
public static final YRotProvider MOB_ATTACK_TARGET_LOOK = (DynamicAnimation self, LivingEntityPatch<?> entitypatch) -> {
if (!entitypatch.isLogicalClient() && entitypatch instanceof MobPatch<?> mobpatch) {
AnimationPlayer player = entitypatch.getAnimator().getPlayerFor(self.getAccessor());
float elapsedTime = player.getElapsedTime();
EntityState state = self.getState(entitypatch, elapsedTime);
if (state.getLevel() == 1 && !state.turningLocked()) {
mobpatch.getOriginal().getNavigation().stop();
entitypatch.getOriginal().attackAnim = 2;
LivingEntity target = entitypatch.getTarget();
if (target != null) {
float currentYRot = Mth.wrapDegrees(entitypatch.getOriginal().getYRot());
float clampedYRot = entitypatch.getYRotDeltaTo(target);
return currentYRot + clampedYRot;
}
}
}
return entitypatch.getYRot();
};
/******************************************************
* MoveCoordSetters
* Consider that getAnimationPlayer(self) returns null at the beginning.
******************************************************/
/**
* Sets a raw animation coordinate as action animation's coord
* - read by {@link MoveCoordFunctions#MODEL_COORD}
*/
public static final MoveCoordSetter RAW_COORD = (self, entitypatch, transformSheet) -> {
transformSheet.readFrom(self.getCoord().copyAll());
};
/**
* Sets a raw animation coordinate multiplied by entity's pitch as action animation's coord
* - read by {@link MoveCoordFunctions#MODEL_COORD}
*/
public static final MoveCoordSetter RAW_COORD_WITH_X_ROT = (self, entitypatch, transformSheet) -> {
TransformSheet sheet = self.getCoord().copyAll();
float xRot = entitypatch.getOriginal().getXRot();
for (Keyframe kf : sheet.getKeyframes()) {
kf.transform().translation().rotate(-xRot, Vec3f.X_AXIS);
}
transformSheet.readFrom(sheet);
};
/**
* Trace the origin point(0, 0, 0) in blender coord system as the destination
* - specify the {@link ActionAnimationProperty#DEST_LOCATION_PROVIDER} or it will act as {@link MoveCoordFunctions#RAW_COORD}.
* - the first keyframe's location is where the entity is in world
* - you can specify target frame distance by {@link ActionAnimationProperty#COORD_START_KEYFRAME_INDEX}, {@link ActionAnimationProperty#COORD_DEST_KEYFRAME_INDEX}
* - the coord after destination frame will not be scaled or rotated by distance gap between start location and end location in world coord
* - entity's x rotation is not affected by this coord function
* - entity's y rotation is the direction toward a destination, or you can give specific rotation value by {@link ActionAnimation#ENTITY_Y_ROT AnimationProperty}
* - no movements in link animation
* - read by {@link MoveCoordFunctions#WORLD_COORD}
*/
public static final MoveCoordSetter TRACE_ORIGIN_AS_DESTINATION = (self, entitypatch, transformSheet) -> {
if (self.isLinkAnimation()) {
transformSheet.readFrom(TransformSheet.EMPTY_SHEET_PROVIDER.apply(entitypatch.getOriginal().position()));
return;
}
Keyframe[] coordKeyframes = self.getCoord().getKeyframes();
int startFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_START_KEYFRAME_INDEX).orElse(0);
int destFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_DEST_KEYFRAME_INDEX).orElse(coordKeyframes.length - 1);
Vec3 destInWorld = self.getRealAnimation().get().getProperty(ActionAnimationProperty.DEST_LOCATION_PROVIDER).orElse(NO_DEST).get(self, entitypatch);
if (destInWorld == null) {
Vec3f beginningPosition = coordKeyframes[0].transform().translation().copy().multiply(1.0F, 1.0F, -1.0F);
beginningPosition.rotate(-entitypatch.getYRot(), Vec3f.Y_AXIS);
destInWorld = entitypatch.getOriginal().position().add(-beginningPosition.x, -beginningPosition.y, -beginningPosition.z);
}
Vec3 startInWorld = entitypatch.getAnimator().getVariables().getOrDefault(ActionAnimation.BEGINNING_LOCATION, self.getRealAnimation());
if (startInWorld == null) {
startInWorld = entitypatch.getOriginal().position();
}
Vec3 toTargetInWorld = destInWorld.subtract(startInWorld);
float yRot = (float)Mth.wrapDegrees(MathUtils.getYRotOfVector(toTargetInWorld));
Optional<YRotProvider> destYRotProvider = self.getRealAnimation().get().getProperty(ActionAnimationProperty.DEST_COORD_YROT_PROVIDER);
float destYRot = destYRotProvider.isEmpty() ? yRot : destYRotProvider.get().get(self, entitypatch);
TransformSheet result = self.getCoord().transformToWorldCoordOriginAsDest(entitypatch, startInWorld, destInWorld, yRot, destYRot, startFrame, destFrame);
transformSheet.readFrom(result);
};
/**
* Trace the target entity's position (use it with MODEL_COORD)
* - the location of the last keyfram is basis to limit maximum distance
* - rotation is where the entity is looking
*/
public static final MoveCoordSetter TRACE_TARGET_DISTANCE = (self, entitypatch, transformSheet) -> {
Vec3 destLocation = self.getRealAnimation().get().getProperty(ActionAnimationProperty.DEST_LOCATION_PROVIDER).orElse(NO_DEST).get(self, entitypatch);
if (destLocation != null) {
TransformSheet transform = self.getCoord().copyAll();
Keyframe[] coord = transform.getKeyframes();
Keyframe[] realAnimationCoord = self.getRealAnimation().get().getCoord().getKeyframes();
Vec3 startInWorld = entitypatch.getAnimator().getVariables().getOrDefault(ActionAnimation.BEGINNING_LOCATION, self.getRealAnimation());
if (startInWorld == null) {
startInWorld = entitypatch.getOriginal().position();
}
int startFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_START_KEYFRAME_INDEX).orElse(0);
int realAnimationEndFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_DEST_KEYFRAME_INDEX).orElse(self.getRealAnimation().get().getCoord().getKeyframes().length - 1);
Vec3 toDestWorld = destLocation.subtract(startInWorld);
Vec3f toDestAnim = realAnimationCoord[realAnimationEndFrame].transform().translation();
LivingEntity attackTarget = entitypatch.getTarget();
// Calculate Entity-Entity collide radius
float entityRadius = 0.0F;
if (attackTarget != null) {
float reach = 0.0F;
if (self.getRealAnimation().get() instanceof AttackAnimation attackAnimation) {
Optional<Float> reachOpt = attackAnimation.getProperty(AttackAnimationProperty.REACH);
if (reachOpt.isPresent()) {
reach = reachOpt.get();
} else {
AnimationPlayer player = entitypatch.getAnimator().getPlayerFor(self.getAccessor());
if (player != null) {
Phase phase = attackAnimation.getPhaseByTime(player.getElapsedTime());
reach = entitypatch.getReach(phase.hand);
}
}
}
entityRadius = (attackTarget.getBbWidth() + entitypatch.getOriginal().getBbWidth()) * 0.7F + reach;
}
float worldLength = Math.max((float)toDestWorld.length() - entityRadius, 0.0F);
float animLength = toDestAnim.length();
float dot = entitypatch.getAnimator().getVariables().getOrDefault(ActionAnimation.INITIAL_LOOK_VEC_DOT, self.getRealAnimation());
float lookLength = Mth.lerp(dot, animLength, worldLength);
float scale = Math.min(lookLength / animLength, 1.0F);
if (self.isLinkAnimation()) {
scale *= coord[coord.length - 1].transform().translation().length() / animLength;
}
int endFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_DEST_KEYFRAME_INDEX).orElse(coord.length - 1);
for (int i = startFrame; i <= endFrame; i++) {
Vec3f translation = coord[i].transform().translation();
translation.x *= scale;
if (translation.z < 0.0F) {
translation.z *= scale;
}
}
transformSheet.readFrom(transform);
} else {
transformSheet.readFrom(self.getCoord().copyAll());
}
};
/**
* Trace the target entity's position (use it MODEL_COORD)
* - the location of the last keyframe is a basis to limit maximum distance
* - rotation is the direction toward a target entity
*/
public static final MoveCoordSetter TRACE_TARGET_LOCATION_ROTATION = (self, entitypatch, transformSheet) -> {
Vec3 destLocation = self.getRealAnimation().get().getProperty(ActionAnimationProperty.DEST_LOCATION_PROVIDER).orElse(NO_DEST).get(self, entitypatch);
if (destLocation != null) {
TransformSheet transform = self.getCoord().copyAll();
Keyframe[] coord = transform.getKeyframes();
Keyframe[] realAnimationCoord = self.getRealAnimation().get().getCoord().getKeyframes();
Vec3 startInWorld = entitypatch.getAnimator().getVariables().getOrDefault(ActionAnimation.BEGINNING_LOCATION, self.getRealAnimation());
if (startInWorld == null) {
startInWorld = entitypatch.getOriginal().position();
}
int startFrame = self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_START_KEYFRAME_INDEX).orElse(0);
int endFrame = self.isLinkAnimation() ? coord.length - 1 : self.getRealAnimation().get().getProperty(ActionAnimationProperty.COORD_DEST_KEYFRAME_INDEX).orElse(coord.length - 1);
Vec3 toDestWorld = destLocation.subtract(startInWorld);
Vec3f toDestAnim = realAnimationCoord[endFrame].transform().translation();
LivingEntity attackTarget = entitypatch.getTarget();
// Calculate Entity-Entity collide radius
float entityRadius = 0.0F;
if (attackTarget != null) {
float reach = 0.0F;
if (self.getRealAnimation().get() instanceof AttackAnimation attackAnimation) {
Optional<Float> reachOpt = attackAnimation.getProperty(AttackAnimationProperty.REACH);
if (reachOpt.isPresent()) {
reach = reachOpt.get();
} else {
AnimationPlayer player = entitypatch.getAnimator().getPlayerFor(self.getAccessor());
if (player != null) {
Phase phase = attackAnimation.getPhaseByTime(player.getElapsedTime());
reach = entitypatch.getReach(phase.hand);
}
}
}
entityRadius = (attackTarget.getBbWidth() + entitypatch.getOriginal().getBbWidth()) * 0.7F + reach;
}
float worldLength = Math.max((float)toDestWorld.length() - entityRadius, 0.0F);
float animLength = toDestAnim.length();
float scale = Math.min(worldLength / animLength, 1.0F);
if (self.isLinkAnimation()) {
scale *= coord[endFrame].transform().translation().length() / animLength;
}
for (int i = startFrame; i <= endFrame; i++) {
Vec3f translation = coord[i].transform().translation();
translation.x *= scale;
if (translation.z < 0.0F) {
translation.z *= scale;
}
}
transformSheet.readFrom(transform);
} else {
transformSheet.readFrom(self.getCoord().copyAll());
}
};
public static final MoveCoordSetter VEX_TRACE = (self, entitypatch, transformSheet) -> {
if (!self.isLinkAnimation()) {
TransformSheet transform = self.getCoord().copyAll();
if (entitypatch.getTarget() != null) {
Keyframe[] keyframes = transform.getKeyframes();
Vec3 pos = entitypatch.getOriginal().position();
Vec3 targetpos = entitypatch.getTarget().getEyePosition();
double flyDistance = Math.max(5.0D, targetpos.subtract(pos).length() * 2);
transform.forEach((index, keyframe) -> {
keyframe.transform().translation().scale((float)(flyDistance / Math.abs(keyframes[keyframes.length - 1].transform().translation().z)));
});
Vec3 toTarget = targetpos.subtract(pos);
float xRot = (float)-MathUtils.getXRotOfVector(toTarget);
float yRot = (float)MathUtils.getYRotOfVector(toTarget);
entitypatch.setYRot(yRot);
transform.forEach((index, keyframe) -> {
keyframe.transform().translation().rotateDegree(Vec3f.X_AXIS, xRot);
keyframe.transform().translation().rotateDegree(Vec3f.Y_AXIS, 180.0F - yRot);
keyframe.transform().translation().add(entitypatch.getOriginal().position());
});
transformSheet.readFrom(transform);
} else {
transform.forEach((index, keyframe) -> {
keyframe.transform().translation().rotateDegree(Vec3f.Y_AXIS, 180.0F - entitypatch.getYRot());
keyframe.transform().translation().add(entitypatch.getOriginal().position());
});
}
}
};
}

View File

@@ -0,0 +1,405 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.types;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.ForgeMod;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.anim.AnimationPlayer;
import com.tiedup.remake.rig.anim.AnimationVariables;
import com.tiedup.remake.rig.anim.AnimationVariables.IndependentAnimationVariableKey;
import com.tiedup.remake.rig.anim.AnimationVariables.SharedAnimationVariableKey;
import com.tiedup.remake.rig.armature.JointTransform;
import com.tiedup.remake.rig.anim.Keyframe;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.TransformSheet;
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
import com.tiedup.remake.rig.anim.property.AnimationProperty.AttackAnimationProperty;
import com.tiedup.remake.rig.anim.property.AnimationProperty.PlaybackSpeedModifier;
import com.tiedup.remake.rig.anim.property.AnimationProperty.StaticAnimationProperty;
import com.tiedup.remake.rig.anim.property.MoveCoordFunctions;
import com.tiedup.remake.rig.anim.property.MoveCoordFunctions.MoveCoordGetter;
import com.tiedup.remake.rig.anim.property.MoveCoordFunctions.MoveCoordSetter;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.anim.client.Layer;
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
import com.tiedup.remake.rig.armature.Armature;
import com.tiedup.remake.rig.util.TimePairList;
import com.tiedup.remake.rig.math.MathUtils;
import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.math.Vec3f;
import com.tiedup.remake.rig.patch.LocalPlayerPatch;
import yesman.epicfight.main.EpicFightSharedConstants;
import yesman.epicfight.network.EpicFightNetworkManager;
import yesman.epicfight.network.client.CPSyncPlayerAnimationPosition;
import yesman.epicfight.network.server.SPSyncAnimationPosition;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public class ActionAnimation extends MainFrameAnimation {
public static final SharedAnimationVariableKey<TransformSheet> ACTION_ANIMATION_COORD = AnimationVariables.shared((animator) -> new TransformSheet(), false);
public static final IndependentAnimationVariableKey<Vec3> BEGINNING_LOCATION = AnimationVariables.independent((animator) -> animator.getEntityPatch().getOriginal().position(), true);
public static final IndependentAnimationVariableKey<Float> INITIAL_LOOK_VEC_DOT = AnimationVariables.independent((animator) -> 1.0F, true);
public ActionAnimation(float transitionTime, AnimationAccessor<? extends ActionAnimation> accessor, AssetAccessor<? extends Armature> armature) {
this(transitionTime, Float.MAX_VALUE, accessor, armature);
}
public ActionAnimation(float transitionTime, float postDelay, AnimationAccessor<? extends ActionAnimation> accessor, AssetAccessor<? extends Armature> armature) {
super(transitionTime, accessor, armature);
this.stateSpectrumBlueprint.clear()
.newTimePair(0.0F, postDelay)
.addState(EntityState.MOVEMENT_LOCKED, true)
.addState(EntityState.UPDATE_LIVING_MOTION, false)
.addState(EntityState.CAN_BASIC_ATTACK, false)
.addState(EntityState.CAN_SKILL_EXECUTION, false)
.addState(EntityState.TURNING_LOCKED, true)
.newTimePair(0.0F, Float.MAX_VALUE)
.addState(EntityState.INACTION, true);
this.addProperty(StaticAnimationProperty.FIXED_HEAD_ROTATION, true);
}
/**
* For resourcepack animations
*/
public ActionAnimation(float transitionTime, float postDelay, String path, AssetAccessor<? extends Armature> armature) {
super(transitionTime, path, armature);
this.stateSpectrumBlueprint.clear()
.newTimePair(0.0F, postDelay)
.addState(EntityState.MOVEMENT_LOCKED, true)
.addState(EntityState.UPDATE_LIVING_MOTION, false)
.addState(EntityState.CAN_BASIC_ATTACK, false)
.addState(EntityState.CAN_SKILL_EXECUTION, false)
.addState(EntityState.TURNING_LOCKED, true)
.newTimePair(0.0F, Float.MAX_VALUE)
.addState(EntityState.INACTION, true);
this.addProperty(StaticAnimationProperty.FIXED_HEAD_ROTATION, true);
}
@Override
public void putOnPlayer(AnimationPlayer animationPlayer, LivingEntityPatch<?> entitypatch) {
if (entitypatch.shouldMoveOnCurrentSide(this)) {
MoveCoordSetter moveCoordSetter = this.getProperty(ActionAnimationProperty.COORD_SET_BEGIN).orElse(MoveCoordFunctions.RAW_COORD);
moveCoordSetter.set(this, entitypatch, entitypatch.getAnimator().getVariables().getOrDefaultSharedVariable(ACTION_ANIMATION_COORD));
}
super.putOnPlayer(animationPlayer, entitypatch);
}
protected void initCoordVariables(LivingEntityPatch<?> entitypatch) {
Vec3 start = entitypatch.getOriginal().position();
if (entitypatch.getTarget() != null) {
Vec3 targetTracePosition = entitypatch.getTarget().position();
Vec3 toDestWorld = targetTracePosition.subtract(start);
float dot = Mth.clamp((float)toDestWorld.normalize().dot(MathUtils.getVectorForRotation(0.0F, entitypatch.getYRot())), 0.0F, 1.0F);
entitypatch.getAnimator().getVariables().put(INITIAL_LOOK_VEC_DOT, this.getAccessor(), dot);
}
entitypatch.getAnimator().getVariables().put(BEGINNING_LOCATION, this.getAccessor(), start);
}
@Override
public void begin(LivingEntityPatch<?> entitypatch) {
entitypatch.cancelItemUse();
super.begin(entitypatch);
if (entitypatch.shouldMoveOnCurrentSide(this)) {
entitypatch.beginAction(this);
this.initCoordVariables(entitypatch);
if (this.getProperty(ActionAnimationProperty.STOP_MOVEMENT).orElse(false)) {
entitypatch.getOriginal().setDeltaMovement(0.0D, entitypatch.getOriginal().getDeltaMovement().y, 0.0D);
entitypatch.getOriginal().xxa = 0.0F;
entitypatch.getOriginal().yya = 0.0F;
entitypatch.getOriginal().zza = 0.0F;
}
}
}
@Override
public void tick(LivingEntityPatch<?> entitypatch) {
super.tick(entitypatch);
if (this.getProperty(ActionAnimationProperty.REMOVE_DELTA_MOVEMENT).orElse(false)) {
double gravity = this.getProperty(ActionAnimationProperty.MOVE_VERTICAL).orElse(false) ? 0.0D : entitypatch.getOriginal().getDeltaMovement().y;
entitypatch.getOriginal().setDeltaMovement(0.0D, gravity, 0.0D);
}
this.move(entitypatch, this.getAccessor());
}
@Override
public void linkTick(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> linkAnimation) {
if (this.getProperty(ActionAnimationProperty.REMOVE_DELTA_MOVEMENT).orElse(false)) {
double gravity = this.getProperty(ActionAnimationProperty.MOVE_VERTICAL).orElse(false) ? 0.0D : entitypatch.getOriginal().getDeltaMovement().y;
entitypatch.getOriginal().setDeltaMovement(0.0D, gravity, 0.0D);
}
this.move(entitypatch, linkAnimation);
}
protected void move(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> animation) {
if (!this.validateMovement(entitypatch, animation)) {
return;
}
float elapsedTime = entitypatch.getAnimator().getPlayerFor(this.getAccessor()).getElapsedTime();
if (this.getState(EntityState.INACTION, entitypatch, elapsedTime)) {
LivingEntity livingentity = entitypatch.getOriginal();
Vec3 vec3 = this.getCoordVector(entitypatch, animation);
livingentity.move(MoverType.SELF, vec3);
if (entitypatch.isLogicalClient()) {
EpicFightNetworkManager.sendToServer(new CPSyncPlayerAnimationPosition(livingentity.getId(), elapsedTime, livingentity.position(), animation.get().isLinkAnimation() ? 2 : 1));
} else {
EpicFightNetworkManager.sendToAllPlayerTrackingThisEntity(new SPSyncAnimationPosition(livingentity.getId(), elapsedTime, livingentity.position(), animation.get().isLinkAnimation() ? 2 : 1), livingentity);
}
}
}
protected boolean validateMovement(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> animation) {
if (!entitypatch.shouldMoveOnCurrentSide(this)) {
return false;
}
if (animation.get().isLinkAnimation()) {
if (!this.getProperty(ActionAnimationProperty.MOVE_ON_LINK).orElse(true)) {
return false;
} else {
return this.shouldMove(0.0F);
}
} else {
return this.shouldMove(entitypatch.getAnimator().getPlayerFor(animation).getElapsedTime());
}
}
protected boolean shouldMove(float currentTime) {
if (this.properties.containsKey(ActionAnimationProperty.MOVE_TIME)) {
TimePairList moveTimes = this.getProperty(ActionAnimationProperty.MOVE_TIME).get();
return moveTimes.isTimeInPairs(currentTime);
} else {
return true;
}
}
@Override
public void modifyPose(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
if (this.getProperty(ActionAnimationProperty.COORD).isEmpty()) {
this.correctRootJoint(animation, pose, entitypatch, time, partialTicks);
}
super.modifyPose(animation, pose, entitypatch, time, partialTicks);
}
public void correctRootJoint(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
JointTransform jt = pose.orElseEmpty("Root");
Vec3f jointPosition = jt.translation();
OpenMatrix4f toRootTransformApplied = entitypatch.getArmature().searchJointByName("Root").getLocalTransform().removeTranslation();
OpenMatrix4f toOrigin = OpenMatrix4f.invert(toRootTransformApplied, null);
Vec3f worldPosition = OpenMatrix4f.transform3v(toRootTransformApplied, jointPosition, null);
worldPosition.x = 0.0F;
worldPosition.y = (this.getProperty(ActionAnimationProperty.MOVE_VERTICAL).orElse(false) && worldPosition.y > 0.0F) ? 0.0F : worldPosition.y;
worldPosition.z = 0.0F;
OpenMatrix4f.transform3v(toOrigin, worldPosition, worldPosition);
jointPosition.x = worldPosition.x;
jointPosition.y = worldPosition.y;
jointPosition.z = worldPosition.z;
}
@Override
public void setLinkAnimation(AssetAccessor<? extends DynamicAnimation> fromAnimation, Pose startPose, boolean isOnSameLayer, float transitionTimeModifier, LivingEntityPatch<?> entitypatch, LinkAnimation dest) {
dest.resetNextStartTime();
float playTime = this.getPlaySpeed(entitypatch, dest);
PlaybackSpeedModifier playSpeedModifier = this.getRealAnimation().get().getProperty(StaticAnimationProperty.PLAY_SPEED_MODIFIER).orElse(null);
if (playSpeedModifier != null) {
playTime = playSpeedModifier.modify(this, entitypatch, playTime, 0.0F, playTime);
}
playTime = Math.abs(playTime) * EpicFightSharedConstants.A_TICK;
float linkTime = (transitionTimeModifier > 0.0F) ? transitionTimeModifier + this.transitionTime : this.transitionTime;
float totalTime = playTime * (int)Math.ceil(linkTime / playTime);
float nextStartTime = Math.max(0.0F, -transitionTimeModifier);
nextStartTime += totalTime - linkTime;
dest.setNextStartTime(nextStartTime);
dest.getAnimationClip().reset();
dest.setTotalTime(totalTime);
dest.setConnectedAnimations(fromAnimation, this.getAccessor());
Pose nextStartPose = this.getPoseByTime(entitypatch, nextStartTime, 1.0F);
if (entitypatch.shouldMoveOnCurrentSide(this) && this.getProperty(ActionAnimationProperty.MOVE_ON_LINK).orElse(true)) {
this.correctRawZCoord(entitypatch, nextStartPose, nextStartTime);
}
Map<String, JointTransform> data1 = startPose.getJointTransformData();
Map<String, JointTransform> data2 = nextStartPose.getJointTransformData();
Set<String> joint1 = new HashSet<> (isOnSameLayer ? data1.keySet() : Set.of());
Set<String> joint2 = new HashSet<> (data2.keySet());
if (entitypatch.isLogicalClient()) {
JointMaskEntry entry = fromAnimation.get().getJointMaskEntry(entitypatch, false).orElse(null);
JointMaskEntry entry2 = this.getJointMaskEntry(entitypatch, true).orElse(null);
if (entry != null && entitypatch.isLogicalClient()) {
joint1.removeIf((jointName) -> entry.isMasked(fromAnimation.get().getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(Layer.LayerType.BASE_LAYER) == Layer.LayerType.BASE_LAYER ?
entitypatch.getClientAnimator().currentMotion() : entitypatch.getClientAnimator().currentCompositeMotion(), jointName));
}
if (entry2 != null && entitypatch.isLogicalClient()) {
joint2.removeIf((jointName) -> entry2.isMasked(this.getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(Layer.LayerType.BASE_LAYER) == Layer.LayerType.BASE_LAYER ?
entitypatch.getCurrentLivingMotion() : entitypatch.currentCompositeMotion, jointName));
}
}
joint1.addAll(joint2);
if (linkTime != totalTime) {
Pose pose = this.getPoseByTime(entitypatch, 0.0F, 0.0F);
Map<String, JointTransform> poseData = pose.getJointTransformData();
if (entitypatch.shouldMoveOnCurrentSide(this) && this.getProperty(ActionAnimationProperty.MOVE_ON_LINK).orElse(true)) {
this.correctRawZCoord(entitypatch, pose, 0.0F);
}
for (String jointName : joint1) {
Keyframe[] keyframes = new Keyframe[3];
keyframes[0] = new Keyframe(0.0F, data1.getOrDefault(jointName, JointTransform.empty()));
keyframes[1] = new Keyframe(linkTime, poseData.getOrDefault(jointName, JointTransform.empty()));
keyframes[2] = new Keyframe(totalTime, data2.getOrDefault(jointName, JointTransform.empty()));
TransformSheet sheet = new TransformSheet(keyframes);
dest.getAnimationClip().addJointTransform(jointName, sheet);
}
} else {
for (String jointName : joint1) {
Keyframe[] keyframes = new Keyframe[2];
keyframes[0] = new Keyframe(0.0F, data1.getOrDefault(jointName, JointTransform.empty()));
keyframes[1] = new Keyframe(totalTime, data2.getOrDefault(jointName, JointTransform.empty()));
TransformSheet sheet = new TransformSheet(keyframes);
dest.getAnimationClip().addJointTransform(jointName, sheet);
}
}
dest.loadCoord(null);
this.getProperty(ActionAnimationProperty.COORD).ifPresent((coord) -> {
Keyframe[] keyframes = new Keyframe[2];
keyframes[0] = new Keyframe(0.0F, JointTransform.empty());
keyframes[1] = new Keyframe(totalTime, coord.getKeyframes()[0].transform());
TransformSheet sheet = new TransformSheet(keyframes);
dest.loadCoord(sheet);
});
if (entitypatch.shouldMoveOnCurrentSide(this)) {
MoveCoordSetter moveCoordSetter = this.getProperty(ActionAnimationProperty.COORD_SET_BEGIN).orElse(MoveCoordFunctions.RAW_COORD);
moveCoordSetter.set(dest, entitypatch, entitypatch.getAnimator().getVariables().getOrDefaultSharedVariable(ACTION_ANIMATION_COORD));
}
}
public void correctRawZCoord(LivingEntityPatch<?> entitypatch, Pose pose, float poseTime) {
JointTransform jt = pose.orElseEmpty("Root");
if (this.getProperty(ActionAnimationProperty.COORD).isEmpty()) {
TransformSheet coordTransform = this.getTransfroms().get("Root");
jt.translation().add(0.0F, 0.0F, coordTransform.getInterpolatedTranslation(poseTime).z);
}
}
/**
* Get an expected movement vector in specific time
*
* @param entitypatch
* @param elapseTime
* @return
*/
public Vec3 getExpectedMovement(LivingEntityPatch<?> entitypatch, float elapseTime) {
this.initCoordVariables(entitypatch);
TransformSheet coordTransform = new TransformSheet();
MoveCoordSetter moveCoordSetter = this.getProperty(ActionAnimationProperty.COORD_SET_BEGIN).orElse(MoveCoordFunctions.RAW_COORD);
moveCoordSetter.set(this, entitypatch, coordTransform);
MoveCoordGetter moveGetter = this.getProperty(ActionAnimationProperty.COORD_GET).orElse(MoveCoordFunctions.MODEL_COORD);
Vec3f move = moveGetter.get(this, entitypatch, coordTransform, 0.0F, elapseTime);
return move.toDoubleVector();
}
protected Vec3 getCoordVector(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> animation) {
AnimationPlayer player = entitypatch.getAnimator().getPlayerFor(animation);
TimePairList coordUpdateTime = this.getProperty(ActionAnimationProperty.COORD_UPDATE_TIME).orElse(null);
boolean inUpdateTime = coordUpdateTime == null || coordUpdateTime.isTimeInPairs(player.getElapsedTime());
boolean getRawCoord = this.getProperty(AttackAnimationProperty.FIXED_MOVE_DISTANCE).orElse(!inUpdateTime);
TransformSheet transformSheet = entitypatch.getAnimator().getVariables().getOrDefaultSharedVariable(ACTION_ANIMATION_COORD);
MoveCoordSetter moveCoordsetter = getRawCoord ? MoveCoordFunctions.RAW_COORD : this.getProperty(ActionAnimationProperty.COORD_SET_TICK).orElse(null);
if (moveCoordsetter != null) {
moveCoordsetter.set(animation.get(), entitypatch, transformSheet);
}
boolean hasNoGravity = entitypatch.getOriginal().isNoGravity();
boolean moveVertical = this.getProperty(ActionAnimationProperty.MOVE_VERTICAL).orElse(this.getProperty(ActionAnimationProperty.COORD).isPresent());
MoveCoordGetter moveGetter = getRawCoord ? MoveCoordFunctions.MODEL_COORD : this.getProperty(ActionAnimationProperty.COORD_GET).orElse(MoveCoordFunctions.MODEL_COORD);
Vec3f move = moveGetter.get(animation.get(), entitypatch, transformSheet, player.getPrevElapsedTime(), player.getElapsedTime());
LivingEntity livingentity = entitypatch.getOriginal();
Vec3 motion = livingentity.getDeltaMovement();
this.getProperty(ActionAnimationProperty.NO_GRAVITY_TIME).ifPresentOrElse((noGravityTime) -> {
if (noGravityTime.isTimeInPairs(animation.get().isLinkAnimation() ? 0.0F : player.getElapsedTime())) {
livingentity.setDeltaMovement(motion.x, 0.0D, motion.z);
} else {
move.y = 0.0F;
}
}, () -> {
if (moveVertical && move.y > 0.0F && !hasNoGravity) {
double gravity = livingentity.getAttribute(ForgeMod.ENTITY_GRAVITY.get()).getValue();
livingentity.setDeltaMovement(motion.x, motion.y < 0.0D ? motion.y + gravity : 0.0D, motion.z);
}
});
if (!moveVertical) {
move.y = 0.0F;
}
if (inUpdateTime) {
this.getProperty(ActionAnimationProperty.ENTITY_YROT_PROVIDER).ifPresent((entityYRotProvider) -> {
float yRot = entityYRotProvider.get(animation.get(), entitypatch);
entitypatch.setYRot(yRot);
});
}
return move.toDoubleVector();
}
@OnlyIn(Dist.CLIENT)
public boolean shouldPlayerMove(LocalPlayerPatch playerpatch) {
return playerpatch.isLogicalClient();
}
}

View File

@@ -0,0 +1,570 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.types;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.entity.PartEntity;
import net.minecraftforge.registries.RegistryObject;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.anim.AnimationPlayer;
import com.tiedup.remake.rig.anim.AnimationVariables;
import com.tiedup.remake.rig.anim.AnimationVariables.SharedAnimationVariableKey;
import com.tiedup.remake.rig.armature.Joint;
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
import com.tiedup.remake.rig.anim.property.AnimationProperty.AttackAnimationProperty;
import com.tiedup.remake.rig.anim.property.AnimationProperty.AttackPhaseProperty;
import com.tiedup.remake.rig.anim.property.MoveCoordFunctions;
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
import com.tiedup.remake.rig.asset.AssetAccessor;
import yesman.epicfight.api.collider.Collider;
import com.tiedup.remake.rig.armature.Armature;
import com.tiedup.remake.rig.util.AttackResult;
import com.tiedup.remake.rig.util.HitEntityList;
import com.tiedup.remake.rig.math.MathUtils;
import com.tiedup.remake.rig.math.ValueModifier;
import yesman.epicfight.main.EpicFightSharedConstants;
import yesman.epicfight.particle.HitParticleType;
import com.tiedup.remake.rig.patch.HumanoidMobPatch;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
import com.tiedup.remake.rig.patch.PlayerPatch;
import com.tiedup.remake.rig.patch.ServerPlayerPatch;
import yesman.epicfight.world.damagesource.EpicFightDamageSource;
import yesman.epicfight.world.damagesource.EpicFightDamageSources;
import yesman.epicfight.world.entity.eventlistener.AttackEndEvent;
import yesman.epicfight.world.entity.eventlistener.AttackPhaseEndEvent;
import yesman.epicfight.world.entity.eventlistener.PlayerEventListener.EventType;
public class AttackAnimation extends ActionAnimation {
/** Entities that collided **/
public static final SharedAnimationVariableKey<List<Entity>> ATTACK_TRIED_ENTITIES = AnimationVariables.shared((animator) -> Lists.newArrayList(), false);
/** Entities that actually hurt **/
public static final SharedAnimationVariableKey<List<LivingEntity>> ACTUALLY_HIT_ENTITIES = AnimationVariables.shared((animator) -> Lists.newArrayList(), false);
public final Phase[] phases;
public AttackAnimation(float transitionTime, float antic, float preDelay, float contact, float recovery, @Nullable Collider collider, Joint colliderJoint, AnimationAccessor<? extends AttackAnimation> accessor, AssetAccessor<? extends Armature> armature) {
this(transitionTime, accessor, armature, new Phase(0.0F, antic, preDelay, contact, recovery, Float.MAX_VALUE, colliderJoint, collider));
}
public AttackAnimation(float transitionTime, float antic, float preDelay, float contact, float recovery, InteractionHand hand, @Nullable Collider collider, Joint colliderJoint, AnimationAccessor<? extends AttackAnimation> accessor, AssetAccessor<? extends Armature> armature) {
this(transitionTime, accessor, armature, new Phase(0.0F, antic, preDelay, contact, recovery, Float.MAX_VALUE, hand, colliderJoint, collider));
}
public AttackAnimation(float transitionTime, AnimationAccessor<? extends AttackAnimation> accessor, AssetAccessor<? extends Armature> armature, Phase... phases) {
super(transitionTime, accessor, armature);
this.addProperty(ActionAnimationProperty.COORD_SET_BEGIN, MoveCoordFunctions.TRACE_TARGET_DISTANCE);
this.addProperty(ActionAnimationProperty.COORD_SET_TICK, MoveCoordFunctions.TRACE_TARGET_DISTANCE);
this.addProperty(ActionAnimationProperty.COORD_GET, MoveCoordFunctions.MODEL_COORD);
this.addProperty(ActionAnimationProperty.DEST_LOCATION_PROVIDER, MoveCoordFunctions.ATTACK_TARGET_LOCATION);
this.addProperty(ActionAnimationProperty.ENTITY_YROT_PROVIDER, MoveCoordFunctions.MOB_ATTACK_TARGET_LOOK);
this.addProperty(ActionAnimationProperty.STOP_MOVEMENT, true);
this.phases = phases;
this.stateSpectrumBlueprint.clear();
for (Phase phase : phases) {
if (!phase.noStateBind) {
this.bindPhaseState(phase);
}
}
}
/**
* For resourcepack animation
*/
public AttackAnimation(float convertTime, float antic, float preDelay, float contact, float recovery, InteractionHand hand, @Nullable Collider collider, Joint colliderJoint, String path, AssetAccessor<? extends Armature> armature) {
this(convertTime, path, armature, new Phase(0.0F, antic, preDelay, contact, recovery, Float.MAX_VALUE, hand, colliderJoint, collider));
}
/**
* For resourcepack animation
*/
public AttackAnimation(float convertTime, String path, AssetAccessor<? extends Armature> armature, Phase... phases) {
super(convertTime, 0.0F, path, armature);
this.addProperty(ActionAnimationProperty.COORD_SET_BEGIN, MoveCoordFunctions.TRACE_TARGET_DISTANCE);
this.addProperty(ActionAnimationProperty.COORD_SET_TICK, MoveCoordFunctions.TRACE_TARGET_DISTANCE);
this.addProperty(ActionAnimationProperty.COORD_GET, MoveCoordFunctions.MODEL_COORD);
this.addProperty(ActionAnimationProperty.DEST_LOCATION_PROVIDER, MoveCoordFunctions.ATTACK_TARGET_LOCATION);
this.addProperty(ActionAnimationProperty.ENTITY_YROT_PROVIDER, MoveCoordFunctions.MOB_ATTACK_TARGET_LOOK);
this.addProperty(ActionAnimationProperty.STOP_MOVEMENT, true);
this.phases = phases;
this.stateSpectrumBlueprint.clear();
for (Phase phase : phases) {
if (!phase.noStateBind) {
this.bindPhaseState(phase);
}
}
}
protected void bindPhaseState(Phase phase) {
float preDelay = phase.preDelay;
this.stateSpectrumBlueprint
.newTimePair(phase.start, preDelay)
.addState(EntityState.PHASE_LEVEL, 1)
.newTimePair(phase.start, phase.contact)
.addState(EntityState.CAN_SKILL_EXECUTION, false)
.newTimePair(phase.start, phase.recovery)
.addState(EntityState.MOVEMENT_LOCKED, true)
.addState(EntityState.UPDATE_LIVING_MOTION, false)
.addState(EntityState.CAN_BASIC_ATTACK, false)
.newTimePair(phase.start, phase.end)
.addState(EntityState.INACTION, true)
.newTimePair(phase.antic, phase.end)
.addState(EntityState.TURNING_LOCKED, true)
.newTimePair(preDelay, phase.contact)
.addState(EntityState.ATTACKING, true)
.addState(EntityState.PHASE_LEVEL, 2)
.newTimePair(phase.contact, phase.end)
.addState(EntityState.PHASE_LEVEL, 3)
;
}
@Override
public void begin(LivingEntityPatch<?> entitypatch) {
super.begin(entitypatch);
entitypatch.setLastAttackSuccess(false);
}
@Override
public void linkTick(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> linkAnimation) {
super.linkTick(entitypatch, linkAnimation);
if (!entitypatch.isLogicalClient()) {
this.attackTick(entitypatch, linkAnimation);
}
}
@Override
public void tick(LivingEntityPatch<?> entitypatch) {
super.tick(entitypatch);
if (!entitypatch.isLogicalClient()) {
this.attackTick(entitypatch, this.getAccessor());
}
}
@Override
public void end(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {
super.end(entitypatch, nextAnimation, isEnd);
if (entitypatch instanceof ServerPlayerPatch playerpatch) {
if (isEnd) {
playerpatch.getEventListener().triggerEvents(EventType.ATTACK_ANIMATION_END_EVENT, new AttackEndEvent(playerpatch, this.getAccessor()));
}
AnimationPlayer player = entitypatch.getAnimator().getPlayerFor(this.getAccessor());
float elapsedTime = player.getElapsedTime();
EntityState state = this.getState(entitypatch, elapsedTime);
if (!isEnd && state.attacking()) {
playerpatch.getEventListener().triggerEvents(EventType.ATTACK_PHASE_END_EVENT, new AttackPhaseEndEvent(playerpatch, this.getAccessor(), this.getPhaseByTime(elapsedTime), this.getPhaseOrderByTime(elapsedTime)));
}
}
if (entitypatch instanceof HumanoidMobPatch<?> mobpatch && entitypatch.isLogicalClient()) {
Mob entity = mobpatch.getOriginal();
if (entity.getTarget() != null && !entity.getTarget().isAlive()) {
entity.setTarget(null);
}
}
}
protected void attackTick(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> animation) {
AnimationPlayer player = entitypatch.getAnimator().getPlayerFor(this.getAccessor());
float prevElapsedTime = player.getPrevElapsedTime();
float elapsedTime = player.getElapsedTime();
EntityState prevState = animation.get().getState(entitypatch, prevElapsedTime);
EntityState state = animation.get().getState(entitypatch, elapsedTime);
Phase phase = this.getPhaseByTime(animation.get().isLinkAnimation() ? 0.0F : elapsedTime);
if (prevState.attacking() || state.attacking() || (prevState.getLevel() <= 2 && state.getLevel() > 2)) {
if (!prevState.attacking() || (phase != this.getPhaseByTime(prevElapsedTime) && (state.attacking() || (prevState.getLevel() <= 2 && state.getLevel() > 2)))) {
entitypatch.onStrike(this, phase.hand);
entitypatch.playSound(this.getSwingSound(entitypatch, phase), 0.0F, 0.0F);
entitypatch.removeHurtEntities();
}
this.hurtCollidingEntities(entitypatch, prevElapsedTime, elapsedTime, prevState, state, phase);
if ((!state.attacking() || elapsedTime >= this.getTotalTime()) && entitypatch instanceof ServerPlayerPatch playerpatch) {
playerpatch.getEventListener().triggerEvents(EventType.ATTACK_PHASE_END_EVENT, new AttackPhaseEndEvent(playerpatch, this.getAccessor(), phase, this.getPhaseOrderByTime(elapsedTime)));
}
}
}
protected void hurtCollidingEntities(LivingEntityPatch<?> entitypatch, float prevElapsedTime, float elapsedTime, EntityState prevState, EntityState state, Phase phase) {
LivingEntity entity = entitypatch.getOriginal();
float prevPoseTime = prevState.attacking() ? prevElapsedTime : phase.preDelay;
float poseTime = state.attacking() ? elapsedTime : phase.contact;
List<Entity> list = this.getPhaseByTime(elapsedTime).getCollidingEntities(entitypatch, this, prevPoseTime, poseTime, this.getPlaySpeed(entitypatch, this));
if (!list.isEmpty()) {
HitEntityList hitEntities = new HitEntityList(entitypatch, list, phase.getProperty(AttackPhaseProperty.HIT_PRIORITY).orElse(HitEntityList.Priority.DISTANCE));
int maxStrikes = this.getMaxStrikes(entitypatch, phase);
while (entitypatch.getCurrentlyActuallyHitEntities().size() < maxStrikes && hitEntities.next()) {
Entity target = hitEntities.getEntity();
LivingEntity trueEntity = this.getTrueEntity(target);
if (trueEntity != null && trueEntity.isAlive() && !entitypatch.getCurrentlyAttackTriedEntities().contains(trueEntity) && !entitypatch.isTargetInvulnerable(target)) {
if (target instanceof LivingEntity || target instanceof PartEntity) {
AABB aabb = target.getBoundingBox();
if (MathUtils.canBeSeen(target, entity, target.position().distanceTo(entity.getEyePosition()) + aabb.getCenter().distanceTo(new Vec3(aabb.maxX, aabb.maxY, aabb.maxZ)))) {
EpicFightDamageSource damagesource = this.getEpicFightDamageSource(entitypatch, target, phase);
int prevInvulTime = target.invulnerableTime;
target.invulnerableTime = 0;
AttackResult attackResult = entitypatch.attack(damagesource, target, phase.hand);
target.invulnerableTime = prevInvulTime;
if (attackResult.resultType.dealtDamage()) {
SoundEvent hitSound = this.getHitSound(entitypatch, phase);
if (hitSound != null) {
target.level().playSound(null, target.getX(), target.getY(), target.getZ(), this.getHitSound(entitypatch, phase), target.getSoundSource(), 1.0F, 1.0F);
}
this.spawnHitParticle((ServerLevel)target.level(), entitypatch, target, phase);
}
entitypatch.getCurrentlyAttackTriedEntities().add(trueEntity);
if (attackResult.resultType.shouldCount()) {
entitypatch.getCurrentlyActuallyHitEntities().add(trueEntity);
}
}
}
}
}
}
}
public LivingEntity getTrueEntity(Entity entity) {
if (entity instanceof LivingEntity livingEntity) {
return livingEntity;
} else if (entity instanceof PartEntity<?> partEntity) {
Entity parentEntity = partEntity.getParent();
if (parentEntity instanceof LivingEntity livingEntity) {
return livingEntity;
}
}
return null;
}
protected int getMaxStrikes(LivingEntityPatch<?> entitypatch, Phase phase) {
return phase.getProperty(AttackPhaseProperty.MAX_STRIKES_MODIFIER)
.map(valueModifier -> (int)ValueModifier.calculator().attach(valueModifier).getResult(entitypatch.getMaxStrikes(phase.hand)))
.orElse(entitypatch.getMaxStrikes(phase.hand));
}
protected SoundEvent getSwingSound(LivingEntityPatch<?> entitypatch, Phase phase) {
return phase.getProperty(AttackPhaseProperty.SWING_SOUND).orElse(entitypatch.getSwingSound(phase.hand));
}
protected SoundEvent getHitSound(LivingEntityPatch<?> entitypatch, Phase phase) {
return phase.getProperty(AttackPhaseProperty.HIT_SOUND).orElse(entitypatch.getWeaponHitSound(phase.hand));
}
public EpicFightDamageSource getEpicFightDamageSource(LivingEntityPatch<?> entitypatch, Entity target, Phase phase) {
return this.getEpicFightDamageSource(entitypatch.getDamageSource(this.getAccessor(), phase.hand), entitypatch, target, phase);
}
public EpicFightDamageSource getEpicFightDamageSource(DamageSource originalSource, LivingEntityPatch<?> entitypatch, Entity target, Phase phase) {
if (phase == null) {
phase = this.getPhaseByTime(entitypatch.getAnimator().getPlayerFor(this.getAccessor()).getElapsedTime());
}
EpicFightDamageSource epicfightSource;
if (originalSource instanceof EpicFightDamageSource epicfightDamageSource) {
epicfightSource = epicfightDamageSource;
} else {
epicfightSource = EpicFightDamageSources.fromVanillaDamageSource(originalSource).setAnimation(this.getAccessor());
}
phase.getProperty(AttackPhaseProperty.DAMAGE_MODIFIER).ifPresent(opt -> {
epicfightSource.attachDamageModifier(opt);
});
phase.getProperty(AttackPhaseProperty.ARMOR_NEGATION_MODIFIER).ifPresent(opt -> {
epicfightSource.attachArmorNegationModifier(opt);
});
phase.getProperty(AttackPhaseProperty.IMPACT_MODIFIER).ifPresent(opt -> {
epicfightSource.attachImpactModifier(opt);
});
phase.getProperty(AttackPhaseProperty.STUN_TYPE).ifPresent(opt -> {
epicfightSource.setStunType(opt);
});
phase.getProperty(AttackPhaseProperty.SOURCE_TAG).ifPresent(opt -> {
opt.forEach(epicfightSource::addRuntimeTag);
});
phase.getProperty(AttackPhaseProperty.EXTRA_DAMAGE).ifPresent(opt -> {
opt.forEach(epicfightSource::addExtraDamage);
});
phase.getProperty(AttackPhaseProperty.SOURCE_LOCATION_PROVIDER).ifPresentOrElse(opt -> {
epicfightSource.setInitialPosition(opt.apply(entitypatch));
}, () -> {
epicfightSource.setInitialPosition(entitypatch.getOriginal().position());
});
return epicfightSource;
}
protected void spawnHitParticle(ServerLevel world, LivingEntityPatch<?> attacker, Entity hit, Phase phase) {
Optional<RegistryObject<HitParticleType>> particleOptional = phase.getProperty(AttackPhaseProperty.PARTICLE);
HitParticleType particle = particleOptional.isPresent() ? particleOptional.get().get() : attacker.getWeaponHitParticle(phase.hand);
particle.spawnParticleWithArgument(world, null, null, hit, attacker.getOriginal());
}
@Override
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
if (entitypatch instanceof PlayerPatch<?> playerpatch) {
Phase phase = this.getPhaseByTime(playerpatch.getAnimator().getPlayerFor(this.getAccessor()).getElapsedTime());
float speedFactor = this.getProperty(AttackAnimationProperty.ATTACK_SPEED_FACTOR).orElse(1.0F);
Optional<Float> property = this.getProperty(AttackAnimationProperty.BASIS_ATTACK_SPEED);
float correctedSpeed = property.map((value) -> playerpatch.getAttackSpeed(phase.hand) / value).orElse(this.getTotalTime() * playerpatch.getAttackSpeed(phase.hand));
correctedSpeed = Math.round(correctedSpeed * 1000.0F) / 1000.0F;
return 1.0F + (correctedSpeed - 1.0F) * speedFactor;
}
return 1.0F;
}
@SuppressWarnings("unchecked")
public <V, A extends AttackAnimation> A addProperty(AttackPhaseProperty<V> propertyType, V value) {
return (A)this.addProperty(propertyType, value, 0);
}
@SuppressWarnings("unchecked")
public <V, A extends AttackAnimation> A addProperty(AttackPhaseProperty<V> propertyType, V value, int index) {
this.phases[index].addProperty(propertyType, value);
return (A)this;
}
public <A extends AttackAnimation> A removeProperty(AttackPhaseProperty<?> propertyType) {
return this.removeProperty(propertyType, 0);
}
@SuppressWarnings("unchecked")
public <A extends AttackAnimation> A removeProperty(AttackPhaseProperty<?> propertyType, int index) {
this.phases[index].removeProperty(propertyType);
return (A)this;
}
public Phase getPhaseByTime(float elapsedTime) {
Phase currentPhase = null;
for (Phase phase : this.phases) {
currentPhase = phase;
if (phase.end > elapsedTime) {
break;
}
}
return currentPhase;
}
public int getPhaseOrderByTime(float elapsedTime) {
int i = 0;
for (Phase phase : this.phases) {
if (phase.end > elapsedTime) {
break;
}
i++;
}
return i;
}
@Override
public Object getModifiedLinkState(StateFactor<?> factor, Object val, LivingEntityPatch<?> entitypatch, float elapsedTime) {
if (factor == EntityState.ATTACKING && elapsedTime < this.getPlaySpeed(entitypatch, this) * EpicFightSharedConstants.A_TICK) {
return false;
}
return val;
}
@Override
@OnlyIn(Dist.CLIENT)
public void renderDebugging(PoseStack poseStack, MultiBufferSource buffer, LivingEntityPatch<?> entitypatch, float playbackTime, float partialTicks) {
AnimationPlayer animPlayer = entitypatch.getAnimator().getPlayerFor(this.getAccessor());
float prevElapsedTime = animPlayer.getPrevElapsedTime();
float elapsedTime = animPlayer.getElapsedTime();
Phase phase = this.getPhaseByTime(playbackTime);
for (Pair<Joint, Collider> colliderInfo : phase.colliders) {
Collider collider = colliderInfo.getSecond();
if (collider == null) {
collider = entitypatch.getColliderMatching(phase.hand);
}
collider.draw(poseStack, buffer, entitypatch, this, colliderInfo.getFirst(), prevElapsedTime, elapsedTime, partialTicks, this.getPlaySpeed(entitypatch, this));
}
}
public static class JointColliderPair extends Pair<Joint, Collider> {
public JointColliderPair(Joint first, Collider second) {
super(first, second);
}
public static JointColliderPair of(Joint joint, Collider collider) {
return new JointColliderPair(joint, collider);
}
}
public static class Phase {
private final Map<AttackPhaseProperty<?>, Object> properties = Maps.newHashMap();
public final float start;
public final float antic;
public final float preDelay;
public final float contact;
public final float recovery;
public final float end;
public final InteractionHand hand;
public JointColliderPair[] colliders;
//public final Joint first;
//public final Collider second;
public final boolean noStateBind;
public Phase(float start, float antic, float contact, float recovery, float end, Joint joint, Collider collider) {
this(start, antic, contact, recovery, end, InteractionHand.MAIN_HAND, joint, collider);
}
public Phase(float start, float antic, float contact, float recovery, float end, InteractionHand hand, Joint joint, Collider collider) {
this(start, antic, antic, contact, recovery, end, hand, joint, collider);
}
public Phase(float start, float antic, float preDelay, float contact, float recovery, float end, Joint joint, Collider collider) {
this(start, antic, preDelay, contact, recovery, end, InteractionHand.MAIN_HAND, joint, collider);
}
public Phase(float start, float antic, float preDelay, float contact, float recovery, float end, InteractionHand hand, Joint joint, Collider collider) {
this(start, antic, preDelay, contact, recovery, end, false, hand, joint, collider);
}
public Phase(InteractionHand hand, Joint joint, Collider collider) {
this(0, 0, 0, 0, 0, 0, true, hand, joint, collider);
}
public Phase(float start, float antic, float preDelay, float contact, float recovery, float end, boolean noStateBind, InteractionHand hand, Joint joint, Collider collider) {
this(start, antic, preDelay, contact, recovery, end, noStateBind, hand, JointColliderPair.of(joint, collider));
}
public Phase(float start, float antic, float preDelay, float contact, float recovery, float end, InteractionHand hand, JointColliderPair... colliders) {
this(start, antic, preDelay, contact, recovery, end, false, hand, colliders);
}
public Phase(float start, float antic, float preDelay, float contact, float recovery, float end, boolean noStateBind, InteractionHand hand, JointColliderPair... colliders) {
if (start > end) {
throw new IllegalArgumentException("Phase create exception: Start time is bigger than end time");
}
this.start = start;
this.antic = antic;
this.preDelay = preDelay;
this.contact = contact;
this.recovery = recovery;
this.end = end;
this.colliders = colliders;
this.hand = hand;
this.noStateBind = noStateBind;
}
public <V> Phase addProperty(AttackPhaseProperty<V> propertyType, V value) {
this.properties.put(propertyType, value);
return this;
}
public Phase removeProperty(AttackPhaseProperty<?> propertyType) {
this.properties.remove(propertyType);
return this;
}
public void addProperties(Set<Map.Entry<AttackPhaseProperty<?>, Object>> set) {
for(Map.Entry<AttackPhaseProperty<?>, Object> entry : set) {
this.properties.put(entry.getKey(), entry.getValue());
}
}
@SuppressWarnings("unchecked")
public <V> Optional<V> getProperty(AttackPhaseProperty<V> propertyType) {
return (Optional<V>) Optional.ofNullable(this.properties.get(propertyType));
}
public List<Entity> getCollidingEntities(LivingEntityPatch<?> entitypatch, AttackAnimation animation, float prevElapsedTime, float elapsedTime, float attackSpeed) {
Set<Entity> entities = Sets.newHashSet();
for (Pair<Joint, Collider> colliderInfo : this.colliders) {
Collider collider = colliderInfo.getSecond();
if (collider == null) {
collider = entitypatch.getColliderMatching(this.hand);
}
entities.addAll(collider.updateAndSelectCollideEntity(entitypatch, animation, prevElapsedTime, elapsedTime, colliderInfo.getFirst(), attackSpeed));
}
return new ArrayList<>(entities);
}
public JointColliderPair[] getColliders() {
return this.colliders;
}
public InteractionHand getHand() {
return this.hand;
}
}
}

View File

@@ -0,0 +1,179 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.types;
import java.util.Optional;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.anim.AnimationClip;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.anim.client.Layer;
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
@OnlyIn(Dist.CLIENT)
public class ConcurrentLinkAnimation extends DynamicAnimation implements AnimationAccessor<ConcurrentLinkAnimation> {
protected AssetAccessor<? extends StaticAnimation> nextAnimation;
protected AssetAccessor<? extends DynamicAnimation> currentAnimation;
protected float startsAt;
public ConcurrentLinkAnimation() {
this.animationClip = new AnimationClip();
}
public void acceptFrom(AssetAccessor<? extends DynamicAnimation> currentAnimation, AssetAccessor<? extends StaticAnimation> nextAnimation, float time) {
this.currentAnimation = currentAnimation;
this.nextAnimation = nextAnimation;
this.startsAt = time;
this.setTotalTime(nextAnimation.get().getTransitionTime());
}
@Override
public void tick(LivingEntityPatch<?> entitypatch) {
this.nextAnimation.get().linkTick(entitypatch, this);
}
@Override
public void end(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {
if (!isEnd) {
this.nextAnimation.get().end(entitypatch, nextAnimation, isEnd);
} else {
if (this.startsAt > 0.0F) {
entitypatch.getAnimator().getPlayer(this).ifPresent(player -> {
player.setElapsedTime(this.startsAt);
player.markDoNotResetTime();
});
this.startsAt = 0.0F;
}
}
}
@Override
public EntityState getState(LivingEntityPatch<?> entitypatch, float time) {
return this.nextAnimation.get().getState(entitypatch, 0.0F);
}
@Override
public <T> T getState(StateFactor<T> stateFactor, LivingEntityPatch<?> entitypatch, float time) {
return this.nextAnimation.get().getState(stateFactor, entitypatch, 0.0F);
}
@Override
public Pose getPoseByTime(LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
float elapsed = time + this.startsAt;
float currentElapsed = elapsed % this.currentAnimation.get().getTotalTime();
float nextElapsed = elapsed % this.nextAnimation.get().getTotalTime();
Pose currentAnimPose = this.currentAnimation.get().getPoseByTime(entitypatch, currentElapsed, 1.0F);
Pose nextAnimPose = this.nextAnimation.get().getPoseByTime(entitypatch, nextElapsed, 1.0F);
float interpolate = time / this.getTotalTime();
Pose interpolatedPose = Pose.interpolatePose(currentAnimPose, nextAnimPose, interpolate);
JointMaskEntry maskEntry = this.nextAnimation.get().getJointMaskEntry(entitypatch, true).orElse(null);
if (maskEntry != null && entitypatch.isLogicalClient()) {
interpolatedPose.disableJoint((entry) ->
maskEntry.isMasked(
this.nextAnimation.get().getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(Layer.LayerType.BASE_LAYER) == Layer.LayerType.BASE_LAYER ? entitypatch.getClientAnimator().currentMotion() : entitypatch.getClientAnimator().currentCompositeMotion()
, entry.getKey()
));
}
return interpolatedPose;
}
@Override
public void modifyPose(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
this.nextAnimation.get().modifyPose(this, pose, entitypatch, time, partialTicks);
}
@Override
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
return this.nextAnimation.get().getPlaySpeed(entitypatch, animation);
}
public void setNextAnimation(AnimationAccessor<? extends StaticAnimation> animation) {
this.nextAnimation = animation;
}
@OnlyIn(Dist.CLIENT)
@Override
public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
return this.nextAnimation.get().getJointMaskEntry(entitypatch, useCurrentMotion);
}
@Override
public boolean isMainFrameAnimation() {
return this.nextAnimation.get().isMainFrameAnimation();
}
@Override
public boolean isReboundAnimation() {
return this.nextAnimation.get().isReboundAnimation();
}
@Override
public AssetAccessor<? extends StaticAnimation> getRealAnimation() {
return this.nextAnimation;
}
@Override
public String toString() {
return "ConcurrentLinkAnimation: Mix " + this.currentAnimation + " and " + this.nextAnimation;
}
@Override
public AnimationClip getAnimationClip() {
return this.animationClip;
}
@Override
public boolean hasTransformFor(String joint) {
return this.nextAnimation.get().hasTransformFor(joint);
}
@Override
public boolean isLinkAnimation() {
return true;
}
@Override
public ConcurrentLinkAnimation get() {
return this;
}
@Override
public ResourceLocation registryName() {
return null;
}
@Override
public boolean isPresent() {
return true;
}
@Override
public int id() {
return -1;
}
@Override
public AnimationAccessor<? extends DynamicAnimation> getAccessor() {
return this;
}
@Override
public boolean inRegistry() {
return false;
}
}

View File

@@ -0,0 +1,200 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.types;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.anim.AnimationClip;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.anim.AnimationPlayer;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.TransformSheet;
import com.tiedup.remake.rig.anim.property.AnimationProperty;
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
import yesman.epicfight.main.EpicFightMod;
import yesman.epicfight.main.EpicFightSharedConstants;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public abstract class DynamicAnimation {
protected final boolean isRepeat;
protected final float transitionTime;
protected AnimationClip animationClip;
public DynamicAnimation() {
this(EpicFightSharedConstants.GENERAL_ANIMATION_TRANSITION_TIME, false);
}
public DynamicAnimation(float transitionTime, boolean isRepeat) {
this.isRepeat = isRepeat;
this.transitionTime = transitionTime;
}
public final Pose getRawPose(float time) {
return this.getAnimationClip().getPoseInTime(time);
}
public Pose getPoseByTime(LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
Pose pose = this.getRawPose(time);
this.modifyPose(this, pose, entitypatch, time, partialTicks);
return pose;
}
/** Modify the pose both this and link animation. **/
public void modifyPose(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
}
public void putOnPlayer(AnimationPlayer animationPlayer, LivingEntityPatch<?> entitypatch) {
animationPlayer.setPlayAnimation(this.getAccessor());
animationPlayer.tick(entitypatch);
animationPlayer.begin(this.getAccessor(), entitypatch);
}
/**
* Called when the animation put on the {@link AnimationPlayer}
* @param entitypatch
*/
public void begin(LivingEntityPatch<?> entitypatch) {}
/**
* Called each tick when the animation is played
* @param entitypatch
*/
public void tick(LivingEntityPatch<?> entitypatch) {}
/**
* Called when both the animation finished or stopped by other animation.
* @param entitypatch
* @param nextAnimation the next animation to play after the animation ends
* @param isEnd whether the animation completed or not
*
* if @param isEnd true, nextAnimation is null
* if @param isEnd false, nextAnimation is not null
*/
public void end(LivingEntityPatch<?> entitypatch, @Nullable AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {}
public void linkTick(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> linkAnimation) {};
public boolean hasTransformFor(String joint) {
return this.getTransfroms().containsKey(joint);
}
@OnlyIn(Dist.CLIENT)
public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
return Optional.empty();
}
public EntityState getState(LivingEntityPatch<?> entitypatch, float time) {
return EntityState.DEFAULT_STATE;
}
public TypeFlexibleHashMap<StateFactor<?>> getStatesMap(LivingEntityPatch<?> entitypatch, float time) {
return new TypeFlexibleHashMap<> (false);
}
public <T> T getState(StateFactor<T> stateFactor, LivingEntityPatch<?> entitypatch, float time) {
return stateFactor.defaultValue();
}
public AnimationClip getAnimationClip() {
return this.animationClip;
}
public Map<String, TransformSheet> getTransfroms() {
return this.getAnimationClip().getJointTransforms();
}
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
return 1.0F;
}
public TransformSheet getCoord() {
return this.getTransfroms().containsKey("Root") ? this.getTransfroms().get("Root") : TransformSheet.EMPTY_SHEET;
}
public void setTotalTime(float totalTime) {
this.getAnimationClip().setClipTime(totalTime);
}
public float getTotalTime() {
return this.getAnimationClip().getClipTime();
}
public float getTransitionTime() {
return this.transitionTime;
}
public boolean isRepeat() {
return this.isRepeat;
}
public boolean canBePlayedReverse() {
return false;
}
public ResourceLocation getRegistryName() {
return EpicFightMod.identifier("");
}
public int getId() {
return -1;
}
public <V> Optional<V> getProperty(AnimationProperty<V> propertyType) {
return Optional.empty();
}
public boolean isBasicAttackAnimation() {
return false;
}
public boolean isMainFrameAnimation() {
return false;
}
public boolean isReboundAnimation() {
return false;
}
public boolean isMetaAnimation() {
return false;
}
public boolean isClientAnimation() {
return false;
}
public boolean isStaticAnimation() {
return false;
}
public abstract <A extends DynamicAnimation> AnimationAccessor<? extends DynamicAnimation> getAccessor();
public abstract AssetAccessor<? extends StaticAnimation> getRealAnimation();
public boolean isLinkAnimation() {
return false;
}
public boolean doesHeadRotFollowEntityHead() {
return false;
}
@OnlyIn(Dist.CLIENT)
public void renderDebugging(PoseStack poseStack, MultiBufferSource buffer, LivingEntityPatch<?> entitypatch, float playTime, float partialTicks) {
}
}

View File

@@ -0,0 +1,146 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.types;
import java.util.function.Consumer;
import java.util.function.Function;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraftforge.event.entity.ProjectileImpactEvent;
import com.tiedup.remake.rig.util.AttackResult;
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
public class EntityState {
public static class StateFactor<T> implements TypeFlexibleHashMap.TypeKey<T> {
private final String name;
private final T defaultValue;
public StateFactor(String name, T defaultValue) {
this.name = name;
this.defaultValue = defaultValue;
}
public String toString() {
return this.name;
}
public T defaultValue() {
return this.defaultValue;
}
}
public static final EntityState DEFAULT_STATE = new EntityState(new TypeFlexibleHashMap<>(true));
public static final StateFactor<Boolean> TURNING_LOCKED = new StateFactor<>("turningLocked", false);
public static final StateFactor<Boolean> MOVEMENT_LOCKED = new StateFactor<>("movementLocked", false);
public static final StateFactor<Boolean> ATTACKING = new StateFactor<>("attacking", false);
public static final StateFactor<Boolean> CAN_BASIC_ATTACK = new StateFactor<>("canBasicAttack", true);
public static final StateFactor<Boolean> CAN_SKILL_EXECUTION = new StateFactor<>("canExecuteSkill", true);
public static final StateFactor<Boolean> CAN_USE_ITEM = new StateFactor<>("canUseItem", true);
public static final StateFactor<Boolean> CAN_SWITCH_HAND_ITEM = new StateFactor<>("canSwitchHandItem", true);
public static final StateFactor<Boolean> INACTION = new StateFactor<>("takingAction", false);
public static final StateFactor<Boolean> KNOCKDOWN = new StateFactor<>("knockdown", false);
public static final StateFactor<Boolean> LOCKON_ROTATE = new StateFactor<>("lockonRotate", false);
public static final StateFactor<Boolean> UPDATE_LIVING_MOTION = new StateFactor<>("updateLivingMotion", true);
public static final StateFactor<Integer> HURT_LEVEL = new StateFactor<>("hurtLevel", 0);
public static final StateFactor<Integer> PHASE_LEVEL = new StateFactor<>("phaseLevel", 0);
public static final StateFactor<Function<DamageSource, AttackResult.ResultType>> ATTACK_RESULT = new StateFactor<>("attackResultModifier", (damagesource) -> AttackResult.ResultType.SUCCESS);
public static final StateFactor<Consumer<ProjectileImpactEvent>> PROJECTILE_IMPACT_RESULT = new StateFactor<>("projectileImpactResult", (event) -> {});
private final TypeFlexibleHashMap<StateFactor<?>> stateMap;
public EntityState(TypeFlexibleHashMap<StateFactor<?>> states) {
this.stateMap = states;
}
public <T> void setState(StateFactor<T> stateFactor, T val) {
this.stateMap.put(stateFactor, (Object)val);
}
public <T> T getState(StateFactor<T> stateFactor) {
return this.stateMap.getOrDefault(stateFactor);
}
public TypeFlexibleHashMap<StateFactor<?>> getStateMap() {
return this.stateMap;
}
public boolean turningLocked() {
return this.getState(EntityState.TURNING_LOCKED);
}
public boolean movementLocked() {
return this.getState(EntityState.MOVEMENT_LOCKED);
}
public boolean attacking() {
return this.getState(EntityState.ATTACKING);
}
public AttackResult.ResultType attackResult(DamageSource damagesource) {
return this.getState(EntityState.ATTACK_RESULT).apply(damagesource);
}
public void setProjectileImpactResult(ProjectileImpactEvent event) {
this.getState(EntityState.PROJECTILE_IMPACT_RESULT).accept(event);
}
public boolean canBasicAttack() {
return this.getState(EntityState.CAN_BASIC_ATTACK);
}
public boolean canUseSkill() {
return this.getState(EntityState.CAN_SKILL_EXECUTION);
}
public boolean canUseItem() {
return this.canUseSkill() && this.getState(EntityState.CAN_USE_ITEM);
}
public boolean canSwitchHoldingItem() {
return !this.inaction() && this.getState(EntityState.CAN_SWITCH_HAND_ITEM);
}
public boolean inaction() {
return this.getState(EntityState.INACTION);
}
public boolean updateLivingMotion() {
return this.getState(EntityState.UPDATE_LIVING_MOTION);
}
public boolean hurt() {
return this.getState(EntityState.HURT_LEVEL) > 0;
}
public int hurtLevel() {
return this.getState(EntityState.HURT_LEVEL);
}
public boolean knockDown() {
return this.getState(EntityState.KNOCKDOWN);
}
public boolean lockonRotate() {
return this.getState(EntityState.LOCKON_ROTATE);
}
/**
* 1: anticipation
* 2: attacking
* 3: recovery
* @return level
*/
public int getLevel() {
return this.getState(EntityState.PHASE_LEVEL);
}
@Override
public String toString() {
return this.stateMap.toString();
}
}

View File

@@ -0,0 +1,124 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.types;
import java.util.Optional;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.anim.AnimationClip;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.property.AnimationProperty;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.anim.client.Layer.Priority;
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
import yesman.epicfight.gameasset.Animations;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
@OnlyIn(Dist.CLIENT)
public class LayerOffAnimation extends DynamicAnimation implements AnimationAccessor<LayerOffAnimation> {
private AssetAccessor<? extends DynamicAnimation> lastAnimation;
private Pose lastPose;
private final Priority layerPriority;
public LayerOffAnimation(Priority layerPriority) {
this.layerPriority = layerPriority;
this.animationClip = new AnimationClip();
}
public void setLastPose(Pose pose) {
this.lastPose = pose;
}
@Override
public void end(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {
if (entitypatch.isLogicalClient() && isEnd) {
entitypatch.getClientAnimator().baseLayer.disableLayer(this.layerPriority);
}
}
@Override
public Pose getPoseByTime(LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
Pose lowerLayerPose = entitypatch.getClientAnimator().getComposedLayerPoseBelow(this.layerPriority, Minecraft.getInstance().getFrameTime());
Pose interpolatedPose = Pose.interpolatePose(this.lastPose, lowerLayerPose, time / this.getTotalTime());
interpolatedPose.disableJoint((joint) -> !this.lastPose.hasTransform(joint.getKey()));
return interpolatedPose;
}
@Override
public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
return this.lastAnimation.get().getJointMaskEntry(entitypatch, useCurrentMotion);
}
@Override
public <V> Optional<V> getProperty(AnimationProperty<V> propertyType) {
return this.lastAnimation.get().getProperty(propertyType);
}
public void setLastAnimation(AssetAccessor<? extends DynamicAnimation> animation) {
this.lastAnimation = animation;
}
@Override
public boolean doesHeadRotFollowEntityHead() {
return this.lastAnimation.get().doesHeadRotFollowEntityHead();
}
@Override
public AssetAccessor<? extends StaticAnimation> getRealAnimation() {
return Animations.EMPTY_ANIMATION;
}
@Override
public AnimationClip getAnimationClip() {
return this.animationClip;
}
@Override
public boolean hasTransformFor(String joint) {
return this.lastPose.hasTransform(joint);
}
@Override
public boolean isLinkAnimation() {
return true;
}
@Override
public LayerOffAnimation get() {
return this;
}
@Override
public ResourceLocation registryName() {
return null;
}
@Override
public boolean isPresent() {
return true;
}
@Override
public int id() {
return -1;
}
@Override
public AnimationAccessor<? extends LayerOffAnimation> getAccessor() {
return this;
}
@Override
public boolean inRegistry() {
return false;
}
}

View File

@@ -0,0 +1,244 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.types;
import java.util.Map;
import java.util.Optional;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.anim.AnimationClip;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.armature.JointTransform;
import com.tiedup.remake.rig.anim.Keyframe;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.TransformSheet;
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public class LinkAnimation extends DynamicAnimation implements AnimationAccessor<LinkAnimation> {
protected TransformSheet coord;
protected AssetAccessor<? extends DynamicAnimation> fromAnimation;
protected AssetAccessor<? extends StaticAnimation> toAnimation;
protected float nextStartTime;
public LinkAnimation() {
this.animationClip = new AnimationClip();
}
@Override
public void tick(LivingEntityPatch<?> entitypatch) {
this.toAnimation.get().linkTick(entitypatch, this);
}
@Override
public void end(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {
if (!isEnd) {
this.toAnimation.get().end(entitypatch, nextAnimation, isEnd);
} else {
if (this.nextStartTime > 0.0F) {
entitypatch.getAnimator().getPlayer(this).ifPresent(player -> {
player.setElapsedTime(this.nextStartTime);
player.markDoNotResetTime();
});
}
}
}
@Override
public TypeFlexibleHashMap<StateFactor<?>> getStatesMap(LivingEntityPatch<?> entitypatch, float time) {
float timeInRealAnimation = Math.max(time - (this.getTotalTime() - this.nextStartTime), 0.0F);
TypeFlexibleHashMap<StateFactor<?>> map = this.toAnimation.get().getStatesMap(entitypatch, timeInRealAnimation);
for (Map.Entry<StateFactor<?>, Object> entry : map.entrySet()) {
Object val = this.toAnimation.get().getModifiedLinkState(entry.getKey(), entry.getValue(), entitypatch, time);
map.put(entry.getKey(), val);
}
return map;
}
@Override
public EntityState getState(LivingEntityPatch<?> entitypatch, float time) {
float timeInRealAnimation = Math.max(time - (this.getTotalTime() - this.nextStartTime), 0.0F);
EntityState state = this.toAnimation.get().getState(entitypatch, timeInRealAnimation);
TypeFlexibleHashMap<StateFactor<?>> map = state.getStateMap();
for (Map.Entry<StateFactor<?>, Object> entry : map.entrySet()) {
Object val = this.toAnimation.get().getModifiedLinkState(entry.getKey(), entry.getValue(), entitypatch, time);
map.put(entry.getKey(), val);
}
return state;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getState(StateFactor<T> stateFactor, LivingEntityPatch<?> entitypatch, float time) {
float timeInRealAnimation = Math.max(time - (this.getTotalTime() - this.nextStartTime), 0.0F);
T state = this.toAnimation.get().getState(stateFactor, entitypatch, timeInRealAnimation);
return (T)this.toAnimation.get().getModifiedLinkState(stateFactor, state, entitypatch, time);
}
@Override
public Pose getPoseByTime(LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
Pose nextStartingPose = this.toAnimation.get().getPoseByTime(entitypatch, this.nextStartTime, partialTicks);
/**
* Update dest pose
*/
for (Map.Entry<String, JointTransform> entry : nextStartingPose.getJointTransformData().entrySet()) {
if (this.animationClip.hasJointTransform(entry.getKey())) {
Keyframe[] keyframe = this.animationClip.getJointTransform(entry.getKey()).getKeyframes();
JointTransform jt = keyframe[keyframe.length - 1].transform();
JointTransform newJt = nextStartingPose.getJointTransformData().get(entry.getKey());
newJt.translation().set(jt.translation());
jt.copyFrom(newJt);
}
}
return super.getPoseByTime(entitypatch, time, partialTicks);
}
@Override
public void modifyPose(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
// Bad implementation: Add root joint as coord in loading animation
if (this.toAnimation.get() instanceof ActionAnimation actionAnimation) {
if (!this.getTransfroms().containsKey("Coord")) {
actionAnimation.correctRootJoint(this, pose, entitypatch, time, partialTicks);
}
}
}
@Override
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
return this.toAnimation.get().getPlaySpeed(entitypatch, animation);
}
public void setConnectedAnimations(AssetAccessor<? extends DynamicAnimation> from, AssetAccessor<? extends StaticAnimation> to) {
this.fromAnimation = from.get().getRealAnimation();
this.toAnimation = to;
}
public AssetAccessor<? extends StaticAnimation> getNextAnimation() {
return this.toAnimation;
}
@Override
public TransformSheet getCoord() {
if (this.coord != null) {
return this.coord;
} else if (this.getTransfroms().containsKey("Root")) {
return this.getTransfroms().get("Root");
}
return TransformSheet.EMPTY_SHEET;
}
@OnlyIn(Dist.CLIENT)
public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
return useCurrentMotion ? this.toAnimation.get().getJointMaskEntry(entitypatch, true) : this.fromAnimation.get().getJointMaskEntry(entitypatch, false);
}
@Override
public boolean isMainFrameAnimation() {
return this.toAnimation.get().isMainFrameAnimation();
}
@Override
public boolean isReboundAnimation() {
return this.toAnimation.get().isReboundAnimation();
}
@Override
public boolean doesHeadRotFollowEntityHead() {
return this.fromAnimation.get().doesHeadRotFollowEntityHead() && this.toAnimation.get().doesHeadRotFollowEntityHead();
}
@Override
public AssetAccessor<? extends StaticAnimation> getRealAnimation() {
return this.toAnimation;
}
public AssetAccessor<? extends DynamicAnimation> getFromAnimation() {
return this.fromAnimation;
}
@Override
public AnimationAccessor<? extends DynamicAnimation> getAccessor() {
return this;
}
public void copyTo(LinkAnimation dest) {
dest.setConnectedAnimations(this.fromAnimation, this.toAnimation);
dest.setTotalTime(this.getTotalTime());
dest.getAnimationClip().reset();
this.getTransfroms().forEach((jointName, transformSheet) -> dest.getAnimationClip().addJointTransform(jointName, transformSheet.copyAll()));
}
public void loadCoord(TransformSheet coord) {
this.coord = coord;
}
public float getNextStartTime() {
return this.nextStartTime;
}
public void setNextStartTime(float nextStartTime) {
this.nextStartTime = nextStartTime;
}
public void resetNextStartTime() {
this.nextStartTime = 0.0F;
}
@Override
public boolean isLinkAnimation() {
return true;
}
@Override
public String toString() {
return "From " + this.fromAnimation + " to " + this.toAnimation;
}
@Override
public AnimationClip getAnimationClip() {
return this.animationClip;
}
@Override
public LinkAnimation get() {
return this;
}
@Override
public ResourceLocation registryName() {
return null;
}
@Override
public boolean isPresent() {
return true;
}
@Override
public int id() {
return -1;
}
@Override
public boolean inRegistry() {
return false;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.types;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.anim.property.AnimationProperty.StaticAnimationProperty;
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.anim.client.Layer;
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
import com.tiedup.remake.rig.armature.Armature;
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
import com.tiedup.remake.rig.patch.PlayerPatch;
import com.tiedup.remake.rig.patch.ServerPlayerPatch;
import yesman.epicfight.world.entity.eventlistener.ActionEvent;
import yesman.epicfight.world.entity.eventlistener.PlayerEventListener.EventType;
public class MainFrameAnimation extends StaticAnimation {
public MainFrameAnimation(float convertTime, AnimationAccessor<? extends MainFrameAnimation> accessor, AssetAccessor<? extends Armature> armature) {
super(convertTime, false, accessor, armature);
}
public MainFrameAnimation(float convertTime, String path, AssetAccessor<? extends Armature> armature) {
super(convertTime, false, path, armature);
}
@Override
public void begin(LivingEntityPatch<?> entitypatch) {
if (entitypatch.getAnimator().getPlayerFor(null).getAnimation().get() == this) {
TypeFlexibleHashMap<StateFactor<?>> stateMap = this.stateSpectrum.getStateMap(entitypatch, 0.0F);
TypeFlexibleHashMap<StateFactor<?>> modifiedStateMap = new TypeFlexibleHashMap<> (false);
stateMap.forEach((k, v) -> modifiedStateMap.put(k, this.getModifiedLinkState(k, v, entitypatch, 0.0F)));
entitypatch.updateEntityState(new EntityState(modifiedStateMap));
}
if (entitypatch.isLogicalClient()) {
entitypatch.updateMotion(false);
this.getProperty(StaticAnimationProperty.RESET_LIVING_MOTION).ifPresentOrElse(livingMotion -> {
entitypatch.getClientAnimator().forceResetBeforeAction(livingMotion, livingMotion);
}, () -> {
entitypatch.getClientAnimator().resetMotion(true);
entitypatch.getClientAnimator().resetCompositeMotion();
});
entitypatch.getClientAnimator().getPlayerFor(this.getAccessor()).setReversed(false);
}
super.begin(entitypatch);
if (entitypatch instanceof PlayerPatch<?> playerpatch) {
if (playerpatch.isLogicalClient()) {
if (playerpatch.getOriginal().isLocalPlayer()) {
playerpatch.getEventListener().triggerEvents(EventType.ACTION_EVENT_CLIENT, new ActionEvent<>(playerpatch, this.getAccessor()));
}
} else {
ActionEvent<ServerPlayerPatch> actionEvent = new ActionEvent<>(playerpatch, this.getAccessor());
playerpatch.getEventListener().triggerEvents(EventType.ACTION_EVENT_SERVER, actionEvent);
if (actionEvent.shouldResetActionTick()) {
playerpatch.resetActionTick();
}
}
}
}
@Override
public void tick(LivingEntityPatch<?> entitypatch) {
super.tick(entitypatch);
if (entitypatch.getEntityState().movementLocked()) {
entitypatch.getOriginal().walkAnimation.setSpeed(0);
}
}
@Override
public boolean isMainFrameAnimation() {
return true;
}
@Override
@OnlyIn(Dist.CLIENT)
public Layer.Priority getPriority() {
return this.getProperty(ClientAnimationProperties.PRIORITY).orElse(Layer.Priority.HIGHEST);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.types;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.armature.Armature;
import yesman.epicfight.main.EpicFightSharedConstants;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
public class MovementAnimation extends StaticAnimation {
public MovementAnimation(boolean isRepeat, AnimationAccessor<? extends MovementAnimation> accessor, AssetAccessor<? extends Armature> armature) {
super(EpicFightSharedConstants.GENERAL_ANIMATION_TRANSITION_TIME, isRepeat, accessor, armature);
}
public MovementAnimation(float transitionTime, boolean isRepeat, AnimationAccessor<? extends MovementAnimation> accessor, AssetAccessor<? extends Armature> armature) {
super(transitionTime, isRepeat, accessor, armature);
}
/**
* For datapack animations
*/
public MovementAnimation(float transitionTime, boolean isRepeat, String path, AssetAccessor<? extends Armature> armature) {
super(transitionTime, isRepeat, path, armature);
}
@Override
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
if (animation.isLinkAnimation()) {
return 1.0F;
}
float movementSpeed = 1.0F;
if (Math.abs(entitypatch.getOriginal().walkAnimation.speed() - entitypatch.getOriginal().walkAnimation.speed(1)) < 0.007F) {
movementSpeed *= (entitypatch.getOriginal().walkAnimation.speed() * 1.16F);
}
return movementSpeed;
}
@Override
public boolean canBePlayedReverse() {
return true;
}
}

View File

@@ -0,0 +1,646 @@
/*
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
* by the Epic Fight Team, licensed under GPLv3.
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.anim.types;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import com.google.common.collect.Maps;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import io.netty.util.internal.StringUtil;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.tiedup.remake.rig.anim.AnimationClip;
import com.tiedup.remake.rig.anim.AnimationManager;
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
import com.tiedup.remake.rig.anim.AnimationVariables;
import com.tiedup.remake.rig.anim.AnimationVariables.IndependentAnimationVariableKey;
import com.tiedup.remake.rig.armature.JointTransform;
import com.tiedup.remake.rig.anim.Keyframe;
import com.tiedup.remake.rig.anim.Pose;
import com.tiedup.remake.rig.anim.TransformSheet;
import com.tiedup.remake.rig.anim.property.AnimationEvent;
import com.tiedup.remake.rig.anim.property.AnimationEvent.SimpleEvent;
import com.tiedup.remake.rig.anim.property.AnimationParameters;
import com.tiedup.remake.rig.anim.property.AnimationProperty;
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
import com.tiedup.remake.rig.anim.property.AnimationProperty.PlaybackSpeedModifier;
import com.tiedup.remake.rig.anim.property.AnimationProperty.StaticAnimationProperty;
import com.tiedup.remake.rig.anim.types.EntityState.StateFactor;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.asset.JsonAssetLoader;
import com.tiedup.remake.rig.anim.client.Layer;
import com.tiedup.remake.rig.anim.client.Layer.LayerType;
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
import com.tiedup.remake.rig.anim.client.property.JointMaskEntry;
import com.tiedup.remake.rig.anim.client.property.TrailInfo;
import com.tiedup.remake.rig.exception.AssetLoadingException;
import com.tiedup.remake.rig.armature.Armature;
import yesman.epicfight.api.physics.ik.InverseKinematicsProvider;
import yesman.epicfight.api.physics.ik.InverseKinematicsSimulatable;
import yesman.epicfight.api.physics.ik.InverseKinematicsSimulator;
import yesman.epicfight.api.physics.ik.InverseKinematicsSimulator.BakedInverseKinematicsDefinition;
import yesman.epicfight.api.physics.ik.InverseKinematicsSimulator.InverseKinematicsObject;
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap;
import com.tiedup.remake.rig.math.OpenMatrix4f;
import com.tiedup.remake.rig.math.Vec3f;
import yesman.epicfight.client.ClientEngine;
import com.tiedup.remake.rig.render.TiedUpRenderTypes;
import yesman.epicfight.client.renderer.RenderingTool;
import com.tiedup.remake.rig.render.item.RenderItemBase;
import yesman.epicfight.gameasset.Animations;
import yesman.epicfight.main.EpicFightMod;
import yesman.epicfight.main.EpicFightSharedConstants;
import com.tiedup.remake.rig.patch.LivingEntityPatch;
import com.tiedup.remake.rig.patch.PlayerPatch;
import yesman.epicfight.world.entity.eventlistener.AnimationBeginEvent;
import yesman.epicfight.world.entity.eventlistener.AnimationEndEvent;
import yesman.epicfight.world.entity.eventlistener.PlayerEventListener.EventType;
public class StaticAnimation extends DynamicAnimation implements InverseKinematicsProvider {
public static final IndependentAnimationVariableKey<Boolean> HAD_NO_PHYSICS = AnimationVariables.independent((animator) -> false, true);
public static String getFileHash(ResourceLocation rl) {
String fileHash;
try {
JsonAssetLoader jsonfile = new JsonAssetLoader(AnimationManager.getAnimationResourceManager(), rl);
fileHash = jsonfile.getFileHash();
} catch (AssetLoadingException e) {
fileHash = StringUtil.EMPTY_STRING;
}
return fileHash;
}
protected final Map<AnimationProperty<?>, Object> properties = Maps.newHashMap();
/**
* States will bind into animation on {@link AnimationManager#apply}
*/
protected final StateSpectrum.Blueprint stateSpectrumBlueprint = new StateSpectrum.Blueprint();
protected final StateSpectrum stateSpectrum = new StateSpectrum();
protected final AssetAccessor<? extends Armature> armature;
protected ResourceLocation resourceLocation;
protected AnimationAccessor<? extends StaticAnimation> accessor;
private final String filehash;
public StaticAnimation() {
super(0.0F, true);
this.resourceLocation = EpicFightMod.identifier("emtpy");
this.armature = null;
this.filehash = StringUtil.EMPTY_STRING;
}
public StaticAnimation(boolean isRepeat, AnimationAccessor<? extends StaticAnimation> accessor, AssetAccessor<? extends Armature> armature) {
this(EpicFightSharedConstants.GENERAL_ANIMATION_TRANSITION_TIME, isRepeat, accessor, armature);
}
public StaticAnimation(float transitionTime, boolean isRepeat, AnimationAccessor<? extends StaticAnimation> accessor, AssetAccessor<? extends Armature> armature) {
super(transitionTime, isRepeat);
this.resourceLocation = ResourceLocation.fromNamespaceAndPath(accessor.registryName().getNamespace(), "animmodels/animations/" + accessor.registryName().getPath() + ".json");
this.armature = armature;
this.accessor = accessor;
this.filehash = getFileHash(this.resourceLocation);
}
/* Resourcepack animations */
public StaticAnimation(float transitionTime, boolean isRepeat, String path, AssetAccessor<? extends Armature> armature) {
super(transitionTime, isRepeat);
ResourceLocation registryName = ResourceLocation.parse(path);
this.resourceLocation = ResourceLocation.fromNamespaceAndPath(registryName.getNamespace(), "animmodels/animations/" + registryName.getPath() + ".json");
this.armature = armature;
this.filehash = StringUtil.EMPTY_STRING;
}
/* Multilayer Constructor */
public StaticAnimation(ResourceLocation fileLocation, float transitionTime, boolean isRepeat, String registryName, AssetAccessor<? extends Armature> armature) {
super(transitionTime, isRepeat);
this.resourceLocation = fileLocation;
this.armature = armature;
this.filehash = StringUtil.EMPTY_STRING;
}
public void loadAnimation() {
if (!this.isMetaAnimation()) {
if (this.properties.containsKey(StaticAnimationProperty.IK_DEFINITION)) {
this.animationClip = AnimationManager.getInstance().loadAnimationClip(this, JsonAssetLoader::loadAllJointsClipForAnimation);
this.getProperty(StaticAnimationProperty.IK_DEFINITION).ifPresent(ikDefinitions -> {
boolean correctY = this.getProperty(ActionAnimationProperty.MOVE_VERTICAL).orElse(false);
boolean correctZ = this.isMainFrameAnimation();
List<BakedInverseKinematicsDefinition> bakedIKDefinitionList = ikDefinitions.stream().map(ikDefinition -> ikDefinition.bake(this.armature, this.animationClip.getJointTransforms(), correctY, correctZ)).toList();
this.addProperty(StaticAnimationProperty.BAKED_IK_DEFINITION, bakedIKDefinitionList);
// Remove the unbaked data
this.properties.remove(StaticAnimationProperty.IK_DEFINITION);
});
} else {
this.animationClip = AnimationManager.getInstance().loadAnimationClip(this, JsonAssetLoader::loadClipForAnimation);
}
this.animationClip.bakeKeyframes();
}
}
public void postInit() {
this.stateSpectrum.readFrom(this.stateSpectrumBlueprint);
}
@Override
public AnimationClip getAnimationClip() {
if (this.animationClip == null) {
this.loadAnimation();
}
return this.animationClip;
}
public void setLinkAnimation(final AssetAccessor<? extends DynamicAnimation> fromAnimation, Pose startPose, boolean isOnSameLayer, float transitionTimeModifier, LivingEntityPatch<?> entitypatch, LinkAnimation dest) {
if (!entitypatch.isLogicalClient()) {
startPose = Animations.EMPTY_ANIMATION.getPoseByTime(entitypatch, 0.0F, 1.0F);
}
dest.resetNextStartTime();
float playTime = this.getPlaySpeed(entitypatch, dest);
PlaybackSpeedModifier playSpeedModifier = this.getRealAnimation().get().getProperty(StaticAnimationProperty.PLAY_SPEED_MODIFIER).orElse(null);
if (playSpeedModifier != null) {
playTime = playSpeedModifier.modify(dest, entitypatch, playTime, 0.0F, playTime);
}
playTime = Math.abs(playTime);
playTime *= EpicFightSharedConstants.A_TICK;
float linkTime = transitionTimeModifier > 0.0F ? transitionTimeModifier + this.transitionTime : this.transitionTime;
float totalTime = playTime * (int)Math.ceil(linkTime / playTime);
float nextStartTime = Math.max(0.0F, -transitionTimeModifier);
nextStartTime += totalTime - linkTime;
dest.setNextStartTime(nextStartTime);
dest.getAnimationClip().reset();
dest.setTotalTime(totalTime);
dest.setConnectedAnimations(fromAnimation, this.getAccessor());
Map<String, JointTransform> data1 = startPose.getJointTransformData();
Map<String, JointTransform> data2 = this.getPoseByTime(entitypatch, nextStartTime, 0.0F).getJointTransformData();
Set<String> joint1 = new HashSet<> (isOnSameLayer ? data1.keySet() : Set.of());
Set<String> joint2 = new HashSet<> (data2.keySet());
if (entitypatch.isLogicalClient()) {
JointMaskEntry entry = fromAnimation.get().getJointMaskEntry(entitypatch, false).orElse(null);
JointMaskEntry entry2 = this.getJointMaskEntry(entitypatch, true).orElse(null);
if (entry != null) {
joint1.removeIf((jointName) -> entry.isMasked(fromAnimation.get().getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(Layer.LayerType.BASE_LAYER) == Layer.LayerType.BASE_LAYER ?
entitypatch.getClientAnimator().currentMotion() : entitypatch.getClientAnimator().currentCompositeMotion(), jointName));
}
if (entry2 != null) {
joint2.removeIf((jointName) -> entry2.isMasked(this.getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(Layer.LayerType.BASE_LAYER) == Layer.LayerType.BASE_LAYER ?
entitypatch.getCurrentLivingMotion() : entitypatch.currentCompositeMotion, jointName));
}
}
joint1.addAll(joint2);
if (linkTime != totalTime) {
Map<String, JointTransform> firstPose = this.getPoseByTime(entitypatch, 0.0F, 0.0F).getJointTransformData();
for (String jointName : joint1) {
Keyframe[] keyframes = new Keyframe[3];
keyframes[0] = new Keyframe(0.0F, data1.get(jointName));
keyframes[1] = new Keyframe(linkTime, firstPose.get(jointName));
keyframes[2] = new Keyframe(totalTime, data2.get(jointName));
TransformSheet sheet = new TransformSheet(keyframes);
dest.getAnimationClip().addJointTransform(jointName, sheet);
}
} else {
for (String jointName : joint1) {
Keyframe[] keyframes = new Keyframe[2];
keyframes[0] = new Keyframe(0.0F, data1.get(jointName));
keyframes[1] = new Keyframe(totalTime, data2.get(jointName));
TransformSheet sheet = new TransformSheet(keyframes);
dest.getAnimationClip().addJointTransform(jointName, sheet);
}
}
}
@Override
public void begin(LivingEntityPatch<?> entitypatch) {
// Load if null
this.getAnimationClip();
// Please fix this implementation when minecraft supports any mixinable method that returns noPhysics variable
this.getProperty(StaticAnimationProperty.NO_PHYSICS).ifPresent(val -> {
if (val) {
entitypatch.getAnimator().getVariables().put(HAD_NO_PHYSICS, this.getAccessor(), entitypatch.getOriginal().noPhysics);
entitypatch.getOriginal().noPhysics = true;
}
});
if (entitypatch.isLogicalClient()) {
this.getProperty(ClientAnimationProperties.TRAIL_EFFECT).ifPresent(trailInfos -> {
int idx = 0;
for (TrailInfo trailInfo : trailInfos) {
double eid = Double.longBitsToDouble((long)entitypatch.getOriginal().getId());
double animid = Double.longBitsToDouble((long)this.getId());
double jointId = Double.longBitsToDouble((long)this.armature.get().searchJointByName(trailInfo.joint()).getId());
double index = Double.longBitsToDouble((long)idx++);
if (trailInfo.hand() != null) {
RenderItemBase renderitembase = ClientEngine.getInstance().renderEngine.getItemRenderer(entitypatch.getAdvancedHoldingItemStack(trailInfo.hand()));
if (renderitembase != null && renderitembase.trailInfo() != null) {
trailInfo = renderitembase.trailInfo().overwrite(trailInfo);
}
}
if (!trailInfo.playable()) {
continue;
}
entitypatch.getOriginal().level().addParticle(trailInfo.particle(), eid, 0, animid, jointId, index, 0);
}
});
}
this.getProperty(StaticAnimationProperty.ON_BEGIN_EVENTS).ifPresent(events -> {
for (SimpleEvent<?> event : events) {
event.execute(entitypatch, this.getAccessor(), 0.0F, 0.0F);
}
});
if (entitypatch instanceof PlayerPatch<?> playerpatch) {
playerpatch.getEventListener().triggerEvents(EventType.ANIMATION_BEGIN_EVENT, new AnimationBeginEvent(playerpatch, this));
}
}
@Override
public void end(LivingEntityPatch<?> entitypatch, AssetAccessor<? extends DynamicAnimation> nextAnimation, boolean isEnd) {
if (entitypatch instanceof PlayerPatch<?> playerpatch) {
playerpatch.getEventListener().triggerEvents(EventType.ANIMATION_END_EVENT, new AnimationEndEvent(playerpatch, this, isEnd));
}
this.getProperty(StaticAnimationProperty.ON_END_EVENTS).ifPresent((events) -> {
for (SimpleEvent<?> event : events) {
event.executeWithNewParams(entitypatch, this.getAccessor(), this.getTotalTime(), this.getTotalTime(), event.getParameters() == null ? AnimationParameters.of(isEnd) : AnimationParameters.addParameter(event.getParameters(), isEnd));
}
});
this.getProperty(StaticAnimationProperty.NO_PHYSICS).ifPresent((val) -> {
if (val) {
entitypatch.getOriginal().noPhysics = entitypatch.getAnimator().getVariables().getOrDefault(HAD_NO_PHYSICS, this.getAccessor());
}
});
entitypatch.getAnimator().getVariables().removeAll(this.getAccessor());
}
@Override
public void tick(LivingEntityPatch<?> entitypatch) {
this.getProperty(StaticAnimationProperty.NO_PHYSICS).ifPresent((val) -> {
if (val) {
entitypatch.getOriginal().noPhysics = true;
}
});
this.getProperty(StaticAnimationProperty.TICK_EVENTS).ifPresent((events) -> {
entitypatch.getAnimator().getPlayer(this.getAccessor()).ifPresent(player -> {
for (AnimationEvent<?, ?> event : events) {
float prevElapsed = player.getPrevElapsedTime();
float elapsed = player.getElapsedTime();
event.execute(entitypatch, this.getAccessor(), prevElapsed, elapsed);
}
});
});
}
@Override
public EntityState getState(LivingEntityPatch<?> entitypatch, float time) {
return new EntityState(this.getStatesMap(entitypatch, time));
}
@Override
public TypeFlexibleHashMap<StateFactor<?>> getStatesMap(LivingEntityPatch<?> entitypatch, float time) {
return this.stateSpectrum.getStateMap(entitypatch, time);
}
@Override
public <T> T getState(StateFactor<T> stateFactor, LivingEntityPatch<?> entitypatch, float time) {
return this.stateSpectrum.getSingleState(stateFactor, entitypatch, time);
}
@Override
public Optional<JointMaskEntry> getJointMaskEntry(LivingEntityPatch<?> entitypatch, boolean useCurrentMotion) {
return this.getProperty(ClientAnimationProperties.JOINT_MASK);
}
@Override
public void modifyPose(DynamicAnimation animation, Pose pose, LivingEntityPatch<?> entitypatch, float time, float partialTicks) {
entitypatch.poseTick(animation, pose, time, partialTicks);
this.getProperty(StaticAnimationProperty.POSE_MODIFIER).ifPresent((poseModifier) -> {
poseModifier.modify(animation, pose, entitypatch, time, partialTicks);
});
}
@Override
public boolean isStaticAnimation() {
return true;
}
@Override
public boolean doesHeadRotFollowEntityHead() {
return !this.getProperty(StaticAnimationProperty.FIXED_HEAD_ROTATION).orElse(false);
}
@Override
public int getId() {
return this.accessor.id();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof StaticAnimation staticAnimation) {
if (this.accessor != null && staticAnimation.accessor != null) {
return this.getId() == staticAnimation.getId();
}
}
return super.equals(obj);
}
public boolean idBetween(StaticAnimation a1, StaticAnimation a2) {
return a1.getId() <= this.getId() && a2.getId() >= this.getId();
}
public boolean in(StaticAnimation[] animations) {
for (StaticAnimation animation : animations) {
if (this.equals(animation)) {
return true;
}
}
return false;
}
public boolean in(AnimationAccessor<? extends DynamicAnimation>[] animationProviders) {
for (AnimationAccessor<? extends DynamicAnimation> animationProvider : animationProviders) {
if (this.equals(animationProvider.get())) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
public <A extends StaticAnimation> A setResourceLocation(String namespace, String path) {
this.resourceLocation = ResourceLocation.fromNamespaceAndPath(namespace, "animmodels/animations/" + path + ".json");
return (A)this;
}
public ResourceLocation getLocation() {
return this.resourceLocation;
}
@Override
public ResourceLocation getRegistryName() {
return this.accessor.registryName();
}
public AssetAccessor<? extends Armature> getArmature() {
return this.armature;
}
public String getFileHash() {
return this.filehash;
}
@Override
public float getPlaySpeed(LivingEntityPatch<?> entitypatch, DynamicAnimation animation) {
return 1.0F;
}
@Override
public TransformSheet getCoord() {
return this.getProperty(ActionAnimationProperty.COORD).orElse(super.getCoord());
}
@Override
public String toString() {
String classPath = this.getClass().toString();
return classPath.substring(classPath.lastIndexOf(".") + 1) + " " + this.getLocation();
}
/**
* Internal use only
*/
@Deprecated
public StaticAnimation addPropertyUnsafe(AnimationProperty<?> propertyType, Object value) {
this.properties.put(propertyType, value);
this.getSubAnimations().forEach((subAnimation) -> subAnimation.get().addPropertyUnsafe(propertyType, value));
return this;
}
@SuppressWarnings("unchecked")
public <A extends StaticAnimation, V> A addProperty(StaticAnimationProperty<V> propertyType, V value) {
this.properties.put(propertyType, value);
this.getSubAnimations().forEach((subAnimation) -> subAnimation.get().addProperty(propertyType, value));
return (A)this;
}
@SuppressWarnings("unchecked")
public <A extends StaticAnimation> A removeProperty(StaticAnimationProperty<?> propertyType) {
this.properties.remove(propertyType);
this.getSubAnimations().forEach((subAnimation) -> subAnimation.get().removeProperty(propertyType));
return (A)this;
}
@SafeVarargs
@SuppressWarnings("unchecked")
public final <A extends StaticAnimation> A addEvents(StaticAnimationProperty<?> key, AnimationEvent<?, ?>... events) {
this.properties.computeIfPresent(key, (k, v) -> {
return Stream.concat(((Collection<?>)v).stream(), List.of(events).stream()).toList();
});
this.properties.computeIfAbsent(key, (k) -> {
return List.of(events);
});
this.getSubAnimations().forEach((subAnimation) -> subAnimation.get().addEvents(key, events));
return (A)this;
}
@SuppressWarnings("unchecked")
public <A extends StaticAnimation> A addEvents(AnimationEvent<?, ?>... events) {
this.properties.computeIfPresent(StaticAnimationProperty.TICK_EVENTS, (k, v) -> {
return Stream.concat(((Collection<?>)v).stream(), List.of(events).stream()).toList();
});
this.properties.computeIfAbsent(StaticAnimationProperty.TICK_EVENTS, (k) -> {
return List.of(events);
});
this.getSubAnimations().forEach((subAnimation) -> subAnimation.get().addEvents(events));
return (A)this;
}
@SuppressWarnings("unchecked")
@Override
public <V> Optional<V> getProperty(AnimationProperty<V> propertyType) {
return (Optional<V>) Optional.ofNullable(this.properties.get(propertyType));
}
@OnlyIn(Dist.CLIENT)
public Layer.Priority getPriority() {
return this.getProperty(ClientAnimationProperties.PRIORITY).orElse(Layer.Priority.LOWEST);
}
@OnlyIn(Dist.CLIENT)
public Layer.LayerType getLayerType() {
return this.getProperty(ClientAnimationProperties.LAYER_TYPE).orElse(LayerType.BASE_LAYER);
}
@SuppressWarnings("unchecked")
public <A extends StaticAnimation> A newTimePair(float start, float end) {
this.stateSpectrumBlueprint.newTimePair(start, end);
return (A)this;
}
@SuppressWarnings("unchecked")
public <A extends StaticAnimation> A newConditionalTimePair(Function<LivingEntityPatch<?>, Integer> condition, float start, float end) {
this.stateSpectrumBlueprint.newConditionalTimePair(condition, start, end);
return (A)this;
}
@SuppressWarnings("unchecked")
public <T, A extends StaticAnimation> A addState(StateFactor<T> factor, T val) {
this.stateSpectrumBlueprint.addState(factor, val);
return (A)this;
}
@SuppressWarnings("unchecked")
public <T, A extends StaticAnimation> A removeState(StateFactor<T> factor) {
this.stateSpectrumBlueprint.removeState(factor);
return (A)this;
}
@SuppressWarnings("unchecked")
public <T, A extends StaticAnimation> A addConditionalState(int metadata, StateFactor<T> factor, T val) {
this.stateSpectrumBlueprint.addConditionalState(metadata, factor, val);
return (A)this;
}
@SuppressWarnings("unchecked")
public <T, A extends StaticAnimation> A addStateRemoveOld(StateFactor<T> factor, T val) {
this.stateSpectrumBlueprint.addStateRemoveOld(factor, val);
return (A)this;
}
@SuppressWarnings("unchecked")
public <T, A extends StaticAnimation> A addStateIfNotExist(StateFactor<T> factor, T val) {
this.stateSpectrumBlueprint.addStateIfNotExist(factor, val);
return (A)this;
}
public Object getModifiedLinkState(StateFactor<?> factor, Object val, LivingEntityPatch<?> entitypatch, float elapsedTime) {
return val;
}
public List<AssetAccessor<? extends StaticAnimation>> getSubAnimations() {
return List.of();
}
@Override
public AnimationAccessor<? extends StaticAnimation> getRealAnimation() {
return this.getAccessor();
}
@SuppressWarnings("unchecked")
@Override
public <A extends DynamicAnimation> AnimationAccessor<A> getAccessor() {
return (AnimationAccessor<A>)this.accessor;
}
public void setAccessor(AnimationAccessor<? extends StaticAnimation> accessor) {
this.accessor = accessor;
}
public void invalidate() {
this.accessor = null;
}
public boolean isInvalid() {
return this.accessor == null;
}
@OnlyIn(Dist.CLIENT)
public void renderDebugging(PoseStack poseStack, MultiBufferSource buffer, LivingEntityPatch<?> entitypatch, float playTime, float partialTicks) {
if (entitypatch instanceof InverseKinematicsSimulatable ikSimulatable) {
this.getProperty(StaticAnimationProperty.BAKED_IK_DEFINITION).ifPresent((ikDefinitions) -> {
OpenMatrix4f modelmat = ikSimulatable.getModelMatrix(partialTicks);
LivingEntity originalEntity = entitypatch.getOriginal();
Vec3 entitypos = originalEntity.position();
float x = (float)entitypos.x;
float y = (float)entitypos.y;
float z = (float)entitypos.z;
float xo = (float)originalEntity.xo;
float yo = (float)originalEntity.yo;
float zo = (float)originalEntity.zo;
OpenMatrix4f toModelPos = OpenMatrix4f.mul(OpenMatrix4f.createTranslation(xo + (x - xo) * partialTicks, yo + (y - yo) * partialTicks, zo + (z - zo) * partialTicks), modelmat, null).invert();
for (BakedInverseKinematicsDefinition bakedIKInfo : this.getProperty(StaticAnimationProperty.BAKED_IK_DEFINITION).orElse(null)) {
ikSimulatable.getIKSimulator().getRunningObject(bakedIKInfo.endJoint()).ifPresent((ikObjet) -> {
VertexConsumer vertexBuilder = buffer.getBuffer(EpicFightRenderTypes.debugQuads());
Vec3f worldtargetpos = ikObjet.getDestination();
Vec3f modeltargetpos = OpenMatrix4f.transform3v(toModelPos, worldtargetpos, null).multiply(-1.0F, 1.0F, -1.0F);
RenderingTool.drawQuad(poseStack, vertexBuilder, modeltargetpos, 0.5F, 1.0F, 0.0F, 0.0F);
Vec3f jointWorldPos = ikObjet.getTipPosition(partialTicks);
Vec3f jointModelpos = OpenMatrix4f.transform3v(toModelPos, jointWorldPos, null);
RenderingTool.drawQuad(poseStack, vertexBuilder, jointModelpos.multiply(-1.0F, 1.0F, -1.0F), 0.4F, 0.0F, 0.0F, 1.0F);
Pose pose = new Pose();
for (String jointName : this.getTransfroms().keySet()) {
pose.putJointData(jointName, this.getTransfroms().get(jointName).getInterpolatedTransform(playTime));
}
});
}
});
}
}
@Override
public InverseKinematicsObject createSimulationData(InverseKinematicsProvider provider, InverseKinematicsSimulatable simOwner, InverseKinematicsSimulator.InverseKinematicsBuilder simBuilder) {
return new InverseKinematicsObject(simBuilder);
}
}