Files
TiedUp-/src/main/java/com/tiedup/remake/rig/anim/AnimationManager.java
NotEvil b034184f8a WIP: fork patch/collider/codec stubs, 464->135 compile errors
Phase 0 compile progress (70% reduction). Core data model compile :

Refs yesman.epicfight strippées (hors 4 javadocs) :
- AnimationProperty : combat properties EXTRA_DAMAGE, STUN_TYPE, PARTICLE
- ClientAnimator : playAnimationAt(..., AnimatorControlPacket.Layer, Priority)
- ClothSimulator : OBBCollider -> fork geometry-only dans rig/collider/
- InstantiateInvoker : Collider, ColliderPreset, Armatures, DatapackEditScreen
- MoveCoordFunctions : GrapplingAttackAnimation
- SimulationTypes : InverseKinematicsSimulator (path rewrite)

Stubs patch/ :
- EntityPatch<T> abstract — getOriginal, isLogicalClient, getMatrix, getAngleTo
- LivingEntityPatch<T> abstract — getAnimator, getArmature, getTarget, getYRot*
- MobPatch<T extends Mob> — instanceof check only
- item/CapabilityItem — type marker

Forks utilitaires :
- rig/collider/OBBCollider — geometry only (strip Entity collision, drawInternal)
- anim/types/StateSpectrum — identique EF, imports rewrités
- util/PacketBufferCodec — StreamCodec backport
- util/TimePairList — identique EF
- util/HitEntityList — shell pour Priority enum uniquement
- util/ExtendableEnum + ExtendableEnumManager — register/assign enum

Fix package declarations :
- armature/Joint.java + JointTransform.java : package rig.anim -> rig.armature
- Imports JointTransform ajoutés dans anim/{Pose,Keyframe,TransformSheet}

Residu 135 errors = cluster rendering (Phase 2) :
- render/TiedUpRenderTypes (17) : CompositeState package-private MC
- event/PatchedRenderersEvent (11) : missing PatchedEntityRenderer
- mesh/SkinnedMesh (13) : VanillaMeshPartDefinition, compute shader fields
- asset/JsonAssetLoader (6), anim/LivingMotion (5)
2026-04-22 02:45:18 +02:00

436 lines
19 KiB
Java

/*
* 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 com.tiedup.remake.rig.exception.AssetLoadingException;
import com.tiedup.remake.rig.util.InstantiateInvoker;
import com.tiedup.remake.rig.util.MutableBoolean;
import com.tiedup.remake.rig.TiedUpRigRegistry;
import com.tiedup.remake.rig.TiedUpRigConstants;
@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) {
TiedUpRigConstants.stacktraceIfDevSide("Empty animation accessor: " + animation.registryName(), NoSuchElementException::new);
} else {
TiedUpRigConstants.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 (!TiedUpRigConstants.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) {
// RIG : Armatures.reload() (EF gameasset registry) retiré.
// TiedUpArmatures.reload() sera appelé ici en Phase 2 quand le registry
// sera créé. En Phase 0, no-op.
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) {
TiedUpRigConstants.LOGGER.error("Couldn't parse animation data from {}", animId, resourceReadException);
} catch (Exception e) {
TiedUpRigConstants.LOGGER.error("Failed at constructing {}", animId, e);
}
});
// RIG : SkillManager.reloadAllSkillsAnimations() strippé (combat skills).
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) {
TiedUpRigConstants.logAndStacktraceIfDevSide(Logger::error, "Invalid animation implementation: " + entry.getKey(), AssetLoadingException::new);
init.set(false);
}
entry.getValue().getSubAnimations().forEach((subAnimation) -> {
if (subAnimation == null || subAnimation.get() == null) {
TiedUpRigConstants.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 (TiedUpRigConstants.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 TiedUpRigConstants.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
*/
// RIG : processServerPacket + validateClientAnimationRegistry strippés.
// C'était le protocole datapack-sync EF pour valider que le client a les
// mêmes animations que le serveur au login (important pour les animations
// combat stockées en data/). TiedUp utilise resource pack uniquement
// (assets/) côté client, pas de sync datapack nécessaire.
// Ré-introduire Phase 2+ si on veut un warning quand un pack d'animations
// custom diverge.
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 {
TiedUpRigConstants.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;
}
}
}