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);
|
||||
}
|
||||
}
|
||||
270
src/main/java/com/tiedup/remake/rig/armature/Armature.java
Normal file
270
src/main/java/com/tiedup/remake/rig/armature/Armature.java
Normal file
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* 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.armature;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.anim.Pose;
|
||||
import com.tiedup.remake.rig.asset.JsonAssetLoader;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import yesman.epicfight.main.EpicFightMod;
|
||||
import yesman.epicfight.main.EpicFightSharedConstants;
|
||||
|
||||
public class Armature {
|
||||
private final String name;
|
||||
private final Int2ObjectMap<Joint> jointById;
|
||||
private final Map<String, Joint> jointByName;
|
||||
private final Map<String, Joint.HierarchicalJointAccessor> pathIndexMap;
|
||||
private final int jointCount;
|
||||
private final OpenMatrix4f[] poseMatrices;
|
||||
public final Joint rootJoint;
|
||||
|
||||
public Armature(String name, int jointNumber, Joint rootJoint, Map<String, Joint> jointMap) {
|
||||
this.name = name;
|
||||
this.jointCount = jointNumber;
|
||||
this.rootJoint = rootJoint;
|
||||
this.jointByName = jointMap;
|
||||
this.jointById = new Int2ObjectOpenHashMap<>();
|
||||
this.pathIndexMap = Maps.newHashMap();
|
||||
|
||||
this.jointByName.values().forEach((joint) -> {
|
||||
this.jointById.put(joint.getId(), joint);
|
||||
});
|
||||
|
||||
this.poseMatrices = OpenMatrix4f.allocateMatrixArray(this.jointCount);
|
||||
}
|
||||
|
||||
protected Joint getOrLogException(Map<String, Joint> jointMap, String name) {
|
||||
if (!jointMap.containsKey(name)) {
|
||||
if (EpicFightSharedConstants.IS_DEV_ENV) {
|
||||
EpicFightMod.LOGGER.debug("Cannot find the joint named " + name + " in " + this.getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
return Joint.EMPTY;
|
||||
}
|
||||
|
||||
return jointMap.get(name);
|
||||
}
|
||||
|
||||
public void setPose(Pose pose) {
|
||||
this.getPoseTransform(this.rootJoint, new OpenMatrix4f(), pose, this.poseMatrices, false);
|
||||
}
|
||||
|
||||
public void bakeOriginMatrices() {
|
||||
this.rootJoint.initOriginTransform(new OpenMatrix4f());
|
||||
}
|
||||
|
||||
public OpenMatrix4f[] getPoseMatrices() {
|
||||
return this.poseMatrices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param applyOriginTransform if you need a final pose of the animations, give it false.
|
||||
*/
|
||||
public OpenMatrix4f[] getPoseAsTransformMatrix(Pose pose, boolean applyOriginTransform) {
|
||||
OpenMatrix4f[] jointMatrices = new OpenMatrix4f[this.jointCount];
|
||||
this.getPoseTransform(this.rootJoint, new OpenMatrix4f(), pose, jointMatrices, applyOriginTransform);
|
||||
return jointMatrices;
|
||||
}
|
||||
|
||||
private void getPoseTransform(Joint joint, OpenMatrix4f parentTransform, Pose pose, OpenMatrix4f[] jointMatrices, boolean applyOriginTransform) {
|
||||
OpenMatrix4f result = pose.orElseEmpty(joint.getName()).getAnimationBoundMatrix(joint, parentTransform);
|
||||
jointMatrices[joint.getId()] = result;
|
||||
|
||||
for (Joint joints : joint.getSubJoints()) {
|
||||
this.getPoseTransform(joints, result, pose, jointMatrices, applyOriginTransform);
|
||||
}
|
||||
|
||||
if (applyOriginTransform) {
|
||||
result.mulBack(joint.getToOrigin());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inapposite past perfect
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public OpenMatrix4f getBindedTransformFor(Pose pose, Joint joint) {
|
||||
return this.getBoundTransformByJointIndex(pose, this.searchPathIndex(joint.getName()).createAccessTicket(this.rootJoint));
|
||||
}
|
||||
|
||||
public OpenMatrix4f getBoundTransformFor(Pose pose, Joint joint) {
|
||||
return this.getBoundTransformByJointIndex(pose, this.searchPathIndex(joint.getName()).createAccessTicket(this.rootJoint));
|
||||
}
|
||||
|
||||
public OpenMatrix4f getBoundTransformByJointIndex(Pose pose, Joint.AccessTicket pathIndices) {
|
||||
return this.getBoundJointTransformRecursively(pose, this.rootJoint, new OpenMatrix4f(), pathIndices);
|
||||
}
|
||||
|
||||
private OpenMatrix4f getBoundJointTransformRecursively(Pose pose, Joint joint, OpenMatrix4f parentTransform, Joint.AccessTicket pathIndices) {
|
||||
JointTransform jt = pose.orElseEmpty(joint.getName());
|
||||
OpenMatrix4f result = jt.getAnimationBoundMatrix(joint, parentTransform);
|
||||
|
||||
return pathIndices.hasNext() ? this.getBoundJointTransformRecursively(pose, pathIndices.next(), result, pathIndices) : result;
|
||||
}
|
||||
|
||||
public boolean hasJoint(String name) {
|
||||
return this.jointByName.containsKey(name);
|
||||
}
|
||||
|
||||
public Joint searchJointById(int id) {
|
||||
return this.jointById.get(id);
|
||||
}
|
||||
|
||||
public Joint searchJointByName(String name) {
|
||||
return this.jointByName.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and record joint path from root to terminal
|
||||
*
|
||||
* @param terminalJointName
|
||||
* @return
|
||||
*/
|
||||
public Joint.HierarchicalJointAccessor searchPathIndex(String terminalJointName) {
|
||||
return this.searchPathIndex(this.rootJoint, terminalJointName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and record joint path to terminal
|
||||
*
|
||||
* @param start
|
||||
* @param terminalJointName
|
||||
* @return
|
||||
*/
|
||||
public Joint.HierarchicalJointAccessor searchPathIndex(Joint start, String terminalJointName) {
|
||||
String signature = start.getName() + "-" + terminalJointName;
|
||||
|
||||
if (this.pathIndexMap.containsKey(signature)) {
|
||||
return this.pathIndexMap.get(signature);
|
||||
} else {
|
||||
Joint.HierarchicalJointAccessor.Builder pathBuilder = start.searchPath(Joint.HierarchicalJointAccessor.builder(), terminalJointName);
|
||||
Joint.HierarchicalJointAccessor accessor;
|
||||
|
||||
if (pathBuilder == null) {
|
||||
throw new IllegalArgumentException("Failed to get joint path index for " + terminalJointName);
|
||||
} else {
|
||||
accessor = pathBuilder.build();
|
||||
this.pathIndexMap.put(signature, accessor);
|
||||
}
|
||||
|
||||
return accessor;
|
||||
}
|
||||
}
|
||||
|
||||
public void gatherAllJointsInPathToTerminal(String terminalJointName, Collection<String> jointsInPath) {
|
||||
if (!this.jointByName.containsKey(terminalJointName)) {
|
||||
throw new NoSuchElementException("No " + terminalJointName + " joint in this armature!");
|
||||
}
|
||||
|
||||
Joint.HierarchicalJointAccessor pathIndices = this.searchPathIndex(terminalJointName);
|
||||
Joint.AccessTicket accessTicket = pathIndices.createAccessTicket(this.rootJoint);
|
||||
|
||||
Joint joint = this.rootJoint;
|
||||
jointsInPath.add(joint.getName());
|
||||
|
||||
while (accessTicket.hasNext()) {
|
||||
jointsInPath.add(accessTicket.next().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public int getJointNumber() {
|
||||
return this.jointCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Armature deepCopy() {
|
||||
Map<String, Joint> oldToNewJoint = Maps.newHashMap();
|
||||
oldToNewJoint.put("empty", Joint.EMPTY);
|
||||
|
||||
Joint newRoot = this.copyHierarchy(this.rootJoint, oldToNewJoint);
|
||||
newRoot.initOriginTransform(new OpenMatrix4f());
|
||||
Armature newArmature = null;
|
||||
|
||||
// Uses reflection to keep the type of copied armature
|
||||
try {
|
||||
Constructor<? extends Armature> constructor = this.getClass().getConstructor(String.class, int.class, Joint.class, Map.class);
|
||||
newArmature = constructor.newInstance(this.name, this.jointCount, newRoot, oldToNewJoint);
|
||||
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
throw new IllegalStateException("Armature copy failed! " + e);
|
||||
}
|
||||
|
||||
return newArmature;
|
||||
}
|
||||
|
||||
private Joint copyHierarchy(Joint joint, Map<String, Joint> oldToNewJoint) {
|
||||
if (joint == Joint.EMPTY) {
|
||||
return Joint.EMPTY;
|
||||
}
|
||||
|
||||
Joint newJoint = new Joint(joint.getName(), joint.getId(), joint.getLocalTransform());
|
||||
oldToNewJoint.put(joint.getName(), newJoint);
|
||||
|
||||
for (Joint subJoint : joint.getSubJoints()) {
|
||||
newJoint.addSubJoints(this.copyHierarchy(subJoint, oldToNewJoint));
|
||||
}
|
||||
|
||||
return newJoint;
|
||||
}
|
||||
|
||||
public JsonObject toJsonObject() {
|
||||
JsonObject root = new JsonObject();
|
||||
JsonObject armature = new JsonObject();
|
||||
|
||||
JsonArray jointNamesArray = new JsonArray();
|
||||
JsonArray jointHierarchy = new JsonArray();
|
||||
|
||||
this.jointById.int2ObjectEntrySet().stream().sorted((entry1, entry2) -> Integer.compare(entry1.getIntKey(), entry2.getIntKey())).forEach((entry) -> jointNamesArray.add(entry.getValue().getName()));
|
||||
armature.add("joints", jointNamesArray);
|
||||
armature.add("hierarchy", jointHierarchy);
|
||||
|
||||
exportJoint(jointHierarchy, this.rootJoint, true);
|
||||
|
||||
root.add("armature", armature);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static void exportJoint(JsonArray parent, Joint joint, boolean root) {
|
||||
JsonObject jointJson = new JsonObject();
|
||||
jointJson.addProperty("name", joint.getName());
|
||||
|
||||
JsonArray transformMatrix = new JsonArray();
|
||||
OpenMatrix4f localMatrixInBlender = new OpenMatrix4f(joint.getLocalTransform());
|
||||
|
||||
if (root) {
|
||||
localMatrixInBlender.mulFront(OpenMatrix4f.invert(JsonAssetLoader.BLENDER_TO_MINECRAFT_COORD, null));
|
||||
}
|
||||
|
||||
localMatrixInBlender.transpose();
|
||||
localMatrixInBlender.toList().forEach(transformMatrix::add);
|
||||
jointJson.add("transform", transformMatrix);
|
||||
parent.add(jointJson);
|
||||
|
||||
if (!joint.getSubJoints().isEmpty()) {
|
||||
JsonArray children = new JsonArray();
|
||||
jointJson.add("children", children);
|
||||
joint.getSubJoints().forEach((joint$2) -> exportJoint(children, joint$2, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.armature;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.armature.types.HumanLikeArmature;
|
||||
|
||||
public class HumanoidArmature extends Armature implements HumanLikeArmature {
|
||||
public final Joint thighR;
|
||||
public final Joint legR;
|
||||
public final Joint kneeR;
|
||||
public final Joint thighL;
|
||||
public final Joint legL;
|
||||
public final Joint kneeL;
|
||||
public final Joint torso;
|
||||
public final Joint chest;
|
||||
public final Joint head;
|
||||
public final Joint shoulderR;
|
||||
public final Joint armR;
|
||||
public final Joint handR;
|
||||
public final Joint toolR;
|
||||
public final Joint elbowR;
|
||||
public final Joint shoulderL;
|
||||
public final Joint armL;
|
||||
public final Joint handL;
|
||||
public final Joint toolL;
|
||||
public final Joint elbowL;
|
||||
|
||||
public HumanoidArmature(String name, int jointNumber, Joint rootJoint, Map<String, Joint> jointMap) {
|
||||
super(name, jointNumber, rootJoint, jointMap);
|
||||
|
||||
this.thighR = this.getOrLogException(jointMap, "Thigh_R");
|
||||
this.legR = this.getOrLogException(jointMap, "Leg_R");
|
||||
this.kneeR = this.getOrLogException(jointMap, "Knee_R");
|
||||
this.thighL = this.getOrLogException(jointMap, "Thigh_L");
|
||||
this.legL = this.getOrLogException(jointMap, "Leg_L");
|
||||
this.kneeL = this.getOrLogException(jointMap, "Knee_L");
|
||||
this.torso = this.getOrLogException(jointMap, "Torso");
|
||||
this.chest = this.getOrLogException(jointMap, "Chest");
|
||||
this.head = this.getOrLogException(jointMap, "Head");
|
||||
this.shoulderR = this.getOrLogException(jointMap, "Shoulder_R");
|
||||
this.armR = this.getOrLogException(jointMap, "Arm_R");
|
||||
this.handR = this.getOrLogException(jointMap, "Hand_R");
|
||||
this.toolR = this.getOrLogException(jointMap, "Tool_R");
|
||||
this.elbowR = this.getOrLogException(jointMap, "Elbow_R");
|
||||
this.shoulderL = this.getOrLogException(jointMap, "Shoulder_L");
|
||||
this.armL = this.getOrLogException(jointMap, "Arm_L");
|
||||
this.handL = this.getOrLogException(jointMap, "Hand_L");
|
||||
this.toolL = this.getOrLogException(jointMap, "Tool_L");
|
||||
this.elbowL = this.getOrLogException(jointMap, "Elbow_L");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint leftToolJoint() {
|
||||
return this.toolL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint rightToolJoint() {
|
||||
return this.toolR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint backToolJoint() {
|
||||
return this.chest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint leftHandJoint() {
|
||||
return this.handL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint rightHandJoint() {
|
||||
return this.handR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint leftArmJoint() {
|
||||
return this.armL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint rightArmJoint() {
|
||||
return this.armR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint leftLegJoint() {
|
||||
return this.legL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint rightLegJoint() {
|
||||
return this.legR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint leftThighJoint() {
|
||||
return this.thighL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint rightThighJoint() {
|
||||
return this.thighR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Joint headJoint() {
|
||||
return this.head;
|
||||
}
|
||||
}
|
||||
278
src/main/java/com/tiedup/remake/rig/armature/Joint.java
Normal file
278
src/main/java/com/tiedup/remake/rig/armature/Joint.java
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* 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.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
public class Joint {
|
||||
public static final Joint EMPTY = new Joint("empty", -1, new OpenMatrix4f());
|
||||
|
||||
private final List<Joint> subJoints = Lists.newArrayList();
|
||||
private final int jointId;
|
||||
private final String jointName;
|
||||
private final OpenMatrix4f localTransform;
|
||||
private final OpenMatrix4f toOrigin = new OpenMatrix4f();
|
||||
|
||||
public Joint(String name, int jointId, OpenMatrix4f localTransform) {
|
||||
this.jointId = jointId;
|
||||
this.jointName = name;
|
||||
this.localTransform = localTransform.unmodifiable();
|
||||
}
|
||||
|
||||
public void addSubJoints(Joint... joints) {
|
||||
for (Joint joint : joints) {
|
||||
if (!this.subJoints.contains(joint)) {
|
||||
this.subJoints.add(joint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSubJoints(Joint... joints) {
|
||||
for (Joint joint : joints) {
|
||||
this.subJoints.remove(joint);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Joint> getAllJoints() {
|
||||
List<Joint> list = Lists.newArrayList();
|
||||
this.getSubJoints(list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public void iterSubJoints(Consumer<Joint> iterTask) {
|
||||
iterTask.accept(this);
|
||||
|
||||
for (Joint joint : this.subJoints) {
|
||||
joint.iterSubJoints(iterTask);
|
||||
}
|
||||
}
|
||||
|
||||
private void getSubJoints(List<Joint> list) {
|
||||
list.add(this);
|
||||
|
||||
for (Joint joint : this.subJoints) {
|
||||
joint.getSubJoints(list);
|
||||
}
|
||||
}
|
||||
|
||||
public void initOriginTransform(OpenMatrix4f parentTransform) {
|
||||
OpenMatrix4f modelTransform = OpenMatrix4f.mul(parentTransform, this.localTransform, null);
|
||||
OpenMatrix4f.invert(modelTransform, this.toOrigin);
|
||||
|
||||
for (Joint joint : this.subJoints) {
|
||||
joint.initOriginTransform(modelTransform);
|
||||
}
|
||||
}
|
||||
|
||||
public OpenMatrix4f getLocalTransform() {
|
||||
return this.localTransform;
|
||||
}
|
||||
|
||||
public OpenMatrix4f getToOrigin() {
|
||||
return this.toOrigin;
|
||||
}
|
||||
|
||||
public List<Joint> getSubJoints() {
|
||||
return this.subJoints;
|
||||
}
|
||||
|
||||
// Null if index out of range
|
||||
@Nullable
|
||||
public Joint getSubJoint(int index) {
|
||||
if (index < 0 || this.subJoints.size() <= index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.subJoints.get(index);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.jointName;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return this.jointId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Joint joint) {
|
||||
return this.jointName.equals(joint.jointName) && this.jointId == joint.jointId;
|
||||
} else {
|
||||
return super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.jointName.hashCode() ^ this.jointId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the method that memorize path search results. {@link Armature#searchPathIndex(Joint, String)}
|
||||
*
|
||||
* @param builder
|
||||
* @param jointName
|
||||
* @return
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public HierarchicalJointAccessor.Builder searchPath(HierarchicalJointAccessor.Builder builder, String jointName) {
|
||||
if (jointName.equals(this.getName())) {
|
||||
return builder;
|
||||
} else {
|
||||
int i = 0;
|
||||
|
||||
for (Joint subJoint : this.subJoints) {
|
||||
HierarchicalJointAccessor.Builder nextBuilder = subJoint.searchPath(builder.append(i), jointName);
|
||||
i++;
|
||||
|
||||
if (nextBuilder != null) {
|
||||
return nextBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\nid: " + this.jointId);
|
||||
sb.append("\nname: " + this.jointName);
|
||||
sb.append("\nlocal transform: " + this.localTransform);
|
||||
sb.append("\nto origin: " + this.toOrigin);
|
||||
sb.append("\nchildren: [");
|
||||
|
||||
int idx = 0;
|
||||
|
||||
for (Joint joint : this.subJoints) {
|
||||
idx++;
|
||||
sb.append(joint.jointName);
|
||||
|
||||
if (idx != this.subJoints.size()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("]\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String printIncludingChildren() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(this.toString());
|
||||
|
||||
for (Joint joint : this.subJoints) {
|
||||
sb.append(joint.printIncludingChildren());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static class HierarchicalJointAccessor {
|
||||
private Queue<Integer> indicesToTerminal;
|
||||
private final String signature;
|
||||
|
||||
private HierarchicalJointAccessor(Builder builder) {
|
||||
this.indicesToTerminal = builder.indicesToTerminal;
|
||||
this.signature = builder.signature;
|
||||
}
|
||||
|
||||
public AccessTicket createAccessTicket(Joint rootJoint) {
|
||||
return new AccessTicket(this.indicesToTerminal, rootJoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof HierarchicalJointAccessor accessor) {
|
||||
this.signature.equals(accessor.signature);
|
||||
}
|
||||
|
||||
return super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.signature.hashCode();
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder(new LinkedList<> (), "");
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Queue<Integer> indicesToTerminal;
|
||||
private String signature;
|
||||
|
||||
private Builder(Queue<Integer> indicesToTerminal, String signature) {
|
||||
this.indicesToTerminal = indicesToTerminal;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public Builder append(int index) {
|
||||
String signatureNext;
|
||||
|
||||
if (this.indicesToTerminal.isEmpty()) {
|
||||
signatureNext = this.signature + String.valueOf(index);
|
||||
} else {
|
||||
signatureNext = this.signature + "-" + String.valueOf(index);
|
||||
}
|
||||
|
||||
Queue<Integer> nextQueue = new LinkedList<> (this.indicesToTerminal);
|
||||
nextQueue.add(index);
|
||||
|
||||
return new Builder(nextQueue, signatureNext);
|
||||
}
|
||||
|
||||
public HierarchicalJointAccessor build() {
|
||||
return new HierarchicalJointAccessor(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class AccessTicket implements Iterator<Joint> {
|
||||
Queue<Integer> accecssStack;
|
||||
Joint joint;
|
||||
|
||||
private AccessTicket(Queue<Integer> indicesToTerminal, Joint rootJoint) {
|
||||
this.accecssStack = new LinkedList<> (indicesToTerminal);
|
||||
this.joint = rootJoint;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return !this.accecssStack.isEmpty();
|
||||
}
|
||||
|
||||
public Joint next() {
|
||||
if (this.hasNext()) {
|
||||
int nextIndex = this.accecssStack.poll();
|
||||
this.joint = this.joint.subJoints.get(nextIndex);
|
||||
} else {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
return this.joint;
|
||||
}
|
||||
}
|
||||
}
|
||||
215
src/main/java/com/tiedup/remake/rig/armature/JointTransform.java
Normal file
215
src/main/java/com/tiedup/remake/rig/armature/JointTransform.java
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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 org.joml.Quaternionf;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import com.tiedup.remake.rig.math.AnimationTransformEntry;
|
||||
import com.tiedup.remake.rig.math.MathUtils;
|
||||
import com.tiedup.remake.rig.math.MatrixOperation;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
|
||||
public class JointTransform {
|
||||
public static final String ANIMATION_TRANSFORM = "animation_transform";
|
||||
public static final String JOINT_LOCAL_TRANSFORM = "joint_local_transform";
|
||||
public static final String PARENT = "parent";
|
||||
public static final String RESULT1 = "front_result";
|
||||
public static final String RESULT2 = "overwrite_rotation";
|
||||
|
||||
public static class TransformEntry {
|
||||
public final MatrixOperation multiplyFunction;
|
||||
public final JointTransform transform;
|
||||
|
||||
public TransformEntry(MatrixOperation multiplyFunction, JointTransform transform) {
|
||||
this.multiplyFunction = multiplyFunction;
|
||||
this.transform = transform;
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<String, TransformEntry> entries = Maps.newHashMap();
|
||||
private final Vec3f translation;
|
||||
private final Vec3f scale;
|
||||
private final Quaternionf rotation;
|
||||
|
||||
public JointTransform(Vec3f translation, Quaternionf rotation, Vec3f scale) {
|
||||
this.translation = translation;
|
||||
this.rotation = rotation;
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public Vec3f translation() {
|
||||
return this.translation;
|
||||
}
|
||||
|
||||
public Quaternionf rotation() {
|
||||
return this.rotation;
|
||||
}
|
||||
|
||||
public Vec3f scale() {
|
||||
return this.scale;
|
||||
}
|
||||
|
||||
public void clearTransform() {
|
||||
this.translation.set(0.0F, 0.0F, 0.0F);
|
||||
this.rotation.set(0.0F, 0.0F, 0.0F, 1.0F);
|
||||
this.scale.set(1.0F, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
public JointTransform copy() {
|
||||
return JointTransform.empty().copyFrom(this);
|
||||
}
|
||||
|
||||
public JointTransform copyFrom(JointTransform jt) {
|
||||
Vec3f newV = jt.translation();
|
||||
Quaternionf newQ = jt.rotation();
|
||||
Vec3f newS = jt.scale;
|
||||
this.translation.set(newV);
|
||||
this.rotation.set(newQ);
|
||||
this.scale.set(newS);
|
||||
this.entries.putAll(jt.entries);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void jointLocal(JointTransform transform, MatrixOperation multiplyFunction) {
|
||||
this.entries.put(JOINT_LOCAL_TRANSFORM, new TransformEntry(multiplyFunction, this.mergeIfExist(JOINT_LOCAL_TRANSFORM, transform)));
|
||||
}
|
||||
|
||||
public void parent(JointTransform transform, MatrixOperation multiplyFunction) {
|
||||
this.entries.put(PARENT, new TransformEntry(multiplyFunction, this.mergeIfExist(PARENT, transform)));
|
||||
}
|
||||
|
||||
public void animationTransform(JointTransform transform, MatrixOperation multiplyFunction) {
|
||||
this.entries.put(ANIMATION_TRANSFORM, new TransformEntry(multiplyFunction, this.mergeIfExist(ANIMATION_TRANSFORM, transform)));
|
||||
}
|
||||
|
||||
public void frontResult(JointTransform transform, MatrixOperation multiplyFunction) {
|
||||
this.entries.put(RESULT1, new TransformEntry(multiplyFunction, this.mergeIfExist(RESULT1, transform)));
|
||||
}
|
||||
|
||||
public void overwriteRotation(JointTransform transform) {
|
||||
this.entries.put(RESULT2, new TransformEntry(OpenMatrix4f::mul, this.mergeIfExist(RESULT2, transform)));
|
||||
}
|
||||
|
||||
public JointTransform mergeIfExist(String entryName, JointTransform transform) {
|
||||
if (this.entries.containsKey(entryName)) {
|
||||
TransformEntry transformEntry = this.entries.get(entryName);
|
||||
return JointTransform.mul(transform, transformEntry.transform, transformEntry.multiplyFunction);
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
public OpenMatrix4f getAnimationBoundMatrix(Joint joint, OpenMatrix4f parentTransform) {
|
||||
AnimationTransformEntry animationTransformEntry = new AnimationTransformEntry();
|
||||
|
||||
for (Map.Entry<String, TransformEntry> entry : this.entries.entrySet()) {
|
||||
animationTransformEntry.put(entry.getKey(), entry.getValue().transform.toMatrix(), entry.getValue().multiplyFunction);
|
||||
}
|
||||
|
||||
animationTransformEntry.put(ANIMATION_TRANSFORM, this.toMatrix(), OpenMatrix4f::mul);
|
||||
animationTransformEntry.put(JOINT_LOCAL_TRANSFORM, joint.getLocalTransform());
|
||||
animationTransformEntry.put(PARENT, parentTransform);
|
||||
|
||||
return animationTransformEntry.getResult();
|
||||
}
|
||||
|
||||
public OpenMatrix4f toMatrix() {
|
||||
return new OpenMatrix4f().translate(this.translation).mulBack(OpenMatrix4f.fromQuaternion(this.rotation)).scale(this.scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("translation:%s, rotation:%s, scale:%s %d entries ", this.translation, this.rotation, this.scale, this.entries.size());
|
||||
}
|
||||
|
||||
public static JointTransform interpolateTransform(JointTransform prev, JointTransform next, float progression, JointTransform dest) {
|
||||
if (dest == null) {
|
||||
dest = JointTransform.empty();
|
||||
}
|
||||
|
||||
MathUtils.lerpVector(prev.translation, next.translation, progression, dest.translation);
|
||||
MathUtils.lerpQuaternion(prev.rotation, next.rotation, progression, dest.rotation);
|
||||
MathUtils.lerpVector(prev.scale, next.scale, progression, dest.scale);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static JointTransform interpolate(JointTransform prev, JointTransform next, float progression) {
|
||||
return interpolate(prev, next, progression, null);
|
||||
}
|
||||
|
||||
public static JointTransform interpolate(JointTransform prev, JointTransform next, float progression, JointTransform dest) {
|
||||
if (dest == null) {
|
||||
dest = JointTransform.empty();
|
||||
}
|
||||
|
||||
if (prev == null || next == null) {
|
||||
dest.clearTransform();
|
||||
return dest;
|
||||
}
|
||||
|
||||
progression = Mth.clamp(progression, 0.0F, 1.0F);
|
||||
interpolateTransform(prev, next, progression, dest);
|
||||
dest.entries.clear();
|
||||
|
||||
for (Map.Entry<String, TransformEntry> entry : prev.entries.entrySet()) {
|
||||
JointTransform transform = next.entries.containsKey(entry.getKey()) ? next.entries.get(entry.getKey()).transform : JointTransform.empty();
|
||||
dest.entries.put(entry.getKey(), new TransformEntry(entry.getValue().multiplyFunction, interpolateTransform(entry.getValue().transform, transform, progression, null)));
|
||||
}
|
||||
|
||||
for (Map.Entry<String, TransformEntry> entry : next.entries.entrySet()) {
|
||||
if (!dest.entries.containsKey(entry.getKey())) {
|
||||
dest.entries.put(entry.getKey(), new TransformEntry(entry.getValue().multiplyFunction, interpolateTransform(JointTransform.empty(), entry.getValue().transform, progression, null)));
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static JointTransform fromMatrixWithoutScale(OpenMatrix4f matrix) {
|
||||
return new JointTransform(matrix.toTranslationVector(), matrix.toQuaternion(), new Vec3f(1.0F, 1.0F, 1.0F));
|
||||
}
|
||||
|
||||
public static JointTransform translation(Vec3f vec) {
|
||||
return JointTransform.translationRotation(vec, new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F));
|
||||
}
|
||||
|
||||
public static JointTransform rotation(Quaternionf quat) {
|
||||
return JointTransform.translationRotation(new Vec3f(0.0F, 0.0F, 0.0F), quat);
|
||||
}
|
||||
|
||||
public static JointTransform scale(Vec3f vec) {
|
||||
return new JointTransform(new Vec3f(0.0F, 0.0F, 0.0F), new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F), vec);
|
||||
}
|
||||
|
||||
public static JointTransform fromMatrix(OpenMatrix4f matrix) {
|
||||
return new JointTransform(matrix.toTranslationVector(), matrix.toQuaternion(), matrix.toScaleVector());
|
||||
}
|
||||
|
||||
public static JointTransform translationRotation(Vec3f vec, Quaternionf quat) {
|
||||
return new JointTransform(vec, quat, new Vec3f(1.0F, 1.0F, 1.0F));
|
||||
}
|
||||
|
||||
public static JointTransform mul(JointTransform left, JointTransform right, MatrixOperation operation) {
|
||||
return JointTransform.fromMatrix(operation.mul(left.toMatrix(), right.toMatrix(), null));
|
||||
}
|
||||
|
||||
public static JointTransform fromPrimitives(float locX, float locY, float locZ, float quatX, float quatY, float quatZ, float quatW, float scaX, float scaY, float scaZ) {
|
||||
return new JointTransform(new Vec3f(locX, locY, locZ), new Quaternionf(quatX, quatY, quatZ, quatW), new Vec3f(scaX, scaY, scaZ));
|
||||
}
|
||||
|
||||
public static JointTransform empty() {
|
||||
return new JointTransform(new Vec3f(0.0F, 0.0F, 0.0F), new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F), new Vec3f(1.0F, 1.0F, 1.0F));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.armature.types;
|
||||
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
|
||||
/**
|
||||
* This class is not being used by Epic Fight, but is left to meet various purposes of developers
|
||||
* Also presents developers which joints are necessary when an armature would be Human-like
|
||||
*/
|
||||
public interface HumanLikeArmature extends ToolHolderArmature {
|
||||
public Joint leftHandJoint();
|
||||
public Joint rightHandJoint();
|
||||
public Joint leftArmJoint();
|
||||
public Joint rightArmJoint();
|
||||
public Joint leftLegJoint();
|
||||
public Joint rightLegJoint();
|
||||
public Joint leftThighJoint();
|
||||
public Joint rightThighJoint();
|
||||
public Joint headJoint();
|
||||
}
|
||||
69
src/main/java/com/tiedup/remake/rig/asset/AssetAccessor.java
Normal file
69
src/main/java/com/tiedup/remake/rig/asset/AssetAccessor.java
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.asset;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* An accessor class
|
||||
* @param <O> {@link Object} can be any object
|
||||
*/
|
||||
public interface AssetAccessor<O> extends Supplier<O> {
|
||||
O get();
|
||||
|
||||
ResourceLocation registryName();
|
||||
|
||||
default boolean isPresent() {
|
||||
return this.get() != null;
|
||||
}
|
||||
|
||||
default boolean isEmpty() {
|
||||
return !this.isPresent();
|
||||
}
|
||||
|
||||
boolean inRegistry();
|
||||
|
||||
default boolean checkType(Class<?> cls) {
|
||||
return cls.isAssignableFrom(this.get().getClass());
|
||||
}
|
||||
|
||||
default O orElse(O whenNull) {
|
||||
return this.isPresent() ? this.get() : whenNull;
|
||||
}
|
||||
|
||||
default void ifPresent(Consumer<O> action) {
|
||||
if (this.isPresent()) {
|
||||
action.accept(this.get());
|
||||
}
|
||||
}
|
||||
|
||||
default void ifPresentOrElse(Consumer<O> action, Runnable whenNull) {
|
||||
if (this.isPresent()) {
|
||||
action.accept(this.get());
|
||||
} else {
|
||||
whenNull.run();
|
||||
}
|
||||
}
|
||||
|
||||
default void doOrThrow(Consumer<O> action) {
|
||||
if (this.isPresent()) {
|
||||
action.accept(this.get());
|
||||
} else {
|
||||
throw new NoSuchElementException("No asset " + this.registryName());
|
||||
}
|
||||
}
|
||||
|
||||
default void checkNotNull() {
|
||||
if (!this.isPresent()) {
|
||||
throw new NoSuchElementException("No asset " + this.registryName());
|
||||
}
|
||||
}
|
||||
}
|
||||
833
src/main/java/com/tiedup/remake/rig/asset/JsonAssetLoader.java
Normal file
833
src/main/java/com/tiedup/remake/rig/asset/JsonAssetLoader.java
Normal file
@@ -0,0 +1,833 @@
|
||||
/*
|
||||
* 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.asset;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
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.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 io.netty.util.internal.StringUtil;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import com.tiedup.remake.rig.anim.AnimationClip;
|
||||
import com.tiedup.remake.rig.armature.Joint;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.anim.Keyframe;
|
||||
import com.tiedup.remake.rig.anim.TransformSheet;
|
||||
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
|
||||
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.MainFrameAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.mesh.ClassicMesh;
|
||||
import com.tiedup.remake.rig.mesh.CompositeMesh;
|
||||
import com.tiedup.remake.rig.mesh.Mesh;
|
||||
import com.tiedup.remake.rig.mesh.MeshPartDefinition;
|
||||
import com.tiedup.remake.rig.mesh.Meshes;
|
||||
import com.tiedup.remake.rig.mesh.Meshes.MeshContructor;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.mesh.SoftBodyTranslatable;
|
||||
import com.tiedup.remake.rig.mesh.StaticMesh;
|
||||
import com.tiedup.remake.rig.mesh.VertexBuilder;
|
||||
import com.tiedup.remake.rig.mesh.transformer.VanillaModelTransformer.VanillaMeshPartDefinition;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject.ClothPart.ConstraintType;
|
||||
import com.tiedup.remake.rig.exception.AssetLoadingException;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.util.ParseUtil;
|
||||
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 yesman.epicfight.gameasset.Armatures.ArmatureContructor;
|
||||
import yesman.epicfight.main.EpicFightMod;
|
||||
import yesman.epicfight.main.EpicFightSharedConstants;
|
||||
|
||||
public class JsonAssetLoader {
|
||||
public static final OpenMatrix4f BLENDER_TO_MINECRAFT_COORD = OpenMatrix4f.createRotatorDeg(-90.0F, Vec3f.X_AXIS);
|
||||
public static final OpenMatrix4f MINECRAFT_TO_BLENDER_COORD = OpenMatrix4f.invert(BLENDER_TO_MINECRAFT_COORD, null);
|
||||
public static final String UNGROUPED_NAME = "noGroups";
|
||||
public static final String COORD_BONE = "Coord";
|
||||
public static final String ROOT_BONE = "Root";
|
||||
|
||||
private JsonObject rootJson;
|
||||
|
||||
// Used for deciding armature name, other resources are nullable
|
||||
@Nullable
|
||||
private ResourceLocation resourceLocation;
|
||||
private String filehash;
|
||||
|
||||
public JsonAssetLoader(ResourceManager resourceManager, ResourceLocation resourceLocation) throws AssetLoadingException {
|
||||
JsonReader jsonReader = null;
|
||||
this.resourceLocation = resourceLocation;
|
||||
|
||||
try {
|
||||
try {
|
||||
if (resourceManager == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
Resource resource = resourceManager.getResource(resourceLocation).orElseThrow();
|
||||
InputStream inputStream = resource.open();
|
||||
InputStreamReader isr = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
|
||||
jsonReader = new JsonReader(isr);
|
||||
jsonReader.setLenient(true);
|
||||
this.rootJson = Streams.parse(jsonReader).getAsJsonObject();
|
||||
} catch (NoSuchElementException e) {
|
||||
// In this case, reads the animation data from mod.jar (Especially in a server)
|
||||
Class<?> modClass = ModList.get().getModObjectById(resourceLocation.getNamespace()).orElseThrow(() -> new AssetLoadingException("No modid " + resourceLocation)).getClass();
|
||||
InputStream inputStream = modClass.getResourceAsStream("/assets/" + resourceLocation.getNamespace() + "/" + resourceLocation.getPath());
|
||||
|
||||
if (inputStream == null) {
|
||||
modClass = ModList.get().getModObjectById(EpicFightMod.MODID).get().getClass();
|
||||
inputStream = modClass.getResourceAsStream("/assets/" + resourceLocation.getNamespace() + "/" + resourceLocation.getPath());
|
||||
}
|
||||
|
||||
//Still null, throws exception.
|
||||
if (inputStream == null) {
|
||||
throw new AssetLoadingException("Can't find resource file: " + resourceLocation);
|
||||
}
|
||||
|
||||
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
|
||||
InputStreamReader reader = new InputStreamReader(bufferedInputStream, StandardCharsets.UTF_8);
|
||||
|
||||
jsonReader = new JsonReader(reader);
|
||||
jsonReader.setLenient(true);
|
||||
this.rootJson = Streams.parse(jsonReader).getAsJsonObject();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssetLoadingException("Can't read " + resourceLocation.toString() + " because of " + e);
|
||||
} finally {
|
||||
if (jsonReader != null) {
|
||||
try {
|
||||
jsonReader.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.filehash = ParseUtil.getBytesSHA256Hash(this.rootJson.toString().getBytes());
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public JsonAssetLoader(InputStream inputstream, ResourceLocation resourceLocation) throws AssetLoadingException {
|
||||
JsonReader jsonReader = null;
|
||||
this.resourceLocation = resourceLocation;
|
||||
|
||||
jsonReader = new JsonReader(new InputStreamReader(inputstream, StandardCharsets.UTF_8));
|
||||
jsonReader.setLenient(true);
|
||||
this.rootJson = Streams.parse(jsonReader).getAsJsonObject();
|
||||
|
||||
try {
|
||||
jsonReader.close();
|
||||
} catch (IOException e) {
|
||||
throw new AssetLoadingException("Can't read " + resourceLocation.toString() + ": " + e);
|
||||
}
|
||||
|
||||
this.filehash = StringUtil.EMPTY_STRING;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public JsonAssetLoader(JsonObject rootJson, ResourceLocation rl) {
|
||||
this.rootJson = rootJson;
|
||||
this.resourceLocation = rl;
|
||||
this.filehash = StringUtil.EMPTY_STRING;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static Mesh.RenderProperties getRenderProperties(JsonObject json) {
|
||||
if (!json.has("render_properties")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonObject properties = json.getAsJsonObject("render_properties");
|
||||
Mesh.RenderProperties.Builder renderProperties = Mesh.RenderProperties.Builder.create();
|
||||
|
||||
if (properties.has("transparent")) {
|
||||
renderProperties.transparency(properties.get("transparent").getAsBoolean());
|
||||
}
|
||||
|
||||
if (properties.has("texture_path")) {
|
||||
renderProperties.customTexturePath(properties.get("texture_path").getAsString());
|
||||
}
|
||||
|
||||
if (properties.has("color")) {
|
||||
JsonArray jsonarray = properties.getAsJsonArray("color");
|
||||
renderProperties.customColor(jsonarray.get(0).getAsFloat(), jsonarray.get(1).getAsFloat(), jsonarray.get(2).getAsFloat());
|
||||
}
|
||||
|
||||
return renderProperties.build();
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public ResourceLocation getParent() {
|
||||
return this.rootJson.has("parent") ? ResourceLocation.parse(this.rootJson.get("parent").getAsString()) : null;
|
||||
}
|
||||
|
||||
private static final float DEFAULT_PARTICLE_MASS = 0.16F;
|
||||
private static final float DEFAULT_SELF_COLLISON = 0.05F;
|
||||
|
||||
@Nullable
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public Map<String, SoftBodyTranslatable.ClothSimulationInfo> loadClothInformation(Float[] positionArray) {
|
||||
JsonObject obj = this.rootJson.getAsJsonObject("vertices");
|
||||
JsonObject clothInfoObj = obj.getAsJsonObject("cloth_info");
|
||||
|
||||
if (clothInfoObj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, SoftBodyTranslatable.ClothSimulationInfo> clothInfo = Maps.newHashMap();
|
||||
|
||||
for (Map.Entry<String, JsonElement> e : clothInfoObj.entrySet()) {
|
||||
JsonObject clothObject = e.getValue().getAsJsonObject();
|
||||
int[] particlesArray = ParseUtil.toIntArrayPrimitive(clothObject.get("particles").getAsJsonObject().get("array").getAsJsonArray());
|
||||
float[] weightsArray = ParseUtil.toFloatArrayPrimitive(clothObject.get("weights").getAsJsonObject().get("array").getAsJsonArray());
|
||||
float particleMass = clothObject.has("particle_mass") ? clothObject.get("particle_mass").getAsFloat() : DEFAULT_PARTICLE_MASS;
|
||||
float selfCollision = clothObject.has("self_collision") ? clothObject.get("self_collision").getAsFloat() : DEFAULT_SELF_COLLISON;
|
||||
|
||||
JsonArray constraintsArray = clothObject.get("constraints").getAsJsonArray();
|
||||
List<int[]> constraintsList = new ArrayList<> (constraintsArray.size());
|
||||
float[] compliances = new float[constraintsArray.size()];
|
||||
ConstraintType[] constraintType = new ConstraintType[constraintsArray.size()];
|
||||
float[] rootDistances = new float[particlesArray.length / 2];
|
||||
|
||||
int i = 0;
|
||||
|
||||
for (JsonElement element : constraintsArray) {
|
||||
JsonObject asJsonObject = element.getAsJsonObject();
|
||||
|
||||
if (asJsonObject.has("unused") && GsonHelper.getAsBoolean(asJsonObject, "unused")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
constraintType[i] = ConstraintType.valueOf(GsonHelper.getAsString(asJsonObject, "type").toUpperCase(Locale.ROOT));
|
||||
compliances[i] = GsonHelper.getAsFloat(asJsonObject, "compliance");
|
||||
constraintsList.add(ParseUtil.toIntArrayPrimitive(asJsonObject.get("array").getAsJsonArray()));
|
||||
element.getAsJsonObject().get("compliance");
|
||||
i++;
|
||||
}
|
||||
|
||||
List<Vec3> rootParticles = Lists.newArrayList();
|
||||
|
||||
for (int j = 0; j < particlesArray.length / 2; j++) {
|
||||
int weightIndex = particlesArray[j * 2 + 1];
|
||||
float weight = weightsArray[weightIndex];
|
||||
|
||||
if (weight == 0.0F) {
|
||||
int posId = particlesArray[j * 2];
|
||||
rootParticles.add(new Vec3(positionArray[posId * 3 + 0], positionArray[posId * 3 + 1], positionArray[posId * 3 + 2]));
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < particlesArray.length / 2; j++) {
|
||||
int posId = particlesArray[j * 2];
|
||||
Vec3 position = new Vec3(positionArray[posId * 3 + 0], positionArray[posId * 3 + 1], positionArray[posId * 3 + 2]);
|
||||
Vec3 nearest = MathUtils.getNearestVector(position, rootParticles);
|
||||
rootDistances[j] = (float)position.distanceTo(nearest);
|
||||
}
|
||||
|
||||
int[] normalOffsetMappingArray = null;
|
||||
|
||||
if (clothObject.has("normal_offsets")) {
|
||||
normalOffsetMappingArray = ParseUtil.toIntArrayPrimitive(clothObject.get("normal_offsets").getAsJsonObject().get("array").getAsJsonArray());
|
||||
}
|
||||
|
||||
SoftBodyTranslatable.ClothSimulationInfo clothSimulInfo = new SoftBodyTranslatable.ClothSimulationInfo(particleMass, selfCollision, constraintsList, constraintType, compliances, particlesArray, weightsArray, rootDistances, normalOffsetMappingArray);
|
||||
clothInfo.put(e.getKey(), clothSimulInfo);
|
||||
}
|
||||
|
||||
return clothInfo;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public <T extends ClassicMesh> T loadClassicMesh(MeshContructor<ClassicMesh.ClassicMeshPart, VertexBuilder, T> constructor) {
|
||||
ResourceLocation parent = this.getParent();
|
||||
|
||||
if (parent != null) {
|
||||
T mesh = Meshes.getOrCreate(parent, (jsonLoader) -> jsonLoader.loadClassicMesh(constructor)).get();
|
||||
return constructor.invoke(null, null, mesh, getRenderProperties(this.rootJson));
|
||||
} else {
|
||||
JsonObject obj = this.rootJson.getAsJsonObject("vertices");
|
||||
JsonObject positions = obj.getAsJsonObject("positions");
|
||||
JsonObject normals = obj.getAsJsonObject("normals");
|
||||
JsonObject uvs = obj.getAsJsonObject("uvs");
|
||||
JsonObject parts = obj.getAsJsonObject("parts");
|
||||
JsonObject indices = obj.getAsJsonObject("indices");
|
||||
Float[] positionArray = ParseUtil.toFloatArray(positions.get("array").getAsJsonArray());
|
||||
|
||||
for (int i = 0; i < positionArray.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f posVector = new Vec4f(positionArray[k], positionArray[k+1], positionArray[k+2], 1.0F);
|
||||
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, posVector, posVector);
|
||||
positionArray[k] = posVector.x;
|
||||
positionArray[k+1] = posVector.y;
|
||||
positionArray[k+2] = posVector.z;
|
||||
}
|
||||
|
||||
Float[] normalArray = ParseUtil.toFloatArray(normals.get("array").getAsJsonArray());
|
||||
|
||||
for (int i = 0; i < normalArray.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f normVector = new Vec4f(normalArray[k], normalArray[k+1], normalArray[k+2], 1.0F);
|
||||
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, normVector, normVector);
|
||||
normalArray[k] = normVector.x;
|
||||
normalArray[k+1] = normVector.y;
|
||||
normalArray[k+2] = normVector.z;
|
||||
}
|
||||
|
||||
Float[] uvArray = ParseUtil.toFloatArray(uvs.get("array").getAsJsonArray());
|
||||
|
||||
Map<String, Number[]> arrayMap = Maps.newHashMap();
|
||||
Map<MeshPartDefinition, List<VertexBuilder>> meshMap = Maps.newHashMap();
|
||||
|
||||
arrayMap.put("positions", positionArray);
|
||||
arrayMap.put("normals", normalArray);
|
||||
arrayMap.put("uvs", uvArray);
|
||||
|
||||
if (parts != null) {
|
||||
for (Map.Entry<String, JsonElement> e : parts.entrySet()) {
|
||||
meshMap.put(VanillaMeshPartDefinition.of(e.getKey(), getRenderProperties(e.getValue().getAsJsonObject())), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(e.getValue().getAsJsonObject().get("array").getAsJsonArray())));
|
||||
}
|
||||
}
|
||||
|
||||
if (indices != null) {
|
||||
meshMap.put(VanillaMeshPartDefinition.of(UNGROUPED_NAME), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(indices.get("array").getAsJsonArray())));
|
||||
}
|
||||
|
||||
T mesh = constructor.invoke(arrayMap, meshMap, null, getRenderProperties(this.rootJson));
|
||||
mesh.putSoftBodySimulationInfo(this.loadClothInformation(positionArray));
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public <T extends SkinnedMesh> T loadSkinnedMesh(MeshContructor<SkinnedMesh.SkinnedMeshPart, VertexBuilder, T> constructor) {
|
||||
ResourceLocation parent = this.getParent();
|
||||
|
||||
if (parent != null) {
|
||||
T mesh = Meshes.getOrCreate(parent, (jsonLoader) -> jsonLoader.loadSkinnedMesh(constructor)).get();
|
||||
return constructor.invoke(null, null, mesh, getRenderProperties(this.rootJson));
|
||||
} else {
|
||||
JsonObject obj = this.rootJson.getAsJsonObject("vertices");
|
||||
JsonObject positions = obj.getAsJsonObject("positions");
|
||||
JsonObject normals = obj.getAsJsonObject("normals");
|
||||
JsonObject uvs = obj.getAsJsonObject("uvs");
|
||||
JsonObject vdincies = obj.getAsJsonObject("vindices");
|
||||
JsonObject weights = obj.getAsJsonObject("weights");
|
||||
JsonObject vcounts = obj.getAsJsonObject("vcounts");
|
||||
JsonObject parts = obj.getAsJsonObject("parts");
|
||||
JsonObject indices = obj.getAsJsonObject("indices");
|
||||
|
||||
Float[] positionArray = ParseUtil.toFloatArray(positions.get("array").getAsJsonArray());
|
||||
|
||||
for (int i = 0; i < positionArray.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f posVector = new Vec4f(positionArray[k], positionArray[k+1], positionArray[k+2], 1.0F);
|
||||
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, posVector, posVector);
|
||||
positionArray[k] = posVector.x;
|
||||
positionArray[k+1] = posVector.y;
|
||||
positionArray[k+2] = posVector.z;
|
||||
}
|
||||
|
||||
Float[] normalArray = ParseUtil.toFloatArray(normals.get("array").getAsJsonArray());
|
||||
|
||||
for (int i = 0; i < normalArray.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f normVector = new Vec4f(normalArray[k], normalArray[k+1], normalArray[k+2], 1.0F);
|
||||
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, normVector, normVector);
|
||||
normalArray[k] = normVector.x;
|
||||
normalArray[k+1] = normVector.y;
|
||||
normalArray[k+2] = normVector.z;
|
||||
}
|
||||
|
||||
Float[] uvArray = ParseUtil.toFloatArray(uvs.get("array").getAsJsonArray());
|
||||
Float[] weightArray = ParseUtil.toFloatArray(weights.get("array").getAsJsonArray());
|
||||
Integer[] affectingJointCounts = ParseUtil.toIntArray(vcounts.get("array").getAsJsonArray());
|
||||
Integer[] affectingJointIndices = ParseUtil.toIntArray(vdincies.get("array").getAsJsonArray());
|
||||
|
||||
Map<String, Number[]> arrayMap = Maps.newHashMap();
|
||||
Map<MeshPartDefinition, List<VertexBuilder>> meshMap = Maps.newHashMap();
|
||||
arrayMap.put("positions", positionArray);
|
||||
arrayMap.put("normals", normalArray);
|
||||
arrayMap.put("uvs", uvArray);
|
||||
arrayMap.put("weights", weightArray);
|
||||
arrayMap.put("vcounts", affectingJointCounts);
|
||||
arrayMap.put("vindices", affectingJointIndices);
|
||||
|
||||
if (parts != null) {
|
||||
for (Map.Entry<String, JsonElement> e : parts.entrySet()) {
|
||||
meshMap.put(VanillaMeshPartDefinition.of(e.getKey(), getRenderProperties(e.getValue().getAsJsonObject())), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(e.getValue().getAsJsonObject().get("array").getAsJsonArray())));
|
||||
}
|
||||
}
|
||||
|
||||
if (indices != null) {
|
||||
meshMap.put(VanillaMeshPartDefinition.of(UNGROUPED_NAME), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(indices.get("array").getAsJsonArray())));
|
||||
}
|
||||
|
||||
T mesh = constructor.invoke(arrayMap, meshMap, null, getRenderProperties(this.rootJson));
|
||||
mesh.putSoftBodySimulationInfo(this.loadClothInformation(positionArray));
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public CompositeMesh loadCompositeMesh() throws AssetLoadingException {
|
||||
if (!this.rootJson.has("meshes")) {
|
||||
throw new AssetLoadingException("Composite mesh loading exception: lower meshes undefined");
|
||||
}
|
||||
|
||||
JsonAssetLoader clothLoader = new JsonAssetLoader(this.rootJson.get("meshes").getAsJsonObject().get("cloth").getAsJsonObject(), null);
|
||||
JsonAssetLoader staticLoader = new JsonAssetLoader(this.rootJson.get("meshes").getAsJsonObject().get("static").getAsJsonObject(), null);
|
||||
SoftBodyTranslatable softBodyMesh = (SoftBodyTranslatable)clothLoader.loadMesh(false);
|
||||
StaticMesh<?> staticMesh = (StaticMesh<?>)staticLoader.loadMesh(false);
|
||||
|
||||
if (!softBodyMesh.canStartSoftBodySimulation()) {
|
||||
throw new AssetLoadingException("Composite mesh loading exception: soft mesh doesn't have cloth info");
|
||||
}
|
||||
|
||||
return new CompositeMesh(staticMesh, softBodyMesh);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public Mesh loadMesh() throws AssetLoadingException {
|
||||
return this.loadMesh(true);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
private Mesh loadMesh(boolean allowCompositeMesh) throws AssetLoadingException {
|
||||
if (!this.rootJson.has("mesh_loader")) {
|
||||
throw new AssetLoadingException("Mesh loading exception: No mesh loader provided!");
|
||||
}
|
||||
|
||||
String loader = this.rootJson.get("mesh_loader").getAsString();
|
||||
|
||||
switch (loader) {
|
||||
case "classic_mesh" -> {
|
||||
return this.loadClassicMesh(ClassicMesh::new);
|
||||
}
|
||||
case "skinned_mesh" -> {
|
||||
return this.loadSkinnedMesh(SkinnedMesh::new);
|
||||
}
|
||||
case "composite_mesh" -> {
|
||||
if (!allowCompositeMesh) {
|
||||
throw new AssetLoadingException("Can't have a composite mesh inside another composite mesh");
|
||||
}
|
||||
|
||||
return this.loadCompositeMesh();
|
||||
}
|
||||
default -> {
|
||||
throw new AssetLoadingException("Mesh loading exception: Unsupported mesh loader: " + loader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends Armature> T loadArmature(ArmatureContructor<T> constructor) {
|
||||
if (this.resourceLocation == null) {
|
||||
throw new AssetLoadingException("Can't load armature: Resource location is null.");
|
||||
}
|
||||
|
||||
JsonObject obj = this.rootJson.getAsJsonObject("armature");
|
||||
TransformFormat transformFormat = getAsTransformFormatOrDefault(obj, "armature_format");
|
||||
JsonObject hierarchy = obj.get("hierarchy").getAsJsonArray().get(0).getAsJsonObject();
|
||||
JsonArray nameAsVertexGroups = obj.getAsJsonArray("joints");
|
||||
Map<String, Integer> jointIds = Maps.newHashMap();
|
||||
|
||||
int id = 0;
|
||||
|
||||
for (int i = 0; i < nameAsVertexGroups.size(); i++) {
|
||||
String name = nameAsVertexGroups.get(i).getAsString();
|
||||
|
||||
if (name.equals(COORD_BONE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
jointIds.put(name, id);
|
||||
id++;
|
||||
}
|
||||
|
||||
Map<String, Joint> jointMap = Maps.newHashMap();
|
||||
Joint joint = getJoint(hierarchy, jointIds, jointMap, transformFormat, true);
|
||||
joint.initOriginTransform(new OpenMatrix4f());
|
||||
|
||||
String armatureName = this.resourceLocation.toString().replaceAll("(animmodels/|\\.json)", "");
|
||||
|
||||
return constructor.invoke(armatureName, jointMap.size(), joint, jointMap);
|
||||
}
|
||||
|
||||
private static Joint getJoint(JsonObject object, Map<String, Integer> jointIdMap, Map<String, Joint> jointMap, TransformFormat transformFormat, boolean root) {
|
||||
String name = object.get("name").getAsString();
|
||||
|
||||
if (!jointIdMap.containsKey(name)) {
|
||||
throw new AssetLoadingException("Can't load joint: joint name " + name + " doesn't exist in armature hierarchy.");
|
||||
}
|
||||
|
||||
// Skip Coord bone
|
||||
if (name.equals(COORD_BONE)) {
|
||||
JsonArray coordChildren = object.get("children").getAsJsonArray();
|
||||
|
||||
if (coordChildren.isEmpty()) {
|
||||
throw new AssetLoadingException("No children for Coord bone");
|
||||
} else if (coordChildren.size() > 1) {
|
||||
throw new AssetLoadingException("Coord bone can't have multiple children");
|
||||
} else {
|
||||
return getJoint(coordChildren.get(0).getAsJsonObject(), jointIdMap, jointMap, transformFormat, false);
|
||||
}
|
||||
}
|
||||
|
||||
JsonElement transform = GsonHelper.getNonNull(object, "transform");
|
||||
|
||||
// WORKAROUND: The case when transform format is wrongly specified!
|
||||
if (transformFormat == TransformFormat.ATTRIBUTES && transform.isJsonArray()) {
|
||||
transformFormat = TransformFormat.MATRIX;
|
||||
} else if (transformFormat == TransformFormat.MATRIX && transform.isJsonObject()) {
|
||||
transformFormat = TransformFormat.ATTRIBUTES;
|
||||
}
|
||||
|
||||
OpenMatrix4f localMatrix = null;
|
||||
|
||||
switch (transformFormat) {
|
||||
case MATRIX -> {
|
||||
float[] matrixElements = ParseUtil.toFloatArrayPrimitive(GsonHelper.convertToJsonArray(transform, "transform"));
|
||||
localMatrix = OpenMatrix4f.load(null, matrixElements);
|
||||
localMatrix.transpose();
|
||||
|
||||
if (root) {
|
||||
localMatrix.mulFront(BLENDER_TO_MINECRAFT_COORD);
|
||||
}
|
||||
}
|
||||
case ATTRIBUTES -> {
|
||||
JsonObject transformObject = transform.getAsJsonObject();
|
||||
JsonArray locArray = transformObject.get("loc").getAsJsonArray();
|
||||
JsonArray rotArray = transformObject.get("rot").getAsJsonArray();
|
||||
JsonArray scaArray = transformObject.get("sca").getAsJsonArray();
|
||||
JointTransform jointTransform
|
||||
= JointTransform.fromPrimitives(
|
||||
locArray.get(0).getAsFloat()
|
||||
, locArray.get(1).getAsFloat()
|
||||
, locArray.get(2).getAsFloat()
|
||||
, -rotArray.get(1).getAsFloat()
|
||||
, -rotArray.get(2).getAsFloat()
|
||||
, -rotArray.get(3).getAsFloat()
|
||||
, rotArray.get(0).getAsFloat()
|
||||
, scaArray.get(0).getAsFloat()
|
||||
, scaArray.get(1).getAsFloat()
|
||||
, scaArray.get(2).getAsFloat()
|
||||
);
|
||||
|
||||
localMatrix = jointTransform.toMatrix();
|
||||
|
||||
if (root) {
|
||||
localMatrix.mulFront(BLENDER_TO_MINECRAFT_COORD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Joint joint = new Joint(name, jointIdMap.get(name), localMatrix);
|
||||
jointMap.put(name, joint);
|
||||
|
||||
if (object.has("children")) {
|
||||
for (JsonElement children : object.get("children").getAsJsonArray()) {
|
||||
joint.addSubJoints(getJoint(children.getAsJsonObject(), jointIdMap, jointMap, transformFormat, false));
|
||||
}
|
||||
}
|
||||
|
||||
return joint;
|
||||
}
|
||||
|
||||
public AnimationClip loadClipForAnimation(StaticAnimation animation) {
|
||||
if (this.rootJson == null) {
|
||||
throw new AssetLoadingException("Can't find animation in path: " + animation);
|
||||
}
|
||||
|
||||
if (animation.getArmature() == null) {
|
||||
EpicFightMod.LOGGER.error("Animation " + animation + " doesn't have an armature.");
|
||||
}
|
||||
|
||||
TransformFormat format = getAsTransformFormatOrDefault(this.rootJson, "format");
|
||||
JsonArray array = this.rootJson.get("animation").getAsJsonArray();
|
||||
boolean action = animation instanceof MainFrameAnimation;
|
||||
boolean attack = animation instanceof AttackAnimation;
|
||||
boolean noTransformData = !action && !attack && FMLEnvironment.dist == Dist.DEDICATED_SERVER;
|
||||
boolean root = true;
|
||||
Armature armature = animation.getArmature().get();
|
||||
Set<String> allowedJoints = Sets.newLinkedHashSet();
|
||||
|
||||
if (attack) {
|
||||
for (Phase phase : ((AttackAnimation)animation).phases) {
|
||||
for (AttackAnimation.JointColliderPair colliderInfo : phase.getColliders()) {
|
||||
armature.gatherAllJointsInPathToTerminal(colliderInfo.getFirst().getName(), allowedJoints);
|
||||
}
|
||||
}
|
||||
} else if (action) {
|
||||
allowedJoints.add(ROOT_BONE);
|
||||
}
|
||||
|
||||
AnimationClip clip = new AnimationClip();
|
||||
|
||||
for (JsonElement element : array) {
|
||||
JsonObject jObject = element.getAsJsonObject();
|
||||
String name = jObject.get("name").getAsString();
|
||||
|
||||
if (attack && FMLEnvironment.dist == Dist.DEDICATED_SERVER && !allowedJoints.contains(name)) {
|
||||
if (name.equals(COORD_BONE)) {
|
||||
root = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Joint joint = armature.searchJointByName(name);
|
||||
|
||||
if (joint == null) {
|
||||
if (name.equals(COORD_BONE)) {
|
||||
TransformSheet sheet = getTransformSheet(jObject, new OpenMatrix4f(), true, format);
|
||||
|
||||
if (action) {
|
||||
((ActionAnimation)animation).addProperty(ActionAnimationProperty.COORD, sheet);
|
||||
}
|
||||
|
||||
root = false;
|
||||
continue;
|
||||
} else {
|
||||
EpicFightMod.LOGGER.debug("[EpicFightMod] No joint named " + name + " in " + animation);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
TransformSheet sheet = getTransformSheet(jObject, OpenMatrix4f.invert(joint.getLocalTransform(), null), root, format);
|
||||
|
||||
if (!noTransformData) {
|
||||
clip.addJointTransform(name, sheet);
|
||||
}
|
||||
|
||||
float maxFrameTime = sheet.maxFrameTime();
|
||||
|
||||
if (clip.getClipTime() < maxFrameTime) {
|
||||
clip.setClipTime(maxFrameTime);
|
||||
}
|
||||
|
||||
root = false;
|
||||
}
|
||||
|
||||
return clip;
|
||||
}
|
||||
|
||||
public AnimationClip loadAllJointsClipForAnimation(StaticAnimation animation) {
|
||||
TransformFormat format = getAsTransformFormatOrDefault(this.rootJson, "format");
|
||||
JsonArray array = this.rootJson.get("animation").getAsJsonArray();
|
||||
boolean root = true;
|
||||
|
||||
if (animation.getArmature() == null) {
|
||||
EpicFightMod.LOGGER.error("Animation " + animation + " doesn't have an armature.");
|
||||
}
|
||||
|
||||
Armature armature = animation.getArmature().get();
|
||||
AnimationClip clip = new AnimationClip();
|
||||
|
||||
for (JsonElement element : array) {
|
||||
JsonObject jObject = element.getAsJsonObject();
|
||||
String name = jObject.get("name").getAsString();
|
||||
Joint joint = armature.searchJointByName(name);
|
||||
|
||||
if (joint == null) {
|
||||
if (EpicFightSharedConstants.IS_DEV_ENV) {
|
||||
EpicFightMod.LOGGER.debug(animation.getRegistryName() + ": No joint named " + name + " in armature");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
TransformSheet sheet = getTransformSheet(jObject, OpenMatrix4f.invert(joint.getLocalTransform(), null), root, format);
|
||||
clip.addJointTransform(name, sheet);
|
||||
float maxFrameTime = sheet.maxFrameTime();
|
||||
|
||||
if (clip.getClipTime() < maxFrameTime) {
|
||||
clip.setClipTime(maxFrameTime);
|
||||
}
|
||||
|
||||
root = false;
|
||||
}
|
||||
|
||||
return clip;
|
||||
}
|
||||
|
||||
public JsonObject getRootJson() {
|
||||
return this.rootJson;
|
||||
}
|
||||
|
||||
public String getFileHash() {
|
||||
return this.filehash;
|
||||
}
|
||||
|
||||
public static TransformFormat getAsTransformFormatOrDefault(JsonObject jsonObject, String propertyName) {
|
||||
return jsonObject.has(propertyName) ? ParseUtil.enumValueOfOrNull(TransformFormat.class, GsonHelper.getAsString(jsonObject, propertyName)) : TransformFormat.MATRIX;
|
||||
}
|
||||
|
||||
public AnimationClip loadAnimationClip(Armature armature) {
|
||||
TransformFormat format = getAsTransformFormatOrDefault(this.rootJson, "format");
|
||||
JsonArray array = this.rootJson.get("animation").getAsJsonArray();
|
||||
AnimationClip clip = new AnimationClip();
|
||||
boolean root = true;
|
||||
|
||||
for (JsonElement element : array) {
|
||||
JsonObject jObject = element.getAsJsonObject();
|
||||
String name = jObject.get("name").getAsString();
|
||||
Joint joint = armature.searchJointByName(name);
|
||||
|
||||
if (joint == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TransformSheet sheet = getTransformSheet(element.getAsJsonObject(), OpenMatrix4f.invert(joint.getLocalTransform(), null), root, format);
|
||||
clip.addJointTransform(name, sheet);
|
||||
float maxFrameTime = sheet.maxFrameTime();
|
||||
|
||||
if (clip.getClipTime() < maxFrameTime) {
|
||||
clip.setClipTime(maxFrameTime);
|
||||
}
|
||||
|
||||
root = false;
|
||||
}
|
||||
|
||||
return clip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param jObject
|
||||
* @param invLocalTransform nullable if transformFormat == {@link TransformFormat#ATTRIBUTES}
|
||||
* @param rootCorrection no matter what the value is if transformFormat == {@link TransformFormat#ATTRIBUTES}
|
||||
* @param transformFormat
|
||||
* @return
|
||||
*/
|
||||
public static TransformSheet getTransformSheet(JsonObject jObject, @Nullable OpenMatrix4f invLocalTransform, boolean rootCorrection, TransformFormat transformFormat) throws AssetLoadingException, JsonParseException {
|
||||
JsonArray timeArray = jObject.getAsJsonArray("time");
|
||||
JsonArray transformArray = jObject.getAsJsonArray("transform");
|
||||
|
||||
if (timeArray.size() != transformArray.size()) {
|
||||
throw new AssetLoadingException(
|
||||
"Can't read transform sheet: the size of timestamp and transform array is different."
|
||||
+ "timestamp array size: " + timeArray.size() + ", transform array size: " + transformArray.size()
|
||||
);
|
||||
}
|
||||
|
||||
int timesCount = timeArray.size();
|
||||
List<Keyframe> keyframeList = Lists.newArrayList();
|
||||
|
||||
for (int i = 0; i < timesCount; i++) {
|
||||
float timeStamp = timeArray.get(i).getAsFloat();
|
||||
|
||||
if (timeStamp < 0.0F) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// WORKAROUND: The case when transform format is wrongly specified!
|
||||
if (transformFormat == TransformFormat.ATTRIBUTES && transformArray.get(i).isJsonArray()) {
|
||||
transformFormat = TransformFormat.MATRIX;
|
||||
} else if (transformFormat == TransformFormat.MATRIX && transformArray.get(i).isJsonObject()) {
|
||||
transformFormat = TransformFormat.ATTRIBUTES;
|
||||
}
|
||||
|
||||
switch (transformFormat) {
|
||||
case MATRIX -> {
|
||||
JsonArray matrixArray = transformArray.get(i).getAsJsonArray();
|
||||
float[] matrixElements = new float[16];
|
||||
|
||||
for (int j = 0; j < 16; j++) {
|
||||
matrixElements[j] = matrixArray.get(j).getAsFloat();
|
||||
}
|
||||
|
||||
OpenMatrix4f matrix = OpenMatrix4f.load(null, matrixElements);
|
||||
matrix.transpose();
|
||||
|
||||
if (rootCorrection) {
|
||||
matrix.mulFront(BLENDER_TO_MINECRAFT_COORD);
|
||||
}
|
||||
|
||||
matrix.mulFront(invLocalTransform);
|
||||
|
||||
JointTransform transform = JointTransform.fromMatrix(matrix);
|
||||
transform.rotation().normalize();
|
||||
keyframeList.add(new Keyframe(timeStamp, transform));
|
||||
}
|
||||
case ATTRIBUTES -> {
|
||||
JsonObject transformObject = transformArray.get(i).getAsJsonObject();
|
||||
JsonArray locArray = transformObject.get("loc").getAsJsonArray();
|
||||
JsonArray rotArray = transformObject.get("rot").getAsJsonArray();
|
||||
JsonArray scaArray = transformObject.get("sca").getAsJsonArray();
|
||||
JointTransform transform
|
||||
= JointTransform.fromPrimitives(
|
||||
locArray.get(0).getAsFloat()
|
||||
, locArray.get(1).getAsFloat()
|
||||
, locArray.get(2).getAsFloat()
|
||||
, -rotArray.get(1).getAsFloat()
|
||||
, -rotArray.get(2).getAsFloat()
|
||||
, -rotArray.get(3).getAsFloat()
|
||||
, rotArray.get(0).getAsFloat()
|
||||
, scaArray.get(0).getAsFloat()
|
||||
, scaArray.get(1).getAsFloat()
|
||||
, scaArray.get(2).getAsFloat()
|
||||
);
|
||||
|
||||
keyframeList.add(new Keyframe(timeStamp, transform));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TransformSheet sheet = new TransformSheet(keyframeList);
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how the transform is expressed in json
|
||||
*
|
||||
* {@link TransformFormat#MATRIX} be like,
|
||||
* [0, 1, 2, ..., 15]
|
||||
*
|
||||
* {@link TransformFormat#ATTRIBUTES} be like,
|
||||
* {
|
||||
* "loc": [0, 0, 0],
|
||||
* "rot": [0, 0, 0, 1],
|
||||
* "sca": [1, 1, 1],
|
||||
* }
|
||||
*/
|
||||
public enum TransformFormat {
|
||||
MATRIX, ATTRIBUTES
|
||||
}
|
||||
}
|
||||
25
src/main/java/com/tiedup/remake/rig/asset/SelfAccessor.java
Normal file
25
src/main/java/com/tiedup/remake/rig/asset/SelfAccessor.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.asset;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public record SelfAccessor<A>(ResourceLocation registryName, A asset) implements AssetAccessor<A> {
|
||||
public static <A> SelfAccessor<A> create(ResourceLocation registryName, A asset) {
|
||||
return new SelfAccessor<> (registryName, asset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public A get() {
|
||||
return this.asset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRegistry() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
135
src/main/java/com/tiedup/remake/rig/cloth/AbstractSimulator.java
Normal file
135
src/main/java/com/tiedup/remake/rig/cloth/AbstractSimulator.java
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.cloth;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import yesman.epicfight.api.physics.PhysicsSimulator;
|
||||
import yesman.epicfight.api.physics.SimulationObject;
|
||||
import yesman.epicfight.api.physics.SimulationObject.SimulationObjectBuilder;
|
||||
import yesman.epicfight.api.physics.SimulationProvider;
|
||||
|
||||
public abstract class AbstractSimulator<KEY, B extends SimulationObjectBuilder, PV extends SimulationProvider<O, SO, B, PV>, O, SO extends SimulationObject<B, PV, O>> implements PhysicsSimulator<KEY, B, PV, O, SO> {
|
||||
protected Map<KEY, ObjectWrapper> simulationObjects = Maps.newHashMap();
|
||||
|
||||
@Override
|
||||
public void tick(O simObject) {
|
||||
this.simulationObjects.values().removeIf((keyWrapper) -> {
|
||||
if (keyWrapper.isRunning()) {
|
||||
if (!keyWrapper.runWhen.getAsBoolean()) {
|
||||
keyWrapper.stopRunning();
|
||||
|
||||
if (!keyWrapper.permanent) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (keyWrapper.runWhen.getAsBoolean()) {
|
||||
keyWrapper.startRunning(simObject);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a simulation object and run. Remove when @Param until returns false
|
||||
*/
|
||||
@Override
|
||||
public void runUntil(KEY key, PV provider, B builder, BooleanSupplier until) {
|
||||
this.simulationObjects.put(key, new ObjectWrapper(provider, until, false, builder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an undeleted simulation object. Run simulation when @Param when returns true
|
||||
*/
|
||||
@Override
|
||||
public void runWhen(KEY key, PV provider, B builder, BooleanSupplier when) {
|
||||
this.simulationObjects.put(key, new ObjectWrapper(provider, when, true, builder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop simulation
|
||||
*/
|
||||
@Override
|
||||
public void stop(KEY key) {
|
||||
this.simulationObjects.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart with the same condition but with another provider
|
||||
*/
|
||||
@Override
|
||||
public void restart(KEY key) {
|
||||
ObjectWrapper kwrap = this.simulationObjects.get(key);
|
||||
|
||||
if (kwrap != null) {
|
||||
this.stop(key);
|
||||
this.simulationObjects.put(key, new ObjectWrapper(kwrap.provider, kwrap.runWhen, kwrap.permanent, kwrap.builder));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning(KEY key) {
|
||||
return this.simulationObjects.containsKey(key) ? this.simulationObjects.get(key).isRunning() : false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<SO> getRunningObject(KEY key) {
|
||||
if (!this.simulationObjects.containsKey(key)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(this.simulationObjects.get(key).simulationObject);
|
||||
}
|
||||
|
||||
public List<Pair<KEY, SO>> getAllRunningObjects() {
|
||||
return this.simulationObjects.entrySet().stream().filter((entry) -> entry.getValue().isRunning()).map((entry) -> Pair.of(entry.getKey(), entry.getValue().simulationObject)).toList();
|
||||
}
|
||||
|
||||
protected class ObjectWrapper {
|
||||
final PV provider;
|
||||
final B builder;
|
||||
final BooleanSupplier runWhen;
|
||||
final boolean permanent;
|
||||
|
||||
SO simulationObject;
|
||||
boolean isRunning;
|
||||
|
||||
ObjectWrapper(PV key, BooleanSupplier runWhen, boolean permanent, B builder) {
|
||||
this.provider = key;
|
||||
this.runWhen = runWhen;
|
||||
this.permanent = permanent;
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public void startRunning(O simObject) {
|
||||
this.simulationObject = this.provider.createSimulationData(this.provider, simObject, this.builder);
|
||||
|
||||
if (this.simulationObject != null) {
|
||||
this.isRunning = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void stopRunning() {
|
||||
this.isRunning = false;
|
||||
this.simulationObject = null;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return this.isRunning;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.cloth;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
/**
|
||||
* 0: Root,
|
||||
* 1: Thigh_R,
|
||||
* 2: "Leg_R",
|
||||
* 3: "Knee_R",
|
||||
* 4: "Thigh_L",
|
||||
* 5: "Leg_L",
|
||||
* 6: "Knee_L",
|
||||
* 7: "Torso",
|
||||
* 8: "Chest",
|
||||
* 9: "Head",
|
||||
* 10: "Shoulder_R",
|
||||
* 11: "Arm_R",
|
||||
* 12: "Hand_R",
|
||||
* 13: "Tool_R",
|
||||
* 14: "Elbow_R",
|
||||
* 15: "Shoulder_L",
|
||||
* 16: "Arm_L",
|
||||
* 17: "Hand_L",
|
||||
* 18: "Tool_L",
|
||||
* 19: "Elbow_L"
|
||||
**/
|
||||
|
||||
public class ClothColliderPresets {
|
||||
public static final List<Pair<Function<ClothSimulatable, OpenMatrix4f>, ClothSimulator.ClothOBBCollider>> BIPED_SLIM = ImmutableList.<Pair<Function<ClothSimulatable, OpenMatrix4f>, ClothSimulator.ClothOBBCollider>>builder()
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[1], new ClothSimulator.ClothOBBCollider(0.125D, 0.24D, 0.125D, 0.0D, 0.22D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[2], new ClothSimulator.ClothOBBCollider(0.125D, 0.1875D, 0.125D, 0.0D, 0.1875D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[4], new ClothSimulator.ClothOBBCollider(0.125D, 0.24D, 0.125D, 0.0D, 0.22D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[5], new ClothSimulator.ClothOBBCollider(0.125D, 0.1875D, 0.125D, 0.0D, 0.1875D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[7], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.13D, 0.0D, 0.125D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[8], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.13D, 0.0D, 0.3D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[9], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.25D, 0.0D, 0.2D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[11], new ClothSimulator.ClothOBBCollider(0.12D, 0.24D, 0.125D, -0.05D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[12], new ClothSimulator.ClothOBBCollider(0.12D, 0.1875D, 0.125D, -0.05D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[16], new ClothSimulator.ClothOBBCollider(0.12D, 0.24D, 0.125D, 0.05D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[17], new ClothSimulator.ClothOBBCollider(0.12D, 0.1875D, 0.125D, 0.05D, 0.14D, 0.0D)))
|
||||
.build();
|
||||
|
||||
public static final List<Pair<Function<ClothSimulatable, OpenMatrix4f>, ClothSimulator.ClothOBBCollider>> BIPED = ImmutableList.<Pair<Function<ClothSimulatable, OpenMatrix4f>, ClothSimulator.ClothOBBCollider>>builder()
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[1], new ClothSimulator.ClothOBBCollider(0.125D, 0.24D, 0.125D, 0.0D, 0.22D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[2], new ClothSimulator.ClothOBBCollider(0.125D, 0.1875D, 0.125D, 0.0D, 0.1875D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[4], new ClothSimulator.ClothOBBCollider(0.125D, 0.24D, 0.125D, 0.0D, 0.22D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[5], new ClothSimulator.ClothOBBCollider(0.125D, 0.1875D, 0.125D, 0.0D, 0.1875D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[7], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.13D, 0.0D, 0.125D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[8], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.13D, 0.0D, 0.3D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[9], new ClothSimulator.ClothOBBCollider(0.25D, 0.25D, 0.25D, 0.0D, 0.2D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[11], new ClothSimulator.ClothOBBCollider(0.13D, 0.24D, 0.13D, -0.0D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[12], new ClothSimulator.ClothOBBCollider(0.13D, 0.1875D, 0.13D, -0.0D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[16], new ClothSimulator.ClothOBBCollider(0.13D, 0.24D, 0.13D, 0.0D, 0.14D, 0.0D)))
|
||||
.add(Pair.of((simObject) -> simObject.getArmature().getPoseMatrices()[17], new ClothSimulator.ClothOBBCollider(0.13D, 0.1875D, 0.13D, 0.0D, 0.14D, 0.0D)))
|
||||
.build();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.cloth;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.anim.Animator;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import yesman.epicfight.api.physics.SimulatableObject;
|
||||
|
||||
public interface ClothSimulatable extends SimulatableObject {
|
||||
@Nullable
|
||||
Armature getArmature();
|
||||
|
||||
@Nullable
|
||||
Animator getSimulatableAnimator();
|
||||
|
||||
boolean invalid();
|
||||
public Vec3 getObjectVelocity();
|
||||
public float getYRot();
|
||||
public float getYRotO();
|
||||
|
||||
// Cloth object requires providing location info for 2 steps before for accurate continuous collide detection.
|
||||
public Vec3 getAccurateCloakLocation(float partialFrame);
|
||||
public Vec3 getAccuratePartialLocation(float partialFrame);
|
||||
public float getAccurateYRot(float partialFrame);
|
||||
public float getYRotDelta(float partialFrame);
|
||||
public float getScale();
|
||||
public float getGravity();
|
||||
|
||||
ClothSimulator getClothSimulator();
|
||||
}
|
||||
1931
src/main/java/com/tiedup/remake/rig/cloth/ClothSimulator.java
Normal file
1931
src/main/java/com/tiedup/remake/rig/cloth/ClothSimulator.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.event;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import com.tiedup.remake.rig.render.PatchedEntityRenderer;
|
||||
import com.tiedup.remake.rig.render.item.RenderItemBase;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public abstract class PatchedRenderersEvent extends Event implements IModBusEvent {
|
||||
public static class RegisterItemRenderer extends PatchedRenderersEvent {
|
||||
private final Map<ResourceLocation, Function<JsonElement, RenderItemBase>> itemRenderers;
|
||||
|
||||
public RegisterItemRenderer(Map<ResourceLocation, Function<JsonElement, RenderItemBase>> itemRenderers) {
|
||||
this.itemRenderers = itemRenderers;
|
||||
}
|
||||
|
||||
public void addItemRenderer(ResourceLocation rl, Function<JsonElement, RenderItemBase> provider) {
|
||||
if (this.itemRenderers.containsKey(rl)) {
|
||||
throw new IllegalArgumentException("Item renderer " + rl + " already registered.");
|
||||
}
|
||||
|
||||
this.itemRenderers.put(rl, provider);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Add extends PatchedRenderersEvent {
|
||||
private final Map<EntityType<?>, Function<EntityType<?>, PatchedEntityRenderer>> entityRendererProvider;
|
||||
private final EntityRendererProvider.Context context;
|
||||
|
||||
public Add(Map<EntityType<?>, Function<EntityType<?>, PatchedEntityRenderer>> entityRendererProvider, EntityRendererProvider.Context context) {
|
||||
this.entityRendererProvider = entityRendererProvider;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void addPatchedEntityRenderer(EntityType<?> entityType, Function<EntityType<?>, PatchedEntityRenderer> provider) {
|
||||
this.entityRendererProvider.put(entityType, provider);
|
||||
}
|
||||
|
||||
public EntityRendererProvider.Context getContext() {
|
||||
return this.context;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Modify extends PatchedRenderersEvent {
|
||||
private final Map<EntityType<?>, PatchedEntityRenderer> renderers;
|
||||
|
||||
public Modify(Map<EntityType<?>, PatchedEntityRenderer> renderers) {
|
||||
this.renderers = renderers;
|
||||
}
|
||||
|
||||
public PatchedEntityRenderer get(EntityType<?> entityType) {
|
||||
return this.renderers.get(entityType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.event;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.render.PatchedEntityRenderer;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class PrepareModelEvent extends Event {
|
||||
private final SkinnedMesh mesh;
|
||||
private final LivingEntityPatch<?> entitypatch;
|
||||
private final MultiBufferSource buffer;
|
||||
private final PoseStack poseStack;
|
||||
private final int packedLight;
|
||||
private final float partialTicks;
|
||||
|
||||
private final PatchedEntityRenderer<?, ?, ?, ?> renderer;
|
||||
|
||||
public PrepareModelEvent(PatchedEntityRenderer<?, ?, ?, ?> renderer, SkinnedMesh mesh, LivingEntityPatch<?> entitypatch, MultiBufferSource buffer, PoseStack poseStack, int packedLight, float partialTicks) {
|
||||
this.renderer = renderer;
|
||||
this.mesh = mesh;
|
||||
this.entitypatch = entitypatch;
|
||||
this.buffer = buffer;
|
||||
this.poseStack = poseStack;
|
||||
this.packedLight = packedLight;
|
||||
this.partialTicks = partialTicks;
|
||||
}
|
||||
|
||||
public SkinnedMesh getMesh() {
|
||||
return this.mesh;
|
||||
}
|
||||
|
||||
public LivingEntityPatch<?> getEntityPatch() {
|
||||
return this.entitypatch;
|
||||
}
|
||||
|
||||
public MultiBufferSource getBuffer() {
|
||||
return this.buffer;
|
||||
}
|
||||
|
||||
public PoseStack getPoseStack() {
|
||||
return this.poseStack;
|
||||
}
|
||||
|
||||
public int getPackedLight() {
|
||||
return this.packedLight;
|
||||
}
|
||||
|
||||
public float getPartialTicks() {
|
||||
return this.partialTicks;
|
||||
}
|
||||
|
||||
public PatchedEntityRenderer<?, ?, ?, ?> getRenderer() {
|
||||
return this.renderer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.event;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.client.model.EntityModel;
|
||||
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.render.layer.LayerUtil;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
|
||||
public class RegisterResourceLayersEvent<E extends LivingEntity, T extends LivingEntityPatch<E>, M extends EntityModel<E>, R extends LivingEntityRenderer<E, M>, AM extends SkinnedMesh> extends Event {
|
||||
private final Map<ResourceLocation, LayerUtil.LayerProvider<E, T, M, R, AM>> layersbyid;
|
||||
|
||||
public RegisterResourceLayersEvent(Map<ResourceLocation, LayerUtil.LayerProvider<E, T, M, R, AM>> layersbyid) {
|
||||
this.layersbyid = layersbyid;
|
||||
}
|
||||
|
||||
public void register(ResourceLocation rl, LayerUtil.LayerProvider<E, T, M, R, AM> layerAdder) {
|
||||
this.layersbyid.put(rl, layerAdder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class AnimationInvokeException extends RuntimeException {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public AnimationInvokeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AnimationInvokeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AnimationInvokeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class AssetLoadingException extends RuntimeException {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public AssetLoadingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AssetLoadingException(String message, Throwable ex) {
|
||||
super(message, ex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class DatapackException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DatapackException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
public class ShaderParsingException extends RuntimeException {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ShaderParsingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ShaderParsingException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ShaderParsingException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.math;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
|
||||
public class AnimationTransformEntry {
|
||||
private static final String[] BINDING_PRIORITY = {JointTransform.PARENT, JointTransform.JOINT_LOCAL_TRANSFORM, JointTransform.ANIMATION_TRANSFORM, JointTransform.RESULT1, JointTransform.RESULT2};
|
||||
private final Map<String, Pair<OpenMatrix4f, MatrixOperation>> matrices = Maps.newHashMap();
|
||||
|
||||
public void put(String entryPosition, OpenMatrix4f matrix) {
|
||||
this.put(entryPosition, matrix, OpenMatrix4f::mul);
|
||||
}
|
||||
|
||||
public void put(String entryPosition, OpenMatrix4f matrix, MatrixOperation operation) {
|
||||
if (this.matrices.containsKey(entryPosition)) {
|
||||
Pair<OpenMatrix4f, MatrixOperation> appliedTransform = this.matrices.get(entryPosition);
|
||||
OpenMatrix4f result = appliedTransform.getSecond().mul(appliedTransform.getFirst(), matrix, null);
|
||||
this.matrices.put(entryPosition, Pair.of(result, operation));
|
||||
} else {
|
||||
this.matrices.put(entryPosition, Pair.of(new OpenMatrix4f(matrix), operation));
|
||||
}
|
||||
}
|
||||
|
||||
public OpenMatrix4f getResult() {
|
||||
OpenMatrix4f result = new OpenMatrix4f();
|
||||
|
||||
for (String entryName : BINDING_PRIORITY) {
|
||||
if (this.matrices.containsKey(entryName)) {
|
||||
Pair<OpenMatrix4f, MatrixOperation> pair = this.matrices.get(entryName);
|
||||
pair.getSecond().mul(result, pair.getFirst(), result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
599
src/main/java/com/tiedup/remake/rig/math/MathUtils.java
Normal file
599
src/main/java/com/tiedup/remake/rig/math/MathUtils.java
Normal file
@@ -0,0 +1,599 @@
|
||||
/*
|
||||
* 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.math;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.joml.Math;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.joml.Vector4i;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public class MathUtils {
|
||||
public static final Vec3 XP = new Vec3(1.0D, 0.0D, 0.0D);
|
||||
public static final Vec3 XN = new Vec3(-1.0D, 0.0D, 0.0D);
|
||||
public static final Vec3 YP = new Vec3(0.0D, 1.0D, 0.0D);
|
||||
public static final Vec3 YN = new Vec3(0.0D, -1.0D, 0.0D);
|
||||
public static final Vec3 ZP = new Vec3(0.0D, 0.0D, 1.0D);
|
||||
public static final Vec3 ZN = new Vec3(0.0D, 0.0D, -1.0D);
|
||||
|
||||
public static OpenMatrix4f getModelMatrixIntegral(float xPosO, float xPos, float yPosO, float yPos, float zPosO, float zPos, float xRotO, float xRot, float yRotO, float yRot, float partialTick, float scaleX, float scaleY, float scaleZ) {
|
||||
OpenMatrix4f modelMatrix = new OpenMatrix4f();
|
||||
Vec3f translation = new Vec3f(-(xPosO + (xPos - xPosO) * partialTick), ((yPosO + (yPos - yPosO) * partialTick)), -(zPosO + (zPos - zPosO) * partialTick));
|
||||
float partialXRot = Mth.rotLerp(partialTick, xRotO, xRot);
|
||||
float partialYRot = Mth.rotLerp(partialTick, yRotO, yRot);
|
||||
modelMatrix.translate(translation).rotateDeg(-partialYRot, Vec3f.Y_AXIS).rotateDeg(-partialXRot, Vec3f.X_AXIS).scale(scaleX, scaleY, scaleZ);
|
||||
|
||||
return modelMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blender 2.79 bezier curve
|
||||
* @param t: 0 ~ 1
|
||||
* @retur
|
||||
*/
|
||||
public static double bezierCurve(double t) {
|
||||
double p1 = 0.0D;
|
||||
double p2 = 0.0D;
|
||||
double p3 = 1.0D;
|
||||
double p4 = 1.0D;
|
||||
double v1, v2, v3, v4;
|
||||
|
||||
v1 = p1;
|
||||
v2 = 3.0D * (p2 - p1);
|
||||
v3 = 3.0D * (p1 - 2.0D * p2 + p3);
|
||||
v4 = p4 - p1 + 3.0D * (p2 - p3);
|
||||
|
||||
return v1 + t * v2 + t * t * v3 + t * t * t * v4;
|
||||
}
|
||||
|
||||
public static float bezierCurve(float t) {
|
||||
return (float)bezierCurve((double)t);
|
||||
}
|
||||
|
||||
public static int getSign(double value) {
|
||||
return value > 0.0D ? 1 : -1;
|
||||
}
|
||||
|
||||
public static Vec3 getVectorForRotation(float pitch, float yaw) {
|
||||
float f = pitch * (float) Math.PI / 180F;
|
||||
float f1 = -yaw * (float) Math.PI / 180F;
|
||||
float f2 = Mth.cos(f1);
|
||||
float f3 = Mth.sin(f1);
|
||||
float f4 = Mth.cos(f);
|
||||
float f5 = Mth.sin(f);
|
||||
|
||||
return new Vec3(f3 * f4, -f5, f2 * f4);
|
||||
}
|
||||
|
||||
public static float lerpBetween(float f1, float f2, float zero2one) {
|
||||
float f = 0;
|
||||
|
||||
for (f = f2 - f1; f < -180.0F; f += 360.0F) {
|
||||
}
|
||||
|
||||
while (f >= 180.0F) {
|
||||
f -= 360.0F;
|
||||
}
|
||||
|
||||
return f1 + zero2one * f;
|
||||
}
|
||||
|
||||
public static float rotlerp(float from, float to, float limit) {
|
||||
float f = Mth.wrapDegrees(to - from);
|
||||
|
||||
if (f > limit) {
|
||||
f = limit;
|
||||
}
|
||||
|
||||
if (f < -limit) {
|
||||
f = -limit;
|
||||
}
|
||||
|
||||
float f1 = from + f;
|
||||
|
||||
while (f1 >= 180.0F) {
|
||||
f1 -= 360.0F;
|
||||
}
|
||||
|
||||
while (f1 <= -180.0F) {
|
||||
f1 += 360.0F;
|
||||
}
|
||||
|
||||
return f1;
|
||||
}
|
||||
|
||||
public static float rotWrap(double d) {
|
||||
while (d >= 180.0) {
|
||||
d -= 360.0;
|
||||
}
|
||||
while (d < -180.0) {
|
||||
d += 360.0;
|
||||
}
|
||||
return (float)d;
|
||||
}
|
||||
|
||||
public static float wrapRadian(float pValue) {
|
||||
float maxRot = (float)Math.PI * 2.0F;
|
||||
float f = pValue % maxRot;
|
||||
|
||||
if (f >= Math.PI) {
|
||||
f -= maxRot;
|
||||
}
|
||||
|
||||
if (f < -Math.PI) {
|
||||
f += maxRot;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
public static float lerpDegree(float from, float to, float progression) {
|
||||
from = Mth.wrapDegrees(from);
|
||||
to = Mth.wrapDegrees(to);
|
||||
|
||||
if (Math.abs(from - to) > 180.0F) {
|
||||
if (to < 0.0F) {
|
||||
from -= 360.0F;
|
||||
} else if (to > 0.0F) {
|
||||
from += 360.0F;
|
||||
}
|
||||
}
|
||||
|
||||
return Mth.lerp(progression, from, to);
|
||||
}
|
||||
|
||||
public static float findNearestRotation(float src, float rotation) {
|
||||
float diff = Math.abs(src - rotation);
|
||||
float idealRotation = rotation;
|
||||
int sign = Mth.sign(src - rotation);
|
||||
|
||||
if (sign == 0) {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
float next = idealRotation + sign * 360.0F;
|
||||
|
||||
if (Math.abs(src - next) > diff) {
|
||||
return idealRotation;
|
||||
}
|
||||
|
||||
idealRotation = next;
|
||||
diff = Math.abs(src - next);
|
||||
}
|
||||
}
|
||||
|
||||
public static Vec3 getNearestVector(Vec3 from, Vec3... vectors) {
|
||||
double minLength = 1000000.0D;
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < vectors.length; i++) {
|
||||
if (vectors[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double distSqr = from.distanceToSqr(vectors[i]);
|
||||
|
||||
if (distSqr < minLength) {
|
||||
minLength = distSqr;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return vectors[index];
|
||||
}
|
||||
|
||||
public static Vec3 getNearestVector(Vec3 from, List<Vec3> vectors) {
|
||||
return getNearestVector(from, vectors.toArray(new Vec3[0]));
|
||||
}
|
||||
|
||||
public static int greatest(int... iList) {
|
||||
int max = Integer.MIN_VALUE;
|
||||
|
||||
for (int i : iList) {
|
||||
if (max < i) {
|
||||
max = i;
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
public static int least(int... iList) {
|
||||
int min = Integer.MAX_VALUE;
|
||||
|
||||
for (int i : iList) {
|
||||
if (min > i) {
|
||||
min = i;
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
public static float greatest(float... fList) {
|
||||
float max = -1000000.0F;
|
||||
|
||||
for (float f : fList) {
|
||||
if (max < f) {
|
||||
max = f;
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
public static float least(float... fList) {
|
||||
float min = 1000000.0F;
|
||||
|
||||
for (float f : fList) {
|
||||
if (min > f) {
|
||||
min = f;
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
public static double greatest(double... dList) {
|
||||
double max = -1000000.0D;
|
||||
|
||||
for (double d : dList) {
|
||||
if (max < d) {
|
||||
max = d;
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
public static double least(double... dList) {
|
||||
double min = 1000000.0D;
|
||||
|
||||
for (double d : dList) {
|
||||
if (min > d) {
|
||||
min = d;
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public static void translateStack(PoseStack poseStack, OpenMatrix4f mat) {
|
||||
poseStack.translate(mat.m30, mat.m31, mat.m32);
|
||||
}
|
||||
|
||||
private static final OpenMatrix4f OPEN_MATRIX_BUFFER = new OpenMatrix4f();
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public static void rotateStack(PoseStack poseStack, OpenMatrix4f mat) {
|
||||
OpenMatrix4f.transpose(mat, OPEN_MATRIX_BUFFER);
|
||||
poseStack.mulPose(getQuaternionFromMatrix(OPEN_MATRIX_BUFFER));
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public static void scaleStack(PoseStack poseStack, OpenMatrix4f mat) {
|
||||
OpenMatrix4f.transpose(mat, OPEN_MATRIX_BUFFER);
|
||||
Vector3f vector = getScaleVectorFromMatrix(OPEN_MATRIX_BUFFER);
|
||||
poseStack.scale(vector.x(), vector.y(), vector.z());
|
||||
}
|
||||
|
||||
private static final Matrix4f MATRIX4F = new Matrix4f();
|
||||
private static final Matrix3f MATRIX3F = new Matrix3f();
|
||||
|
||||
public static void mulStack(PoseStack poseStack, OpenMatrix4f mat) {
|
||||
OpenMatrix4f.exportToMojangMatrix(mat, MATRIX4F);
|
||||
MATRIX3F.set(MATRIX4F);
|
||||
poseStack.mulPoseMatrix(MATRIX4F);
|
||||
poseStack.last().normal().mul(MATRIX3F);
|
||||
}
|
||||
|
||||
public static double getAngleBetween(Vec3f a, Vec3f b) {
|
||||
Vec3f normA = Vec3f.normalize(a, null);
|
||||
Vec3f normB = Vec3f.normalize(b, null);
|
||||
|
||||
double cos = (normA.x * normB.x + normA.y * normB.y + normA.z * normB.z);
|
||||
return Math.toDegrees(Math.acos(cos));
|
||||
}
|
||||
|
||||
public static double getAngleBetween(Vec3 a, Vec3 b) {
|
||||
Vec3 normA = a.normalize();
|
||||
Vec3 normB = b.normalize();
|
||||
|
||||
double cos = (normA.x * normB.x + normA.y * normB.y + normA.z * normB.z);
|
||||
return Math.toDegrees(Math.safeAcos(cos));
|
||||
}
|
||||
|
||||
public static float getAngleBetween(Quaternionf a, Quaternionf b) {
|
||||
float dot = a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
return 2.0F * (Math.safeAcos(MathUtils.getSign(dot) * b.w) - Math.safeAcos(a.w));
|
||||
}
|
||||
|
||||
public static double getXRotOfVector(Vec3 vec) {
|
||||
Vec3 normalized = vec.normalize();
|
||||
return -(Math.atan2(normalized.y, (float)Math.sqrt(normalized.x * normalized.x + normalized.z * normalized.z)) * (180D / Math.PI));
|
||||
}
|
||||
|
||||
public static double getYRotOfVector(Vec3 vec) {
|
||||
Vec3 normalized = vec.normalize();
|
||||
return Math.atan2(normalized.z, normalized.x) * (180D / Math.PI) - 90.0F;
|
||||
}
|
||||
|
||||
private static Quaternionf getQuaternionFromMatrix(OpenMatrix4f mat) {
|
||||
Quaternionf quat = new Quaternionf(0, 0, 0, 1);
|
||||
quat.setFromUnnormalized(OpenMatrix4f.exportToMojangMatrix(mat.transpose(null)));
|
||||
return quat;
|
||||
}
|
||||
|
||||
public static Vec3f lerpVector(Vec3f start, Vec3f end, float delta) {
|
||||
return lerpVector(start, end, delta, new Vec3f());
|
||||
}
|
||||
|
||||
public static Vec3f lerpVector(Vec3f start, Vec3f end, float delta, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.x = start.x + (end.x - start.x) * delta;
|
||||
dest.y = start.y + (end.y - start.y) * delta;
|
||||
dest.z = start.z + (end.z - start.z) * delta;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Vec3 lerpVector(Vec3 start, Vec3 end, float delta) {
|
||||
return new Vec3(start.x + (end.x - start.x) * delta, start.y + (end.y - start.y) * delta, start.z + (end.z - start.z) * delta);
|
||||
}
|
||||
|
||||
public static Vector3f lerpMojangVector(Vector3f start, Vector3f end, float delta) {
|
||||
float x = start.x() + (end.x() - start.x()) * delta;
|
||||
float y = start.y() + (end.y() - start.y()) * delta;
|
||||
float z = start.z() + (end.z() - start.z()) * delta;
|
||||
return new Vector3f(x, y, z);
|
||||
}
|
||||
|
||||
public static Vec3 projectVector(Vec3 from, Vec3 to) {
|
||||
double dot = to.dot(from);
|
||||
double normalScale = 1.0D / ((to.x * to.x) + (to.y * to.y) + (to.z * to.z));
|
||||
|
||||
return new Vec3(dot * to.x * normalScale, dot * to.y * normalScale, dot * to.z * normalScale);
|
||||
}
|
||||
|
||||
public static Vec3f projectVector(Vec3f from, Vec3f to, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
float dot = Vec3f.dot(to, from);
|
||||
float normalScale = 1.0F / ((to.x * to.x) + (to.y * to.y) + (to.z * to.z));
|
||||
|
||||
dest.x = dot * to.x * normalScale;
|
||||
dest.y = dot * to.y * normalScale;
|
||||
dest.z = dot * to.z * normalScale;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static void setQuaternion(Quaternionf quat, float x, float y, float z, float w) {
|
||||
quat.set(x, y, z, w);
|
||||
}
|
||||
|
||||
public static Quaternionf mulQuaternion(Quaternionf left, Quaternionf right, Quaternionf dest) {
|
||||
if (dest == null) {
|
||||
dest = new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F);
|
||||
}
|
||||
|
||||
float f = left.x();
|
||||
float f1 = left.y();
|
||||
float f2 = left.z();
|
||||
float f3 = left.w();
|
||||
float f4 = right.x();
|
||||
float f5 = right.y();
|
||||
float f6 = right.z();
|
||||
float f7 = right.w();
|
||||
float i = f3 * f4 + f * f7 + f1 * f6 - f2 * f5;
|
||||
float j = f3 * f5 - f * f6 + f1 * f7 + f2 * f4;
|
||||
float k = f3 * f6 + f * f5 - f1 * f4 + f2 * f7;
|
||||
float r = f3 * f7 - f * f4 - f1 * f5 - f2 * f6;
|
||||
|
||||
dest.set(i, j, k, r);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Quaternionf lerpQuaternion(Quaternionf from, Quaternionf to, float delta) {
|
||||
return lerpQuaternion(from, to, delta, null);
|
||||
}
|
||||
|
||||
public static Quaternionf lerpQuaternion(Quaternionf from, Quaternionf to, float delta, Quaternionf dest) {
|
||||
if (dest == null) {
|
||||
dest = new Quaternionf();
|
||||
}
|
||||
|
||||
float fromX = from.x();
|
||||
float fromY = from.y();
|
||||
float fromZ = from.z();
|
||||
float fromW = from.w();
|
||||
float toX = to.x();
|
||||
float toY = to.y();
|
||||
float toZ = to.z();
|
||||
float toW = to.w();
|
||||
float resultX;
|
||||
float resultY;
|
||||
float resultZ;
|
||||
float resultW;
|
||||
float dot = fromW * toW + fromX * toX + fromY * toY + fromZ * toZ;
|
||||
float blendI = 1.0F - delta;
|
||||
|
||||
if (dot < 0.0F) {
|
||||
resultW = blendI * fromW + delta * -toW;
|
||||
resultX = blendI * fromX + delta * -toX;
|
||||
resultY = blendI * fromY + delta * -toY;
|
||||
resultZ = blendI * fromZ + delta * -toZ;
|
||||
} else {
|
||||
resultW = blendI * fromW + delta * toW;
|
||||
resultX = blendI * fromX + delta * toX;
|
||||
resultY = blendI * fromY + delta * toY;
|
||||
resultZ = blendI * fromZ + delta * toZ;
|
||||
}
|
||||
|
||||
dest.set(resultX, resultY, resultZ, resultW);
|
||||
dest.normalize();
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
private static Vector3f getScaleVectorFromMatrix(OpenMatrix4f mat) {
|
||||
Vec3f a = new Vec3f(mat.m00, mat.m10, mat.m20);
|
||||
Vec3f b = new Vec3f(mat.m01, mat.m11, mat.m21);
|
||||
Vec3f c = new Vec3f(mat.m02, mat.m12, mat.m22);
|
||||
return new Vector3f(a.length(), b.length(), c.length());
|
||||
}
|
||||
|
||||
public static <T> Set<Set<T>> getSubset(Collection<T> collection) {
|
||||
Set<Set<T>> subsets = new HashSet<> ();
|
||||
List<T> asList = new ArrayList<> (collection);
|
||||
createSubset(0, asList, new HashSet<> (), subsets);
|
||||
|
||||
return subsets;
|
||||
}
|
||||
|
||||
private static <T> void createSubset(int idx, List<T> elements, Set<T> parent, Set<Set<T>> subsets) {
|
||||
for (int i = idx; i < elements.size(); i++) {
|
||||
Set<T> subset = new HashSet<> (parent);
|
||||
subset.add(elements.get(i));
|
||||
subsets.add(subset);
|
||||
|
||||
createSubset(i + 1, elements, subset, subsets);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getLeastAngleVectorIdx(Vec3f src, Vec3f... candidates) {
|
||||
int leastVectorIdx = -1;
|
||||
int current = 0;
|
||||
float maxDot = -10000.0F;
|
||||
|
||||
for (Vec3f normzlizedVec : Stream.of(candidates).map((vec) -> vec.normalize()).collect(Collectors.toList())) {
|
||||
float dot = Vec3f.dot(src, normzlizedVec);
|
||||
|
||||
if (maxDot < dot) {
|
||||
maxDot = dot;
|
||||
leastVectorIdx = current;
|
||||
}
|
||||
|
||||
current++;
|
||||
}
|
||||
|
||||
return leastVectorIdx;
|
||||
}
|
||||
|
||||
public static Vec3f getLeastAngleVector(Vec3f src, Vec3f... candidates) {
|
||||
return candidates[getLeastAngleVectorIdx(src, candidates)];
|
||||
}
|
||||
|
||||
public static boolean canBeSeen(Entity target, Entity watcher, double maxDistance) {
|
||||
if (target.level() != watcher.level()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double sqr = maxDistance * maxDistance;
|
||||
Level level = target.level();
|
||||
Vec3 vec1 = watcher.getEyePosition();
|
||||
|
||||
double height = target.getBoundingBox().maxY - target.getBoundingBox().minY;
|
||||
Vec3 vec2 = target.position().add(0.0D, height * 0.15D, 0.0D);
|
||||
Vec3 vec3 = target.position().add(0.0D, height * 0.5D, 0.0D);
|
||||
Vec3 vec4 = target.position().add(0.0D, height * 0.95D, 0.0D);
|
||||
|
||||
return vec1.distanceToSqr(vec2) < sqr && level.clip(new ClipContext(vec1, vec2, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, watcher)).getType() == HitResult.Type.MISS ||
|
||||
vec1.distanceToSqr(vec3) < sqr && level.clip(new ClipContext(vec1, vec3, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, watcher)).getType() == HitResult.Type.MISS ||
|
||||
vec1.distanceToSqr(vec4) < sqr && level.clip(new ClipContext(vec1, vec4, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, watcher)).getType() == HitResult.Type.MISS;
|
||||
}
|
||||
|
||||
public static int packColor(int r, int g, int b, int a) {
|
||||
int ir = r << 16;
|
||||
int ig = g << 8;
|
||||
int ib = b;
|
||||
int ia = a << 24;
|
||||
|
||||
return ir | ig | ib | ia;
|
||||
}
|
||||
|
||||
public static void unpackColor(int packedColor, Vector4i result) {
|
||||
int b = (packedColor & 0x000000FF);
|
||||
int g = (packedColor & 0x0000FF00) >>> 8;
|
||||
int r = (packedColor & 0x00FF0000) >>> 16;
|
||||
int a = (packedColor & 0xFF000000) >>> 24;
|
||||
|
||||
result.x = r;
|
||||
result.y = g;
|
||||
result.z = b;
|
||||
result.w = a;
|
||||
}
|
||||
|
||||
public static byte normalIntValue(float pNum) {
|
||||
return (byte)((int)(Mth.clamp(pNum, -1.0F, 1.0F) * 127.0F) & 255);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a value within bounds
|
||||
*/
|
||||
public static int wrapClamp(int value, int min, int max) {
|
||||
int stride = max - min + 1;
|
||||
while (value < min) value += stride;
|
||||
while (value > max) value -= stride;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the world coordinate system to -1~1 screen coord system
|
||||
* @param projection current projection matrix
|
||||
* @param modelView current model-view matrix
|
||||
* @param position a source vector to transform
|
||||
*/
|
||||
public static Vec2 worldToScreenCoord(Matrix4f projectionMatrix, Camera camera, Vec3 position) {
|
||||
Vector4f relativeCamera = new Vector4f((float)camera.getPosition().x() - (float)position.x(), (float)camera.getPosition().y() - (float)position.y(), (float)camera.getPosition().z() - (float)position.z(), 1.0F);
|
||||
relativeCamera.rotate(Axis.YP.rotationDegrees(camera.getYRot() + 180.0F));
|
||||
relativeCamera.rotate(Axis.XP.rotationDegrees(camera.getXRot()));
|
||||
relativeCamera.mul(projectionMatrix);
|
||||
|
||||
float depth = relativeCamera.w;
|
||||
relativeCamera.mul(1.0F / relativeCamera.w());
|
||||
|
||||
if (depth < 0.0F) {
|
||||
relativeCamera.x = -relativeCamera.x;
|
||||
relativeCamera.y = -relativeCamera.y;
|
||||
}
|
||||
|
||||
return new Vec2(relativeCamera.x(), relativeCamera.y());
|
||||
}
|
||||
|
||||
private MathUtils() {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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.math;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MatrixOperation {
|
||||
OpenMatrix4f mul(OpenMatrix4f left, OpenMatrix4f right, OpenMatrix4f dest);
|
||||
}
|
||||
953
src/main/java/com/tiedup/remake/rig/math/OpenMatrix4f.java
Normal file
953
src/main/java/com/tiedup/remake/rig/math/OpenMatrix4f.java
Normal file
@@ -0,0 +1,953 @@
|
||||
/*
|
||||
* 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.math;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Math;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
|
||||
public class OpenMatrix4f {
|
||||
private static final FloatBuffer MATRIX_TRANSFORMER = ByteBuffer.allocateDirect(16 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
|
||||
private static final Vec3f VECTOR_STORAGE = new Vec3f();
|
||||
private static final Vec4f VEC4_STORAGE = new Vec4f();
|
||||
|
||||
public static final OpenMatrix4f IDENTITY = new OpenMatrix4f();
|
||||
|
||||
/*
|
||||
* m00 m01 m02 m03
|
||||
* m10 m11 m12 m13
|
||||
* m20 m21 m22 m23
|
||||
* m30 m31 m32 m33
|
||||
*/
|
||||
public float m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33;
|
||||
|
||||
private final boolean immutable;
|
||||
|
||||
public OpenMatrix4f() {
|
||||
this.setIdentity();
|
||||
this.immutable = false;
|
||||
}
|
||||
|
||||
public OpenMatrix4f(final OpenMatrix4f src) {
|
||||
this(src, false);
|
||||
}
|
||||
|
||||
public OpenMatrix4f(final OpenMatrix4f src, boolean immutable) {
|
||||
load(src);
|
||||
this.immutable = immutable;
|
||||
}
|
||||
|
||||
public OpenMatrix4f(final JointTransform jointTransform) {
|
||||
load(OpenMatrix4f.fromQuaternion(jointTransform.rotation()).translate(jointTransform.translation()).scale(jointTransform.scale()));
|
||||
this.immutable = false;
|
||||
}
|
||||
|
||||
public OpenMatrix4f(
|
||||
float m00, float m01, float m02, float m03
|
||||
, float m10, float m11, float m12, float m13
|
||||
, float m20, float m21, float m22, float m23
|
||||
, float m30, float m31, float m32, float m33
|
||||
) {
|
||||
this.m00 = m00;
|
||||
this.m01 = m01;
|
||||
this.m02 = m02;
|
||||
this.m03 = m03;
|
||||
this.m10 = m10;
|
||||
this.m11 = m11;
|
||||
this.m12 = m12;
|
||||
this.m13 = m13;
|
||||
this.m20 = m20;
|
||||
this.m21 = m21;
|
||||
this.m22 = m22;
|
||||
this.m23 = m23;
|
||||
this.m30 = m30;
|
||||
this.m31 = m31;
|
||||
this.m32 = m32;
|
||||
this.m33 = m33;
|
||||
this.immutable = false;
|
||||
}
|
||||
|
||||
public OpenMatrix4f setIdentity() {
|
||||
return setIdentity(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given matrix to be the identity matrix.
|
||||
* @param m The matrix to set to the identity
|
||||
* @return m
|
||||
*/
|
||||
public static OpenMatrix4f setIdentity(OpenMatrix4f m) {
|
||||
if (m.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
m.m00 = 1.0f;
|
||||
m.m01 = 0.0f;
|
||||
m.m02 = 0.0f;
|
||||
m.m03 = 0.0f;
|
||||
m.m10 = 0.0f;
|
||||
m.m11 = 1.0f;
|
||||
m.m12 = 0.0f;
|
||||
m.m13 = 0.0f;
|
||||
m.m20 = 0.0f;
|
||||
m.m21 = 0.0f;
|
||||
m.m22 = 1.0f;
|
||||
m.m23 = 0.0f;
|
||||
m.m30 = 0.0f;
|
||||
m.m31 = 0.0f;
|
||||
m.m32 = 0.0f;
|
||||
m.m33 = 1.0f;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public OpenMatrix4f load(OpenMatrix4f src) {
|
||||
return load(src, this);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f load(OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.m00 = src.m00;
|
||||
dest.m01 = src.m01;
|
||||
dest.m02 = src.m02;
|
||||
dest.m03 = src.m03;
|
||||
dest.m10 = src.m10;
|
||||
dest.m11 = src.m11;
|
||||
dest.m12 = src.m12;
|
||||
dest.m13 = src.m13;
|
||||
dest.m20 = src.m20;
|
||||
dest.m21 = src.m21;
|
||||
dest.m22 = src.m22;
|
||||
dest.m23 = src.m23;
|
||||
dest.m30 = src.m30;
|
||||
dest.m31 = src.m31;
|
||||
dest.m32 = src.m32;
|
||||
dest.m33 = src.m33;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f load(@Nullable OpenMatrix4f dest, float[] elements) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.m00 = elements[0];
|
||||
dest.m01 = elements[1];
|
||||
dest.m02 = elements[2];
|
||||
dest.m03 = elements[3];
|
||||
dest.m10 = elements[4];
|
||||
dest.m11 = elements[5];
|
||||
dest.m12 = elements[6];
|
||||
dest.m13 = elements[7];
|
||||
dest.m20 = elements[8];
|
||||
dest.m21 = elements[9];
|
||||
dest.m22 = elements[10];
|
||||
dest.m23 = elements[11];
|
||||
dest.m30 = elements[12];
|
||||
dest.m31 = elements[13];
|
||||
dest.m32 = elements[14];
|
||||
dest.m33 = elements[15];
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f load(@Nullable OpenMatrix4f dest, FloatBuffer buf) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
buf.position(0);
|
||||
|
||||
dest.m00 = buf.get();
|
||||
dest.m01 = buf.get();
|
||||
dest.m02 = buf.get();
|
||||
dest.m03 = buf.get();
|
||||
dest.m10 = buf.get();
|
||||
dest.m11 = buf.get();
|
||||
dest.m12 = buf.get();
|
||||
dest.m13 = buf.get();
|
||||
dest.m20 = buf.get();
|
||||
dest.m21 = buf.get();
|
||||
dest.m22 = buf.get();
|
||||
dest.m23 = buf.get();
|
||||
dest.m30 = buf.get();
|
||||
dest.m31 = buf.get();
|
||||
dest.m32 = buf.get();
|
||||
dest.m33 = buf.get();
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f load(FloatBuffer buf) {
|
||||
return OpenMatrix4f.load(this, buf);
|
||||
}
|
||||
|
||||
public OpenMatrix4f store(FloatBuffer buf) {
|
||||
buf.put(m00);
|
||||
buf.put(m01);
|
||||
buf.put(m02);
|
||||
buf.put(m03);
|
||||
buf.put(m10);
|
||||
buf.put(m11);
|
||||
buf.put(m12);
|
||||
buf.put(m13);
|
||||
buf.put(m20);
|
||||
buf.put(m21);
|
||||
buf.put(m22);
|
||||
buf.put(m23);
|
||||
buf.put(m30);
|
||||
buf.put(m31);
|
||||
buf.put(m32);
|
||||
buf.put(m33);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Float> toList() {
|
||||
List<Float> elements = Lists.newArrayList();
|
||||
|
||||
elements.add(0, m00);
|
||||
elements.add(1, m01);
|
||||
elements.add(2, m02);
|
||||
elements.add(3, m03);
|
||||
elements.add(4, m10);
|
||||
elements.add(5, m11);
|
||||
elements.add(6, m12);
|
||||
elements.add(7, m13);
|
||||
elements.add(8, m20);
|
||||
elements.add(9, m21);
|
||||
elements.add(10, m22);
|
||||
elements.add(11, m23);
|
||||
elements.add(12, m30);
|
||||
elements.add(13, m31);
|
||||
elements.add(14, m32);
|
||||
elements.add(15, m33);
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
public OpenMatrix4f unmodifiable() {
|
||||
return new OpenMatrix4f(this, true);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f add(OpenMatrix4f left, OpenMatrix4f right, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.m00 = left.m00 + right.m00;
|
||||
dest.m01 = left.m01 + right.m01;
|
||||
dest.m02 = left.m02 + right.m02;
|
||||
dest.m03 = left.m03 + right.m03;
|
||||
dest.m10 = left.m10 + right.m10;
|
||||
dest.m11 = left.m11 + right.m11;
|
||||
dest.m12 = left.m12 + right.m12;
|
||||
dest.m13 = left.m13 + right.m13;
|
||||
dest.m20 = left.m20 + right.m20;
|
||||
dest.m21 = left.m21 + right.m21;
|
||||
dest.m22 = left.m22 + right.m22;
|
||||
dest.m23 = left.m23 + right.m23;
|
||||
dest.m30 = left.m30 + right.m30;
|
||||
dest.m31 = left.m31 + right.m31;
|
||||
dest.m32 = left.m32 + right.m32;
|
||||
dest.m33 = left.m33 + right.m33;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f mulFront(OpenMatrix4f mulTransform) {
|
||||
return OpenMatrix4f.mul(mulTransform, this, this);
|
||||
}
|
||||
|
||||
public OpenMatrix4f mulBack(OpenMatrix4f mulTransform) {
|
||||
return OpenMatrix4f.mul(this, mulTransform, this);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f mul(OpenMatrix4f left, OpenMatrix4f right, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
float m00 = left.m00 * right.m00 + left.m10 * right.m01 + left.m20 * right.m02 + left.m30 * right.m03;
|
||||
float m01 = left.m01 * right.m00 + left.m11 * right.m01 + left.m21 * right.m02 + left.m31 * right.m03;
|
||||
float m02 = left.m02 * right.m00 + left.m12 * right.m01 + left.m22 * right.m02 + left.m32 * right.m03;
|
||||
float m03 = left.m03 * right.m00 + left.m13 * right.m01 + left.m23 * right.m02 + left.m33 * right.m03;
|
||||
float m10 = left.m00 * right.m10 + left.m10 * right.m11 + left.m20 * right.m12 + left.m30 * right.m13;
|
||||
float m11 = left.m01 * right.m10 + left.m11 * right.m11 + left.m21 * right.m12 + left.m31 * right.m13;
|
||||
float m12 = left.m02 * right.m10 + left.m12 * right.m11 + left.m22 * right.m12 + left.m32 * right.m13;
|
||||
float m13 = left.m03 * right.m10 + left.m13 * right.m11 + left.m23 * right.m12 + left.m33 * right.m13;
|
||||
float m20 = left.m00 * right.m20 + left.m10 * right.m21 + left.m20 * right.m22 + left.m30 * right.m23;
|
||||
float m21 = left.m01 * right.m20 + left.m11 * right.m21 + left.m21 * right.m22 + left.m31 * right.m23;
|
||||
float m22 = left.m02 * right.m20 + left.m12 * right.m21 + left.m22 * right.m22 + left.m32 * right.m23;
|
||||
float m23 = left.m03 * right.m20 + left.m13 * right.m21 + left.m23 * right.m22 + left.m33 * right.m23;
|
||||
float m30 = left.m00 * right.m30 + left.m10 * right.m31 + left.m20 * right.m32 + left.m30 * right.m33;
|
||||
float m31 = left.m01 * right.m30 + left.m11 * right.m31 + left.m21 * right.m32 + left.m31 * right.m33;
|
||||
float m32 = left.m02 * right.m30 + left.m12 * right.m31 + left.m22 * right.m32 + left.m32 * right.m33;
|
||||
float m33 = left.m03 * right.m30 + left.m13 * right.m31 + left.m23 * right.m32 + left.m33 * right.m33;
|
||||
|
||||
dest.m00 = m00;
|
||||
dest.m01 = m01;
|
||||
dest.m02 = m02;
|
||||
dest.m03 = m03;
|
||||
dest.m10 = m10;
|
||||
dest.m11 = m11;
|
||||
dest.m12 = m12;
|
||||
dest.m13 = m13;
|
||||
dest.m20 = m20;
|
||||
dest.m21 = m21;
|
||||
dest.m22 = m22;
|
||||
dest.m23 = m23;
|
||||
dest.m30 = m30;
|
||||
dest.m31 = m31;
|
||||
dest.m32 = m32;
|
||||
dest.m33 = m33;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f mulMatrices(OpenMatrix4f... srcs) {
|
||||
OpenMatrix4f result = new OpenMatrix4f();
|
||||
|
||||
for (OpenMatrix4f src : srcs) {
|
||||
result.mulBack(src);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f mulAsOrigin(OpenMatrix4f left, OpenMatrix4f right, OpenMatrix4f dest) {
|
||||
float x = right.m30;
|
||||
float y = right.m31;
|
||||
float z = right.m32;
|
||||
|
||||
OpenMatrix4f result = mul(left, right, dest);
|
||||
result.m30 = x;
|
||||
result.m31 = y;
|
||||
result.m32 = z;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f mulAsOriginInverse(OpenMatrix4f left, OpenMatrix4f right, OpenMatrix4f dest) {
|
||||
return mulAsOrigin(right, left, dest);
|
||||
}
|
||||
|
||||
public static Vec4f transform(OpenMatrix4f matrix, Vec4f src, @Nullable Vec4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec4f();
|
||||
}
|
||||
|
||||
float x = matrix.m00 * src.x + matrix.m10 * src.y + matrix.m20 * src.z + matrix.m30 * src.w;
|
||||
float y = matrix.m01 * src.x + matrix.m11 * src.y + matrix.m21 * src.z + matrix.m31 * src.w;
|
||||
float z = matrix.m02 * src.x + matrix.m12 * src.y + matrix.m22 * src.z + matrix.m32 * src.w;
|
||||
float w = matrix.m03 * src.x + matrix.m13 * src.y + matrix.m23 * src.z + matrix.m33 * src.w;
|
||||
|
||||
dest.x = x;
|
||||
dest.y = y;
|
||||
dest.z = z;
|
||||
dest.w = w;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Vec3 transform(OpenMatrix4f matrix, Vec3 src) {
|
||||
double x = matrix.m00 * src.x + matrix.m10 * src.y + matrix.m20 * src.z + matrix.m30;
|
||||
double y = matrix.m01 * src.x + matrix.m11 * src.y + matrix.m21 * src.z + matrix.m31;
|
||||
double z = matrix.m02 * src.x + matrix.m12 * src.y + matrix.m22 * src.z + matrix.m32;
|
||||
|
||||
return new Vec3(x, y ,z);
|
||||
}
|
||||
|
||||
public static Vec3f transform3v(OpenMatrix4f matrix, Vec3f src, @Nullable Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
VEC4_STORAGE.set(src.x, src.y, src.z, 1.0F);
|
||||
|
||||
Vec4f result = transform(matrix, VEC4_STORAGE, null);
|
||||
dest.x = result.x;
|
||||
dest.y = result.y;
|
||||
dest.z = result.z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f transpose() {
|
||||
return transpose(this);
|
||||
}
|
||||
|
||||
public OpenMatrix4f transpose(OpenMatrix4f dest) {
|
||||
return transpose(this, dest);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f transpose(OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
float m00 = src.m00;
|
||||
float m01 = src.m10;
|
||||
float m02 = src.m20;
|
||||
float m03 = src.m30;
|
||||
float m10 = src.m01;
|
||||
float m11 = src.m11;
|
||||
float m12 = src.m21;
|
||||
float m13 = src.m31;
|
||||
float m20 = src.m02;
|
||||
float m21 = src.m12;
|
||||
float m22 = src.m22;
|
||||
float m23 = src.m32;
|
||||
float m30 = src.m03;
|
||||
float m31 = src.m13;
|
||||
float m32 = src.m23;
|
||||
float m33 = src.m33;
|
||||
|
||||
dest.m00 = m00;
|
||||
dest.m01 = m01;
|
||||
dest.m02 = m02;
|
||||
dest.m03 = m03;
|
||||
dest.m10 = m10;
|
||||
dest.m11 = m11;
|
||||
dest.m12 = m12;
|
||||
dest.m13 = m13;
|
||||
dest.m20 = m20;
|
||||
dest.m21 = m21;
|
||||
dest.m22 = m22;
|
||||
dest.m23 = m23;
|
||||
dest.m30 = m30;
|
||||
dest.m31 = m31;
|
||||
dest.m32 = m32;
|
||||
dest.m33 = m33;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public float determinant() {
|
||||
float f = m00 * ((m11 * m22 * m33 + m12 * m23 * m31 + m13 * m21 * m32) - m13 * m22 * m31 - m11 * m23 * m32 - m12 * m21 * m33);
|
||||
f -= m01 * ((m10 * m22 * m33 + m12 * m23 * m30 + m13 * m20 * m32) - m13 * m22 * m30 - m10 * m23 * m32 - m12 * m20 * m33);
|
||||
f += m02 * ((m10 * m21 * m33 + m11 * m23 * m30 + m13 * m20 * m31) - m13 * m21 * m30 - m10 * m23 * m31 - m11 * m20 * m33);
|
||||
f -= m03 * ((m10 * m21 * m32 + m11 * m22 * m30 + m12 * m20 * m31) - m12 * m21 * m30 - m10 * m22 * m31 - m11 * m20 * m32);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
private static float determinant3x3(float t00, float t01, float t02, float t10, float t11, float t12, float t20, float t21, float t22) {
|
||||
return t00 * (t11 * t22 - t12 * t21) + t01 * (t12 * t20 - t10 * t22) + t02 * (t10 * t21 - t11 * t20);
|
||||
}
|
||||
|
||||
public OpenMatrix4f invert() {
|
||||
return OpenMatrix4f.invert(this, this);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f invert(OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
float determinant = src.determinant();
|
||||
|
||||
if (determinant != 0.0F) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
float determinant_inv = 1.0F / determinant;
|
||||
|
||||
float t00 = determinant3x3(src.m11, src.m12, src.m13, src.m21, src.m22, src.m23, src.m31, src.m32, src.m33);
|
||||
float t01 = -determinant3x3(src.m10, src.m12, src.m13, src.m20, src.m22, src.m23, src.m30, src.m32, src.m33);
|
||||
float t02 = determinant3x3(src.m10, src.m11, src.m13, src.m20, src.m21, src.m23, src.m30, src.m31, src.m33);
|
||||
float t03 = -determinant3x3(src.m10, src.m11, src.m12, src.m20, src.m21, src.m22, src.m30, src.m31, src.m32);
|
||||
float t10 = -determinant3x3(src.m01, src.m02, src.m03, src.m21, src.m22, src.m23, src.m31, src.m32, src.m33);
|
||||
float t11 = determinant3x3(src.m00, src.m02, src.m03, src.m20, src.m22, src.m23, src.m30, src.m32, src.m33);
|
||||
float t12 = -determinant3x3(src.m00, src.m01, src.m03, src.m20, src.m21, src.m23, src.m30, src.m31, src.m33);
|
||||
float t13 = determinant3x3(src.m00, src.m01, src.m02, src.m20, src.m21, src.m22, src.m30, src.m31, src.m32);
|
||||
float t20 = determinant3x3(src.m01, src.m02, src.m03, src.m11, src.m12, src.m13, src.m31, src.m32, src.m33);
|
||||
float t21 = -determinant3x3(src.m00, src.m02, src.m03, src.m10, src.m12, src.m13, src.m30, src.m32, src.m33);
|
||||
float t22 = determinant3x3(src.m00, src.m01, src.m03, src.m10, src.m11, src.m13, src.m30, src.m31, src.m33);
|
||||
float t23 = -determinant3x3(src.m00, src.m01, src.m02, src.m10, src.m11, src.m12, src.m30, src.m31, src.m32);
|
||||
float t30 = -determinant3x3(src.m01, src.m02, src.m03, src.m11, src.m12, src.m13, src.m21, src.m22, src.m23);
|
||||
float t31 = determinant3x3(src.m00, src.m02, src.m03, src.m10, src.m12, src.m13, src.m20, src.m22, src.m23);
|
||||
float t32 = -determinant3x3(src.m00, src.m01, src.m03, src.m10, src.m11, src.m13, src.m20, src.m21, src.m23);
|
||||
float t33 = determinant3x3(src.m00, src.m01, src.m02, src.m10, src.m11, src.m12, src.m20, src.m21, src.m22);
|
||||
|
||||
dest.m00 = t00 * determinant_inv;
|
||||
dest.m11 = t11 * determinant_inv;
|
||||
dest.m22 = t22 * determinant_inv;
|
||||
dest.m33 = t33 * determinant_inv;
|
||||
dest.m01 = t10 * determinant_inv;
|
||||
dest.m10 = t01 * determinant_inv;
|
||||
dest.m20 = t02 * determinant_inv;
|
||||
dest.m02 = t20 * determinant_inv;
|
||||
dest.m12 = t21 * determinant_inv;
|
||||
dest.m21 = t12 * determinant_inv;
|
||||
dest.m03 = t30 * determinant_inv;
|
||||
dest.m30 = t03 * determinant_inv;
|
||||
dest.m13 = t31 * determinant_inv;
|
||||
dest.m31 = t13 * determinant_inv;
|
||||
dest.m32 = t23 * determinant_inv;
|
||||
dest.m23 = t32 * determinant_inv;
|
||||
|
||||
return dest;
|
||||
} else {
|
||||
dest.setIdentity();
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
public OpenMatrix4f translate(float x, float y, float z) {
|
||||
VECTOR_STORAGE.set(x, y, z);
|
||||
return translate(VECTOR_STORAGE, this);
|
||||
}
|
||||
|
||||
public OpenMatrix4f translate(Vec3f vec) {
|
||||
return translate(vec, this);
|
||||
}
|
||||
|
||||
public OpenMatrix4f translate(Vec3f vec, OpenMatrix4f dest) {
|
||||
return translate(vec, this, dest);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f translate(Vec3f vec, OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.m30 += src.m00 * vec.x + src.m10 * vec.y + src.m20 * vec.z;
|
||||
dest.m31 += src.m01 * vec.x + src.m11 * vec.y + src.m21 * vec.z;
|
||||
dest.m32 += src.m02 * vec.x + src.m12 * vec.y + src.m22 * vec.z;
|
||||
dest.m33 += src.m03 * vec.x + src.m13 * vec.y + src.m23 * vec.z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f createTranslation(float x, float y, float z) {
|
||||
return ofTranslation(x, y, z, null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f ofTranslation(float x, float y, float z, OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.setIdentity();
|
||||
dest.m30 = x;
|
||||
dest.m31 = y;
|
||||
dest.m32 = z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f createScale(float x, float y, float z) {
|
||||
return ofScale(x, y, z, null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f ofScale(float x, float y, float z, OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.setIdentity();
|
||||
dest.m00 = x;
|
||||
dest.m11 = y;
|
||||
dest.m22 = z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f rotateDeg(float angle, Vec3f axis) {
|
||||
return rotate((float)Math.toRadians(angle), axis);
|
||||
}
|
||||
|
||||
public OpenMatrix4f rotate(float angle, Vec3f axis) {
|
||||
return rotate(angle, axis, this);
|
||||
}
|
||||
|
||||
public OpenMatrix4f rotate(float angle, Vec3f axis, OpenMatrix4f dest) {
|
||||
return rotate(angle, axis, this, dest);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f createRotatorDeg(float degree, Vec3f axis) {
|
||||
return rotate((float)Math.toRadians(degree), axis, new OpenMatrix4f(), null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f ofRotationDegree(float degree, Vec3f axis, @Nullable OpenMatrix4f dest) {
|
||||
dest.setIdentity();
|
||||
return rotate((float)Math.toRadians(degree), axis, dest, dest);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f rotate(float angle, Vec3f axis, OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
float c = (float) Math.cos(angle);
|
||||
float s = (float) Math.sin(angle);
|
||||
float oneminusc = 1.0f - c;
|
||||
float xy = axis.x * axis.y;
|
||||
float yz = axis.y * axis.z;
|
||||
float xz = axis.x * axis.z;
|
||||
float xs = axis.x * s;
|
||||
float ys = axis.y * s;
|
||||
float zs = axis.z * s;
|
||||
|
||||
float f00 = axis.x * axis.x * oneminusc+c;
|
||||
float f01 = xy * oneminusc + zs;
|
||||
float f02 = xz * oneminusc - ys;
|
||||
// n[3] not used
|
||||
float f10 = xy * oneminusc - zs;
|
||||
float f11 = axis.y * axis.y * oneminusc+c;
|
||||
float f12 = yz * oneminusc + xs;
|
||||
// n[7] not used
|
||||
float f20 = xz * oneminusc + ys;
|
||||
float f21 = yz * oneminusc - xs;
|
||||
float f22 = axis.z * axis.z * oneminusc+c;
|
||||
|
||||
float t00 = src.m00 * f00 + src.m10 * f01 + src.m20 * f02;
|
||||
float t01 = src.m01 * f00 + src.m11 * f01 + src.m21 * f02;
|
||||
float t02 = src.m02 * f00 + src.m12 * f01 + src.m22 * f02;
|
||||
float t03 = src.m03 * f00 + src.m13 * f01 + src.m23 * f02;
|
||||
float t10 = src.m00 * f10 + src.m10 * f11 + src.m20 * f12;
|
||||
float t11 = src.m01 * f10 + src.m11 * f11 + src.m21 * f12;
|
||||
float t12 = src.m02 * f10 + src.m12 * f11 + src.m22 * f12;
|
||||
float t13 = src.m03 * f10 + src.m13 * f11 + src.m23 * f12;
|
||||
|
||||
dest.m20 = src.m00 * f20 + src.m10 * f21 + src.m20 * f22;
|
||||
dest.m21 = src.m01 * f20 + src.m11 * f21 + src.m21 * f22;
|
||||
dest.m22 = src.m02 * f20 + src.m12 * f21 + src.m22 * f22;
|
||||
dest.m23 = src.m03 * f20 + src.m13 * f21 + src.m23 * f22;
|
||||
dest.m00 = t00;
|
||||
dest.m01 = t01;
|
||||
dest.m02 = t02;
|
||||
dest.m03 = t03;
|
||||
dest.m10 = t10;
|
||||
dest.m11 = t11;
|
||||
dest.m12 = t12;
|
||||
dest.m13 = t13;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Vec3f toTranslationVector() {
|
||||
return toTranslationVector(this);
|
||||
}
|
||||
|
||||
public Vec3f toTranslationVector(Vec3f dest) {
|
||||
return toTranslationVector(this, dest);
|
||||
}
|
||||
|
||||
public static Vec3f toTranslationVector(OpenMatrix4f matrix) {
|
||||
return toTranslationVector(matrix, null);
|
||||
}
|
||||
|
||||
public static Vec3f toTranslationVector(OpenMatrix4f matrix, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.x = matrix.m30;
|
||||
dest.y = matrix.m31;
|
||||
dest.z = matrix.m32;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Quaternionf toQuaternion() {
|
||||
return OpenMatrix4f.toQuaternion(this);
|
||||
}
|
||||
|
||||
public Quaternionf toQuaternion(Quaternionf dest) {
|
||||
return OpenMatrix4f.toQuaternion(this, dest);
|
||||
}
|
||||
|
||||
public static Quaternionf toQuaternion(OpenMatrix4f matrix) {
|
||||
return toQuaternion(matrix, new Quaternionf());
|
||||
}
|
||||
|
||||
private static final OpenMatrix4f MATRIX_STORAGE = new OpenMatrix4f();
|
||||
|
||||
public static Quaternionf toQuaternion(OpenMatrix4f matrix, Quaternionf dest) {
|
||||
if (dest == null) {
|
||||
dest = new Quaternionf();
|
||||
}
|
||||
|
||||
OpenMatrix4f.load(matrix, MATRIX_STORAGE);
|
||||
|
||||
float w, x, y, z;
|
||||
MATRIX_STORAGE.transpose();
|
||||
|
||||
float lenX = MATRIX_STORAGE.m00 * MATRIX_STORAGE.m00 + MATRIX_STORAGE.m01 * MATRIX_STORAGE.m01 + MATRIX_STORAGE.m02 * MATRIX_STORAGE.m02;
|
||||
float lenY = MATRIX_STORAGE.m10 * MATRIX_STORAGE.m10 + MATRIX_STORAGE.m11 * MATRIX_STORAGE.m11 + MATRIX_STORAGE.m12 * MATRIX_STORAGE.m12;
|
||||
float lenZ = MATRIX_STORAGE.m20 * MATRIX_STORAGE.m20 + MATRIX_STORAGE.m21 * MATRIX_STORAGE.m21 + MATRIX_STORAGE.m22 * MATRIX_STORAGE.m22;
|
||||
|
||||
if (lenX == 0.0F || lenY == 0.0F || lenZ == 0.0F) {
|
||||
return new Quaternionf(0.0F, 0.0F, 0.0F, 1.0F);
|
||||
}
|
||||
|
||||
lenX = Math.invsqrt(lenX);
|
||||
lenY = Math.invsqrt(lenY);
|
||||
lenZ = Math.invsqrt(lenZ);
|
||||
|
||||
MATRIX_STORAGE.m00 *= lenX; MATRIX_STORAGE.m01 *= lenX; MATRIX_STORAGE.m02 *= lenX;
|
||||
MATRIX_STORAGE.m10 *= lenY; MATRIX_STORAGE.m11 *= lenY; MATRIX_STORAGE.m12 *= lenY;
|
||||
MATRIX_STORAGE.m20 *= lenZ; MATRIX_STORAGE.m21 *= lenZ; MATRIX_STORAGE.m22 *= lenZ;
|
||||
|
||||
float t;
|
||||
float tr = MATRIX_STORAGE.m00 + MATRIX_STORAGE.m11 + MATRIX_STORAGE.m22;
|
||||
|
||||
if (tr >= 0.0F) {
|
||||
t = (float)Math.sqrt(tr + 1.0F);
|
||||
w = t * 0.5F;
|
||||
t = 0.5F / t;
|
||||
x = (MATRIX_STORAGE.m12 - MATRIX_STORAGE.m21) * t;
|
||||
y = (MATRIX_STORAGE.m20 - MATRIX_STORAGE.m02) * t;
|
||||
z = (MATRIX_STORAGE.m01 - MATRIX_STORAGE.m10) * t;
|
||||
} else {
|
||||
if (MATRIX_STORAGE.m00 >= MATRIX_STORAGE.m11 && MATRIX_STORAGE.m00 >= MATRIX_STORAGE.m22) {
|
||||
t = (float)Math.sqrt(MATRIX_STORAGE.m00 - (MATRIX_STORAGE.m11 + MATRIX_STORAGE.m22) + 1.0);
|
||||
x = t * 0.5F;
|
||||
t = 0.5F / t;
|
||||
y = (MATRIX_STORAGE.m10 + MATRIX_STORAGE.m01) * t;
|
||||
z = (MATRIX_STORAGE.m02 + MATRIX_STORAGE.m20) * t;
|
||||
w = (MATRIX_STORAGE.m12 - MATRIX_STORAGE.m21) * t;
|
||||
} else if (MATRIX_STORAGE.m11 > MATRIX_STORAGE.m22) {
|
||||
t = (float)Math.sqrt(MATRIX_STORAGE.m11 - (MATRIX_STORAGE.m22 + MATRIX_STORAGE.m00) + 1.0F);
|
||||
y = t * 0.5F;
|
||||
t = 0.5F / t;
|
||||
z = (MATRIX_STORAGE.m21 + MATRIX_STORAGE.m12) * t;
|
||||
x = (MATRIX_STORAGE.m10 + MATRIX_STORAGE.m01) * t;
|
||||
w = (MATRIX_STORAGE.m20 - MATRIX_STORAGE.m02) * t;
|
||||
} else {
|
||||
t = (float)Math.sqrt(MATRIX_STORAGE.m22 - (MATRIX_STORAGE.m00 + MATRIX_STORAGE.m11) + 1.0F);
|
||||
z = t * 0.5F;
|
||||
t = 0.5F / t;
|
||||
x = (MATRIX_STORAGE.m02 + MATRIX_STORAGE.m20) * t;
|
||||
y = (MATRIX_STORAGE.m21 + MATRIX_STORAGE.m12) * t;
|
||||
w = (MATRIX_STORAGE.m01 - MATRIX_STORAGE.m10) * t;
|
||||
}
|
||||
}
|
||||
|
||||
dest.x = x;
|
||||
dest.y = y;
|
||||
dest.z = z;
|
||||
dest.w = w;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static OpenMatrix4f fromQuaternion(Quaternionf quaternion) {
|
||||
return fromQuaternion(quaternion, null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f fromQuaternion(Quaternionf quaternion, OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
float x = quaternion.x();
|
||||
float y = quaternion.y();
|
||||
float z = quaternion.z();
|
||||
float w = quaternion.w();
|
||||
float xy = x * y;
|
||||
float xz = x * z;
|
||||
float xw = x * w;
|
||||
float yz = y * z;
|
||||
float yw = y * w;
|
||||
float zw = z * w;
|
||||
float xSquared = 2F * x * x;
|
||||
float ySquared = 2F * y * y;
|
||||
float zSquared = 2F * z * z;
|
||||
dest.m00 = 1.0F - ySquared - zSquared;
|
||||
dest.m01 = 2.0F * (xy - zw);
|
||||
dest.m02 = 2.0F * (xz + yw);
|
||||
dest.m10 = 2.0F * (xy + zw);
|
||||
dest.m11 = 1.0F - xSquared - zSquared;
|
||||
dest.m12 = 2.0F * (yz - xw);
|
||||
dest.m20 = 2.0F * (xz - yw);
|
||||
dest.m21 = 2.0F * (yz + xw);
|
||||
dest.m22 = 1.0F - xSquared - ySquared;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f scale(float x, float y, float z) {
|
||||
VECTOR_STORAGE.set(x, y, z);
|
||||
return this.scale(VECTOR_STORAGE);
|
||||
}
|
||||
|
||||
public OpenMatrix4f scale(Vec3f vec) {
|
||||
return scale(vec, this, this);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f scale(Vec3f vec, OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.m00 = src.m00 * vec.x;
|
||||
dest.m01 = src.m01 * vec.x;
|
||||
dest.m02 = src.m02 * vec.x;
|
||||
dest.m03 = src.m03 * vec.x;
|
||||
dest.m10 = src.m10 * vec.y;
|
||||
dest.m11 = src.m11 * vec.y;
|
||||
dest.m12 = src.m12 * vec.y;
|
||||
dest.m13 = src.m13 * vec.y;
|
||||
dest.m20 = src.m20 * vec.z;
|
||||
dest.m21 = src.m21 * vec.z;
|
||||
dest.m22 = src.m22 * vec.z;
|
||||
dest.m23 = src.m23 * vec.z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Vec3f toScaleVector() {
|
||||
return toScaleVector(null);
|
||||
}
|
||||
|
||||
public Vec3f toScaleVector(Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
VECTOR_STORAGE.set(this.m00, this.m01, this.m02);
|
||||
dest.x = VECTOR_STORAGE.length();
|
||||
|
||||
VECTOR_STORAGE.set(this.m10, this.m11, this.m12);
|
||||
dest.y = VECTOR_STORAGE.length();
|
||||
|
||||
VECTOR_STORAGE.set(this.m20, this.m21, this.m22);
|
||||
dest.z = VECTOR_STORAGE.length();
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f removeTranslation() {
|
||||
return removeTranslation(this, null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f removeTranslation(OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
dest.load(src);
|
||||
dest.m30 = 0.0F;
|
||||
dest.m31 = 0.0F;
|
||||
dest.m32 = 0.0F;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public OpenMatrix4f removeScale() {
|
||||
return removeScale(this, null);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f removeScale(OpenMatrix4f src, @Nullable OpenMatrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new OpenMatrix4f();
|
||||
} else if (dest.immutable) {
|
||||
throw new UnsupportedOperationException("Can't modify immutable matrix");
|
||||
}
|
||||
|
||||
VECTOR_STORAGE.set(src.m00, src.m01, src.m02);
|
||||
float xScale = VECTOR_STORAGE.length();
|
||||
|
||||
VECTOR_STORAGE.set(src.m10, src.m11, src.m12);
|
||||
float yScale = VECTOR_STORAGE.length();
|
||||
|
||||
VECTOR_STORAGE.set(src.m20, src.m21, src.m22);
|
||||
float zScale = VECTOR_STORAGE.length();
|
||||
|
||||
dest.load(src);
|
||||
dest.scale(1.0F / xScale, 1.0F / yScale, 1.0F / zScale);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\n" +
|
||||
String.format("%.4f", m00) + " " + String.format("%.4f", m01) + " " + String.format("%.4f", m02) + " " + String.format("%.4f", m03) + "\n" +
|
||||
String.format("%.4f", m10) + " " + String.format("%.4f", m11) + " " + String.format("%.4f", m12) + " " + String.format("%.4f", m13) + "\n" +
|
||||
String.format("%.4f", m20) + " " + String.format("%.4f", m21) + " " + String.format("%.4f", m22) + " " + String.format("%.4f", m23) + "\n" +
|
||||
String.format("%.4f", m30) + " " + String.format("%.4f", m31) + " " + String.format("%.4f", m32) + " " + String.format("%.4f", m33) + "\n"
|
||||
;
|
||||
}
|
||||
|
||||
public static Matrix4f exportToMojangMatrix(OpenMatrix4f src) {
|
||||
return exportToMojangMatrix(src, null);
|
||||
}
|
||||
|
||||
public static Matrix4f exportToMojangMatrix(OpenMatrix4f src, Matrix4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Matrix4f();
|
||||
}
|
||||
|
||||
MATRIX_TRANSFORMER.position(0);
|
||||
src.store(MATRIX_TRANSFORMER);
|
||||
MATRIX_TRANSFORMER.position(0);
|
||||
|
||||
return dest.set(MATRIX_TRANSFORMER);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f importFromMojangMatrix(Matrix4f src) {
|
||||
MATRIX_TRANSFORMER.position(0);
|
||||
src.get(MATRIX_TRANSFORMER);
|
||||
|
||||
return OpenMatrix4f.load(null, MATRIX_TRANSFORMER);
|
||||
}
|
||||
|
||||
public static OpenMatrix4f[] allocateMatrixArray(int size) {
|
||||
OpenMatrix4f[] matrixArray = new OpenMatrix4f[size];
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
matrixArray[i] = new OpenMatrix4f();
|
||||
}
|
||||
|
||||
return matrixArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.math;
|
||||
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
public class QuaternionUtils {
|
||||
public static Axis XN = new Axis(-1.0F, 0.0F, 0.0F);
|
||||
public static Axis XP = new Axis(1.0F, 0.0F, 0.0F);
|
||||
public static Axis YN = new Axis(0.0F, -1.0F, 0.0F);
|
||||
public static Axis YP = new Axis(0.0F, 1.0F, 0.0F);
|
||||
public static Axis ZN = new Axis(0.0F, 0.0F, -1.0F);
|
||||
public static Axis ZP = new Axis(0.0F, 0.0F, 1.0F);
|
||||
|
||||
public static Quaternionf rotationDegrees(Vector3f axis, float degress) {
|
||||
float angle = degress * (float) Math.PI / 180;
|
||||
return rotation(axis, angle);
|
||||
}
|
||||
|
||||
public static Quaternionf rotation(Vector3f axis, float angle) {
|
||||
Quaternionf quat = new Quaternionf();
|
||||
quat.setAngleAxis(angle, axis.x, axis.y, axis.z);
|
||||
return quat;
|
||||
}
|
||||
|
||||
public static class Axis {
|
||||
private final Vector3f axis;
|
||||
|
||||
public Axis(float x, float y, float z) {
|
||||
this.axis = new Vector3f(x, y, z);
|
||||
}
|
||||
|
||||
public Quaternionf rotation(float angle) {
|
||||
return QuaternionUtils.rotation(axis, angle);
|
||||
}
|
||||
|
||||
public Quaternionf rotationDegrees(float degrees) {
|
||||
return QuaternionUtils.rotationDegrees(axis, degrees);
|
||||
}
|
||||
}
|
||||
}
|
||||
139
src/main/java/com/tiedup/remake/rig/math/ValueModifier.java
Normal file
139
src/main/java/com/tiedup/remake/rig/math/ValueModifier.java
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.math;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
|
||||
public interface ValueModifier {
|
||||
public static final Codec<Unified> CODEC =
|
||||
RecordCodecBuilder.create(instance -> instance.group(
|
||||
Codec.FLOAT.fieldOf("adder").forGetter(Unified::adder),
|
||||
Codec.FLOAT.fieldOf("multiplier").forGetter(Unified::multiplier),
|
||||
Codec.FLOAT.fieldOf("setter").forGetter(Unified::setter)
|
||||
).apply(instance, Unified::new)
|
||||
);
|
||||
|
||||
public void attach(ResultCalculator calculator);
|
||||
|
||||
public static ValueModifier adder(float value) {
|
||||
return new Adder(value);
|
||||
}
|
||||
|
||||
public static ValueModifier multiplier(float value) {
|
||||
return new Multiplier(value);
|
||||
}
|
||||
|
||||
public static ValueModifier setter(float arg) {
|
||||
return new Setter(arg);
|
||||
}
|
||||
|
||||
public static record Adder(float adder) implements ValueModifier {
|
||||
@Override
|
||||
public void attach(ResultCalculator calculator) {
|
||||
calculator.add += this.adder;
|
||||
}
|
||||
}
|
||||
|
||||
public static record Multiplier(float multiplier) implements ValueModifier {
|
||||
@Override
|
||||
public void attach(ResultCalculator calculator) {
|
||||
calculator.multiply *= this.multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
public static record Setter(float setter) implements ValueModifier {
|
||||
@Override
|
||||
public void attach(ResultCalculator calculator) {
|
||||
if (Float.isNaN(calculator.set)) {
|
||||
calculator.set = this.setter;
|
||||
} else if (!Float.isNaN(this.setter)) {
|
||||
calculator.set = Math.min(calculator.set, this.setter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static record Unified(float adder, float multiplier, float setter) implements ValueModifier {
|
||||
@Override
|
||||
public void attach(ResultCalculator calculator) {
|
||||
if (Float.isNaN(calculator.set)) {
|
||||
calculator.set = this.setter;
|
||||
} else if (!Float.isNaN(this.setter)) {
|
||||
calculator.set = Math.min(calculator.set, this.setter);
|
||||
}
|
||||
|
||||
calculator.add += this.adder;
|
||||
calculator.multiply *= this.multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
public static ResultCalculator calculator() {
|
||||
return new ResultCalculator();
|
||||
}
|
||||
|
||||
public static class ResultCalculator implements ValueModifier {
|
||||
private float set = Float.NaN;
|
||||
private float add = 0.0F;
|
||||
private float multiply = 1.0F;
|
||||
|
||||
public ResultCalculator attach(ValueModifier valueModifier) {
|
||||
valueModifier.attach(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach(ResultCalculator calculator) {
|
||||
if (Float.isNaN(calculator.set)) {
|
||||
calculator.set = this.set;
|
||||
} else if (!Float.isNaN(this.set)) {
|
||||
calculator.set = Math.min(calculator.set, this.set);
|
||||
}
|
||||
|
||||
calculator.add += this.add;
|
||||
calculator.multiply *= this.multiply;
|
||||
}
|
||||
|
||||
public ValueModifier toValueModifier() {
|
||||
if (Float.isNaN(this.set)) {
|
||||
if (Float.compare(this.add, 0.0F) == 0 && Float.compare(this.multiply, 1.0F) != 0) {
|
||||
return new Multiplier(this.multiply);
|
||||
} else if (Float.compare(this.add, 0.0F) != 0 && Float.compare(this.multiply, 1.0F) == 0) {
|
||||
return new Adder(this.add);
|
||||
}
|
||||
} else if (Float.compare(this.add, 0.0F) == 0 && Float.compare(this.multiply, 1.0F) == 0) {
|
||||
return new Setter(this.set);
|
||||
}
|
||||
|
||||
return new Unified(this.set, this.add, this.multiply);
|
||||
}
|
||||
|
||||
public void set(float f) {
|
||||
this.set = f;
|
||||
}
|
||||
|
||||
public void add(float f) {
|
||||
this.add += add;
|
||||
}
|
||||
|
||||
public void multiply(float f) {
|
||||
this.multiply *= f;
|
||||
}
|
||||
|
||||
public float getResult(float baseValue) {
|
||||
float result = baseValue;
|
||||
|
||||
if (!Float.isNaN(this.set)) {
|
||||
result = this.set;
|
||||
}
|
||||
|
||||
result += this.add;
|
||||
result *= this.multiply;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/main/java/com/tiedup/remake/rig/math/Vec2f.java
Normal file
33
src/main/java/com/tiedup/remake/rig/math/Vec2f.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.math;
|
||||
|
||||
public class Vec2f {
|
||||
public float x;
|
||||
public float y;
|
||||
|
||||
public Vec2f() {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
}
|
||||
|
||||
public Vec2f(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public Vec2f scale(float f) {
|
||||
this.x *= f;
|
||||
this.y *= f;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vec2f[" + this.x + ", " + this.y + ", " + "]";
|
||||
}
|
||||
}
|
||||
22
src/main/java/com/tiedup/remake/rig/math/Vec2i.java
Normal file
22
src/main/java/com/tiedup/remake/rig/math/Vec2i.java
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.math;
|
||||
|
||||
public class Vec2i {
|
||||
public int x;
|
||||
public int y;
|
||||
|
||||
public Vec2i(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[%d, %d]", this.x, this.y);
|
||||
}
|
||||
}
|
||||
473
src/main/java/com/tiedup/remake/rig/math/Vec3f.java
Normal file
473
src/main/java/com/tiedup/remake/rig/math/Vec3f.java
Normal file
@@ -0,0 +1,473 @@
|
||||
/*
|
||||
* 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.math;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import yesman.epicfight.main.EpicFightMod;
|
||||
|
||||
public class Vec3f extends Vec2f {
|
||||
public static final Vec3f X_AXIS = new Vec3f(1.0F, 0.0F, 0.0F);
|
||||
public static final Vec3f Y_AXIS = new Vec3f(0.0F, 1.0F, 0.0F);
|
||||
public static final Vec3f Z_AXIS = new Vec3f(0.0F, 0.0F, 1.0F);
|
||||
public static final Vec3f M_X_AXIS = new Vec3f(-1.0F, 0.0F, 0.0F);
|
||||
public static final Vec3f M_Y_AXIS = new Vec3f(0.0F, -1.0F, 0.0F);
|
||||
public static final Vec3f M_Z_AXIS = new Vec3f(0.0F, 0.0F, -1.0F);
|
||||
public static final Vec3f ZERO = new Vec3f(0.0F, 0.0F, 0.0F);
|
||||
|
||||
public float z;
|
||||
|
||||
public Vec3f() {
|
||||
super();
|
||||
this.z = 0;
|
||||
}
|
||||
|
||||
public Vec3f(float x, float y, float z) {
|
||||
super(x, y);
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public Vec3f(double x, double y, double z) {
|
||||
this((float)x, (float)y, (float)z);
|
||||
}
|
||||
|
||||
public Vec3f(Vec3 mojangVec) {
|
||||
this((float)mojangVec.x, (float)mojangVec.y, (float)mojangVec.z);
|
||||
}
|
||||
|
||||
public Vec3f set(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec3f set(Vec3 vec3f) {
|
||||
this.x = (float)vec3f.x;
|
||||
this.y = (float)vec3f.y;
|
||||
this.z = (float)vec3f.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec3f set(Vec3f vec3f) {
|
||||
this.x = vec3f.x;
|
||||
this.y = vec3f.y;
|
||||
this.z = vec3f.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec3f add(float x, float y, float z) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
this.z += z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec3f add(Vec3f vec) {
|
||||
return this.add(vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
public Vec3f add(Vec3 vec) {
|
||||
return this.add((float)vec.x, (float)vec.y, (float)vec.z);
|
||||
}
|
||||
|
||||
public Vec3f sub(float x, float y, float z) {
|
||||
this.x -= x;
|
||||
this.y -= y;
|
||||
this.z -= z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec3f sub(Vec3f vec) {
|
||||
return this.sub(vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
public static Vec3f add(Vec3f left, Vec3f right, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
return new Vec3f(left.x + right.x, left.y + right.y, left.z + right.z);
|
||||
} else {
|
||||
dest.set(left.x + right.x, left.y + right.y, left.z + right.z);
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
public static Vec3f sub(Vec3f left, Vec3f right, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
return new Vec3f(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||
} else {
|
||||
dest.set(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
public Vec3f multiply(Vec3f vec) {
|
||||
return multiply(this, this, vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
public Vec3f multiply(float x, float y, float z) {
|
||||
return multiply(this, this, x, y, z);
|
||||
}
|
||||
|
||||
public static Vec3f multiply(Vec3f src, Vec3f dest, float x, float y, float z) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.x = src.x * x;
|
||||
dest.y = src.y * y;
|
||||
dest.z = src.z * z;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3f scale(float f) {
|
||||
return scale(this, this, f);
|
||||
}
|
||||
|
||||
public static Vec3f scale(Vec3f src, Vec3f dest, float f) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.x = src.x * f;
|
||||
dest.y = src.y * f;
|
||||
dest.z = src.z * f;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Vec3f copy() {
|
||||
return new Vec3f(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
public float length() {
|
||||
return (float) Math.sqrt(this.lengthSqr());
|
||||
}
|
||||
|
||||
public float lengthSqr() {
|
||||
return this.x * this.x + this.y * this.y + this.z * this.z;
|
||||
}
|
||||
|
||||
public float distance(Vec3f opponent) {
|
||||
return (float)Math.sqrt(this.distanceSqr(opponent));
|
||||
}
|
||||
|
||||
public float distanceSqr(Vec3f opponent) {
|
||||
return (float)(Math.pow(this.x - opponent.x, 2) + Math.pow(this.y - opponent.y, 2) + Math.pow(this.z - opponent.z, 2));
|
||||
}
|
||||
|
||||
public float horizontalDistance() {
|
||||
return (float)Math.sqrt(this.x * this.x + this.z * this.z);
|
||||
}
|
||||
|
||||
public float horizontalDistanceSqr() {
|
||||
return this.x * this.x + this.z * this.z;
|
||||
}
|
||||
|
||||
public void rotate(float degree, Vec3f axis) {
|
||||
rotate(degree, axis, this, this);
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
this.x = Float.NaN;
|
||||
this.y = Float.NaN;
|
||||
this.z = Float.NaN;
|
||||
}
|
||||
|
||||
public boolean validateValues() {
|
||||
return Float.isFinite(this.x) && Float.isFinite(this.y) && Float.isFinite(this.z);
|
||||
}
|
||||
|
||||
public static Vec3f rotate(float degree, Vec3f axis, Vec3f src, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
return OpenMatrix4f.transform3v(OpenMatrix4f.createRotatorDeg(degree, axis), src, dest);
|
||||
}
|
||||
|
||||
private static final Vector3f SRC = new Vector3f();
|
||||
private static final Vector3f TRANSFORM_RESULT = new Vector3f();
|
||||
|
||||
public static Vec3f rotate(Quaternionf rot, Vec3f src, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
SRC.set(src.x, src.y, src.z);
|
||||
rot.transform(SRC, TRANSFORM_RESULT);
|
||||
dest.set(TRANSFORM_RESULT.x, TRANSFORM_RESULT.y, TRANSFORM_RESULT.z);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static float dot(Vec3f left, Vec3f right) {
|
||||
return left.x * right.x + left.y * right.y + left.z * right.z;
|
||||
}
|
||||
|
||||
public static Vec3f cross(Vec3f left, Vec3f right, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.set(left.y * right.z - left.z * right.y, right.x * left.z - right.z * left.x, left.x * right.y - left.y * right.x);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static float getAngleBetween(Vec3f a, Vec3f b) {
|
||||
return (float) Math.acos(Math.min(1.0F, Vec3f.dot(a, b) / (a.length() * b.length())));
|
||||
}
|
||||
|
||||
public static Quaternionf getRotatorBetween(Vec3f a, Vec3f b, Quaternionf dest) {
|
||||
if (dest == null) {
|
||||
dest = new Quaternionf();
|
||||
}
|
||||
|
||||
Vec3f axis = Vec3f.cross(a, b, null).normalize();
|
||||
float dotDivLength = Vec3f.dot(a, b) / (a.length() * b.length());
|
||||
|
||||
if (!Float.isFinite(dotDivLength)) {
|
||||
EpicFightMod.LOGGER.info("Warning : given vector's length is zero");
|
||||
(new IllegalArgumentException()).printStackTrace();
|
||||
dotDivLength = 1.0F;
|
||||
}
|
||||
|
||||
float radian = (float)Math.acos(Math.min(1.0F, dotDivLength));
|
||||
dest.setAngleAxis(radian, axis.x, axis.y, axis.z);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Vec3f interpolate(Vec3f from, Vec3f to, float interpolation, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.x = from.x + (to.x - from.x) * interpolation;
|
||||
dest.y = from.y + (to.y - from.y) * interpolation;
|
||||
dest.z = from.z + (to.z - from.z) * interpolation;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Vec3f normalize() {
|
||||
return normalize(this, this);
|
||||
}
|
||||
|
||||
public static Vec3f normalize(Vec3f src, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
float norm = (float) Math.sqrt(src.x * src.x + src.y * src.y + src.z * src.z);
|
||||
|
||||
if (norm > 1E-5F) {
|
||||
dest.x = src.x / norm;
|
||||
dest.y = src.y / norm;
|
||||
dest.z = src.z / norm;
|
||||
} else {
|
||||
dest.x = 0;
|
||||
dest.y = 0;
|
||||
dest.z = 0;
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + this.x + ", " + this.y + ", " + this.z + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
} else if (o instanceof Vec3f vec3f) {
|
||||
return Float.compare(this.x, vec3f.x) == 0 && Float.compare(this.y, vec3f.y) == 0 && Float.compare(this.z, vec3f.z) == 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int j = Float.floatToIntBits(this.x);
|
||||
int i = (int) (j ^ j >>> 32);
|
||||
j = Float.floatToIntBits(this.y);
|
||||
i = 31 * i + (int) (j ^ j >>> 32);
|
||||
j = Float.floatToIntBits(this.z);
|
||||
|
||||
return 31 * i + (int) (j ^ j >>> 32);
|
||||
}
|
||||
|
||||
public static Vec3f average(Collection<Vec3f> vectors, Vec3f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.set(0.0F, 0.0F, 0.0F);
|
||||
|
||||
for (Vec3f v : vectors) {
|
||||
dest.add(v);
|
||||
}
|
||||
|
||||
dest.scale(1.0F / vectors.size());
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static Vec3f average(Vec3f dest, Vec3f... vectors) {
|
||||
if (dest == null) {
|
||||
dest = new Vec3f();
|
||||
}
|
||||
|
||||
dest.set(0.0F, 0.0F, 0.0F);
|
||||
|
||||
for (Vec3f v : vectors) {
|
||||
dest.add(v);
|
||||
}
|
||||
|
||||
dest.scale(vectors.length);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static int getNearest(Vec3f from, List<Vec3f> vectors) {
|
||||
float minLength = Float.MAX_VALUE;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < vectors.size(); i++) {
|
||||
if (vectors.get(i) == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vectors.get(i).validateValues()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float distSqr = from.distanceSqr(vectors.get(i));
|
||||
|
||||
if (distSqr < minLength) {
|
||||
minLength = distSqr;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public static int getNearest(Vec3f from, Vec3f... vectors) {
|
||||
float minLength = Float.MAX_VALUE;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < vectors.length; i++) {
|
||||
if (vectors[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vectors[i].validateValues()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float distSqr = from.distanceSqr(vectors[i]);
|
||||
|
||||
if (distSqr < minLength) {
|
||||
minLength = distSqr;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public static int getMostSimilar(Vec3f start, Vec3f end, Vec3f... vectors) {
|
||||
Vec3f.sub(end, start, BASIS_DIRECTION);
|
||||
float maxDot = Float.MIN_VALUE;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < vectors.length; i++) {
|
||||
if (vectors[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vectors[i].validateValues()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vec3f.sub(vectors[i], start, COMPARISION);
|
||||
float dot = Vec3f.dot(BASIS_DIRECTION, COMPARISION) / BASIS_DIRECTION.length() * COMPARISION.length();
|
||||
|
||||
if (dot > maxDot) {
|
||||
maxDot = dot;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private static final Vec3f BASIS_DIRECTION = new Vec3f();
|
||||
private static final Vec3f COMPARISION = new Vec3f();
|
||||
|
||||
public static int getMostSimilar(Vec3f start, Vec3f end, List<Vec3f> vectors) {
|
||||
Vec3f.sub(end, start, BASIS_DIRECTION);
|
||||
float maxDot = Float.MIN_VALUE;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < vectors.size(); i++) {
|
||||
if (vectors.get(i) == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vectors.get(i).validateValues()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vec3f.sub(vectors.get(i), start, COMPARISION);
|
||||
float dot = Vec3f.dot(BASIS_DIRECTION, COMPARISION) / BASIS_DIRECTION.length() * COMPARISION.length();
|
||||
|
||||
if (dot > maxDot) {
|
||||
maxDot = dot;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public Vector3f toMojangVector() {
|
||||
return new Vector3f(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
public Vec3 toDoubleVector() {
|
||||
return new Vec3(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
public static Vec3f fromMojangVector(Vector3f vec3) {
|
||||
return new Vec3f(vec3.x(), vec3.y(), vec3.z());
|
||||
}
|
||||
|
||||
public static Vec3f fromDoubleVector(Vec3 vec3) {
|
||||
return new Vec3f((float)vec3.x(), (float)vec3.y(), (float)vec3.z());
|
||||
}
|
||||
|
||||
private static final OpenMatrix4f DEST = new OpenMatrix4f();
|
||||
|
||||
public Vec3f rotateDegree(Vec3f axis, float degree) {
|
||||
OpenMatrix4f.ofRotationDegree(degree, axis, DEST);
|
||||
OpenMatrix4f.transform3v(DEST, this, this);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
75
src/main/java/com/tiedup/remake/rig/math/Vec4f.java
Normal file
75
src/main/java/com/tiedup/remake/rig/math/Vec4f.java
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.math;
|
||||
|
||||
public class Vec4f extends Vec3f {
|
||||
public float w;
|
||||
|
||||
public Vec4f() {
|
||||
super();
|
||||
this.w = 0;
|
||||
}
|
||||
|
||||
public Vec4f(float x, float y, float z, float w) {
|
||||
super(x, y, z);
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public Vec4f(Vec3f vec3f) {
|
||||
super(vec3f.x, vec3f.y, vec3f.z);
|
||||
this.w = 1.0F;
|
||||
}
|
||||
|
||||
public void set(float x, float y, float z, float w) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public void set(Vec4f vec4f) {
|
||||
super.set(vec4f);
|
||||
this.w = vec4f.w;
|
||||
}
|
||||
|
||||
public Vec4f add(float x, float y, float z, float w) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
this.z += z;
|
||||
this.w += w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static Vec4f add(Vec4f left, Vec4f right, Vec4f dest) {
|
||||
if (dest == null) {
|
||||
dest = new Vec4f();
|
||||
}
|
||||
|
||||
dest.x = left.x + right.x;
|
||||
dest.y = left.y + right.y;
|
||||
dest.z = left.z + right.z;
|
||||
dest.w = left.w + right.w;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec4f scale(float f) {
|
||||
super.scale(f);
|
||||
this.w *= f;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vec4f transform(OpenMatrix4f matrix) {
|
||||
return OpenMatrix4f.transform(matrix, this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vec4f[" + this.x + ", " + this.y + ", " + this.z + ", " + this.w + "]";
|
||||
}
|
||||
}
|
||||
104
src/main/java/com/tiedup/remake/rig/mesh/ClassicMesh.java
Normal file
104
src/main/java/com/tiedup/remake/rig/mesh/ClassicMesh.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||
* by the Epic Fight Team, licensed under GPLv3.
|
||||
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import com.tiedup.remake.rig.mesh.ClassicMesh.ClassicMeshPart;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import yesman.epicfight.main.EpicFightMod;
|
||||
|
||||
public class ClassicMesh extends StaticMesh<ClassicMeshPart> {
|
||||
public ClassicMesh(Map<String, Number[]> arrayMap, Map<MeshPartDefinition, List<VertexBuilder>> partBuilders, ClassicMesh parent, RenderProperties properties) {
|
||||
super(arrayMap, partBuilders, parent, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, ClassicMeshPart> createModelPart(Map<MeshPartDefinition, List<VertexBuilder>> partBuilders) {
|
||||
Map<String, ClassicMeshPart> parts = Maps.newHashMap();
|
||||
|
||||
partBuilders.forEach((partDefinition, vertexBuilder) -> {
|
||||
parts.put(partDefinition.partName(), new ClassicMeshPart(vertexBuilder, partDefinition.renderProperties(), partDefinition.getModelPartAnimationProvider()));
|
||||
});
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassicMeshPart getOrLogException(Map<String, ClassicMeshPart> parts, String name) {
|
||||
if (!parts.containsKey(name)) {
|
||||
EpicFightMod.LOGGER.debug("Can not find the mesh part named " + name + " in " + this.getClass().getCanonicalName());
|
||||
return null;
|
||||
}
|
||||
|
||||
return parts.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, VertexConsumer vertexConsumer, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay) {
|
||||
for (ClassicMeshPart part : this.parts.values()) {
|
||||
part.draw(poseStack, vertexConsumer, drawingFunction, packedLight, r, g, b, a, overlay);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPosed(PoseStack poseStack, VertexConsumer vertexConsumer, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, Armature armature, OpenMatrix4f[] poses) {
|
||||
this.draw(poseStack, vertexConsumer, drawingFunction, packedLight, r, g, b, a, overlay);
|
||||
}
|
||||
|
||||
public class ClassicMeshPart extends MeshPart {
|
||||
public ClassicMeshPart(List<VertexBuilder> verticies, @Nullable Mesh.RenderProperties renderProperties, @Nullable Supplier<OpenMatrix4f> vanillaPartTracer) {
|
||||
super(verticies, renderProperties, vanillaPartTracer);
|
||||
}
|
||||
|
||||
protected static final Vector4f POSITION = new Vector4f();
|
||||
protected static final Vector3f NORMAL = new Vector3f();
|
||||
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, VertexConsumer bufferbuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay) {
|
||||
if (this.isHidden()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector4f color = this.getColor(r, g, b, a);
|
||||
poseStack.pushPose();
|
||||
OpenMatrix4f transform = this.getVanillaPartTransform();
|
||||
|
||||
if (transform != null) {
|
||||
poseStack.mulPoseMatrix(OpenMatrix4f.exportToMojangMatrix(transform));
|
||||
}
|
||||
|
||||
Matrix4f matrix4f = poseStack.last().pose();
|
||||
Matrix3f matrix3f = poseStack.last().normal();
|
||||
|
||||
for (VertexBuilder vi : this.getVertices()) {
|
||||
getVertexPosition(vi.position, POSITION);
|
||||
getVertexNormal(vi.normal, NORMAL);
|
||||
POSITION.mul(matrix4f);
|
||||
NORMAL.mul(matrix3f);
|
||||
|
||||
drawingFunction.draw(bufferbuilder, POSITION.x(), POSITION.y(), POSITION.z(), NORMAL.x(), NORMAL.y(), NORMAL.z(), packedLight, color.x, color.y, color.z, color.w, uvs[vi.uv * 2], uvs[vi.uv * 2 + 1], overlay);
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/main/java/com/tiedup/remake/rig/mesh/CompositeMesh.java
Normal file
77
src/main/java/com/tiedup/remake/rig/mesh/CompositeMesh.java
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulatable;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObjectBuilder;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
public class CompositeMesh implements Mesh, SoftBodyTranslatable {
|
||||
private final StaticMesh<?> staticMesh;
|
||||
private final SoftBodyTranslatable softBodyMesh;
|
||||
|
||||
public CompositeMesh(StaticMesh<?> staticMesh, SoftBodyTranslatable softBodyMesh) {
|
||||
this.staticMesh = staticMesh;
|
||||
this.softBodyMesh = softBodyMesh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
this.staticMesh.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, VertexConsumer bufferBuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay) {
|
||||
this.staticMesh.draw(poseStack, bufferBuilder, drawingFunction, packedLight, r, g, b, a, overlay);
|
||||
this.softBodyMesh.getOriginalMesh().draw(poseStack, bufferBuilder, drawingFunction, packedLight, r, g, b, a, overlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPosed(PoseStack poseStack, VertexConsumer bufferBuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, Armature armature, OpenMatrix4f[] poses) {
|
||||
this.staticMesh.drawPosed(poseStack, bufferBuilder, drawingFunction, packedLight, r, g, b, a, overlay, armature, poses);
|
||||
this.softBodyMesh.getOriginalMesh().drawPosed(poseStack, bufferBuilder, drawingFunction, packedLight, r, g, b, a, overlay, armature, poses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canStartSoftBodySimulation() {
|
||||
return this.softBodyMesh.canStartSoftBodySimulation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClothObject createSimulationData(@Nullable SoftBodyTranslatable provider, ClothSimulatable simOwner, ClothObjectBuilder simBuilder) {
|
||||
return this.softBodyMesh.createSimulationData(this, simOwner, simBuilder);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public StaticMesh<?> getStaticMesh() {
|
||||
return this.staticMesh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StaticMesh<?> getOriginalMesh() {
|
||||
return (StaticMesh<?>)this.softBodyMesh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putSoftBodySimulationInfo(Map<String, ClothSimulationInfo> sofyBodySimulationInfo) {
|
||||
this.softBodyMesh.putSoftBodySimulationInfo(sofyBodySimulationInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClothSimulationInfo> getSoftBodySimulationInfo() {
|
||||
return this.softBodyMesh.getSoftBodySimulationInfo();
|
||||
}
|
||||
}
|
||||
65
src/main/java/com/tiedup/remake/rig/mesh/HumanoidMesh.java
Normal file
65
src/main/java/com/tiedup/remake/rig/mesh/HumanoidMesh.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.mesh.MeshPartDefinition;
|
||||
import com.tiedup.remake.rig.mesh.Meshes;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
||||
import com.tiedup.remake.rig.mesh.VertexBuilder;
|
||||
|
||||
public class HumanoidMesh extends SkinnedMesh {
|
||||
public final SkinnedMeshPart head;
|
||||
public final SkinnedMeshPart torso;
|
||||
public final SkinnedMeshPart leftArm;
|
||||
public final SkinnedMeshPart rightArm;
|
||||
public final SkinnedMeshPart leftLeg;
|
||||
public final SkinnedMeshPart rightLeg;
|
||||
public final SkinnedMeshPart hat;
|
||||
public final SkinnedMeshPart jacket;
|
||||
public final SkinnedMeshPart leftSleeve;
|
||||
public final SkinnedMeshPart rightSleeve;
|
||||
public final SkinnedMeshPart leftPants;
|
||||
public final SkinnedMeshPart rightPants;
|
||||
|
||||
public HumanoidMesh(Map<String, Number[]> arrayMap, Map<MeshPartDefinition, List<VertexBuilder>> parts, SkinnedMesh parent, RenderProperties properties) {
|
||||
super(arrayMap, parts, parent, properties);
|
||||
|
||||
this.head = this.getOrLogException(this.parts, "head");
|
||||
this.torso = this.getOrLogException(this.parts, "torso");
|
||||
this.leftArm = this.getOrLogException(this.parts, "leftArm");
|
||||
this.rightArm = this.getOrLogException(this.parts, "rightArm");
|
||||
this.leftLeg = this.getOrLogException(this.parts, "leftLeg");
|
||||
this.rightLeg = this.getOrLogException(this.parts, "rightLeg");
|
||||
|
||||
this.hat = this.getOrLogException(this.parts, "hat");
|
||||
this.jacket = this.getOrLogException(this.parts, "jacket");
|
||||
this.leftSleeve = this.getOrLogException(this.parts, "leftSleeve");
|
||||
this.rightSleeve = this.getOrLogException(this.parts, "rightSleeve");
|
||||
this.leftPants = this.getOrLogException(this.parts, "leftPants");
|
||||
this.rightPants = this.getOrLogException(this.parts, "rightPants");
|
||||
}
|
||||
|
||||
public AssetAccessor<? extends SkinnedMesh> getHumanoidArmorModel(EquipmentSlot slot) {
|
||||
switch (slot) {
|
||||
case HEAD:
|
||||
return Meshes.HELMET;
|
||||
case CHEST:
|
||||
return Meshes.CHESTPLATE;
|
||||
case LEGS:
|
||||
return Meshes.LEGGINS;
|
||||
case FEET:
|
||||
return Meshes.BOOTS;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
214
src/main/java/com/tiedup/remake/rig/mesh/Mesh.java
Normal file
214
src/main/java/com/tiedup/remake/rig/mesh/Mesh.java
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.client.extensions.IForgeVertexConsumer;
|
||||
import net.minecraftforge.client.model.IQuadTransformer;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import com.tiedup.remake.rig.render.TiedUpRenderTypes;
|
||||
|
||||
public interface Mesh {
|
||||
|
||||
void initialize();
|
||||
|
||||
/* Draw wihtout mesh deformation */
|
||||
void draw(PoseStack poseStack, VertexConsumer vertexConsumer, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay);
|
||||
|
||||
/* Draw with mesh deformation */
|
||||
void drawPosed(PoseStack poseStack, VertexConsumer vertexConsumer, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, @Nullable Armature armature, OpenMatrix4f[] poses);
|
||||
|
||||
/* Universal method */
|
||||
default void draw(PoseStack poseStack, MultiBufferSource bufferSources, RenderType renderType, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, @Nullable Armature armature, OpenMatrix4f[] poses) {
|
||||
this.drawPosed(poseStack, bufferSources.getBuffer(EpicFightRenderTypes.getTriangulated(renderType)), drawingFunction, packedLight, r, g, b, a, overlay, armature, poses);
|
||||
}
|
||||
|
||||
public static record RenderProperties(ResourceLocation customTexturePath, Vec3f customColor, boolean isTransparent) {
|
||||
public static class Builder {
|
||||
protected String customTexturePath;
|
||||
protected Vec3f customColor = new Vec3f();
|
||||
protected boolean isTransparent;
|
||||
|
||||
public RenderProperties.Builder customTexturePath(String path) {
|
||||
this.customTexturePath = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderProperties.Builder transparency(boolean isTransparent) {
|
||||
this.isTransparent = isTransparent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderProperties.Builder customColor(float r, float g, float b) {
|
||||
this.customColor.x = r;
|
||||
this.customColor.y = g;
|
||||
this.customColor.z = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderProperties build() {
|
||||
return new RenderProperties(this.customTexturePath == null ? null : ResourceLocation.parse(this.customTexturePath), this.customColor, this.isTransparent);
|
||||
}
|
||||
|
||||
public static RenderProperties.Builder create() {
|
||||
return new RenderProperties.Builder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DrawingFunction {
|
||||
public static final DrawingFunction NEW_ENTITY = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ, r, g, b, a, u, v, overlay, packedLight, normX, normY, normZ);
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_TEX = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.uv(u, v);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_TEX_COLOR_NORMAL = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.uv(u, v);
|
||||
builder.color(r, g, b, a);
|
||||
builder.normal(normX, normY, normZ);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_TEX_COLOR_LIGHTMAP = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.uv(u, v);
|
||||
builder.color(r, g, b, a);
|
||||
builder.uv2(packedLight);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_COLOR_LIGHTMAP = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.color(r, g, b, a);
|
||||
builder.uv2(packedLight);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_COLOR_NORMAL = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.color(r, g, b, a);
|
||||
builder.normal(normX, normY, normZ);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public static final DrawingFunction POSITION_COLOR_TEX_LIGHTMAP = (builder, posX, posY, posZ, normX, normY, normZ, packedLight, r, g, b, a, u, v, overlay) -> {
|
||||
builder.vertex(posX, posY, posZ);
|
||||
builder.color(r, g, b, a);
|
||||
builder.uv(u, v);
|
||||
builder.uv2(packedLight);
|
||||
builder.endVertex();
|
||||
};
|
||||
|
||||
public void draw(VertexConsumer vertexConsumer, float posX, float posY, float posZ, float normX, float normY, float normZ, int packedLight, float r, float g, float b, float a, float u, float v, int overlay);
|
||||
|
||||
default void putBulkData(PoseStack.Pose pose, BakedQuad bakedQuad, VertexConsumer vertexConsumer, float red, float green, float blue, float alpha, int packedLight, int packedOverlay, boolean readExistingColor) {
|
||||
putBulkDataWithDrawingFunction(this, vertexConsumer, pose, bakedQuad, new float[] { 1.0F, 1.0F, 1.0F, 1.0F }, red, green, blue, alpha, new int[] { packedLight, packedLight, packedLight, packedLight }, packedOverlay, readExistingColor);
|
||||
}
|
||||
|
||||
static void putBulkDataWithDrawingFunction(DrawingFunction drawingFunction, VertexConsumer builder, PoseStack.Pose pPoseEntry, BakedQuad pQuad, float[] pColorMuls, float pRed, float pGreen, float pBlue, float alpha, int[] pCombinedLights, int pCombinedOverlay, boolean pMulColor) {
|
||||
float[] afloat = new float[] { pColorMuls[0], pColorMuls[1], pColorMuls[2], pColorMuls[3] };
|
||||
int[] aint1 = pQuad.getVertices();
|
||||
Vec3i vec3i = pQuad.getDirection().getNormal();
|
||||
Matrix4f matrix4f = pPoseEntry.pose();
|
||||
Vector3f vector3f = pPoseEntry.normal().transform(new Vector3f((float) vec3i.getX(), (float) vec3i.getY(), (float) vec3i.getZ()));
|
||||
int j = aint1.length / 8;
|
||||
|
||||
try (MemoryStack memorystack = MemoryStack.stackPush()) {
|
||||
ByteBuffer bytebuffer = memorystack.malloc(DefaultVertexFormat.BLOCK.getVertexSize());
|
||||
IntBuffer intbuffer = bytebuffer.asIntBuffer();
|
||||
|
||||
for (int k = 0; k < j; ++k) {
|
||||
intbuffer.clear();
|
||||
intbuffer.put(aint1, k * 8, 8);
|
||||
float f = bytebuffer.getFloat(0);
|
||||
float f1 = bytebuffer.getFloat(4);
|
||||
float f2 = bytebuffer.getFloat(8);
|
||||
float f3;
|
||||
float f4;
|
||||
float f5;
|
||||
|
||||
if (pMulColor) {
|
||||
float f6 = (float) (bytebuffer.get(12) & 255) / 255.0F;
|
||||
float f7 = (float) (bytebuffer.get(13) & 255) / 255.0F;
|
||||
float f8 = (float) (bytebuffer.get(14) & 255) / 255.0F;
|
||||
f3 = f6 * afloat[k] * pRed;
|
||||
f4 = f7 * afloat[k] * pGreen;
|
||||
f5 = f8 * afloat[k] * pBlue;
|
||||
} else {
|
||||
f3 = afloat[k] * pRed;
|
||||
f4 = afloat[k] * pGreen;
|
||||
f5 = afloat[k] * pBlue;
|
||||
}
|
||||
|
||||
int l = applyBakedLighting(pCombinedLights[k], bytebuffer);
|
||||
float f9 = bytebuffer.getFloat(16);
|
||||
float f10 = bytebuffer.getFloat(20);
|
||||
Vector4f vector4f = matrix4f.transform(new Vector4f(f, f1, f2, 1.0F));
|
||||
applyBakedNormals(vector3f, bytebuffer, pPoseEntry.normal());
|
||||
float vertexAlpha = pMulColor ? alpha * (float) (bytebuffer.get(15) & 255) / 255.0F : alpha;
|
||||
drawingFunction.draw(builder, vector4f.x(), vector4f.y(), vector4f.z(), vector3f.x(), vector3f.y(), vector3f.z(), l, f3, f4, f5, vertexAlpha, f9, f10, pCombinedOverlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Code copy from {@link IForgeVertexConsumer#applyBakedLighting}
|
||||
*/
|
||||
static int applyBakedLighting(int packedLight, ByteBuffer data) {
|
||||
int bl = packedLight & 0xFFFF;
|
||||
int sl = (packedLight >> 16) & 0xFFFF;
|
||||
int offset = IQuadTransformer.UV2 * 4; // int offset for vertex 0 * 4 bytes per int
|
||||
int blBaked = Short.toUnsignedInt(data.getShort(offset));
|
||||
int slBaked = Short.toUnsignedInt(data.getShort(offset + 2));
|
||||
bl = Math.max(bl, blBaked);
|
||||
sl = Math.max(sl, slBaked);
|
||||
return bl | (sl << 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Code copy from {@link IForgeVertexConsumer#applyBakedNormals}
|
||||
*/
|
||||
static void applyBakedNormals(Vector3f generated, ByteBuffer data, Matrix3f normalTransform) {
|
||||
byte nx = data.get(28);
|
||||
byte ny = data.get(29);
|
||||
byte nz = data.get(30);
|
||||
if (nx != 0 || ny != 0 || nz != 0)
|
||||
{
|
||||
generated.set(nx / 127f, ny / 127f, nz / 127f);
|
||||
generated.mul(normalTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/main/java/com/tiedup/remake/rig/mesh/MeshPart.java
Normal file
89
src/main/java/com/tiedup/remake/rig/mesh/MeshPart.java
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.render.TiedUpRenderTypes;
|
||||
|
||||
public abstract class MeshPart {
|
||||
protected final List<VertexBuilder> verticies;
|
||||
protected final Mesh.RenderProperties renderProperties;
|
||||
protected final Supplier<OpenMatrix4f> vanillaPartTracer;
|
||||
protected boolean isHidden;
|
||||
|
||||
public MeshPart(List<VertexBuilder> vertices, @Nullable Mesh.RenderProperties renderProperties, @Nullable Supplier<OpenMatrix4f> vanillaPartTracer) {
|
||||
this.verticies = vertices;
|
||||
this.renderProperties = renderProperties;
|
||||
this.vanillaPartTracer = vanillaPartTracer;
|
||||
}
|
||||
|
||||
public abstract void draw(PoseStack poseStack, VertexConsumer bufferbuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay);
|
||||
|
||||
public void setHidden(boolean hidden) {
|
||||
this.isHidden = hidden;
|
||||
}
|
||||
|
||||
public boolean isHidden() {
|
||||
return this.isHidden;
|
||||
}
|
||||
|
||||
public List<VertexBuilder> getVertices() {
|
||||
return this.verticies;
|
||||
}
|
||||
|
||||
public OpenMatrix4f getVanillaPartTransform() {
|
||||
if (this.vanillaPartTracer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.vanillaPartTracer.get();
|
||||
}
|
||||
|
||||
public VertexConsumer getBufferBuilder(RenderType renderType, MultiBufferSource bufferSource) {
|
||||
if (this.renderProperties.customTexturePath() != null) {
|
||||
return bufferSource.getBuffer(EpicFightRenderTypes.replaceTexture(this.renderProperties.customTexturePath(), renderType));
|
||||
}
|
||||
|
||||
return bufferSource.getBuffer(renderType);
|
||||
}
|
||||
|
||||
protected static final Vector4f COLOR = new Vector4f();
|
||||
|
||||
public Vector4f getColor(float r, float g, float b, float a) {
|
||||
if (this.renderProperties != null && this.renderProperties.customColor() != null) {
|
||||
COLOR.set(
|
||||
this.renderProperties.customColor().x
|
||||
, this.renderProperties.customColor().y
|
||||
, this.renderProperties.customColor().z
|
||||
, a
|
||||
);
|
||||
|
||||
return COLOR;
|
||||
} else {
|
||||
COLOR.set(
|
||||
r
|
||||
, g
|
||||
, b
|
||||
, a
|
||||
);
|
||||
|
||||
return COLOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
public interface MeshPartDefinition {
|
||||
String partName();
|
||||
Mesh.RenderProperties renderProperties();
|
||||
Supplier<OpenMatrix4f> getModelPartAnimationProvider();
|
||||
}
|
||||
225
src/main/java/com/tiedup/remake/rig/mesh/Meshes.java
Normal file
225
src/main/java/com/tiedup/remake/rig/mesh/Meshes.java
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.asset.JsonAssetLoader;
|
||||
import com.tiedup.remake.rig.mesh.Mesh.RenderProperties;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulatable;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObjectBuilder;
|
||||
import com.tiedup.remake.rig.mesh.CreeperMesh;
|
||||
import com.tiedup.remake.rig.mesh.DragonMesh;
|
||||
import com.tiedup.remake.rig.mesh.EndermanMesh;
|
||||
import com.tiedup.remake.rig.mesh.HoglinMesh;
|
||||
import com.tiedup.remake.rig.mesh.HumanoidMesh;
|
||||
import com.tiedup.remake.rig.mesh.IronGolemMesh;
|
||||
import com.tiedup.remake.rig.mesh.PiglinMesh;
|
||||
import com.tiedup.remake.rig.mesh.RavagerMesh;
|
||||
import com.tiedup.remake.rig.mesh.SpiderMesh;
|
||||
import com.tiedup.remake.rig.mesh.VexMesh;
|
||||
import com.tiedup.remake.rig.mesh.VillagerMesh;
|
||||
import com.tiedup.remake.rig.mesh.WitherMesh;
|
||||
import yesman.epicfight.main.EpicFightMod;
|
||||
|
||||
public class Meshes implements PreparableReloadListener {
|
||||
private static final Map<ResourceLocation, MeshAccessor<? extends Mesh>> ACCESSORS = Maps.newHashMap();
|
||||
private static final Map<MeshAccessor<? extends Mesh>, Mesh> MESHES = Maps.newHashMap();
|
||||
private static ResourceManager resourceManager = null;
|
||||
|
||||
//For resource reloader
|
||||
public static final Meshes INSTANCE = new Meshes();
|
||||
|
||||
// Entities
|
||||
public static final MeshAccessor<HumanoidMesh> ALEX = MeshAccessor.create(EpicFightMod.MODID, "entity/biped_slim_arm", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
|
||||
public static final MeshAccessor<HumanoidMesh> BIPED = MeshAccessor.create(EpicFightMod.MODID, "entity/biped", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
|
||||
public static final MeshAccessor<HumanoidMesh> BIPED_OLD_TEX = MeshAccessor.create(EpicFightMod.MODID, "entity/biped_old_texture", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
|
||||
public static final MeshAccessor<HumanoidMesh> BIPED_OUTLAYER = MeshAccessor.create(EpicFightMod.MODID, "entity/biped_outlayer", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
|
||||
public static final MeshAccessor<VillagerMesh> VILLAGER_ZOMBIE = MeshAccessor.create(EpicFightMod.MODID, "entity/zombie_villager", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(VillagerMesh::new));
|
||||
public static final MeshAccessor<CreeperMesh> CREEPER = MeshAccessor.create(EpicFightMod.MODID, "entity/creeper", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(CreeperMesh::new));
|
||||
public static final MeshAccessor<EndermanMesh> ENDERMAN = MeshAccessor.create(EpicFightMod.MODID, "entity/enderman", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(EndermanMesh::new));
|
||||
public static final MeshAccessor<HumanoidMesh> SKELETON = MeshAccessor.create(EpicFightMod.MODID, "entity/skeleton", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
|
||||
public static final MeshAccessor<SpiderMesh> SPIDER = MeshAccessor.create(EpicFightMod.MODID, "entity/spider", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(SpiderMesh::new));
|
||||
public static final MeshAccessor<IronGolemMesh> IRON_GOLEM = MeshAccessor.create(EpicFightMod.MODID, "entity/iron_golem", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(IronGolemMesh::new));
|
||||
public static final MeshAccessor<HumanoidMesh> ILLAGER = MeshAccessor.create(EpicFightMod.MODID, "entity/illager", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(VillagerMesh::new));
|
||||
public static final MeshAccessor<VillagerMesh> WITCH = MeshAccessor.create(EpicFightMod.MODID, "entity/witch", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(VillagerMesh::new));
|
||||
public static final MeshAccessor<RavagerMesh> RAVAGER = MeshAccessor.create(EpicFightMod.MODID, "entity/ravager",(jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(RavagerMesh::new));
|
||||
public static final MeshAccessor<VexMesh> VEX = MeshAccessor.create(EpicFightMod.MODID, "entity/vex", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(VexMesh::new));
|
||||
public static final MeshAccessor<PiglinMesh> PIGLIN = MeshAccessor.create(EpicFightMod.MODID, "entity/piglin", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(PiglinMesh::new));
|
||||
public static final MeshAccessor<HoglinMesh> HOGLIN = MeshAccessor.create(EpicFightMod.MODID, "entity/hoglin", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HoglinMesh::new));
|
||||
public static final MeshAccessor<DragonMesh> DRAGON = MeshAccessor.create(EpicFightMod.MODID, "entity/dragon", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(DragonMesh::new));
|
||||
public static final MeshAccessor<WitherMesh> WITHER = MeshAccessor.create(EpicFightMod.MODID, "entity/wither", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(WitherMesh::new));
|
||||
|
||||
// Armors
|
||||
public static final MeshAccessor<SkinnedMesh> HELMET = MeshAccessor.create(EpicFightMod.MODID, "armor/helmet", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(SkinnedMesh::new));
|
||||
public static final MeshAccessor<SkinnedMesh> HELMET_PIGLIN = MeshAccessor.create(EpicFightMod.MODID, "armor/piglin_helmet", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(SkinnedMesh::new));
|
||||
public static final MeshAccessor<SkinnedMesh> HELMET_VILLAGER = MeshAccessor.create(EpicFightMod.MODID, "armor/villager_helmet", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(SkinnedMesh::new));
|
||||
public static final MeshAccessor<SkinnedMesh> CHESTPLATE = MeshAccessor.create(EpicFightMod.MODID, "armor/chestplate", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(SkinnedMesh::new));
|
||||
public static final MeshAccessor<SkinnedMesh> LEGGINS = MeshAccessor.create(EpicFightMod.MODID, "armor/leggins", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(SkinnedMesh::new));
|
||||
public static final MeshAccessor<SkinnedMesh> BOOTS = MeshAccessor.create(EpicFightMod.MODID, "armor/boots", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(SkinnedMesh::new));
|
||||
|
||||
// Particles
|
||||
public static final MeshAccessor<ClassicMesh> AIR_BURST = MeshAccessor.create(EpicFightMod.MODID, "particle/air_burst", (jsonModelLoader) -> jsonModelLoader.loadClassicMesh(ClassicMesh::new));
|
||||
public static final MeshAccessor<ClassicMesh> FORCE_FIELD = MeshAccessor.create(EpicFightMod.MODID, "particle/force_field", (jsonModelLoader) -> jsonModelLoader.loadClassicMesh(ClassicMesh::new));
|
||||
public static final MeshAccessor<ClassicMesh> LASER = MeshAccessor.create(EpicFightMod.MODID, "particle/laser", (jsonModelLoader) -> jsonModelLoader.loadClassicMesh(ClassicMesh::new));
|
||||
|
||||
// Layers
|
||||
public static final MeshAccessor<SkinnedMesh> CAPE_DEFAULT = MeshAccessor.create(EpicFightMod.MODID, "layer/default_cape", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(SkinnedMesh::new));
|
||||
|
||||
public static void reload(ResourceManager resourceManager) {
|
||||
Meshes.resourceManager = resourceManager;
|
||||
|
||||
ACCESSORS.entrySet().removeIf(entry -> !entry.getValue().inRegistry);
|
||||
|
||||
MESHES.values().forEach((mesh) -> {
|
||||
if (mesh instanceof SkinnedMesh skinnedMesh) {
|
||||
skinnedMesh.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
MESHES.clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public static <M extends Mesh> AssetAccessor<M> get(ResourceLocation id) {
|
||||
return (AssetAccessor<M>) ACCESSORS.get(id);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <M extends Mesh> AssetAccessor<M> getOrCreate(ResourceLocation id, Function<JsonAssetLoader, M> jsonLoader) {
|
||||
return ACCESSORS.containsKey(id) ? (AssetAccessor<M>)ACCESSORS.get(id) : MeshAccessor.create(id, jsonLoader, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <M extends Mesh> Set<AssetAccessor<M>> entry(Class<? extends Mesh> filter) {
|
||||
return ACCESSORS.values().stream().filter((accessor) -> filter.isAssignableFrom(accessor.get().getClass())).map((accessor) -> (AssetAccessor<M>)accessor).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static ResourceLocation wrapLocation(ResourceLocation rl) {
|
||||
return rl.getPath().matches("animmodels/.*\\.json") ? rl : ResourceLocation.fromNamespaceAndPath(rl.getNamespace(), "animmodels/" + rl.getPath() + ".json");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> reload(PreparableReloadListener.PreparationBarrier stage, ResourceManager resourceManager, ProfilerFiller preparationsProfiler, ProfilerFiller reloadProfiler, Executor backgroundExecutor, Executor gameExecutor) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
Meshes.reload(resourceManager);
|
||||
}, gameExecutor).thenCompose(stage::wait);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MeshContructor<P extends MeshPart, V extends VertexBuilder, M extends StaticMesh<P>> {
|
||||
M invoke(Map<String, Number[]> arrayMap, Map<MeshPartDefinition, List<V>> parts, M parent, RenderProperties properties);
|
||||
}
|
||||
|
||||
public static record MeshAccessor<M extends Mesh> (ResourceLocation registryName, Function<JsonAssetLoader, M> jsonLoader, boolean inRegistry) implements AssetAccessor<M>, SoftBodyTranslatable {
|
||||
public static <M extends Mesh> MeshAccessor<M> create(String namespaceId, String path, Function<JsonAssetLoader, M> jsonLoader) {
|
||||
return create(ResourceLocation.fromNamespaceAndPath(namespaceId, path), jsonLoader, true);
|
||||
}
|
||||
|
||||
private static <M extends Mesh> MeshAccessor<M> create(ResourceLocation id, Function<JsonAssetLoader, M> jsonLoader, boolean inRegistry) {
|
||||
MeshAccessor<M> accessor = new MeshAccessor<M> (id, jsonLoader, inRegistry);
|
||||
ACCESSORS.put(id, accessor);
|
||||
return accessor;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public M get() {
|
||||
if (!MESHES.containsKey(this)) {
|
||||
JsonAssetLoader jsonModelLoader = new JsonAssetLoader(resourceManager, wrapLocation(this.registryName));
|
||||
MESHES.put(this, this.jsonLoader.apply(jsonModelLoader));
|
||||
}
|
||||
|
||||
return (M)MESHES.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 MeshAccessor 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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canStartSoftBodySimulation() {
|
||||
Mesh mesh = this.get();
|
||||
|
||||
if (mesh instanceof StaticMesh<?> staticMesh) {
|
||||
return staticMesh.canStartSoftBodySimulation();
|
||||
} else if (mesh instanceof CompositeMesh compositeMesh) {
|
||||
return compositeMesh.canStartSoftBodySimulation();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClothObject createSimulationData(SoftBodyTranslatable provider, ClothSimulatable simOwner, ClothObjectBuilder simBuilder) {
|
||||
Mesh mesh = this.get();
|
||||
|
||||
if (mesh instanceof StaticMesh<?> staticMesh) {
|
||||
return staticMesh.createSimulationData(provider, simOwner, simBuilder);
|
||||
} else if (mesh instanceof CompositeMesh compositeMesh) {
|
||||
return compositeMesh.createSimulationData(provider, simOwner, simBuilder);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putSoftBodySimulationInfo(Map<String, ClothSimulationInfo> sofyBodySimulationInfo) {
|
||||
Mesh mesh = this.get();
|
||||
|
||||
if (mesh instanceof SoftBodyTranslatable softBodyTranslatable) {
|
||||
softBodyTranslatable.putSoftBodySimulationInfo(sofyBodySimulationInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ClothSimulationInfo> getSoftBodySimulationInfo() {
|
||||
Mesh mesh = this.get();
|
||||
|
||||
if (mesh instanceof SoftBodyTranslatable softBodyTranslatable) {
|
||||
return softBodyTranslatable.getSoftBodySimulationInfo();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import it.unimi.dsi.fastutil.floats.FloatArrayList;
|
||||
import it.unimi.dsi.fastutil.floats.FloatList;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import com.tiedup.remake.rig.math.Vec2f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
|
||||
public class SingleGroupVertexBuilder {
|
||||
private Vec3f position;
|
||||
private Vec3f normal;
|
||||
private Vec2f textureCoordinate;
|
||||
private Vec3f effectiveJointIDs;
|
||||
private Vec3f effectiveJointWeights;
|
||||
private int effectiveJointNumber;
|
||||
|
||||
public SingleGroupVertexBuilder() {
|
||||
this.position = null;
|
||||
this.normal = null;
|
||||
this.textureCoordinate = null;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder(SingleGroupVertexBuilder vertex) {
|
||||
this.position = vertex.position;
|
||||
this.effectiveJointIDs = vertex.effectiveJointIDs;
|
||||
this.effectiveJointWeights = vertex.effectiveJointWeights;
|
||||
this.effectiveJointNumber = vertex.effectiveJointNumber;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setPosition(Vec3f position) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setNormal(Vec3f vector) {
|
||||
this.normal = vector;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setTextureCoordinate(Vec2f vector) {
|
||||
this.textureCoordinate = vector;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setEffectiveJointIDs(Vec3f effectiveJointIDs) {
|
||||
this.effectiveJointIDs = effectiveJointIDs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setEffectiveJointWeights(Vec3f effectiveJointWeights) {
|
||||
this.effectiveJointWeights = effectiveJointWeights;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SingleGroupVertexBuilder setEffectiveJointNumber(int count) {
|
||||
this.effectiveJointNumber = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
public State compareTextureCoordinateAndNormal(Vec3f normal, Vec2f textureCoord) {
|
||||
if (this.textureCoordinate == null) {
|
||||
return State.EMPTY;
|
||||
} else if (this.textureCoordinate.equals(textureCoord) && this.normal.equals(normal)) {
|
||||
return State.EQUAL;
|
||||
} else {
|
||||
return State.DIFFERENT;
|
||||
}
|
||||
}
|
||||
|
||||
public static SkinnedMesh loadVertexInformation(List<SingleGroupVertexBuilder> vertices, Map<MeshPartDefinition, IntList> indices) {
|
||||
FloatList positions = new FloatArrayList();
|
||||
FloatList normals = new FloatArrayList();
|
||||
FloatList texCoords = new FloatArrayList();
|
||||
IntList animationIndices = new IntArrayList();
|
||||
FloatList jointWeights = new FloatArrayList();
|
||||
IntList affectCountList = new IntArrayList();
|
||||
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
SingleGroupVertexBuilder vertex = vertices.get(i);
|
||||
Vec3f position = vertex.position;
|
||||
Vec3f normal = vertex.normal;
|
||||
Vec2f texCoord = vertex.textureCoordinate;
|
||||
positions.add(position.x);
|
||||
positions.add(position.y);
|
||||
positions.add(position.z);
|
||||
normals.add(normal.x);
|
||||
normals.add(normal.y);
|
||||
normals.add(normal.z);
|
||||
texCoords.add(texCoord.x);
|
||||
texCoords.add(texCoord.y);
|
||||
|
||||
Vec3f effectIDs = vertex.effectiveJointIDs;
|
||||
Vec3f weights = vertex.effectiveJointWeights;
|
||||
int count = Math.min(vertex.effectiveJointNumber, 3);
|
||||
affectCountList.add(count);
|
||||
|
||||
for (int j = 0; j < count; j++) {
|
||||
switch (j) {
|
||||
case 0:
|
||||
animationIndices.add((int) effectIDs.x);
|
||||
jointWeights.add(weights.x);
|
||||
animationIndices.add(jointWeights.size() - 1);
|
||||
break;
|
||||
case 1:
|
||||
animationIndices.add((int) effectIDs.y);
|
||||
jointWeights.add(weights.y);
|
||||
animationIndices.add(jointWeights.size() - 1);
|
||||
break;
|
||||
case 2:
|
||||
animationIndices.add((int) effectIDs.z);
|
||||
jointWeights.add(weights.z);
|
||||
animationIndices.add(jointWeights.size() - 1);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Float[] positionList = positions.toArray(new Float[0]);
|
||||
Float[] normalList = normals.toArray(new Float[0]);
|
||||
Float[] texCoordList = texCoords.toArray(new Float[0]);
|
||||
Integer[] affectingJointIndices = animationIndices.toArray(new Integer[0]);
|
||||
Float[] jointWeightList = jointWeights.toArray(new Float[0]);
|
||||
Integer[] affectJointCounts = affectCountList.toArray(new Integer[0]);
|
||||
Map<String, Number[]> arrayMap = Maps.newHashMap();
|
||||
Map<MeshPartDefinition, List<VertexBuilder>> meshDefinitions = Maps.newHashMap();
|
||||
|
||||
arrayMap.put("positions", positionList);
|
||||
arrayMap.put("normals", normalList);
|
||||
arrayMap.put("uvs", texCoordList);
|
||||
arrayMap.put("weights", jointWeightList);
|
||||
arrayMap.put("vcounts", affectJointCounts);
|
||||
arrayMap.put("vindices", affectingJointIndices);
|
||||
|
||||
for (Map.Entry<MeshPartDefinition, IntList> e : indices.entrySet()) {
|
||||
meshDefinitions.put(e.getKey(), VertexBuilder.create(e.getValue().toIntArray()));
|
||||
}
|
||||
|
||||
return new SkinnedMesh(arrayMap, meshDefinitions, null, null);
|
||||
}
|
||||
|
||||
public enum State {
|
||||
EMPTY, EQUAL, DIFFERENT
|
||||
}
|
||||
}
|
||||
398
src/main/java/com/tiedup/remake/rig/mesh/SkinnedMesh.java
Normal file
398
src/main/java/com/tiedup/remake/rig/mesh/SkinnedMesh.java
Normal file
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import com.tiedup.remake.rig.asset.JsonAssetLoader;
|
||||
import com.tiedup.remake.rig.mesh.SkinnedMesh.SkinnedMeshPart;
|
||||
import com.tiedup.remake.rig.armature.Armature;
|
||||
import com.tiedup.remake.rig.util.ParseUtil;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec4f;
|
||||
import com.tiedup.remake.rig.render.TiedUpRenderTypes;
|
||||
import yesman.epicfight.client.renderer.shader.compute.ComputeShaderSetup;
|
||||
import yesman.epicfight.client.renderer.shader.compute.loader.ComputeShaderProvider;
|
||||
import yesman.epicfight.config.ClientConfig;
|
||||
import yesman.epicfight.main.EpicFightMod;
|
||||
import yesman.epicfight.main.EpicFightSharedConstants;
|
||||
|
||||
public class SkinnedMesh extends StaticMesh<SkinnedMeshPart> {
|
||||
protected final float[] weights;
|
||||
protected final int[] affectingJointCounts;
|
||||
protected final int[][] affectingWeightIndices;
|
||||
protected final int[][] affectingJointIndices;
|
||||
|
||||
private final int maxJointCount;
|
||||
|
||||
@Nullable
|
||||
private ComputeShaderSetup computerShaderSetup;
|
||||
|
||||
public SkinnedMesh(@Nullable Map<String, Number[]> arrayMap, @Nullable Map<MeshPartDefinition, List<VertexBuilder>> partBuilders, @Nullable SkinnedMesh parent, RenderProperties properties) {
|
||||
super(arrayMap, partBuilders, parent, properties);
|
||||
|
||||
this.weights = parent == null ? ParseUtil.unwrapFloatWrapperArray(arrayMap.get("weights")) : parent.weights;
|
||||
this.affectingJointCounts = parent == null ? ParseUtil.unwrapIntWrapperArray(arrayMap.get("vcounts")) : parent.affectingJointCounts;
|
||||
|
||||
if (parent != null) {
|
||||
this.affectingJointIndices = parent.affectingJointIndices;
|
||||
this.affectingWeightIndices = parent.affectingWeightIndices;
|
||||
} else {
|
||||
int[] vindices = ParseUtil.unwrapIntWrapperArray(arrayMap.get("vindices"));
|
||||
this.affectingJointIndices = new int[this.affectingJointCounts.length][];
|
||||
this.affectingWeightIndices = new int[this.affectingJointCounts.length][];
|
||||
int idx = 0;
|
||||
|
||||
for (int i = 0; i < this.affectingJointCounts.length; i++) {
|
||||
int count = this.affectingJointCounts[i];
|
||||
int[] jointId = new int[count];
|
||||
int[] weights = new int[count];
|
||||
|
||||
for (int j = 0; j < count; j++) {
|
||||
jointId[j] = vindices[idx * 2];
|
||||
weights[j] = vindices[idx * 2 + 1];
|
||||
idx++;
|
||||
}
|
||||
|
||||
this.affectingJointIndices[i] = jointId;
|
||||
this.affectingWeightIndices[i] = weights;
|
||||
}
|
||||
}
|
||||
|
||||
int maxJointId = 0;
|
||||
|
||||
for (int[] i : this.affectingJointIndices) {
|
||||
for (int j : i) {
|
||||
if (maxJointId < j) {
|
||||
maxJointId = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.maxJointCount = maxJointId;
|
||||
|
||||
if (ComputeShaderProvider.supportComputeShader()) {
|
||||
if (RenderSystem.isOnRenderThread()) {
|
||||
this.computerShaderSetup = ComputeShaderProvider.getComputeShaderSetup(this);
|
||||
} else {
|
||||
RenderSystem.recordRenderCall(() -> {
|
||||
this.computerShaderSetup = ComputeShaderProvider.getComputeShaderSetup(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (RenderSystem.isOnRenderThread()) {
|
||||
if (this.computerShaderSetup != null) {
|
||||
this.computerShaderSetup.destroyBuffers();
|
||||
}
|
||||
} else {
|
||||
RenderSystem.recordRenderCall(() -> {
|
||||
if (this.computerShaderSetup != null) {
|
||||
this.computerShaderSetup.destroyBuffers();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, SkinnedMeshPart> createModelPart(Map<MeshPartDefinition, List<VertexBuilder>> partBuilders) {
|
||||
Map<String, SkinnedMeshPart> parts = Maps.newHashMap();
|
||||
|
||||
partBuilders.forEach((partDefinition, vertexBuilder) -> {
|
||||
parts.put(partDefinition.partName(), new SkinnedMeshPart(vertexBuilder, partDefinition.renderProperties(), partDefinition.getModelPartAnimationProvider()));
|
||||
});
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SkinnedMeshPart getOrLogException(Map<String, SkinnedMeshPart> parts, String name) {
|
||||
if (!parts.containsKey(name)) {
|
||||
if (EpicFightSharedConstants.IS_DEV_ENV) {
|
||||
EpicFightMod.LOGGER.debug("Cannot find the mesh part named " + name + " in " + this.getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return parts.get(name);
|
||||
}
|
||||
|
||||
private static final Vec4f TRANSFORM = new Vec4f();
|
||||
private static final Vec4f POS = new Vec4f();
|
||||
private static final Vec4f TOTAL_POS = new Vec4f();
|
||||
|
||||
@Override
|
||||
public void getVertexPosition(int positionIndex, Vector4f dest, @Nullable OpenMatrix4f[] poses) {
|
||||
int index = positionIndex * 3;
|
||||
|
||||
POS.set(this.positions[index], this.positions[index + 1], this.positions[index + 2], 1.0F);
|
||||
TOTAL_POS.set(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
|
||||
for (int i = 0; i < this.affectingJointCounts[positionIndex]; i++) {
|
||||
int jointIndex = this.affectingJointIndices[positionIndex][i];
|
||||
int weightIndex = this.affectingWeightIndices[positionIndex][i];
|
||||
float weight = this.weights[weightIndex];
|
||||
|
||||
Vec4f.add(OpenMatrix4f.transform(poses[jointIndex], POS, TRANSFORM).scale(weight), TOTAL_POS, TOTAL_POS);
|
||||
}
|
||||
|
||||
dest.set(TOTAL_POS.x, TOTAL_POS.y, TOTAL_POS.z, 1.0F);
|
||||
}
|
||||
|
||||
private static final Vec4f NORM = new Vec4f();
|
||||
private static final Vec4f TOTAL_NORM = new Vec4f();
|
||||
|
||||
@Override
|
||||
public void getVertexNormal(int positionIndex, int normalIndex, Vector3f dest, @Nullable OpenMatrix4f[] poses) {
|
||||
int index = normalIndex * 3;
|
||||
NORM.set(this.normals[index], this.normals[index + 1], this.normals[index + 2], 1.0F);
|
||||
TOTAL_NORM.set(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
|
||||
for (int i = 0; i < this.affectingJointCounts[positionIndex]; i++) {
|
||||
int jointIndex = this.affectingJointIndices[positionIndex][i];
|
||||
int weightIndex = this.affectingWeightIndices[positionIndex][i];
|
||||
float weight = this.weights[weightIndex];
|
||||
Vec4f.add(OpenMatrix4f.transform(poses[jointIndex], NORM, TRANSFORM).scale(weight), TOTAL_NORM, TOTAL_NORM);
|
||||
}
|
||||
|
||||
dest.set(TOTAL_NORM.x, TOTAL_NORM.y, TOTAL_NORM.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the model without applying animation
|
||||
*/
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, VertexConsumer bufferbuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay) {
|
||||
for (SkinnedMeshPart part : this.parts.values()) {
|
||||
part.draw(poseStack, bufferbuilder, drawingFunction, packedLight, r, g, b, a, overlay);
|
||||
}
|
||||
}
|
||||
|
||||
protected static final Vector4f POSITION = new Vector4f();
|
||||
protected static final Vector3f NORMAL = new Vector3f();
|
||||
|
||||
/**
|
||||
* Draws the model to vanilla buffer
|
||||
*/
|
||||
@Override
|
||||
public void drawPosed(PoseStack poseStack, VertexConsumer bufferbuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, @Nullable Armature armature, OpenMatrix4f[] poses) {
|
||||
Matrix4f pose = poseStack.last().pose();
|
||||
Matrix3f normal = poseStack.last().normal();
|
||||
|
||||
for (SkinnedMeshPart part : this.parts.values()) {
|
||||
if (!part.isHidden()) {
|
||||
OpenMatrix4f transform = part.getVanillaPartTransform();
|
||||
|
||||
for (int i = 0; i < poses.length; i++) {
|
||||
ComputeShaderSetup.TOTAL_POSES[i].load(poses[i]);
|
||||
|
||||
if (armature != null) {
|
||||
ComputeShaderSetup.TOTAL_POSES[i].mulBack(armature.searchJointById(i).getToOrigin());
|
||||
}
|
||||
|
||||
if (transform != null) {
|
||||
ComputeShaderSetup.TOTAL_POSES[i].mulBack(transform);
|
||||
}
|
||||
|
||||
ComputeShaderSetup.TOTAL_NORMALS[i] = ComputeShaderSetup.TOTAL_POSES[i].removeTranslation();
|
||||
}
|
||||
|
||||
for (VertexBuilder vi : part.getVertices()) {
|
||||
this.getVertexPosition(vi.position, POSITION, ComputeShaderSetup.TOTAL_POSES);
|
||||
this.getVertexNormal(vi.position, vi.normal, NORMAL, ComputeShaderSetup.TOTAL_NORMALS);
|
||||
|
||||
POSITION.mul(pose);
|
||||
NORMAL.mul(normal);
|
||||
|
||||
drawingFunction.draw(bufferbuilder, POSITION.x, POSITION.y, POSITION.z, NORMAL.x, NORMAL.y, NORMAL.z, packedLight, r, g, b, a, this.uvs[vi.uv * 2], this.uvs[vi.uv * 2 + 1], overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the model depending on animation shader option
|
||||
* @param armature give this parameter as null if @param poses already bound origin translation
|
||||
* @param poses
|
||||
*/
|
||||
public void draw(PoseStack poseStack, MultiBufferSource bufferSources, RenderType renderType, int packedLight, float r, float g, float b, float a, int overlay, @Nullable Armature armature, OpenMatrix4f[] poses) {
|
||||
this.draw(poseStack, bufferSources, renderType, Mesh.DrawingFunction.NEW_ENTITY, packedLight, r, g, b, a, overlay, armature, poses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, MultiBufferSource bufferSources, RenderType renderType, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay, @Nullable Armature armature, OpenMatrix4f[] poses) {
|
||||
if (ClientConfig.activateComputeShader && this.computerShaderSetup != null) {
|
||||
this.computerShaderSetup.drawWithShader(this, poseStack, bufferSources, EpicFightRenderTypes.getTriangulated(renderType), packedLight, r, g, b, a, overlay, armature, poses);
|
||||
} else {
|
||||
this.drawPosed(poseStack, bufferSources.getBuffer(EpicFightRenderTypes.getTriangulated(renderType)), drawingFunction, packedLight, r, g, b, a, overlay, armature, poses);
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxJointCount() {
|
||||
return this.maxJointCount;
|
||||
}
|
||||
|
||||
public float[] weights() {
|
||||
return this.weights;
|
||||
}
|
||||
|
||||
public int[] affectingJointCounts() {
|
||||
return this.affectingJointCounts;
|
||||
}
|
||||
|
||||
public int[][] affectingWeightIndices() {
|
||||
return this.affectingWeightIndices;
|
||||
}
|
||||
|
||||
public int[][] affectingJointIndices() {
|
||||
return this.affectingJointIndices;
|
||||
}
|
||||
|
||||
public class SkinnedMeshPart extends MeshPart {
|
||||
private ComputeShaderSetup.MeshPartBuffer partVBO;
|
||||
|
||||
public SkinnedMeshPart(List<VertexBuilder> animatedMeshPartList, @Nullable Mesh.RenderProperties renderProperties, @Nullable Supplier<OpenMatrix4f> vanillaPartTracer) {
|
||||
super(animatedMeshPartList, renderProperties, vanillaPartTracer);
|
||||
}
|
||||
|
||||
public void initVBO(ComputeShaderSetup.MeshPartBuffer partVBO) {
|
||||
this.partVBO = partVBO;
|
||||
}
|
||||
|
||||
public ComputeShaderSetup.MeshPartBuffer getPartVBO() {
|
||||
return this.partVBO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(PoseStack poseStack, VertexConsumer bufferBuilder, Mesh.DrawingFunction drawingFunction, int packedLight, float r, float g, float b, float a, int overlay) {
|
||||
if (this.isHidden()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector4f color = this.getColor(r, g, b, a);
|
||||
Matrix4f pose = poseStack.last().pose();
|
||||
Matrix3f normal = poseStack.last().normal();
|
||||
|
||||
for (VertexBuilder vi : this.getVertices()) {
|
||||
getVertexPosition(vi.position, POSITION);
|
||||
getVertexNormal(vi.normal, NORMAL);
|
||||
POSITION.mul(pose);
|
||||
NORMAL.mul(normal);
|
||||
drawingFunction.draw(bufferBuilder, POSITION.x(), POSITION.y(), POSITION.z(), NORMAL.x(), NORMAL.y(), NORMAL.z(), packedLight, color.x, color.y, color.z, color.w, uvs[vi.uv * 2], uvs[vi.uv * 2 + 1], overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this model as Json format
|
||||
*/
|
||||
public JsonObject toJsonObject() {
|
||||
JsonObject root = new JsonObject();
|
||||
JsonObject vertices = new JsonObject();
|
||||
float[] positions = this.positions.clone();
|
||||
float[] normals = this.normals.clone();
|
||||
|
||||
for (int i = 0; i < positions.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f posVector = new Vec4f(positions[k], positions[k+1], positions[k+2], 1.0F);
|
||||
posVector.transform(JsonAssetLoader.MINECRAFT_TO_BLENDER_COORD);
|
||||
positions[k] = posVector.x;
|
||||
positions[k+1] = posVector.y;
|
||||
positions[k+2] = posVector.z;
|
||||
}
|
||||
|
||||
for (int i = 0; i < normals.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
Vec4f normVector = new Vec4f(normals[k], normals[k+1], normals[k+2], 1.0F);
|
||||
normVector.transform(JsonAssetLoader.MINECRAFT_TO_BLENDER_COORD);
|
||||
normals[k] = normVector.x;
|
||||
normals[k+1] = normVector.y;
|
||||
normals[k+2] = normVector.z;
|
||||
}
|
||||
|
||||
IntList affectingJointAndWeightIndices = new IntArrayList();
|
||||
|
||||
for (int i = 0; i < this.affectingJointCounts.length; i++) {
|
||||
for (int j = 0; j < this.affectingJointCounts[j]; j++) {
|
||||
affectingJointAndWeightIndices.add(this.affectingJointIndices[i][j]);
|
||||
affectingJointAndWeightIndices.add(this.affectingWeightIndices[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
vertices.add("positions", ParseUtil.farrayToJsonObject(positions, 3));
|
||||
vertices.add("uvs", ParseUtil.farrayToJsonObject(this.uvs, 2));
|
||||
vertices.add("normals", ParseUtil.farrayToJsonObject(normals, 3));
|
||||
vertices.add("vcounts", ParseUtil.iarrayToJsonObject(this.affectingJointCounts, 1));
|
||||
vertices.add("weights", ParseUtil.farrayToJsonObject(this.weights, 1));
|
||||
vertices.add("vindices", ParseUtil.iarrayToJsonObject(affectingJointAndWeightIndices.toIntArray(), 1));
|
||||
|
||||
if (!this.parts.isEmpty()) {
|
||||
JsonObject parts = new JsonObject();
|
||||
|
||||
for (Map.Entry<String, SkinnedMeshPart> partEntry : this.parts.entrySet()) {
|
||||
IntList indicesArray = new IntArrayList();
|
||||
|
||||
for (VertexBuilder vertexIndicator : partEntry.getValue().getVertices()) {
|
||||
indicesArray.add(vertexIndicator.position);
|
||||
indicesArray.add(vertexIndicator.uv);
|
||||
indicesArray.add(vertexIndicator.normal);
|
||||
}
|
||||
|
||||
parts.add(partEntry.getKey(), ParseUtil.iarrayToJsonObject(indicesArray.toIntArray(), 3));
|
||||
}
|
||||
|
||||
vertices.add("parts", parts);
|
||||
} else {
|
||||
int i = 0;
|
||||
int[] indices = new int[this.vertexCount * 3];
|
||||
|
||||
for (SkinnedMeshPart part : this.parts.values()) {
|
||||
for (VertexBuilder vertexIndicator : part.getVertices()) {
|
||||
indices[i * 3] = vertexIndicator.position;
|
||||
indices[i * 3 + 1] = vertexIndicator.uv;
|
||||
indices[i * 3 + 2] = vertexIndicator.normal;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
vertices.add("indices", ParseUtil.iarrayToJsonObject(indices, 3));
|
||||
}
|
||||
|
||||
root.add("vertices", vertices);
|
||||
|
||||
if (this.renderProperties != null) {
|
||||
JsonObject renderProperties = new JsonObject();
|
||||
renderProperties.addProperty("texture_path", this.renderProperties.customTexturePath().toString());
|
||||
renderProperties.addProperty("transparent", this.renderProperties.isTransparent());
|
||||
root.add("render_properties", renderProperties);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.tiedup.remake.rig.mesh.Meshes.MeshAccessor;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulatable;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject.ClothPart.ConstraintType;
|
||||
import yesman.epicfight.api.physics.SimulationProvider;
|
||||
|
||||
public interface SoftBodyTranslatable extends SimulationProvider<ClothSimulatable, ClothSimulator.ClothObject, ClothSimulator.ClothObjectBuilder, SoftBodyTranslatable> {
|
||||
public static final List<ClothSimulatable> TRACKING_SIMULATION_SUBJECTS = Lists.newArrayList();
|
||||
|
||||
default boolean canStartSoftBodySimulation() {
|
||||
return this.getSoftBodySimulationInfo() != null;
|
||||
}
|
||||
|
||||
void putSoftBodySimulationInfo(Map<String, ClothSimulationInfo> sofyBodySimulationInfo);
|
||||
|
||||
Map<String, ClothSimulationInfo> getSoftBodySimulationInfo();
|
||||
|
||||
default StaticMesh<?> getOriginalMesh() {
|
||||
if (this instanceof MeshAccessor<?> meshAccessor) {
|
||||
return (StaticMesh<?>)meshAccessor.get();
|
||||
} else {
|
||||
return (StaticMesh<?>)this;
|
||||
}
|
||||
}
|
||||
|
||||
public static record ClothSimulationInfo(float particleMass, float selfCollision, List<int[]> constraints, ConstraintType[] constraintTypes, float[] compliances, int[] particles, float[] weights, float[] rootDistance, int[] normalOffsetMapping) {
|
||||
}
|
||||
}
|
||||
150
src/main/java/com/tiedup/remake/rig/mesh/StaticMesh.java
Normal file
150
src/main/java/com/tiedup/remake/rig/mesh/StaticMesh.java
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulatable;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject;
|
||||
import com.tiedup.remake.rig.util.ParseUtil;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
|
||||
public abstract class StaticMesh<P extends MeshPart> implements Mesh, SoftBodyTranslatable {
|
||||
protected final float[] positions;
|
||||
protected final float[] normals;
|
||||
protected final float[] uvs;
|
||||
|
||||
protected final int vertexCount;
|
||||
protected final Mesh.RenderProperties renderProperties;
|
||||
protected final Map<String, P> parts;
|
||||
protected final List<Vec3> normalList;
|
||||
|
||||
private Map<String, ClothSimulationInfo> softBodySimulationInfo;
|
||||
|
||||
/**
|
||||
* @param arrayMap Null if parent is not null
|
||||
* @param partBuilders Null if parent is not null
|
||||
* @param parent Null if arrayMap and parts are not null
|
||||
* @param renderProperties
|
||||
*/
|
||||
public StaticMesh(@Nullable Map<String, Number[]> arrayMap, @Nullable Map<MeshPartDefinition, List<VertexBuilder>> partBuilders, @Nullable StaticMesh<P> parent, Mesh.RenderProperties renderProperties) {
|
||||
this.positions = (parent == null) ? ParseUtil.unwrapFloatWrapperArray(arrayMap.get("positions")) : parent.positions;
|
||||
this.normals = (parent == null) ? ParseUtil.unwrapFloatWrapperArray(arrayMap.get("normals")) : parent.normals;
|
||||
this.uvs = (parent == null) ? ParseUtil.unwrapFloatWrapperArray(arrayMap.get("uvs")) : parent.uvs;
|
||||
this.parts = (parent == null) ? this.createModelPart(partBuilders) : parent.parts;
|
||||
this.renderProperties = renderProperties;
|
||||
|
||||
int totalV = 0;
|
||||
|
||||
for (MeshPart modelpart : this.parts.values()) {
|
||||
totalV += modelpart.getVertices().size();
|
||||
}
|
||||
|
||||
this.vertexCount = totalV;
|
||||
|
||||
if (this.canStartSoftBodySimulation()) {
|
||||
ImmutableList.Builder<Vec3> normalBuilder = ImmutableList.builder();
|
||||
|
||||
for (int i = 0; i < this.normals.length / 3; i++) {
|
||||
normalBuilder.add(new Vec3(this.normals[i * 3], this.normals[i * 3 + 1], this.normals[i * 3 + 2]));
|
||||
}
|
||||
|
||||
this.normalList = normalBuilder.build();
|
||||
} else {
|
||||
this.normalList = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Map<String, P> createModelPart(Map<MeshPartDefinition, List<VertexBuilder>> partBuilders);
|
||||
protected abstract P getOrLogException(Map<String, P> parts, String name);
|
||||
|
||||
public boolean hasPart(String part) {
|
||||
return this.parts.containsKey(part);
|
||||
}
|
||||
|
||||
public MeshPart getPart(String part) {
|
||||
return this.parts.get(part);
|
||||
}
|
||||
|
||||
public Collection<P> getAllParts() {
|
||||
return this.parts.values();
|
||||
}
|
||||
|
||||
public Set<Map.Entry<String, P>> getPartEntry() {
|
||||
return this.parts.entrySet();
|
||||
}
|
||||
|
||||
public void putSoftBodySimulationInfo(Map<String, ClothSimulationInfo> sofyBodySimulationInfo) {
|
||||
this.softBodySimulationInfo = sofyBodySimulationInfo;
|
||||
}
|
||||
|
||||
public Map<String, ClothSimulationInfo> getSoftBodySimulationInfo() {
|
||||
return this.softBodySimulationInfo;
|
||||
}
|
||||
|
||||
public Mesh.RenderProperties getRenderProperties() {
|
||||
return this.renderProperties;
|
||||
}
|
||||
|
||||
public void getVertexPosition(int positionIndex, Vector4f dest) {
|
||||
int index = positionIndex * 3;
|
||||
dest.set(this.positions[index], this.positions[index + 1], this.positions[index + 2], 1.0F);
|
||||
}
|
||||
|
||||
public void getVertexNormal(int normalIndex, Vector3f dest) {
|
||||
int index = normalIndex * 3;
|
||||
dest.set(this.normals[index], this.normals[index + 1], this.normals[index + 2]);
|
||||
}
|
||||
|
||||
public void getVertexPosition(int positionIndex, Vector4f dest, @Nullable OpenMatrix4f[] poses) {
|
||||
this.getVertexPosition(positionIndex, dest);
|
||||
}
|
||||
|
||||
public void getVertexNormal(int positionIndex, int normalIndex, Vector3f dest, @Nullable OpenMatrix4f[] poses) {
|
||||
this.getVertexNormal(normalIndex, dest);
|
||||
}
|
||||
|
||||
public float[] positions() {
|
||||
return this.positions;
|
||||
}
|
||||
|
||||
public float[] normals() {
|
||||
return this.normals;
|
||||
}
|
||||
|
||||
public float[] uvs() {
|
||||
return this.uvs;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<Vec3> normalList() {
|
||||
return this.normalList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
this.parts.values().forEach((part) -> part.setHidden(false));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public ClothSimulator.ClothObject createSimulationData(@Nullable SoftBodyTranslatable provider, ClothSimulatable simObject, ClothSimulator.ClothObjectBuilder simBuilder) {
|
||||
return new ClothObject(simBuilder, provider == null ? this : provider, (Map<String, MeshPart>)this.parts, this.positions);
|
||||
}
|
||||
}
|
||||
62
src/main/java/com/tiedup/remake/rig/mesh/VertexBuilder.java
Normal file
62
src/main/java/com/tiedup/remake/rig/mesh/VertexBuilder.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.mesh;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
|
||||
|
||||
// Vertex Indices
|
||||
public class VertexBuilder {
|
||||
public static List<VertexBuilder> create(int[] drawingIndices) {
|
||||
List<VertexBuilder> vertexIndicators = Lists.newArrayList();
|
||||
|
||||
for (int i = 0; i < drawingIndices.length / 3; i++) {
|
||||
int k = i * 3;
|
||||
int position = drawingIndices[k];
|
||||
int uv = drawingIndices[k + 1];
|
||||
int normal = drawingIndices[k + 2];
|
||||
VertexBuilder vi = new VertexBuilder(position, uv, normal);
|
||||
vertexIndicators.add(vi);
|
||||
}
|
||||
|
||||
return vertexIndicators;
|
||||
}
|
||||
|
||||
public final int position;
|
||||
public final int uv;
|
||||
public final int normal;
|
||||
|
||||
public VertexBuilder(int position, int uv, int normal) {
|
||||
this.position = position;
|
||||
this.uv = uv;
|
||||
this.normal = normal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof VertexBuilder vb) {
|
||||
return this.position == vb.position && this.uv == vb.uv && this.normal == vb.normal;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
|
||||
result = prime * result + this.position;
|
||||
result = prime * result + this.uv;
|
||||
result = prime * result + this.normal;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
519
src/main/java/com/tiedup/remake/rig/patch/ClientPlayerPatch.java
Normal file
519
src/main/java/com/tiedup/remake/rig/patch/ClientPlayerPatch.java
Normal file
@@ -0,0 +1,519 @@
|
||||
/*
|
||||
* 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.patch;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.PlayerRideableJumping;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.UseAnim;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.entity.EntityJoinLevelEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingEvent;
|
||||
import com.tiedup.remake.rig.anim.Animator;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
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.property.AnimationProperty.StaticAnimationProperty;
|
||||
import com.tiedup.remake.rig.anim.types.ActionAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.DynamicAnimation;
|
||||
import com.tiedup.remake.rig.anim.client.ClientAnimator;
|
||||
import com.tiedup.remake.rig.anim.client.Layer;
|
||||
import com.tiedup.remake.rig.event.RenderEpicFightPlayerEvent;
|
||||
import com.tiedup.remake.rig.event.UpdatePlayerMotionEvent;
|
||||
import yesman.epicfight.client.online.EpicSkins;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulatable;
|
||||
import com.tiedup.remake.rig.cloth.ClothSimulator;
|
||||
import yesman.epicfight.api.physics.PhysicsSimulator;
|
||||
import yesman.epicfight.api.physics.SimulationTypes;
|
||||
import com.tiedup.remake.rig.util.EntitySnapshot;
|
||||
import com.tiedup.remake.rig.math.MathUtils;
|
||||
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
import yesman.epicfight.config.ClientConfig;
|
||||
import yesman.epicfight.gameasset.EpicFightSounds;
|
||||
import yesman.epicfight.network.EntityPairingPacketTypes;
|
||||
import yesman.epicfight.network.server.SPEntityPairingPacket;
|
||||
import yesman.epicfight.particle.EpicFightParticles;
|
||||
import com.tiedup.remake.rig.patch.EntityDecorations;
|
||||
import com.tiedup.remake.rig.patch.EntityDecorations.RenderAttributeModifier;
|
||||
import com.tiedup.remake.rig.patch.PlayerPatch;
|
||||
import com.tiedup.remake.rig.patch.item.CapabilityItem;
|
||||
import yesman.epicfight.world.entity.eventlistener.PlayerEventListener.EventType;
|
||||
|
||||
public class AbstractClientPlayerPatch<T extends AbstractClientPlayer> extends PlayerPatch<T> implements ClothSimulatable {
|
||||
private Item prevHeldItem;
|
||||
private Item prevHeldItemOffHand;
|
||||
protected EpicSkins epicSkinsInformation;
|
||||
|
||||
@Override
|
||||
public void onJoinWorld(T entity, EntityJoinLevelEvent event) {
|
||||
super.onJoinWorld(entity, event);
|
||||
|
||||
this.prevHeldItem = Items.AIR;
|
||||
this.prevHeldItemOffHand = Items.AIR;
|
||||
|
||||
EpicSkins.initEpicSkins(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMotion(boolean considerInaction) {
|
||||
if (this.original.getHealth() <= 0.0F) {
|
||||
currentLivingMotion = LivingMotions.DEATH;
|
||||
} else if (!this.state.updateLivingMotion() && considerInaction) {
|
||||
currentLivingMotion = LivingMotions.INACTION;
|
||||
} else {
|
||||
if (original.isFallFlying() || original.isAutoSpinAttack()) {
|
||||
currentLivingMotion = LivingMotions.FLY;
|
||||
} else if (original.getVehicle() != null) {
|
||||
if (original.getVehicle() instanceof PlayerRideableJumping)
|
||||
currentLivingMotion = LivingMotions.MOUNT;
|
||||
else
|
||||
currentLivingMotion = LivingMotions.SIT;
|
||||
} else if (original.isVisuallySwimming()) {
|
||||
currentLivingMotion = LivingMotions.SWIM;
|
||||
} else if (original.isSleeping()) {
|
||||
currentLivingMotion = LivingMotions.SLEEP;
|
||||
} else if (!original.onGround() && original.onClimbable()) {
|
||||
currentLivingMotion = LivingMotions.CLIMB;
|
||||
} else if (!original.getAbilities().flying) {
|
||||
ClientAnimator animator = this.getClientAnimator();
|
||||
|
||||
if (original.isUnderWater() && (original.getY() - this.yo) < -0.005)
|
||||
currentLivingMotion = LivingMotions.FLOAT;
|
||||
else if (original.getY() - this.yo < -0.4F || this.isAirborneState())
|
||||
currentLivingMotion = LivingMotions.FALL;
|
||||
else if (this.isMoving()) {
|
||||
if (original.isCrouching())
|
||||
currentLivingMotion = LivingMotions.SNEAK;
|
||||
else if (original.isSprinting())
|
||||
currentLivingMotion = LivingMotions.RUN;
|
||||
else
|
||||
currentLivingMotion = LivingMotions.WALK;
|
||||
|
||||
animator.baseLayer.animationPlayer.setReversed(this.dz < 0);
|
||||
|
||||
} else {
|
||||
animator.baseLayer.animationPlayer.setReversed(false);
|
||||
|
||||
if (original.isCrouching())
|
||||
currentLivingMotion = LivingMotions.KNEEL;
|
||||
else
|
||||
currentLivingMotion = LivingMotions.IDLE;
|
||||
}
|
||||
} else {
|
||||
if (this.isMoving())
|
||||
currentLivingMotion = LivingMotions.CREATIVE_FLY;
|
||||
else
|
||||
currentLivingMotion = LivingMotions.CREATIVE_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePlayerMotionEvent.BaseLayer baseLayerEvent = new UpdatePlayerMotionEvent.BaseLayer(this, this.currentLivingMotion, !this.state.updateLivingMotion() && considerInaction);
|
||||
this.eventListeners.triggerEvents(EventType.UPDATE_BASE_LIVING_MOTION_EVENT, baseLayerEvent);
|
||||
MinecraftForge.EVENT_BUS.post(baseLayerEvent);
|
||||
|
||||
this.currentLivingMotion = baseLayerEvent.getMotion();
|
||||
|
||||
if (!this.state.updateLivingMotion() && considerInaction) {
|
||||
this.currentCompositeMotion = LivingMotions.NONE;
|
||||
} else {
|
||||
CapabilityItem mainhandItemCap = this.getHoldingItemCapability(InteractionHand.MAIN_HAND);
|
||||
CapabilityItem offhandItemCap = this.getHoldingItemCapability(InteractionHand.OFF_HAND);
|
||||
LivingMotion customLivingMotion = mainhandItemCap.getLivingMotion(this, InteractionHand.MAIN_HAND);
|
||||
|
||||
if (customLivingMotion == null) customLivingMotion = offhandItemCap.getLivingMotion(this, InteractionHand.OFF_HAND);
|
||||
|
||||
// When item capabilities has custom living motion
|
||||
if (customLivingMotion != null)
|
||||
currentCompositeMotion = customLivingMotion;
|
||||
else if (this.original.isUsingItem()) {
|
||||
UseAnim useAnim = this.original.getUseItem().getUseAnimation();
|
||||
if (useAnim == UseAnim.BLOCK)
|
||||
currentCompositeMotion = LivingMotions.BLOCK_SHIELD;
|
||||
else if (useAnim == UseAnim.CROSSBOW)
|
||||
currentCompositeMotion = LivingMotions.RELOAD;
|
||||
else if (useAnim == UseAnim.DRINK)
|
||||
currentCompositeMotion = LivingMotions.DRINK;
|
||||
else if (useAnim == UseAnim.EAT)
|
||||
currentCompositeMotion = LivingMotions.EAT;
|
||||
else if (useAnim == UseAnim.SPYGLASS)
|
||||
currentCompositeMotion = LivingMotions.SPECTATE;
|
||||
else
|
||||
currentCompositeMotion = currentLivingMotion;
|
||||
} else {
|
||||
if (this.getClientAnimator().getCompositeLayer(Layer.Priority.MIDDLE).animationPlayer.getRealAnimation().get().isReboundAnimation())
|
||||
currentCompositeMotion = LivingMotions.SHOT;
|
||||
else if (this.original.swinging && this.original.getSleepingPos().isEmpty())
|
||||
currentCompositeMotion = LivingMotions.DIGGING;
|
||||
else
|
||||
currentCompositeMotion = currentLivingMotion;
|
||||
}
|
||||
|
||||
UpdatePlayerMotionEvent.CompositeLayer compositeLayerEvent = new UpdatePlayerMotionEvent.CompositeLayer(this, this.currentCompositeMotion);
|
||||
this.eventListeners.triggerEvents(EventType.UPDATE_COMPOSITE_LIVING_MOTION_EVENT, compositeLayerEvent);
|
||||
MinecraftForge.EVENT_BUS.post(compositeLayerEvent);
|
||||
|
||||
this.currentCompositeMotion = compositeLayerEvent.getMotion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOldPosUpdate() {
|
||||
this.modelYRotO2 = this.modelYRotO;
|
||||
this.xPosO2 = (float)this.original.xOld;
|
||||
this.yPosO2 = (float)this.original.yOld;
|
||||
this.zPosO2 = (float)this.original.zOld;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clientTick(LivingEvent.LivingTickEvent event) {
|
||||
this.xCloakO2 = this.original.xCloakO;
|
||||
this.yCloakO2 = this.original.yCloakO;
|
||||
this.zCloakO2 = this.original.zCloakO;
|
||||
|
||||
super.clientTick(event);
|
||||
|
||||
if (!this.getEntityState().updateLivingMotion()) {
|
||||
this.original.yBodyRot = this.original.yHeadRot;
|
||||
}
|
||||
|
||||
boolean isMainHandChanged = this.prevHeldItem != this.original.getInventory().getSelected().getItem();
|
||||
boolean isOffHandChanged = this.prevHeldItemOffHand != this.original.getInventory().offhand.get(0).getItem();
|
||||
|
||||
if (isMainHandChanged || isOffHandChanged) {
|
||||
this.updateHeldItem(this.getHoldingItemCapability(InteractionHand.MAIN_HAND), this.getHoldingItemCapability(InteractionHand.OFF_HAND));
|
||||
|
||||
if (isMainHandChanged) {
|
||||
this.prevHeldItem = this.original.getInventory().getSelected().getItem();
|
||||
}
|
||||
|
||||
if (isOffHandChanged) {
|
||||
this.prevHeldItemOffHand = this.original.getInventory().offhand.get(0).getItem();
|
||||
}
|
||||
}
|
||||
|
||||
/** {@link LivingDeathEvent} never fired for client players **/
|
||||
if (this.original.deathTime == 1) {
|
||||
this.getClientAnimator().playDeathAnimation();
|
||||
}
|
||||
|
||||
this.clothSimulator.tick(this);
|
||||
}
|
||||
|
||||
protected boolean isMoving() {
|
||||
return Math.abs(this.dx) > 0.01F || Math.abs(this.dz) > 0.01F;
|
||||
}
|
||||
|
||||
public void updateHeldItem(CapabilityItem mainHandCap, CapabilityItem offHandCap) {
|
||||
this.cancelItemUse();
|
||||
|
||||
this.getClientAnimator().iterAllLayers((layer) -> {
|
||||
if (layer.isOff()) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.animationPlayer.getRealAnimation().get().getProperty(StaticAnimationProperty.ON_ITEM_CHANGE_EVENT).ifPresent((event) -> {
|
||||
event.params(mainHandCap, offHandCap);
|
||||
event.execute(this, layer.animationPlayer.getRealAnimation(), layer.animationPlayer.getPrevElapsedTime(), layer.animationPlayer.getElapsedTime());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entityPairing(SPEntityPairingPacket packet) {
|
||||
super.entityPairing(packet);
|
||||
|
||||
if (packet.getPairingPacketType().is(EntityPairingPacketTypes.class)) {
|
||||
switch (packet.getPairingPacketType().toEnum(EntityPairingPacketTypes.class)) {
|
||||
case TECHNICIAN_ACTIVATED -> {
|
||||
this.original.level().addParticle(EpicFightParticles.WHITE_AFTERIMAGE.get(), this.original.getX(), this.original.getY(), this.original.getZ(), Double.longBitsToDouble(this.original.getId()), 0, 0);
|
||||
}
|
||||
case ADRENALINE_ACTIVATED -> {
|
||||
if (this.original.isLocalPlayer()) {
|
||||
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(EpicFightSounds.ADRENALINE.get(), 1.0F, 1.0F));
|
||||
} else {
|
||||
this.original.playSound(EpicFightSounds.ADRENALINE.get());
|
||||
}
|
||||
|
||||
this.original.level().addParticle(EpicFightParticles.ADRENALINE_PLAYER_BEATING.get(), this.original.getX(), this.original.getY(), this.original.getZ(), Double.longBitsToDouble(this.original.getId()), 0, 0);
|
||||
}
|
||||
case EMERGENCY_ESCAPE_ACTIVATED -> {
|
||||
float yRot = packet.getBuffer().readFloat();
|
||||
this.original.level().addParticle(EpicFightParticles.AIR_BURST.get(), this.original.getX(), this.original.getY() + this.original.getBbHeight() * 0.5F, this.original.getZ(), 90.0F, yRot, 0);
|
||||
|
||||
this.entityDecorations.addColorModifier(EntityDecorations.EMERGENCY_ESCAPE_TRANSPARENCY_MODIFIER, new RenderAttributeModifier<> () {
|
||||
private int tickCount;
|
||||
|
||||
@Override
|
||||
public void modifyValue(Vector4f val, float partialTick) {
|
||||
val.w = (float)Math.pow((this.tickCount + partialTick) / 6.0D, 2.0D) - 0.4F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRemove() {
|
||||
return this.tickCount > 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
++this.tickCount;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overrideRender() {
|
||||
RenderEpicFightPlayerEvent renderepicfightplayerevent = new RenderEpicFightPlayerEvent(this, !ClientConfig.enableOriginalModel || this.isEpicFightMode());
|
||||
MinecraftForge.EVENT_BUS.post(renderepicfightplayerevent);
|
||||
return renderepicfightplayerevent.getShouldRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldMoveOnCurrentSide(ActionAnimation actionAnimation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poseTick(DynamicAnimation animation, Pose pose, float elapsedTime, float partialTick) {
|
||||
if (pose.hasTransform("Head") && this.armature.hasJoint("Head")) {
|
||||
if (animation.doesHeadRotFollowEntityHead()) {
|
||||
float headRelativeRot = Mth.rotLerp(partialTick, Mth.wrapDegrees(this.modelYRotO - this.original.yHeadRotO), Mth.wrapDegrees(this.modelYRot - this.original.yHeadRot));
|
||||
OpenMatrix4f headTransform = this.armature.getBoundTransformFor(pose, this.armature.searchJointByName("Head"));
|
||||
OpenMatrix4f toOriginalRotation = headTransform.removeScale().removeTranslation().invert();
|
||||
Vec3f xAxis = OpenMatrix4f.transform3v(toOriginalRotation, Vec3f.X_AXIS, null);
|
||||
Vec3f yAxis = OpenMatrix4f.transform3v(toOriginalRotation, Vec3f.Y_AXIS, null);
|
||||
OpenMatrix4f headRotation = OpenMatrix4f.createRotatorDeg(headRelativeRot, yAxis).rotateDeg(-Mth.rotLerp(partialTick, this.original.xRotO, this.original.getXRot()), xAxis);
|
||||
pose.orElseEmpty("Head").frontResult(JointTransform.fromMatrix(headRotation), OpenMatrix4f::mul);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenMatrix4f getModelMatrix(float partialTick) {
|
||||
if (this.original.isAutoSpinAttack()) {
|
||||
OpenMatrix4f mat = MathUtils.getModelMatrixIntegral(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0, 0, 0, 0, partialTick, PLAYER_SCALE, PLAYER_SCALE, PLAYER_SCALE);
|
||||
float yRot = MathUtils.lerpBetween(this.original.yRotO, this.original.getYRot(), partialTick);
|
||||
float xRot = MathUtils.lerpBetween(this.original.xRotO, this.original.getXRot(), partialTick);
|
||||
|
||||
mat.rotateDeg(-yRot, Vec3f.Y_AXIS)
|
||||
.rotateDeg(-xRot, Vec3f.X_AXIS)
|
||||
.rotateDeg((this.original.tickCount + partialTick) * -55.0F, Vec3f.Z_AXIS)
|
||||
.translate(0F, -0.39F, 0F);
|
||||
|
||||
return mat;
|
||||
} else if (this.original.isFallFlying()) {
|
||||
OpenMatrix4f mat = MathUtils.getModelMatrixIntegral(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0, 0, 0, 0, partialTick, PLAYER_SCALE, PLAYER_SCALE, PLAYER_SCALE);
|
||||
float f1 = (float)this.original.getFallFlyingTicks() + partialTick;
|
||||
float f2 = Mth.clamp(f1 * f1 / 100.0F, 0.0F, 1.0F);
|
||||
|
||||
mat.rotateDeg(-Mth.rotLerp(partialTick, this.original.yBodyRotO, this.original.yBodyRot), Vec3f.Y_AXIS).rotateDeg(f2 * (-this.original.getXRot()), Vec3f.X_AXIS);
|
||||
|
||||
Vec3 vec3d = this.original.getViewVector(partialTick);
|
||||
Vec3 vec3d1 = this.original.getDeltaMovementLerped(partialTick);
|
||||
double d0 = vec3d1.horizontalDistanceSqr();
|
||||
double d1 = vec3d.horizontalDistanceSqr();
|
||||
|
||||
if (d0 > 0.0D && d1 > 0.0D) {
|
||||
double d2 = (vec3d1.x * vec3d.x + vec3d1.z * vec3d.z) / (Math.sqrt(d0) * Math.sqrt(d1));
|
||||
double d3 = vec3d1.x * vec3d.z - vec3d1.z * vec3d.x;
|
||||
mat.rotate((float)-((Math.signum(d3) * Math.acos(d2))), Vec3f.Z_AXIS);
|
||||
}
|
||||
|
||||
return mat;
|
||||
|
||||
} else if (this.original.isSleeping()) {
|
||||
BlockState blockstate = this.original.getFeetBlockState();
|
||||
float yRot = 0.0F;
|
||||
|
||||
if (blockstate.isBed(this.original.level(), this.original.getSleepingPos().orElse(null), this.original)) {
|
||||
if (blockstate.hasProperty(BlockStateProperties.HORIZONTAL_FACING)) {
|
||||
switch(blockstate.getValue(BlockStateProperties.HORIZONTAL_FACING)) {
|
||||
case EAST:
|
||||
yRot = 90.0F;
|
||||
break;
|
||||
case WEST:
|
||||
yRot = -90.0F;
|
||||
break;
|
||||
case SOUTH:
|
||||
yRot = 180.0F;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MathUtils.getModelMatrixIntegral(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, yRot, yRot, 0, PLAYER_SCALE, PLAYER_SCALE, PLAYER_SCALE);
|
||||
} else {
|
||||
float yRotO;
|
||||
float yRot;
|
||||
float xRotO = 0;
|
||||
float xRot = 0;
|
||||
|
||||
if (this.original.getVehicle() instanceof LivingEntity ridingEntity) {
|
||||
yRotO = ridingEntity.yBodyRotO;
|
||||
yRot = ridingEntity.yBodyRot;
|
||||
} else {
|
||||
yRotO = this.modelYRotO;
|
||||
yRot = this.modelYRot;
|
||||
}
|
||||
|
||||
if (!this.getEntityState().inaction() && this.original.getPose() == net.minecraft.world.entity.Pose.SWIMMING) {
|
||||
float f = this.original.getSwimAmount(partialTick);
|
||||
float f3 = this.original.isInWater() ? this.original.getXRot() : 0;
|
||||
float f4 = Mth.lerp(f, 0.0F, f3);
|
||||
xRotO = f4;
|
||||
xRot = f4;
|
||||
}
|
||||
|
||||
return MathUtils.getModelMatrixIntegral(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, xRotO, xRot, yRotO, yRot, partialTick, PLAYER_SCALE, PLAYER_SCALE, PLAYER_SCALE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setEpicSkinsInformation(EpicSkins epicSkinsInformation) {
|
||||
this.epicSkinsInformation = epicSkinsInformation;
|
||||
}
|
||||
|
||||
public EpicSkins getEpicSkinsInformation() {
|
||||
return this.epicSkinsInformation;
|
||||
}
|
||||
|
||||
public boolean isEpicSkinsLoaded() {
|
||||
return this.epicSkinsInformation != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntitySnapshot<?> captureEntitySnapshot() {
|
||||
return EntitySnapshot.capturePlayer(this);
|
||||
}
|
||||
|
||||
private final ClothSimulator clothSimulator = new ClothSimulator();
|
||||
public float modelYRotO2;
|
||||
public double xPosO2;
|
||||
public double yPosO2;
|
||||
public double zPosO2;
|
||||
public double xCloakO2;
|
||||
public double yCloakO2;
|
||||
public double zCloakO2;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <SIM extends PhysicsSimulator<?, ?, ?, ?, ?>> Optional<SIM> getSimulator(SimulationTypes<?, ?, ?, ?, ?, SIM> simulationType) {
|
||||
if (simulationType == SimulationTypes.CLOTH) {
|
||||
return Optional.of((SIM)this.clothSimulator);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClothSimulator getClothSimulator() {
|
||||
return this.clothSimulator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 getAccurateCloakLocation(float partialFrame) {
|
||||
if (partialFrame < 0.0F) {
|
||||
partialFrame = 1.0F - partialFrame;
|
||||
|
||||
double x = Mth.lerp((double)partialFrame, this.xCloakO2, this.original.xCloakO) - Mth.lerp((double)partialFrame, this.xPosO2, this.original.xo);
|
||||
double y = Mth.lerp((double)partialFrame, this.yCloakO2, this.original.yCloakO) - Mth.lerp((double)partialFrame, this.yPosO2, this.original.yo);
|
||||
double z = Mth.lerp((double)partialFrame, this.zCloakO2, this.original.zCloakO) - Mth.lerp((double)partialFrame, this.zPosO2, this.original.zo);
|
||||
|
||||
return new Vec3(x, y, z);
|
||||
} else {
|
||||
double x = Mth.lerp((double)partialFrame, this.original.xCloakO, this.original.xCloak) - Mth.lerp((double)partialFrame, this.original.xo, this.original.getX());
|
||||
double y = Mth.lerp((double)partialFrame, this.original.yCloakO, this.original.yCloak) - Mth.lerp((double)partialFrame, this.original.yo, this.original.getY());
|
||||
double z = Mth.lerp((double)partialFrame, this.original.zCloakO, this.original.zCloak) - Mth.lerp((double)partialFrame, this.original.zo, this.original.getZ());
|
||||
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 getAccuratePartialLocation(float partialFrame) {
|
||||
if (partialFrame < 0.0F) {
|
||||
partialFrame = 1.0F + partialFrame;
|
||||
|
||||
double x = Mth.lerp((double)partialFrame, this.xPosO2, this.original.xOld);
|
||||
double y = Mth.lerp((double)partialFrame, this.yPosO2, this.original.yOld);
|
||||
double z = Mth.lerp((double)partialFrame, this.zPosO2, this.original.zOld);
|
||||
|
||||
return new Vec3(x, y, z);
|
||||
} else {
|
||||
double x = Mth.lerp((double)partialFrame, this.original.xOld, this.original.getX());
|
||||
double y = Mth.lerp((double)partialFrame, this.original.yOld, this.original.getY());
|
||||
double z = Mth.lerp((double)partialFrame, this.original.zOld, this.original.getZ());
|
||||
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 getObjectVelocity() {
|
||||
return new Vec3(this.original.getX() - this.original.xOld, this.original.getY() - this.original.yOld, this.original.getZ() - this.original.zOld);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAccurateYRot(float partialFrame) {
|
||||
if (partialFrame < 0.0F) {
|
||||
partialFrame = 1.0F + partialFrame;
|
||||
|
||||
return Mth.rotLerp(partialFrame, this.modelYRotO2, this.getYRotO());
|
||||
} else {
|
||||
return Mth.rotLerp(partialFrame, this.getYRotO(), this.getYRot());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYRotDelta(float partialFrame) {
|
||||
if (partialFrame < 0.0F) {
|
||||
partialFrame = 1.0F + partialFrame;
|
||||
|
||||
return Mth.rotLerp(partialFrame, this.modelYRotO2, this.getYRotO()) - this.modelYRotO2;
|
||||
} else {
|
||||
return Mth.rotLerp(partialFrame, this.getYRotO(), this.getYRot()) - this.getYRotO();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean invalid() {
|
||||
return this.original.isRemoved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getScale() {
|
||||
return PLAYER_SCALE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animator getSimulatableAnimator() {
|
||||
return this.animator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGravity() {
|
||||
return this.getOriginal().isUnderWater() ? 0.98F : 9.8F;
|
||||
}
|
||||
}
|
||||
579
src/main/java/com/tiedup/remake/rig/patch/LocalPlayerPatch.java
Normal file
579
src/main/java/com/tiedup/remake/rig/patch/LocalPlayerPatch.java
Normal file
@@ -0,0 +1,579 @@
|
||||
/*
|
||||
* 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.patch;
|
||||
|
||||
import net.minecraft.client.CameraType;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
|
||||
import net.minecraftforge.entity.PartEntity;
|
||||
import net.minecraftforge.event.entity.EntityJoinLevelEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingEvent;
|
||||
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.types.ActionAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.AttackAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.DirectStaticAnimation;
|
||||
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
||||
import com.tiedup.remake.rig.asset.AssetAccessor;
|
||||
import com.tiedup.remake.rig.anim.client.AnimationSubFileReader;
|
||||
import com.tiedup.remake.rig.anim.client.AnimationSubFileReader.PovSettings;
|
||||
import com.tiedup.remake.rig.anim.client.AnimationSubFileReader.PovSettings.ViewLimit;
|
||||
import com.tiedup.remake.rig.anim.client.Layer;
|
||||
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
|
||||
import yesman.epicfight.api.client.camera.EpicFightCameraAPI;
|
||||
import yesman.epicfight.api.client.input.InputManager;
|
||||
import yesman.epicfight.api.client.input.action.MinecraftInputAction;
|
||||
import com.tiedup.remake.rig.math.MathUtils;
|
||||
import yesman.epicfight.client.ClientEngine;
|
||||
import yesman.epicfight.client.events.engine.RenderEngine;
|
||||
import yesman.epicfight.client.gui.screen.SkillBookScreen;
|
||||
import yesman.epicfight.config.ClientConfig;
|
||||
import yesman.epicfight.gameasset.Animations;
|
||||
import yesman.epicfight.main.EpicFightSharedConstants;
|
||||
import yesman.epicfight.network.EpicFightNetworkManager;
|
||||
import yesman.epicfight.network.client.CPAnimatorControl;
|
||||
import yesman.epicfight.network.client.CPChangePlayerMode;
|
||||
import yesman.epicfight.network.client.CPModifyEntityModelYRot;
|
||||
import yesman.epicfight.network.client.CPSetStamina;
|
||||
import yesman.epicfight.network.common.AnimatorControlPacket;
|
||||
import yesman.epicfight.skill.modules.ChargeableSkill;
|
||||
import com.tiedup.remake.rig.patch.LivingEntityPatch;
|
||||
import com.tiedup.remake.rig.patch.PlayerPatch;
|
||||
import com.tiedup.remake.rig.patch.item.CapabilityItem;
|
||||
import yesman.epicfight.world.entity.eventlistener.PlayerEventListener.EventType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LocalPlayerPatch extends AbstractClientPlayerPatch<LocalPlayer> {
|
||||
|
||||
private static final UUID ACTION_EVENT_UUID = UUID.fromString("d1a1e102-1621-11ed-861d-0242ac120002");
|
||||
private Minecraft minecraft;
|
||||
private float staminaO;
|
||||
private int prevChargingAmount;
|
||||
|
||||
private AnimationSubFileReader.PovSettings povSettings;
|
||||
private FirstPersonLayer firstPersonLayer = new FirstPersonLayer();
|
||||
|
||||
@Override
|
||||
public void onConstructed(LocalPlayer entity) {
|
||||
super.onConstructed(entity);
|
||||
this.minecraft = Minecraft.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onJoinWorld(LocalPlayer player, EntityJoinLevelEvent event) {
|
||||
super.onJoinWorld(player, event);
|
||||
|
||||
this.eventListeners.addEventListener(EventType.ACTION_EVENT_CLIENT, ACTION_EVENT_UUID, (playerEvent) -> {
|
||||
ClientEngine.getInstance().controlEngine.unlockHotkeys();
|
||||
});
|
||||
}
|
||||
|
||||
public void onRespawnLocalPlayer(ClientPlayerNetworkEvent.Clone event) {
|
||||
this.onJoinWorld(event.getNewPlayer(), new EntityJoinLevelEvent(event.getNewPlayer(), event.getNewPlayer().level()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(LivingEvent.LivingTickEvent event) {
|
||||
this.staminaO = this.getStamina();
|
||||
|
||||
if (this.isHoldingAny() && this.getHoldingSkill() instanceof ChargeableSkill) {
|
||||
this.prevChargingAmount = this.getChargingAmount();
|
||||
} else {
|
||||
this.prevChargingAmount = 0;
|
||||
}
|
||||
|
||||
super.tick(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientTick(LivingEvent.LivingTickEvent event) {
|
||||
this.staminaO = this.getStamina();
|
||||
|
||||
super.clientTick(event);
|
||||
|
||||
// Handle first person animation
|
||||
final AssetAccessor<? extends StaticAnimation> currentPlaying = this.firstPersonLayer.animationPlayer.getRealAnimation();
|
||||
|
||||
boolean noPovAnimation = this.getClientAnimator().iterVisibleLayersUntilFalse(layer -> {
|
||||
if (layer.isOff()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Optional<DirectStaticAnimation> optPovAnimation = layer.animationPlayer.getRealAnimation().get().getProperty(ClientAnimationProperties.POV_ANIMATION);
|
||||
Optional<PovSettings> optPovSettings = layer.animationPlayer.getRealAnimation().get().getProperty(ClientAnimationProperties.POV_SETTINGS);
|
||||
|
||||
optPovAnimation.ifPresent(povAnimation -> {
|
||||
if (!povAnimation.equals(currentPlaying.get())) {
|
||||
this.firstPersonLayer.playAnimation(povAnimation, layer.animationPlayer.getRealAnimation(), this, 0.0F);
|
||||
this.povSettings = optPovSettings.get();
|
||||
}
|
||||
});
|
||||
|
||||
return !optPovAnimation.isPresent();
|
||||
});
|
||||
|
||||
if (noPovAnimation && !currentPlaying.equals(Animations.EMPTY_ANIMATION)) {
|
||||
this.firstPersonLayer.off();
|
||||
}
|
||||
|
||||
this.firstPersonLayer.update(this);
|
||||
|
||||
if (this.firstPersonLayer.animationPlayer.getAnimation().equals(Animations.EMPTY_ANIMATION)) {
|
||||
this.povSettings = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overrideRender() {
|
||||
// Disable rendering the player when animated first person model disabled
|
||||
if (this.original.is(this.minecraft.player)) {
|
||||
if (this.minecraft.options.getCameraType().isFirstPerson() && !ClientConfig.enableAnimatedFirstPersonModel) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return super.overrideRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LivingEntity getTarget() {
|
||||
return EpicFightCameraAPI.getInstance().getFocusingEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toVanillaMode(boolean synchronize) {
|
||||
if (this.playerMode != PlayerMode.VANILLA) {
|
||||
ClientEngine.getInstance().renderEngine.downSlideSkillUI();
|
||||
|
||||
if (ClientConfig.autoSwitchCamera) {
|
||||
this.minecraft.options.setCameraType(CameraType.FIRST_PERSON);
|
||||
}
|
||||
|
||||
if (synchronize) {
|
||||
EpicFightNetworkManager.sendToServer(new CPChangePlayerMode(PlayerMode.VANILLA));
|
||||
}
|
||||
}
|
||||
|
||||
super.toVanillaMode(synchronize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toEpicFightMode(boolean synchronize) {
|
||||
if (this.playerMode != PlayerMode.EPICFIGHT) {
|
||||
ClientEngine.getInstance().renderEngine.upSlideSkillUI();
|
||||
|
||||
if (ClientConfig.autoSwitchCamera) {
|
||||
this.minecraft.options.setCameraType(CameraType.THIRD_PERSON_BACK);
|
||||
}
|
||||
|
||||
if (synchronize) {
|
||||
EpicFightNetworkManager.sendToServer(new CPChangePlayerMode(PlayerMode.EPICFIGHT));
|
||||
}
|
||||
}
|
||||
|
||||
super.toEpicFightMode(synchronize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFirstPerson() {
|
||||
return this.minecraft.options.getCameraType() == CameraType.FIRST_PERSON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldBlockMoving() {
|
||||
return InputManager.isActionActive(MinecraftInputAction.MOVE_BACKWARD) || InputManager.isActionActive(MinecraftInputAction.SNEAK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldMoveOnCurrentSide(ActionAnimation actionAnimation) {
|
||||
if (!this.isLogicalClient()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return actionAnimation.shouldPlayerMove(this);
|
||||
}
|
||||
|
||||
public float getStaminaO() {
|
||||
return this.staminaO;
|
||||
}
|
||||
|
||||
public int getPrevChargingAmount() {
|
||||
return this.prevChargingAmount;
|
||||
}
|
||||
|
||||
public FirstPersonLayer getFirstPersonLayer() {
|
||||
return this.firstPersonLayer;
|
||||
}
|
||||
|
||||
public AnimationSubFileReader.PovSettings getPovSettings() {
|
||||
return this.povSettings;
|
||||
}
|
||||
|
||||
public boolean hasCameraAnimation() {
|
||||
return this.povSettings != null && this.povSettings.cameraTransform() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStamina(float value) {
|
||||
EpicFightNetworkManager.sendToServer(new CPSetStamina(value, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModelYRot(float amount, boolean sendPacket) {
|
||||
super.setModelYRot(amount, sendPacket);
|
||||
|
||||
if (sendPacket) {
|
||||
EpicFightNetworkManager.sendToServer(new CPModifyEntityModelYRot(amount));
|
||||
}
|
||||
}
|
||||
|
||||
public float getModelYRot() {
|
||||
return this.modelYRot;
|
||||
}
|
||||
|
||||
public void setModelYRotInGui(float rotDeg) {
|
||||
this.useModelYRot = true;
|
||||
this.modelYRot = rotDeg;
|
||||
}
|
||||
|
||||
public void disableModelYRotInGui(float originalDeg) {
|
||||
this.useModelYRot = false;
|
||||
this.modelYRot = originalDeg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableModelYRot(boolean sendPacket) {
|
||||
super.disableModelYRot(sendPacket);
|
||||
|
||||
if (sendPacket) {
|
||||
EpicFightNetworkManager.sendToServer(new CPModifyEntityModelYRot());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double checkXTurn(double xRot) {
|
||||
if (xRot == 0.0D) {
|
||||
return xRot;
|
||||
}
|
||||
|
||||
if (ClientConfig.enablePovAction && this.minecraft.options.getCameraType().isFirstPerson() && this.isEpicFightMode() && !this.getFirstPersonLayer().isOff()) {
|
||||
ViewLimit viewLimit = this.getPovSettings().viewLimit();
|
||||
|
||||
if (viewLimit != null) {
|
||||
float xRotDest = this.original.getXRot() + (float)xRot * 0.15F;
|
||||
|
||||
if (xRotDest <= viewLimit.xRotMin() || xRotDest >= viewLimit.xRotMax()) {
|
||||
return 0.0D;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return xRot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double checkYTurn(double yRot) {
|
||||
if (yRot == 0.0D) {
|
||||
return yRot;
|
||||
}
|
||||
|
||||
if (ClientConfig.enablePovAction && this.minecraft.options.getCameraType().isFirstPerson() && this.isEpicFightMode() && !this.getFirstPersonLayer().isOff()) {
|
||||
ViewLimit viewLimit = this.getPovSettings().viewLimit();
|
||||
|
||||
if (viewLimit != null) {
|
||||
float yCamera = Mth.wrapDegrees(this.original.getYRot());
|
||||
float yBody = MathUtils.findNearestRotation(yCamera, this.getYRot());
|
||||
float yRotDest = yCamera + (float)yRot * 0.15F;
|
||||
float yRotClamped = Mth.clamp(yRotDest, yBody + viewLimit.yRotMin(), yBody + viewLimit.yRotMax());
|
||||
|
||||
if (yRotDest != yRotClamped) {
|
||||
return 0.0D;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return yRot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginAction(ActionAnimation animation) {
|
||||
EpicFightCameraAPI cameraApi = EpicFightCameraAPI.getInstance();
|
||||
|
||||
if (cameraApi.isTPSMode()) {
|
||||
if (cameraApi.getFocusingEntity() != null && animation instanceof AttackAnimation) {
|
||||
cameraApi.alignPlayerLookToCrosshair(false, true, true);
|
||||
} else {
|
||||
cameraApi.alignPlayerLookToCameraRotation(false, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.useModelYRot || animation.getProperty(ActionAnimationProperty.SYNC_CAMERA).orElse(false)) {
|
||||
this.modelYRot = this.original.getYRot();
|
||||
}
|
||||
|
||||
if (cameraApi.getFocusingEntity() != null && cameraApi.isLockingOnTarget() && !cameraApi.getFocusingEntity().isRemoved()) {
|
||||
Vec3 playerPosition = this.original.position();
|
||||
Vec3 targetPosition = cameraApi.getFocusingEntity().position();
|
||||
Vec3 toTarget = targetPosition.subtract(playerPosition);
|
||||
this.original.setYRot((float)MathUtils.getYRotOfVector(toTarget));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Play an animation after the current animation is finished
|
||||
* @param animation
|
||||
*/
|
||||
@Override
|
||||
public void reserveAnimation(AssetAccessor<? extends StaticAnimation> animation) {
|
||||
this.animator.reserveAnimation(animation);
|
||||
EpicFightNetworkManager.sendToServer(new CPAnimatorControl(AnimatorControlPacket.Action.RESERVE, animation, 0.0F, false, false, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Play an animation without convert time
|
||||
* @param animation
|
||||
*/
|
||||
@Override
|
||||
public void playAnimationInstantly(AssetAccessor<? extends StaticAnimation> animation) {
|
||||
this.animator.playAnimationInstantly(animation);
|
||||
EpicFightNetworkManager.sendToServer(new CPAnimatorControl(AnimatorControlPacket.Action.PLAY_INSTANTLY, animation, 0.0F, false, false, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a shooting animation to end aim pose
|
||||
* This method doesn't send packet from client to server
|
||||
*/
|
||||
@Override
|
||||
public void playShootingAnimation() {
|
||||
this.animator.playShootingAnimation();
|
||||
EpicFightNetworkManager.sendToServer(new CPAnimatorControl(AnimatorControlPacket.Action.SHOT, -1, 0.0F, false, true, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playing an animation
|
||||
* @param animation
|
||||
* @param transitionTimeModifier
|
||||
*/
|
||||
@Override
|
||||
public void stopPlaying(AssetAccessor<? extends StaticAnimation> animation) {
|
||||
this.animator.stopPlaying(animation);
|
||||
EpicFightNetworkManager.sendToServer(new CPAnimatorControl(AnimatorControlPacket.Action.STOP, animation, -1.0F, false, false, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Play an animation ensuring synchronization between client-server
|
||||
* Plays animation when getting response from server if it called in client side.
|
||||
* Do not call this in client side for non-player entities.
|
||||
*
|
||||
* @param animation
|
||||
* @param transitionTimeModifier
|
||||
*/
|
||||
@Override
|
||||
public void playAnimationSynchronized(AssetAccessor<? extends StaticAnimation> animation, float transitionTimeModifier) {
|
||||
EpicFightNetworkManager.sendToServer(new CPAnimatorControl(AnimatorControlPacket.Action.PLAY, animation, transitionTimeModifier, false, false, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Play an animation only in client side, including all clients tracking this entity
|
||||
* @param animation
|
||||
* @param convertTimeModifier
|
||||
*/
|
||||
@Override
|
||||
public void playAnimationInClientSide(AssetAccessor<? extends StaticAnimation> animation, float transitionTimeModifier) {
|
||||
this.animator.playAnimation(animation, transitionTimeModifier);
|
||||
EpicFightNetworkManager.sendToServer(new CPAnimatorControl(AnimatorControlPacket.Action.PLAY, animation, transitionTimeModifier, false, true, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause an animator until it receives a proper order
|
||||
* @param action SOFT_PAUSE: resume when next animation plays
|
||||
* HARD_PAUSE: resume when hard pause is set false
|
||||
* @param pause
|
||||
**/
|
||||
@Override
|
||||
public void pauseAnimator(AnimatorControlPacket.Action action, boolean pause) {
|
||||
super.pauseAnimator(action, pause);
|
||||
EpicFightNetworkManager.sendToServer(new CPAnimatorControl(action, -1, 0.0F, pause, false, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openSkillBook(ItemStack itemstack, InteractionHand hand) {
|
||||
if (itemstack.hasTag() && itemstack.getTag().contains("skill")) {
|
||||
Minecraft.getInstance().setScreen(new SkillBookScreen(this.original, itemstack, hand));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetHolding() {
|
||||
if (this.holdingSkill != null) {
|
||||
ClientEngine.getInstance().controlEngine.releaseAllServedKeys();
|
||||
}
|
||||
|
||||
super.resetHolding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHeldItem(CapabilityItem mainHandCap, CapabilityItem offHandCap) {
|
||||
super.updateHeldItem(mainHandCap, offHandCap);
|
||||
|
||||
if (!ClientConfig.preferenceWork.checkHitResult()) {
|
||||
if (ClientConfig.combatPreferredItems.contains(this.original.getMainHandItem().getItem())) {
|
||||
this.toEpicFightMode(true);
|
||||
} else if (ClientConfig.miningPreferredItems.contains(this.original.getMainHandItem().getItem())) {
|
||||
this.toVanillaMode(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Judge the next behavior depending on player's item preference and where he's looking at
|
||||
* @return true if the next action is swing a weapon, false if the next action is breaking a block
|
||||
*/
|
||||
public boolean canPlayAttackAnimation() {
|
||||
if (this.isVanillaMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EpicFightCameraAPI cameraApi = EpicFightCameraAPI.getInstance();
|
||||
|
||||
HitResult hitResult =
|
||||
(EpicFightCameraAPI.getInstance().isTPSMode() && cameraApi.getCrosshairHitResult() != null && cameraApi.getCrosshairHitResult().getLocation().distanceToSqr(this.original.getEyePosition()) < this.original.getBlockReach() * this.original.getBlockReach())
|
||||
? cameraApi.getCrosshairHitResult() : this.minecraft.hitResult;
|
||||
|
||||
if (hitResult == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
EntityHitResult entityHitResult = RenderEngine.asEntityHitResult(hitResult);
|
||||
|
||||
if (entityHitResult != null) {
|
||||
Entity hitEntity = entityHitResult.getEntity();
|
||||
|
||||
if (!(hitEntity instanceof LivingEntity) && !(hitEntity instanceof PartEntity)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (EpicFightCameraAPI.getInstance().isLockingOnTarget()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ClientConfig.preferenceWork.checkHitResult()) {
|
||||
if (ClientConfig.combatPreferredItems.contains(this.original.getMainHandItem().getItem())) {
|
||||
BlockHitResult blockHitResult = RenderEngine.asBlockHitResult(this.minecraft.hitResult);
|
||||
|
||||
if (blockHitResult != null && this.minecraft.level != null) {
|
||||
BlockPos bp = blockHitResult.getBlockPos();
|
||||
BlockState bs = this.minecraft.level.getBlockState(bp);
|
||||
return !this.original.getMainHandItem().getItem().canAttackBlock(bs, this.original.level(), bp, this.original) || !this.original.getMainHandItem().isCorrectToolForDrops(bs);
|
||||
}
|
||||
} else {
|
||||
return RenderEngine.hitResultNotEquals(this.minecraft.hitResult, HitResult.Type.BLOCK);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return this.getPlayerMode() == PlayerPatch.PlayerMode.EPICFIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
public class FirstPersonLayer extends Layer {
|
||||
private TransformSheet linkCameraTransform = new TransformSheet(List.of(new Keyframe(0.0F, JointTransform.empty()), new Keyframe(Float.MAX_VALUE, JointTransform.empty())));
|
||||
|
||||
public FirstPersonLayer() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public void playAnimation(AssetAccessor<? extends StaticAnimation> nextFirstPersonAnimation, AssetAccessor<? extends StaticAnimation> originalAnimation, LivingEntityPatch<?> entitypatch, float transitionTimeModifier) {
|
||||
Optional<PovSettings> povSettings = originalAnimation.get().getProperty(ClientAnimationProperties.POV_SETTINGS);
|
||||
|
||||
boolean hasPrevCameraAnimation = LocalPlayerPatch.this.povSettings != null && LocalPlayerPatch.this.povSettings.cameraTransform() != null;
|
||||
boolean hasNextCameraAnimation = povSettings.isPresent() && povSettings.get().cameraTransform() != null;
|
||||
|
||||
// Activate pov animation
|
||||
if (hasPrevCameraAnimation || hasNextCameraAnimation) {
|
||||
if (hasPrevCameraAnimation) {
|
||||
this.linkCameraTransform.getKeyframes()[0].transform().copyFrom(LocalPlayerPatch.this.povSettings.cameraTransform().getInterpolatedTransform(this.animationPlayer.getElapsedTime()));
|
||||
} else {
|
||||
this.linkCameraTransform.getKeyframes()[0].transform().copyFrom(JointTransform.empty());
|
||||
}
|
||||
|
||||
if (hasNextCameraAnimation) {
|
||||
this.linkCameraTransform.getKeyframes()[1].transform().copyFrom(povSettings.get().cameraTransform().getKeyframes()[0].transform());
|
||||
} else {
|
||||
this.linkCameraTransform.getKeyframes()[1].transform().clearTransform();
|
||||
}
|
||||
|
||||
this.linkCameraTransform.getKeyframes()[1].setTime(nextFirstPersonAnimation.get().getTransitionTime());
|
||||
}
|
||||
|
||||
super.playAnimation(nextFirstPersonAnimation, entitypatch, transitionTimeModifier);
|
||||
}
|
||||
|
||||
public void off() {
|
||||
// Off camera animation
|
||||
if (LocalPlayerPatch.this.povSettings != null && LocalPlayerPatch.this.povSettings.cameraTransform() != null) {
|
||||
this.linkCameraTransform.getKeyframes()[0].transform().copyFrom(LocalPlayerPatch.this.povSettings.cameraTransform().getInterpolatedTransform(this.animationPlayer.getElapsedTime()));
|
||||
this.linkCameraTransform.getKeyframes()[1].transform().copyFrom(JointTransform.empty());
|
||||
this.linkCameraTransform.getKeyframes()[1].setTime(EpicFightSharedConstants.GENERAL_ANIMATION_TRANSITION_TIME);
|
||||
}
|
||||
|
||||
super.off(LocalPlayerPatch.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pose getCurrentPose(LivingEntityPatch<?> entitypatch) {
|
||||
return this.animationPlayer.isEmpty() ? super.getCurrentPose(entitypatch) : this.animationPlayer.getCurrentPose(entitypatch, 0.0F);
|
||||
}
|
||||
|
||||
public TransformSheet getLinkCameraTransform() {
|
||||
return this.linkCameraTransform;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link EpicFightCameraAPI#isLockingOnTarget()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public boolean isTargetLockedOn() {
|
||||
return EpicFightCameraAPI.getInstance().isLockingOnTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link EpicFightCameraAPI#setLockOn(boolean)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public void setLockOn(boolean targetLockedOn) {
|
||||
EpicFightCameraAPI.getInstance().setLockOn(targetLockedOn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link EpicFightCameraAPI#toggleLockOn()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public void toggleLockOn() {
|
||||
this.setLockOn(!EpicFightCameraAPI.getInstance().isLockingOnTarget());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,660 @@
|
||||
/*
|
||||
* 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.render;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.client.renderer.RenderStateShard;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.inventory.InventoryMenu;
|
||||
import yesman.epicfight.main.EpicFightMod;
|
||||
|
||||
public final class EpicFightRenderTypes extends RenderType {
|
||||
public static RenderType makeTriangulated(RenderType renderType) {
|
||||
if (renderType.mode() == VertexFormat.Mode.TRIANGLES) {
|
||||
return renderType;
|
||||
}
|
||||
|
||||
if (renderType instanceof CompositeRenderType compositeRenderType) {
|
||||
return new CompositeRenderType(renderType.name, renderType.format, VertexFormat.Mode.TRIANGLES, renderType.bufferSize(), renderType.affectsCrumbling(), renderType.sortOnUpload, compositeRenderType.state);
|
||||
} else {
|
||||
return renderType;
|
||||
}
|
||||
}
|
||||
|
||||
private static final BiFunction<ResourceLocation, RenderStateShard.CullStateShard, RenderType> TRIANGULATED_OUTLINE =
|
||||
Util.memoize((texLocation, cullStateShard) -> {
|
||||
return RenderType.create(
|
||||
EpicFightMod.prefix("outline"),
|
||||
DefaultVertexFormat.POSITION_COLOR_TEX,
|
||||
VertexFormat.Mode.TRIANGLES,
|
||||
256,
|
||||
false,
|
||||
false,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(RENDERTYPE_OUTLINE_SHADER)
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(texLocation, false, false))
|
||||
.setCullState(cullStateShard)
|
||||
.setDepthTestState(NO_DEPTH_TEST)
|
||||
.setOutputState(OUTLINE_TARGET)
|
||||
.createCompositeState(RenderType.OutlineProperty.IS_OUTLINE)
|
||||
);
|
||||
});
|
||||
|
||||
private static final Map<String, Map<ResourceLocation, RenderType>> TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE = new HashMap<> ();
|
||||
|
||||
private static final Function<RenderType, RenderType> TRIANGULATED_RENDER_TYPES = Util.memoize(renderType -> {
|
||||
if (renderType.mode() == VertexFormat.Mode.TRIANGLES) {
|
||||
return renderType;
|
||||
}
|
||||
|
||||
if (renderType instanceof CompositeRenderType compositeRenderType) {
|
||||
Optional<ResourceLocation> cutoutTexture;
|
||||
|
||||
if (compositeRenderType.state.textureState instanceof TextureStateShard texStateShard) {
|
||||
cutoutTexture = texStateShard.texture;
|
||||
} else {
|
||||
cutoutTexture = Optional.empty();
|
||||
}
|
||||
|
||||
if (TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.containsKey(renderType.name)) {
|
||||
Map<ResourceLocation, RenderType> renderTypesByTexture = TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.get(renderType.name);
|
||||
|
||||
if (compositeRenderType.state.textureState instanceof TextureStateShard) {
|
||||
ResourceLocation texLocation = cutoutTexture.orElse(null);
|
||||
|
||||
if (renderTypesByTexture.containsKey(texLocation)) {
|
||||
return renderTypesByTexture.get(texLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompositeRenderType triangulatedRenderType = new CompositeRenderType(
|
||||
renderType.name,
|
||||
renderType.format,
|
||||
VertexFormat.Mode.TRIANGLES,
|
||||
renderType.bufferSize(),
|
||||
renderType.affectsCrumbling(),
|
||||
renderType.sortOnUpload,
|
||||
compositeRenderType.state
|
||||
);
|
||||
|
||||
triangulatedRenderType.outline = triangulatedRenderType.outline.isEmpty() ? triangulatedRenderType.outline : cutoutTexture.map(texLocation -> {
|
||||
return TRIANGULATED_OUTLINE.apply(texLocation, compositeRenderType.state.cullState);
|
||||
});
|
||||
|
||||
return triangulatedRenderType;
|
||||
} else {
|
||||
return renderType;
|
||||
}
|
||||
});
|
||||
|
||||
public static RenderType getTriangulated(RenderType renderType) {
|
||||
return TRIANGULATED_RENDER_TYPES.apply(renderType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache all Texture - RenderType entries to replace texture by MeshPart
|
||||
*/
|
||||
public static void addRenderType(String name, ResourceLocation textureLocation, RenderType renderType) {
|
||||
Map<ResourceLocation, RenderType> renderTypesByTexture = TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.computeIfAbsent(name, (k) -> Maps.newHashMap());
|
||||
renderTypesByTexture.put(textureLocation, renderType);
|
||||
}
|
||||
|
||||
// Custom shards
|
||||
protected static final RenderStateShard.ShaderStateShard PARTICLE_SHADER = new RenderStateShard.ShaderStateShard(GameRenderer::getParticleShader);
|
||||
|
||||
public static class ShaderColorStateShard extends RenderStateShard {
|
||||
private Vector4f color;
|
||||
|
||||
public ShaderColorStateShard(Vector4f color) {
|
||||
super(
|
||||
"shader_color",
|
||||
() -> {
|
||||
RenderSystem.setShaderColor(color.x, color.y, color.z, color.w);
|
||||
},
|
||||
() -> {
|
||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
}
|
||||
);
|
||||
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public void setColor(float r, float g, float b, float a) {
|
||||
this.color.set(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MutableCompositeState extends CompositeState {
|
||||
private ShaderColorStateShard shaderColorState = new ShaderColorStateShard(new Vector4f(1.0F));
|
||||
|
||||
public MutableCompositeState(
|
||||
EmptyTextureStateShard pTextureState, ShaderStateShard pShaderState,
|
||||
TransparencyStateShard pTransparencyState, DepthTestStateShard pDepthState, CullStateShard pCullState,
|
||||
LightmapStateShard pLightmapState, OverlayStateShard pOverlayState, LayeringStateShard pLayeringState,
|
||||
OutputStateShard pOutputState, TexturingStateShard pTexturingState, WriteMaskStateShard pWriteMaskState,
|
||||
LineStateShard pLineState, ColorLogicStateShard pColorLogicState, RenderType.OutlineProperty pOutlineProperty
|
||||
) {
|
||||
super(
|
||||
pTextureState, pShaderState, pTransparencyState, pDepthState, pCullState, pLightmapState, pOverlayState,
|
||||
pLayeringState, pOutputState, pTexturingState, pWriteMaskState, pLineState, pColorLogicState, pOutlineProperty
|
||||
);
|
||||
|
||||
List<RenderStateShard> list = new ArrayList<> (this.states);
|
||||
list.add(this.shaderColorState);
|
||||
this.states = ImmutableList.copyOf(list);
|
||||
}
|
||||
|
||||
public void setShaderColor(int r, int g, int b, int a) {
|
||||
this.shaderColorState.setColor(r / 255.0F, g / 255.0F, b / 255.0F, a / 255.0F);
|
||||
}
|
||||
|
||||
public void setShaderColor(float r, float g, float b, float a) {
|
||||
this.shaderColorState.setColor(r, g, b, a);
|
||||
}
|
||||
|
||||
public static EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder mutableStateBuilder() {
|
||||
return new EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder();
|
||||
}
|
||||
|
||||
public static class MutableCompositeStateBuilder {
|
||||
private RenderStateShard.EmptyTextureStateShard textureState = RenderStateShard.NO_TEXTURE;
|
||||
private RenderStateShard.ShaderStateShard shaderState = RenderStateShard.NO_SHADER;
|
||||
private RenderStateShard.TransparencyStateShard transparencyState = RenderStateShard.NO_TRANSPARENCY;
|
||||
private RenderStateShard.DepthTestStateShard depthTestState = RenderStateShard.LEQUAL_DEPTH_TEST;
|
||||
private RenderStateShard.CullStateShard cullState = RenderStateShard.CULL;
|
||||
private RenderStateShard.LightmapStateShard lightmapState = RenderStateShard.NO_LIGHTMAP;
|
||||
private RenderStateShard.OverlayStateShard overlayState = RenderStateShard.NO_OVERLAY;
|
||||
private RenderStateShard.LayeringStateShard layeringState = RenderStateShard.NO_LAYERING;
|
||||
private RenderStateShard.OutputStateShard outputState = RenderStateShard.MAIN_TARGET;
|
||||
private RenderStateShard.TexturingStateShard texturingState = RenderStateShard.DEFAULT_TEXTURING;
|
||||
private RenderStateShard.WriteMaskStateShard writeMaskState = RenderStateShard.COLOR_DEPTH_WRITE;
|
||||
private RenderStateShard.LineStateShard lineState = RenderStateShard.DEFAULT_LINE;
|
||||
private RenderStateShard.ColorLogicStateShard colorLogicState = RenderStateShard.NO_COLOR_LOGIC;
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setTextureState(RenderStateShard.EmptyTextureStateShard pTextureState) {
|
||||
this.textureState = pTextureState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setShaderState(RenderStateShard.ShaderStateShard pShaderState) {
|
||||
this.shaderState = pShaderState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setTransparencyState(RenderStateShard.TransparencyStateShard pTransparencyState) {
|
||||
this.transparencyState = pTransparencyState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setDepthTestState(RenderStateShard.DepthTestStateShard pDepthTestState) {
|
||||
this.depthTestState = pDepthTestState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setCullState(RenderStateShard.CullStateShard pCullState) {
|
||||
this.cullState = pCullState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setLightmapState(RenderStateShard.LightmapStateShard pLightmapState) {
|
||||
this.lightmapState = pLightmapState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setOverlayState(RenderStateShard.OverlayStateShard pOverlayState) {
|
||||
this.overlayState = pOverlayState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setLayeringState(RenderStateShard.LayeringStateShard pLayerState) {
|
||||
this.layeringState = pLayerState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setOutputState(RenderStateShard.OutputStateShard pOutputState) {
|
||||
this.outputState = pOutputState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setTexturingState(RenderStateShard.TexturingStateShard pTexturingState) {
|
||||
this.texturingState = pTexturingState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setWriteMaskState(RenderStateShard.WriteMaskStateShard pWriteMaskState) {
|
||||
this.writeMaskState = pWriteMaskState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setLineState(RenderStateShard.LineStateShard pLineState) {
|
||||
this.lineState = pLineState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState.MutableCompositeStateBuilder setColorLogicState(RenderStateShard.ColorLogicStateShard pColorLogicState) {
|
||||
this.colorLogicState = pColorLogicState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState createCompositeState(boolean pOutline) {
|
||||
return this.createCompositeState(pOutline ? RenderType.OutlineProperty.AFFECTS_OUTLINE : RenderType.OutlineProperty.NONE);
|
||||
}
|
||||
|
||||
public EpicFightRenderTypes.MutableCompositeState createCompositeState(RenderType.OutlineProperty pOutlineState) {
|
||||
return new EpicFightRenderTypes.MutableCompositeState(
|
||||
this.textureState,
|
||||
this.shaderState,
|
||||
this.transparencyState,
|
||||
this.depthTestState,
|
||||
this.cullState,
|
||||
this.lightmapState,
|
||||
this.overlayState,
|
||||
this.layeringState,
|
||||
this.outputState,
|
||||
this.texturingState,
|
||||
this.writeMaskState,
|
||||
this.lineState,
|
||||
this.colorLogicState,
|
||||
pOutlineState
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final RenderType ENTITY_UI_COLORED =
|
||||
create(
|
||||
EpicFightMod.prefix("ui_color")
|
||||
, DefaultVertexFormat.POSITION_COLOR
|
||||
, VertexFormat.Mode.QUADS
|
||||
, 256
|
||||
, true
|
||||
, false
|
||||
, RenderType.CompositeState.builder()
|
||||
.setShaderState(POSITION_COLOR_SHADER)
|
||||
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
|
||||
.setLightmapState(NO_LIGHTMAP)
|
||||
.setOverlayState(NO_OVERLAY)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
private static final Function<ResourceLocation, RenderType> ENTITY_UI_TEXTURE = Util.memoize(
|
||||
(textureLocation) -> create(
|
||||
EpicFightMod.prefix("ui_texture")
|
||||
, DefaultVertexFormat.POSITION_TEX
|
||||
, VertexFormat.Mode.QUADS
|
||||
, 256
|
||||
, true
|
||||
, false
|
||||
, RenderType.CompositeState.builder()
|
||||
.setShaderState(POSITION_TEX_SHADER)
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(textureLocation, false, false))
|
||||
.setTransparencyState(NO_TRANSPARENCY)
|
||||
.setLightmapState(NO_LIGHTMAP)
|
||||
.setOverlayState(NO_OVERLAY)
|
||||
.createCompositeState(false)
|
||||
)
|
||||
);
|
||||
|
||||
private static final RenderType OBB = create(
|
||||
EpicFightMod.prefix("debug_collider")
|
||||
, DefaultVertexFormat.POSITION_COLOR_NORMAL
|
||||
, VertexFormat.Mode.LINE_STRIP
|
||||
, 256
|
||||
, false
|
||||
, false
|
||||
, RenderType.CompositeState.builder()
|
||||
.setShaderState(POSITION_COLOR_SHADER)
|
||||
.setLineState(new RenderStateShard.LineStateShard(OptionalDouble.empty()))
|
||||
.setLayeringState(VIEW_OFFSET_Z_LAYERING)
|
||||
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
|
||||
.setOutputState(ITEM_ENTITY_TARGET)
|
||||
.setWriteMaskState(COLOR_DEPTH_WRITE)
|
||||
.setCullState(NO_CULL)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
private static final RenderType DEBUG_QUADS = create(
|
||||
EpicFightMod.prefix("debug_quad")
|
||||
, DefaultVertexFormat.POSITION_COLOR
|
||||
, VertexFormat.Mode.QUADS
|
||||
, 256
|
||||
, false
|
||||
, false
|
||||
, RenderType.CompositeState.builder()
|
||||
.setShaderState(POSITION_COLOR_SHADER)
|
||||
.setLayeringState(VIEW_OFFSET_Z_LAYERING)
|
||||
.setTransparencyState(NO_TRANSPARENCY)
|
||||
.setWriteMaskState(COLOR_DEPTH_WRITE)
|
||||
.setCullState(NO_CULL)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
private static final RenderType GUI_TRIANGLE = create(
|
||||
EpicFightMod.prefix("gui_triangle")
|
||||
, DefaultVertexFormat.POSITION_COLOR
|
||||
, VertexFormat.Mode.TRIANGLES
|
||||
, 256
|
||||
, false
|
||||
, false
|
||||
, RenderType.CompositeState.builder()
|
||||
.setShaderState(RENDERTYPE_GUI_SHADER)
|
||||
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
|
||||
.setDepthTestState(LEQUAL_DEPTH_TEST)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
private static final Function<ResourceLocation, RenderType> OVERLAY_MODEL = Util.memoize(texLocation -> {
|
||||
return create(
|
||||
EpicFightMod.prefix("overlay_model"),
|
||||
DefaultVertexFormat.NEW_ENTITY,
|
||||
VertexFormat.Mode.TRIANGLES,
|
||||
256,
|
||||
false,
|
||||
false,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(RENDERTYPE_ENTITY_TRANSLUCENT_SHADER)
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(texLocation, false, false))
|
||||
.setWriteMaskState(COLOR_WRITE)
|
||||
.setCullState(NO_CULL)
|
||||
.setDepthTestState(EQUAL_DEPTH_TEST)
|
||||
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
|
||||
.setLightmapState(LIGHTMAP)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
private static final RenderType ENTITY_AFTERIMAGE_WHITE =
|
||||
create(
|
||||
EpicFightMod.prefix("entity_afterimage"),
|
||||
DefaultVertexFormat.PARTICLE,
|
||||
VertexFormat.Mode.TRIANGLES,
|
||||
256,
|
||||
true,
|
||||
true,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(PARTICLE_SHADER)
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(EpicFightMod.identifier("textures/common/white.png"), false, false))
|
||||
.setCullState(NO_CULL)
|
||||
.setWriteMaskState(COLOR_WRITE)
|
||||
.setDepthTestState(EQUAL_DEPTH_TEST)
|
||||
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
|
||||
.setLightmapState(LIGHTMAP)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
private static final RenderType ITEM_AFTERIMAGE_WHITE =
|
||||
create(
|
||||
EpicFightMod.prefix("item_afterimage"),
|
||||
DefaultVertexFormat.PARTICLE,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
true,
|
||||
true,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(PARTICLE_SHADER)
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(EpicFightMod.identifier("textures/common/white.png"), false, false))
|
||||
.setCullState(NO_CULL)
|
||||
.setWriteMaskState(COLOR_WRITE)
|
||||
.setDepthTestState(EQUAL_DEPTH_TEST)
|
||||
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
|
||||
.setLightmapState(LIGHTMAP)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
private static final Function<ResourceLocation, RenderType> ENTITY_PARTICLE = Util.memoize(texLocation -> {
|
||||
return create(
|
||||
EpicFightMod.prefix("entity_particle"),
|
||||
DefaultVertexFormat.NEW_ENTITY,
|
||||
VertexFormat.Mode.TRIANGLES,
|
||||
256,
|
||||
true,
|
||||
true,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(RENDERTYPE_ENTITY_TRANSLUCENT_SHADER)
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(texLocation, false, false))
|
||||
.setWriteMaskState(COLOR_WRITE)
|
||||
.setDepthTestState(EQUAL_DEPTH_TEST)
|
||||
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
|
||||
.setCullState(NO_CULL)
|
||||
.setLightmapState(LIGHTMAP)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
});
|
||||
|
||||
private static final RenderType ITEM_PARTICLE =
|
||||
create(
|
||||
EpicFightMod.prefix("item_particle"),
|
||||
DefaultVertexFormat.NEW_ENTITY,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
true,
|
||||
true,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(RENDERTYPE_ENTITY_TRANSLUCENT_SHADER)
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(InventoryMenu.BLOCK_ATLAS, false, false))
|
||||
.setWriteMaskState(COLOR_WRITE)
|
||||
.setDepthTestState(EQUAL_DEPTH_TEST)
|
||||
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
|
||||
.setCullState(NO_CULL)
|
||||
.setLightmapState(LIGHTMAP)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
private static final Function<ResourceLocation, RenderType> ENTITY_PARTICLE_STENCIL = Util.memoize(texLocation -> {
|
||||
return create(
|
||||
EpicFightMod.prefix("entity_particle_stencil"),
|
||||
DefaultVertexFormat.POSITION_TEX,
|
||||
VertexFormat.Mode.TRIANGLES,
|
||||
256,
|
||||
false,
|
||||
false,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(POSITION_TEX_SHADER)
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(texLocation, false, false))
|
||||
.setWriteMaskState(DEPTH_WRITE)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
});
|
||||
|
||||
private static final RenderType ITEM_PARTICLE_STENCIL =
|
||||
create(
|
||||
EpicFightMod.prefix("item_particle_stencil"),
|
||||
DefaultVertexFormat.POSITION_TEX,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
false,
|
||||
false,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(POSITION_TEX_SHADER)
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(InventoryMenu.BLOCK_ATLAS, false, false))
|
||||
.setWriteMaskState(DEPTH_WRITE)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
private static final RenderType.CompositeRenderType BLOCK_HIGHLIGHT =
|
||||
create(
|
||||
EpicFightMod.prefix("block_highlight"),
|
||||
DefaultVertexFormat.BLOCK,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
false,
|
||||
true,
|
||||
RenderType.CompositeState.builder()
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(EpicFightMod.identifier("textures/common/white.png"), false, false))
|
||||
.setLightmapState(LIGHTMAP)
|
||||
.setShaderState(RENDERTYPE_TRANSLUCENT_SHADER)
|
||||
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
|
||||
.setDepthTestState(EQUAL_DEPTH_TEST)
|
||||
//.setDepthTestState(NO_DEPTH_TEST)
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
private static RenderType replaceTextureShard(ResourceLocation texToReplace, RenderType renderType) {
|
||||
if (renderType instanceof CompositeRenderType compositeRenderType && compositeRenderType.state.textureState instanceof TextureStateShard texStateShard) {
|
||||
CompositeState textureReplacedState = new CompositeState(
|
||||
new RenderStateShard.TextureStateShard(texToReplace, texStateShard.blur, texStateShard.mipmap)
|
||||
, compositeRenderType.state.shaderState
|
||||
, compositeRenderType.state.transparencyState
|
||||
, compositeRenderType.state.depthTestState
|
||||
, compositeRenderType.state.cullState
|
||||
, compositeRenderType.state.lightmapState
|
||||
, compositeRenderType.state.overlayState
|
||||
, compositeRenderType.state.layeringState
|
||||
, compositeRenderType.state.outputState
|
||||
, compositeRenderType.state.texturingState
|
||||
, compositeRenderType.state.writeMaskState
|
||||
, compositeRenderType.state.lineState
|
||||
, compositeRenderType.state.colorLogicState
|
||||
, compositeRenderType.state.outlineProperty
|
||||
);
|
||||
|
||||
return new CompositeRenderType(renderType.name, renderType.format, compositeRenderType.mode(), renderType.bufferSize(), renderType.affectsCrumbling(), renderType.sortOnUpload, textureReplacedState);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static RenderType replaceTexture(ResourceLocation texLocation, RenderType renderType) {
|
||||
if (TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.containsKey(renderType.name)) {
|
||||
Map<ResourceLocation, RenderType> renderTypesByTexture = TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.get(renderType.name);
|
||||
|
||||
if (renderTypesByTexture.containsKey(texLocation)) {
|
||||
return renderTypesByTexture.get(texLocation);
|
||||
}
|
||||
}
|
||||
|
||||
RenderType textureReplacedRenderType = replaceTextureShard(texLocation, renderType);
|
||||
|
||||
if (textureReplacedRenderType == null) {
|
||||
return renderType;
|
||||
}
|
||||
|
||||
Map<ResourceLocation, RenderType> renderTypesByTexture = TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.computeIfAbsent(textureReplacedRenderType.name, k -> Maps.newHashMap());
|
||||
renderTypesByTexture.put(texLocation, textureReplacedRenderType);
|
||||
|
||||
return textureReplacedRenderType;
|
||||
}
|
||||
|
||||
public static RenderType entityUIColor() {
|
||||
return ENTITY_UI_COLORED;
|
||||
}
|
||||
|
||||
public static RenderType entityUITexture(ResourceLocation resourcelocation) {
|
||||
return ENTITY_UI_TEXTURE.apply(resourcelocation);
|
||||
}
|
||||
|
||||
public static RenderType debugCollider() {
|
||||
return OBB;
|
||||
}
|
||||
|
||||
public static RenderType debugQuads() {
|
||||
return DEBUG_QUADS;
|
||||
}
|
||||
|
||||
public static RenderType guiTriangle() {
|
||||
return GUI_TRIANGLE;
|
||||
}
|
||||
|
||||
public static RenderType overlayModel(ResourceLocation textureLocation) {
|
||||
return OVERLAY_MODEL.apply(textureLocation);
|
||||
}
|
||||
|
||||
public static RenderType entityAfterimageStencil(ResourceLocation textureLocation) {
|
||||
return ENTITY_PARTICLE_STENCIL.apply(textureLocation);
|
||||
}
|
||||
|
||||
public static RenderType itemAfterimageStencil() {
|
||||
return ITEM_PARTICLE_STENCIL;
|
||||
}
|
||||
|
||||
public static RenderType entityAfterimageTranslucent(ResourceLocation textureLocation) {
|
||||
return ENTITY_PARTICLE.apply(textureLocation);
|
||||
}
|
||||
|
||||
public static RenderType itemAfterimageTranslucent() {
|
||||
return ITEM_PARTICLE;
|
||||
}
|
||||
|
||||
public static RenderType entityAfterimageWhite() {
|
||||
return ENTITY_AFTERIMAGE_WHITE;
|
||||
}
|
||||
|
||||
public static RenderType itemAfterimageWhite() {
|
||||
return ITEM_AFTERIMAGE_WHITE;
|
||||
}
|
||||
|
||||
public static RenderType blockHighlight() {
|
||||
return BLOCK_HIGHLIGHT;
|
||||
}
|
||||
|
||||
private static final Map<Entity, CompositeRenderType> WORLD_RENDERTYPES_COLORED_GLINT = new HashMap<> ();
|
||||
|
||||
public static void freeUnusedWorldRenderTypes() {
|
||||
WORLD_RENDERTYPES_COLORED_GLINT.entrySet().removeIf(entry -> entry.getKey().isRemoved());
|
||||
}
|
||||
|
||||
public static void clearWorldRenderTypes() {
|
||||
WORLD_RENDERTYPES_COLORED_GLINT.clear();
|
||||
}
|
||||
|
||||
public static RenderType coloredGlintWorldRendertype(Entity owner, float r, float g, float b) {
|
||||
CompositeRenderType glintRenderType = WORLD_RENDERTYPES_COLORED_GLINT.computeIfAbsent(
|
||||
owner,
|
||||
k -> create(
|
||||
EpicFightMod.prefix("colored_glint"),
|
||||
DefaultVertexFormat.POSITION_TEX,
|
||||
VertexFormat.Mode.TRIANGLES,
|
||||
256,
|
||||
false,
|
||||
false,
|
||||
EpicFightRenderTypes.MutableCompositeState.mutableStateBuilder()
|
||||
.setShaderState(RENDERTYPE_ARMOR_ENTITY_GLINT_SHADER)
|
||||
.setTextureState(new RenderStateShard.TextureStateShard(EpicFightMod.identifier("textures/entity/overlay/glint_white.png"), true, false))
|
||||
.setWriteMaskState(COLOR_WRITE)
|
||||
.setCullState(NO_CULL)
|
||||
.setDepthTestState(EQUAL_DEPTH_TEST)
|
||||
.setTransparencyState(GLINT_TRANSPARENCY)
|
||||
.setTexturingState(ENTITY_GLINT_TEXTURING)
|
||||
.createCompositeState(false)
|
||||
));
|
||||
|
||||
((MutableCompositeState)glintRenderType.state).setShaderColor(r, g, b, 1.0F);
|
||||
|
||||
return glintRenderType;
|
||||
}
|
||||
|
||||
public static RenderType coloredGlintWorldRendertype(Entity owner, int r, int g, int b) {
|
||||
return coloredGlintWorldRendertype(owner, r / 255.0F, g / 255.0F, b / 255.0F);
|
||||
}
|
||||
|
||||
//Util class
|
||||
private EpicFightRenderTypes() {
|
||||
super(null, null, null, -1, false, false, null, null);
|
||||
}
|
||||
}
|
||||
372
src/main/java/com/tiedup/remake/rig/util/ParseUtil.java
Normal file
372
src/main/java/com/tiedup/remake/rig/util/ParseUtil.java
Normal file
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
|
||||
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
|
||||
import it.unimi.dsi.fastutil.doubles.DoubleList;
|
||||
import it.unimi.dsi.fastutil.floats.FloatArrayList;
|
||||
import it.unimi.dsi.fastutil.floats.FloatList;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.minecraft.nbt.ByteTag;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.nbt.TagParser;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
|
||||
public class ParseUtil {
|
||||
public static Integer[] toIntArray(JsonArray array) {
|
||||
List<Integer> result = Lists.newArrayList();
|
||||
|
||||
for (JsonElement je : array) {
|
||||
result.add(je.getAsInt());
|
||||
}
|
||||
|
||||
return result.toArray(new Integer[0]);
|
||||
}
|
||||
|
||||
public static Float[] toFloatArray(JsonArray array) {
|
||||
List<Float> result = Lists.newArrayList();
|
||||
|
||||
for (JsonElement je : array) {
|
||||
result.add(je.getAsFloat());
|
||||
}
|
||||
|
||||
return result.toArray(new Float[0]);
|
||||
}
|
||||
|
||||
public static int[] toIntArrayPrimitive(JsonArray array) {
|
||||
IntList result = new IntArrayList();
|
||||
|
||||
for (JsonElement je : array) {
|
||||
result.add(je.getAsInt());
|
||||
}
|
||||
|
||||
return result.toIntArray();
|
||||
}
|
||||
|
||||
public static float[] toFloatArrayPrimitive(JsonArray array) {
|
||||
FloatList result = new FloatArrayList();
|
||||
|
||||
for (JsonElement je : array) {
|
||||
result.add(je.getAsFloat());
|
||||
}
|
||||
|
||||
return result.toFloatArray();
|
||||
}
|
||||
|
||||
public static int[] unwrapIntWrapperArray(Number[] wrapperArray) {
|
||||
int[] iarray = new int[wrapperArray.length];
|
||||
|
||||
for (int i = 0; i < wrapperArray.length; i++) {
|
||||
iarray[i] = (int)wrapperArray[i];
|
||||
}
|
||||
|
||||
return iarray;
|
||||
}
|
||||
|
||||
public static float[] unwrapFloatWrapperArray(Number[] wrapperArray) {
|
||||
float[] farray = new float[wrapperArray.length];
|
||||
|
||||
for (int i = 0; i < wrapperArray.length; i++) {
|
||||
farray[i] = (float)wrapperArray[i];
|
||||
}
|
||||
|
||||
return farray;
|
||||
}
|
||||
|
||||
public static JsonObject farrayToJsonObject(float[] array, int stride) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("stride", stride);
|
||||
jsonObject.addProperty("count", array.length / stride);
|
||||
JsonArray jsonArray = new JsonArray();
|
||||
|
||||
for (float element : array) {
|
||||
jsonArray.add(element);
|
||||
}
|
||||
|
||||
jsonObject.add("array", jsonArray);
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static JsonObject iarrayToJsonObject(int[] array, int stride) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("stride", stride);
|
||||
jsonObject.addProperty("count", array.length / stride);
|
||||
JsonArray jsonArray = new JsonArray();
|
||||
|
||||
for (int element : array) {
|
||||
jsonArray.add(element);
|
||||
}
|
||||
|
||||
jsonObject.add("array", jsonArray);
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static Vec3f toVector3f(JsonArray array) {
|
||||
float[] result = toFloatArrayPrimitive(array);
|
||||
|
||||
if (result.length < 3) {
|
||||
throw new IllegalArgumentException("Requires more than 3 elements to convert into 3d vector.");
|
||||
}
|
||||
|
||||
return new Vec3f(result[0], result[1], result[2]);
|
||||
}
|
||||
|
||||
public static Vec3 toVector3d(JsonArray array) {
|
||||
DoubleList result = new DoubleArrayList();
|
||||
|
||||
for (JsonElement je : array) {
|
||||
result.add(je.getAsDouble());
|
||||
}
|
||||
|
||||
if (result.size() < 3) {
|
||||
throw new IllegalArgumentException("Requires more than 3 elements to convert into 3d vector.");
|
||||
}
|
||||
|
||||
return new Vec3(result.getDouble(0), result.getDouble(1), result.getDouble(2));
|
||||
}
|
||||
|
||||
public static AttributeModifier toAttributeModifier(CompoundTag tag) {
|
||||
AttributeModifier.Operation operation = AttributeModifier.Operation.valueOf(tag.getString("operation").toUpperCase(Locale.ROOT));
|
||||
|
||||
return new AttributeModifier(UUID.fromString(tag.getString("uuid")), tag.getString("name"), tag.getDouble("amount"), operation);
|
||||
}
|
||||
|
||||
public static <T> String nullOrToString(T obj, Function<T, String> toString) {
|
||||
return obj == null ? "" : toString.apply(obj);
|
||||
}
|
||||
|
||||
public static <T, V> V nullOrApply(T obj, Function<T, V> apply) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return apply.apply(obj);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T nvl(T a, T b) {
|
||||
return a == null ? b : a;
|
||||
}
|
||||
|
||||
public static String snakeToSpacedCamel(Object obj) {
|
||||
if (obj == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean upperNext = true;
|
||||
String toStr = obj.toString().toLowerCase(Locale.ROOT);
|
||||
|
||||
for (String sElement : toStr.split("")) {
|
||||
if (upperNext) {
|
||||
sElement = sElement.toUpperCase(Locale.ROOT);
|
||||
upperNext = false;
|
||||
}
|
||||
|
||||
if ("_".equals(sElement)) {
|
||||
upperNext = true;
|
||||
sb.append(" ");
|
||||
} else {
|
||||
sb.append(sElement);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static boolean compareNullables(@Nullable Object obj1, @Nullable Object obj2) {
|
||||
if (obj1 == null) {
|
||||
if (obj2 == null) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return obj1.equals(obj2);
|
||||
}
|
||||
}
|
||||
|
||||
public static String nullParam(Object obj) {
|
||||
return obj == null ? "" : obj.toString();
|
||||
}
|
||||
|
||||
public static <T> String getRegistryName(T obj, IForgeRegistry<T> registry) {
|
||||
return obj == null ? "" : registry.getKey(obj).toString();
|
||||
}
|
||||
|
||||
public static <T extends Tag> T getOrSupply(CompoundTag compTag, String name, Supplier<T> tag) {
|
||||
return getOrDefaultTag(compTag, name, tag.get());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Tag> T getOrDefaultTag(CompoundTag compTag, String name, T tag) {
|
||||
if (compTag.contains(name)) {
|
||||
return (T)compTag.get(name);
|
||||
}
|
||||
|
||||
compTag.put(name, tag);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static <T> boolean isParsableAllowingMinus(String s, Function<String, T> parser) {
|
||||
if ("-".equals(s)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
parser.apply(s);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> boolean isParsable(String s, Function<String, T> parser) {
|
||||
try {
|
||||
parser.apply(s);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> String valueOfOmittingType(T value) {
|
||||
try {
|
||||
return String.valueOf(value).replaceAll("[df]", "");
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T parseOrGet(String value, Function<String, T> parseFunction, T defaultValue) {
|
||||
try {
|
||||
return parseFunction.apply(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static <K, V> Set<Pair<K, V>> mapEntryToPair(Set<Map.Entry<K, V>> entrySet) {
|
||||
return entrySet.stream().map((entry) -> Pair.of(entry.getKey(), entry.getValue())).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T> List<T> remove(Collection<T> collection, T object) {
|
||||
List<T> copied = new ArrayList<> (collection);
|
||||
copied.remove(object);
|
||||
return copied;
|
||||
}
|
||||
|
||||
public static <T extends Enum<T>> T enumValueOfOrNull(Class<T> enumCls, String enumName) {
|
||||
try {
|
||||
return Enum.valueOf(enumCls, enumName.toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonObject convertToJsonObject(CompoundTag compoundtag) {
|
||||
JsonObject root = CompoundTag.CODEC.encodeStart(JsonOps.INSTANCE, compoundtag).get().left().get().getAsJsonObject();
|
||||
|
||||
for (Map.Entry<String, Tag> entry : compoundtag.tags.entrySet()) {
|
||||
if (entry.getValue() instanceof ByteTag byteTag && (byteTag.getAsByte() == 0 || byteTag.getAsByte() == 1)) {
|
||||
root.remove(entry.getKey());
|
||||
root.addProperty(entry.getKey(), byteTag.getAsByte() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public static String toLowerCase(String s) {
|
||||
return s.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public static String toUpperCase(String s) {
|
||||
return s.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public static String getBytesSHA256Hash(byte[] bytes) {
|
||||
String hashString = "";
|
||||
|
||||
try {
|
||||
MessageDigest sh = MessageDigest.getInstance("SHA-256");
|
||||
sh.update(bytes);
|
||||
byte byteData[] = sh.digest();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
for (int i = 0; i < byteData.length; i++) {
|
||||
sb.append(Integer.toString((byteData[i] & 0xFF) + 0x100, 16).substring(1));
|
||||
}
|
||||
|
||||
hashString = sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
hashString = null;
|
||||
}
|
||||
|
||||
return hashString;
|
||||
}
|
||||
|
||||
public static int parseCharacterToNumber(char c) {
|
||||
if (c < '0' || c > '9') {
|
||||
throw new IllegalArgumentException(c + "is not a character represents number");
|
||||
}
|
||||
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
public static <T> T orElse(T value, Supplier<T> defaultVal) {
|
||||
Objects.requireNonNull(defaultVal);
|
||||
|
||||
return value == null ? defaultVal.get() : value;
|
||||
}
|
||||
|
||||
public static CompoundTag parseTagOrThrow(JsonElement jsonElement) {
|
||||
try {
|
||||
return TagParser.parseTag(jsonElement.toString());
|
||||
} catch (CommandSyntaxException e) {
|
||||
throw new RuntimeException("Can't parse element:", e);
|
||||
}
|
||||
}
|
||||
|
||||
private ParseUtil() {}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.util.datastruct;
|
||||
|
||||
import net.minecraft.core.IdMapper;
|
||||
|
||||
public class ClearableIdMapper<I> extends IdMapper<I> {
|
||||
public ClearableIdMapper() {
|
||||
super(512);
|
||||
}
|
||||
|
||||
public ClearableIdMapper(int size) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.tToId.clear();
|
||||
this.idToT.clear();
|
||||
this.nextId = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.util.datastruct;
|
||||
|
||||
public class ModifiablePair<F, S> {
|
||||
public static <F, S> ModifiablePair<F, S> of(F first, S second) {
|
||||
return new ModifiablePair<>(first, second);
|
||||
}
|
||||
|
||||
private F first;
|
||||
private S second;
|
||||
|
||||
private ModifiablePair(F first, S second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public F getFirst() {
|
||||
return this.first;
|
||||
}
|
||||
|
||||
public S getSecond() {
|
||||
return this.second;
|
||||
}
|
||||
|
||||
public void setFirst(F first) {
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public void setSecond(S second) {
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.util.datastruct;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.tiedup.remake.rig.util.datastruct.TypeFlexibleHashMap.TypeKey;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class TypeFlexibleHashMap<A extends TypeKey<?>> extends HashMap<A, Object> {
|
||||
final boolean immutable;
|
||||
|
||||
public TypeFlexibleHashMap(boolean immutable) {
|
||||
this.immutable = immutable;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T put(TypeKey<T> typeKey, T val) {
|
||||
if (this.immutable) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
return (T)super.put((A)typeKey, val);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(A typeKey) {
|
||||
return (T)super.get(typeKey);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getOrDefault(A typeKey) {
|
||||
return (T)super.getOrDefault(typeKey, typeKey.defaultValue());
|
||||
}
|
||||
|
||||
public interface TypeKey<T> {
|
||||
T defaultValue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user