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:
140
src/main/java/com/tiedup/remake/rig/anim/AnimationClip.java
Normal file
140
src/main/java/com/tiedup/remake/rig/anim/AnimationClip.java
Normal 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;
|
||||
}
|
||||
}
|
||||
514
src/main/java/com/tiedup/remake/rig/anim/AnimationManager.java
Normal file
514
src/main/java/com/tiedup/remake/rig/anim/AnimationManager.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
162
src/main/java/com/tiedup/remake/rig/anim/AnimationPlayer.java
Normal file
162
src/main/java/com/tiedup/remake/rig/anim/AnimationPlayer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
267
src/main/java/com/tiedup/remake/rig/anim/AnimationVariables.java
Normal file
267
src/main/java/com/tiedup/remake/rig/anim/AnimationVariables.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
145
src/main/java/com/tiedup/remake/rig/anim/Animator.java
Normal file
145
src/main/java/com/tiedup/remake/rig/anim/Animator.java
Normal 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();
|
||||
}
|
||||
}
|
||||
47
src/main/java/com/tiedup/remake/rig/anim/Keyframe.java
Normal file
47
src/main/java/com/tiedup/remake/rig/anim/Keyframe.java
Normal 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());
|
||||
}
|
||||
}
|
||||
24
src/main/java/com/tiedup/remake/rig/anim/LivingMotion.java
Normal file
24
src/main/java/com/tiedup/remake/rig/anim/LivingMotion.java
Normal 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;
|
||||
}
|
||||
}
|
||||
23
src/main/java/com/tiedup/remake/rig/anim/LivingMotions.java
Normal file
23
src/main/java/com/tiedup/remake/rig/anim/LivingMotions.java
Normal 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;
|
||||
}
|
||||
}
|
||||
113
src/main/java/com/tiedup/remake/rig/anim/Pose.java
Normal file
113
src/main/java/com/tiedup/remake/rig/anim/Pose.java
Normal 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
|
||||
}
|
||||
}
|
||||
160
src/main/java/com/tiedup/remake/rig/anim/ServerAnimator.java
Normal file
160
src/main/java/com/tiedup/remake/rig/anim/ServerAnimator.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
354
src/main/java/com/tiedup/remake/rig/anim/TransformSheet.java
Normal file
354
src/main/java/com/tiedup/remake/rig/anim/TransformSheet.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
359
src/main/java/com/tiedup/remake/rig/anim/client/Layer.java
Normal file
359
src/main/java/com/tiedup/remake/rig/anim/client/Layer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> ();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
146
src/main/java/com/tiedup/remake/rig/anim/types/EntityState.java
Normal file
146
src/main/java/com/tiedup/remake/rig/anim/types/EntityState.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user