diff --git a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java index 797ad33..f42c112 100644 --- a/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java +++ b/core/src/main/java/com/zigythebird/playeranimcore/animation/AnimationData.java @@ -24,55 +24,132 @@ package com.zigythebird.playeranimcore.animation; +import com.zigythebird.playeranimcore.PlayerAnimLib; +import com.zigythebird.playeranimcore.data.DataTicket; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +//TODO Move to the data folder next MC breaking change public class AnimationData { - private float velocity; - private float partialTick; - private final boolean isFirstPersonPass; - - public AnimationData(float velocity, float partialTick, boolean isFirstPersonPass) { - this.velocity = velocity; - this.partialTick = partialTick; - this.isFirstPersonPass = isFirstPersonPass; + public static final DataTicket PARTIAL_TICK = DataTicket.create("partial_tick", Float.class); + public static final DataTicket VELOCITY = DataTicket.create("velocity", Float.class); + public static final DataTicket IS_FIRST_PERSON_PASS = DataTicket.create("first_person_pass", Boolean.class); + + private final Map, Object> data; + + public AnimationData() { + this.data = new Reference2ObjectOpenHashMap<>(); + } + + AnimationData(Map, Object> data) { + this.data = data; } /** * Gets the fractional value of the current game tick that has passed in rendering */ public float getPartialTick() { - return this.partialTick; + return this.getOrDefaultData(PARTIAL_TICK, 0f); } public float getVelocity() { - return this.velocity; + return this.getOrDefaultData(VELOCITY, 0f); } public boolean isFirstPersonPass() { - return this.isFirstPersonPass; + return this.getOrDefaultData(IS_FIRST_PERSON_PASS, false); } /** - * Helper to determine if the player is moving. + * Helper to determine if the entity is moving. */ public boolean isMoving() { - return this.velocity > 0.015F; + return this.getVelocity() > 0.015F; } /** * The less strict counterpart of the method above. */ public boolean isMovingLenient() { - return this.velocity > 1.0E-6F; + return this.getVelocity() > 1.0E-6F; } public void setVelocity(float velocity) { - this.velocity = velocity; + this.addData(VELOCITY, velocity); } public void setPartialTick(float partialTick) { - this.partialTick = partialTick; + this.addData(PARTIAL_TICK, partialTick); + } + + /** + * Add data to the RenderState + * @param dataTicket The DataTicket identifying the data + * @param data The associated data + */ + public void addData(DataTicket dataTicket, @Nullable D data) { + this.data.put(dataTicket, data); + } + + /** + * @return Whether the RenderState has data associated with the given {@link DataTicket} + */ + public boolean hasData(DataTicket dataTicket) { + return this.data.containsKey(dataTicket); + } + + /** + * Get previously set data on the RenderState by its associated {@link DataTicket}. + *

+ * Note that you should NOT be attempting to retrieve data you don't know exists.
+ * Use {@link #hasData(DataTicket)} if unsure + * + * @param dataTicket The DataTicket associated with the data + * @return The data contained on this RenderState, null if the data is set to null, or an exception if the data doesn't exist + */ + public @Nullable D getData(DataTicket dataTicket) { + Object data = this.data.get(dataTicket); + + if (data == null && !hasData(dataTicket)) + throw new IllegalArgumentException("Attempted to retrieve data from AnimationData that does not exist. Check your code!"); + + try { + return (D)data; + } + catch (ClassCastException ex) { + PlayerAnimLib.LOGGER.error("Attempted to retrieve incorrectly typed data from AnimationData. Possibly a mod or DataTicket conflict? Expected: {}, found data type {}", dataTicket, data.getClass().getName(), ex); + + throw ex; + } + } + + /** + * Get previously set data by its associated {@link DataTicket}, + * or a default value if the data does not exist + * + * @param dataTicket The DataTicket associated with the data + * @param defaultValue The fallback value if no data has been set for the given DataTicket + * @return The data contained on this RenderState, null if the data is set to null, or {@code defaultValue} if not present + */ + public D getOrDefaultData(DataTicket dataTicket, @Nullable D defaultValue) { + Object data = this.data.get(dataTicket); + + if (data == null && !hasData(dataTicket)) + return defaultValue; + + try { + return (D)data; + } + catch (ClassCastException ex) { + PlayerAnimLib.LOGGER.error("Attempted to retrieve incorrectly typed data from AnimationData. Possibly a mod or DataTicket conflict? Expected: {}, found data type {}", dataTicket, data.getClass().getName(), ex); + + return defaultValue; + } } public AnimationData copy() { - return new AnimationData(velocity, partialTick, isFirstPersonPass); + return new AnimationData(new Reference2ObjectOpenHashMap<>(this.data)); } } diff --git a/core/src/main/java/com/zigythebird/playeranimcore/data/DataTicket.java b/core/src/main/java/com/zigythebird/playeranimcore/data/DataTicket.java new file mode 100644 index 0000000..fb13b7c --- /dev/null +++ b/core/src/main/java/com/zigythebird/playeranimcore/data/DataTicket.java @@ -0,0 +1,88 @@ +/* + * MIT License + * + * Copyright (c) 2024 GeckoLib + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.zigythebird.playeranimcore.data; + +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +import java.util.Map; +import java.util.Objects; + +/** + * Ticket object to define a typed data object + */ +public class DataTicket { + static final Map, String>, DataTicket> IDENTITY_CACHE = new Object2ObjectOpenHashMap<>(); + + private final String id; + private final Class objectType; + + /** + * @see #create(String, Class) + */ + DataTicket(String id, Class objectType) { + this.id = id; + this.objectType = objectType; + } + + /** + * Create a new DataTicket for a given ID and object type + * Please include a namespace in your ID like namespace mod_id:name to avoid conflicts with other mods. + *

+ * This DataTicket should then be stored statically somewhere and re-used. + */ + public static DataTicket create(String id, Class objectType) { + return (DataTicket)IDENTITY_CACHE.computeIfAbsent(Pair.of(objectType, id), pair -> new DataTicket<>(id, objectType)); + } + + public String id() { + return this.id; + } + + public Class objectType() { + return this.objectType; + } + + @Override + public int hashCode() { + return Objects.hash(this.id, this.objectType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + + if (!(obj instanceof DataTicket other)) + return false; + + return this.objectType == other.objectType && this.id.equals(other.id); + } + + @Override + public String toString() { + return "DataTicket{" + this.id + ": " + this.objectType.getName() + "}"; + } +} \ No newline at end of file diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAnimatedAvatar.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAnimatedAvatar.java index e1fa6e3..1279509 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAnimatedAvatar.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAnimatedAvatar.java @@ -1,10 +1,17 @@ package com.zigythebird.playeranim.accessors; import com.zigythebird.playeranim.animation.AvatarAnimManager; +import com.zigythebird.playeranimcore.animation.AnimationData; import com.zigythebird.playeranimcore.animation.layered.IAnimation; import net.minecraft.resources.Identifier; +import org.jetbrains.annotations.ApiStatus; public interface IAnimatedAvatar { AvatarAnimManager playerAnimLib$getAnimManager(); IAnimation playerAnimLib$getAnimation(Identifier id); + + AnimationData playerAnimlib$getAnimData(); + + @ApiStatus.Internal + boolean playerAnimLib$isAwaitingTick(); } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAvatarAnimationState.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAvatarAnimationState.java index cb0d5b4..b9a3434 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAvatarAnimationState.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/IAvatarAnimationState.java @@ -1,15 +1,20 @@ package com.zigythebird.playeranim.accessors; import com.zigythebird.playeranim.animation.AvatarAnimManager; +import com.zigythebird.playeranimcore.animation.AnimationData; +import org.jetbrains.annotations.ApiStatus; /** * Extension of PlayerRenderState */ public interface IAvatarAnimationState { boolean playerAnimLib$isFirstPersonPass(); - void playerAnimLib$setFirstPersonPass(boolean value); - // AnimationApplier animationApplier + @ApiStatus.Internal void playerAnimLib$setAnimManager(AvatarAnimManager manager); AvatarAnimManager playerAnimLib$getAnimManager(); + + @ApiStatus.Internal + void playerAnimLib$setAnimData(AnimationData data); + AnimationData playerAnimLib$getAnimData(); } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/ILevelRenderState.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/ILevelRenderState.java new file mode 100644 index 0000000..e0d56d2 --- /dev/null +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/accessors/ILevelRenderState.java @@ -0,0 +1,7 @@ +package com.zigythebird.playeranim.accessors; + +import java.util.List; + +public interface ILevelRenderState { + List playerAnimLib$getAnimatedAvatarsToTick(); +} diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java index 9ae7d31..1c53974 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/animation/AvatarAnimManager.java @@ -1,6 +1,6 @@ package com.zigythebird.playeranim.animation; -import com.zigythebird.playeranim.accessors.IAnimatedAvatar; +import com.zigythebird.playeranim.accessors.IAvatarAnimationState; import com.zigythebird.playeranim.util.RenderUtil; import com.zigythebird.playeranimcore.animation.AnimationData; import com.zigythebird.playeranimcore.animation.layered.AnimationStack; @@ -10,8 +10,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.model.geom.ModelPart; import net.minecraft.client.model.geom.PartPose; -import net.minecraft.world.entity.Avatar; -import net.minecraft.world.phys.Vec3; +import net.minecraft.client.renderer.entity.state.AvatarRenderState; import org.jetbrains.annotations.ApiStatus; /** @@ -20,90 +19,55 @@ * Generally speaking, a single working-instance of a player will have a single instance of {@code PlayerAnimManager} associated with it */ public class AvatarAnimManager extends AnimationStack { - private final Avatar avatar; - private float lastUpdateTime; - private boolean isFirstTick = true; - private float tickDelta; - - public AvatarAnimManager(Avatar avatar) { - this.avatar = avatar; - } - /** - * Tick and apply transformations to the model based on the current state of the {@link com.zigythebird.playeranimcore.animation.layered.AnimationContainer} - * - * @param playerAnimManager The PlayerAnimManager instance being used for this animation processor - * @param state An {@link AnimationData} instance applied to this render frame - */ - public void tickAnimation(AnimationStack playerAnimManager, AnimationData state) { - playerAnimManager.getLayers().removeIf(pair -> pair.right() == null || pair.right().canRemove()); - for (Pair pair : playerAnimManager.getLayers()) { - IAnimation animation = pair.right(); - - if (animation.isActive()) - animation.setupAnim(state.copy()); - } - finishFirstTick(); - } + public AvatarAnimManager() {} public float getLastUpdateTime() { return this.lastUpdateTime; } + @ApiStatus.Internal public void updatedAt(float updateTime) { this.lastUpdateTime = updateTime; } - public boolean isFirstTick() { - return this.isFirstTick; - } - - protected void finishFirstTick() { - this.isFirstTick = false; - } - - public float getTickDelta() { - return this.tickDelta; - } - - /** - * If you touch this, you're a horrible person. - */ - @ApiStatus.Internal - public void setTickDelta(float tickDelta) { - this.tickDelta = tickDelta; - } - public void updatePart(ModelPart part, PlayerAnimBone bone) { PartPose initialPose = part.getInitialPose(); bone = this.get3DTransform(bone); RenderUtil.translatePartToBone(part, bone, initialPose); } - public void handleAnimations(float partialTick, boolean fullTick, boolean isFirstPersonPass) { - Vec3 velocity = avatar.getDeltaMovement(); - - AvatarAnimManager animatableManager = ((IAnimatedAvatar)avatar).playerAnimLib$getAnimManager(); - int currentTick = avatar.tickCount; + @ApiStatus.Internal + public void handleAnimations(IAvatarAnimationState state) { + if (state instanceof AvatarRenderState avatarRenderState) { + float currentFrameTime = avatarRenderState.ageInTicks; - float currentFrameTime = currentTick + partialTick; + if (currentFrameTime == this.getLastUpdateTime()) + return; - AnimationData animationData = new AnimationData((float) ((Math.abs(velocity.x) + Math.abs(velocity.z)) / 2f), partialTick, isFirstPersonPass); + AnimationData animationData = state.playerAnimLib$getAnimData(); + // I have to do this due to floating-point error nonsense + animationData.setPartialTick(currentFrameTime - (int)currentFrameTime); - if (fullTick) animatableManager.tick(animationData.copy()); + if (!Minecraft.getInstance().isPaused()) { + for (int i = 0; i < (int)currentFrameTime - (int)this.getLastUpdateTime(); i++) + this.tick(animationData.copy()); - if (!animatableManager.isFirstTick() && currentFrameTime == animatableManager.getLastUpdateTime()) - return; + this.updatedAt(currentFrameTime); + } - if (!Minecraft.getInstance().isPaused()) { - animatableManager.updatedAt(currentFrameTime); + this.setupAnimation(animationData); } - - this.tickAnimation(animatableManager, animationData); } - public Avatar getAvatar() { - return avatar; + protected void setupAnimation(AnimationData state) { + this.getLayers().removeIf(pair -> pair.right() == null || pair.right().canRemove()); + for (Pair pair : this.getLayers()) { + IAnimation animation = pair.right(); + + if (animation.isActive()) + animation.setupAnim(state.copy()); + } } } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarMixin.java index d7b77ec..c0306f0 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarMixin.java @@ -28,7 +28,7 @@ import com.zigythebird.playeranim.animation.AvatarAnimManager; import com.zigythebird.playeranim.api.PlayerAnimationAccess; import com.zigythebird.playeranim.api.PlayerAnimationFactory; -import com.zigythebird.playeranim.util.ClientUtil; +import com.zigythebird.playeranimcore.animation.AnimationData; import com.zigythebird.playeranimcore.animation.layered.IAnimation; import net.minecraft.resources.Identifier; import net.minecraft.world.entity.Avatar; @@ -51,6 +51,10 @@ public abstract class AvatarMixin extends LivingEntity implements IAnimatedAvata private final Map playerAnimLib$modAnimationData = new HashMap<>(); @Unique private final AvatarAnimManager playerAnimLib$animationManager = playerAnimLib$createAnimationStack(); + @Unique + private final AnimationData playerAnimLib$data = new AnimationData(); + @Unique + private boolean playerAnimLib$awaitingTick = false; protected AvatarMixin(EntityType entityType, Level level) { super(entityType, level); @@ -58,7 +62,7 @@ protected AvatarMixin(EntityType entityType, Level level @Unique private AvatarAnimManager playerAnimLib$createAnimationStack() { - AvatarAnimManager manager = new AvatarAnimManager((Avatar) (Object) this); + AvatarAnimManager manager = new AvatarAnimManager(); PlayerAnimationFactory.ANIMATION_DATA_FACTORY.prepareAnimations((Avatar) (Object) this, manager, playerAnimLib$modAnimationData); PlayerAnimationAccess.REGISTER_ANIMATION_EVENT.invoker().registerAnimation((Avatar) (Object) this, manager); return manager; @@ -75,6 +79,18 @@ protected AvatarMixin(EntityType entityType, Level level return null; } + @Override + public AnimationData playerAnimlib$getAnimData() { + return this.playerAnimLib$data; + } + + @Override + public boolean playerAnimLib$isAwaitingTick() { + boolean value = this.playerAnimLib$awaitingTick; + this.playerAnimLib$awaitingTick = false; + return value; + } + @Intrinsic @Override public void tick() { @@ -82,9 +98,8 @@ public void tick() { } @SuppressWarnings({"MixinAnnotationTarget", "UnresolvedMixinReference"}) - @Inject(method = {"tick", "method_5773"}, at = @At("TAIL"), remap = false) + @Inject(method = {"tick", "method_5773"}, at = @At("HEAD"), remap = false) private void tick(CallbackInfo ci) { - if (!this.level().isClientSide()) return; - this.playerAnimLib$animationManager.handleAnimations(0, true, ClientUtil.shouldBeFirstPersonPass()); + this.playerAnimLib$awaitingTick = true; } } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRenderStateMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRenderStateMixin.java index d6b118a..7688c25 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRenderStateMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRenderStateMixin.java @@ -2,6 +2,7 @@ import com.zigythebird.playeranim.accessors.IAvatarAnimationState; import com.zigythebird.playeranim.animation.AvatarAnimManager; +import com.zigythebird.playeranimcore.animation.AnimationData; import net.minecraft.client.renderer.entity.state.AvatarRenderState; import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Mixin; @@ -10,19 +11,14 @@ @Mixin(AvatarRenderState.class) public class AvatarRenderStateMixin implements IAvatarAnimationState { @Unique - boolean playerAnimLib$isFirstPersonPass = false; + AvatarAnimManager playerAnimLib$avatarAnimManager = null; @Unique - AvatarAnimManager playerAnimLib$avatarAnimManager = null; + AnimationData playerAnimLib$data = null; @Override public boolean playerAnimLib$isFirstPersonPass() { - return playerAnimLib$isFirstPersonPass; - } - - @Override - public void playerAnimLib$setFirstPersonPass(boolean value) { - playerAnimLib$isFirstPersonPass = value; + return this.playerAnimLib$data.isFirstPersonPass(); } @Override @@ -34,5 +30,15 @@ public class AvatarRenderStateMixin implements IAvatarAnimationState { public @NotNull AvatarAnimManager playerAnimLib$getAnimManager() { return this.playerAnimLib$avatarAnimManager; } + + @Override + public void playerAnimLib$setAnimData(AnimationData data) { + this.playerAnimLib$data = data; + } + + @Override + public AnimationData playerAnimLib$getAnimData() { + return this.playerAnimLib$data; + } } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRendererMixin.java index cd799c2..d0b99bb 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRendererMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/AvatarRendererMixin.java @@ -26,33 +26,26 @@ import com.zigythebird.playeranim.accessors.IAnimatedAvatar; import com.zigythebird.playeranim.accessors.IAvatarAnimationState; -import com.zigythebird.playeranim.animation.AvatarAnimManager; -import net.minecraft.client.Minecraft; -import net.minecraft.client.model.player.PlayerModel; -import net.minecraft.client.player.AbstractClientPlayer; -import net.minecraft.client.renderer.entity.EntityRendererProvider; -import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import com.zigythebird.playeranimcore.animation.AnimationData; import net.minecraft.client.renderer.entity.player.AvatarRenderer; import net.minecraft.client.renderer.entity.state.AvatarRenderState; import net.minecraft.world.entity.Avatar; +import net.minecraft.world.phys.Vec3; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(value = AvatarRenderer.class, priority = 2000) -public abstract class AvatarRendererMixin extends LivingEntityRenderer { - public AvatarRendererMixin(EntityRendererProvider.Context context, PlayerModel model, float shadowRadius) { - super(context, model, shadowRadius); - } - +public abstract class AvatarRendererMixin { @Inject(method = "extractRenderState(Lnet/minecraft/world/entity/Avatar;Lnet/minecraft/client/renderer/entity/state/AvatarRenderState;F)V", at = @At("HEAD")) private void modifyRenderState(Avatar avatar, AvatarRenderState avatarRenderState, float f, CallbackInfo ci) { - if (avatar instanceof IAnimatedAvatar abstractClientPlayer) { - AvatarAnimManager animation = abstractClientPlayer.playerAnimLib$getAnimManager(); - animation.setTickDelta(f); - - ((IAvatarAnimationState)avatarRenderState).playerAnimLib$setAnimManager(animation); + if (avatar instanceof IAnimatedAvatar animatedAvatar && avatarRenderState instanceof IAvatarAnimationState avatarAnimationState) { + avatarAnimationState.playerAnimLib$setAnimManager(animatedAvatar.playerAnimLib$getAnimManager()); + Vec3 velocity = avatar.getDeltaMovement(); + AnimationData data = animatedAvatar.playerAnimlib$getAnimData().copy(); + data.setVelocity((float) ((Math.abs(velocity.x) + Math.abs(velocity.z)) / 2f)); + avatarAnimationState.playerAnimLib$setAnimData(data); } } } diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRenderStateMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRenderStateMixin.java new file mode 100644 index 0000000..b249d5b --- /dev/null +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRenderStateMixin.java @@ -0,0 +1,29 @@ +package com.zigythebird.playeranim.mixin; + +import com.zigythebird.playeranim.accessors.IAvatarAnimationState; +import com.zigythebird.playeranim.accessors.ILevelRenderState; +import net.minecraft.client.renderer.state.LevelRenderState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.List; + +@Mixin(LevelRenderState.class) +public class LevelRenderStateMixin implements ILevelRenderState { + @Unique + private final List playerAnimLib$animatedAvatarsToTick = new ArrayList<>(); + + @Override + public List playerAnimLib$getAnimatedAvatarsToTick() { + return playerAnimLib$animatedAvatarsToTick; + } + + @Inject(method = "reset", at = @At("TAIL")) + private void reset(CallbackInfo ci) { + this.playerAnimLib$animatedAvatarsToTick.clear(); + } +} diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java new file mode 100644 index 0000000..e1a937e --- /dev/null +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LevelRendererMixin.java @@ -0,0 +1,57 @@ +package com.zigythebird.playeranim.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.blaze3d.vertex.PoseStack; +import com.zigythebird.playeranim.accessors.IAnimatedAvatar; +import com.zigythebird.playeranim.accessors.IAvatarAnimationState; +import com.zigythebird.playeranim.accessors.ILevelRenderState; +import net.minecraft.client.Camera; +import net.minecraft.client.DeltaTracker; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.renderer.entity.state.EntityRenderState; +import net.minecraft.client.renderer.state.LevelRenderState; +import net.minecraft.world.TickRateManager; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LevelRenderer.class) +public abstract class LevelRendererMixin { + @Shadow protected abstract EntityRenderState extractEntity(Entity entity, float partialTick); + + @Shadow @Final private Minecraft minecraft; + + @Inject(method = "extractVisibleEntities", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) + private void doNotTickIfRendering(Camera camera, Frustum frustum, DeltaTracker deltaTracker, LevelRenderState renderState, CallbackInfo ci, @Local Entity entity) { + //When the method is called it will return false from now + //So we won't do an extra tick when the renderer is already about to do one + if (entity instanceof IAnimatedAvatar animatedAvatar) animatedAvatar.playerAnimLib$isAwaitingTick(); + } + + @Inject(method = "extractVisibleEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/EntityRenderDispatcher;shouldRender(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/client/renderer/culling/Frustum;DDD)Z")) + private void tickIfNotRendering(Camera camera, Frustum frustum, DeltaTracker deltaTracker, LevelRenderState renderState, CallbackInfo ci, @Local Entity entity) { + if (entity instanceof IAnimatedAvatar animatedAvatar && animatedAvatar.playerAnimLib$isAwaitingTick() + && renderState instanceof ILevelRenderState levelRenderState) { + TickRateManager tickRateManager = this.minecraft.level.tickRateManager(); + if (this.extractEntity(entity, deltaTracker.getGameTimeDeltaPartialTick(!tickRateManager.isEntityFrozen(entity))) + instanceof IAvatarAnimationState avatarAnimationState) + levelRenderState.playerAnimLib$getAnimatedAvatarsToTick().add(avatarAnimationState); + } + } + + @Inject(method = "submitEntities", at = @At(value = "HEAD")) + private void tickAnimatedAvatars(PoseStack poseStack, LevelRenderState renderState, SubmitNodeCollector nodeCollector, CallbackInfo ci) { + if (renderState instanceof ILevelRenderState levelRenderState) { + for (IAvatarAnimationState state : levelRenderState.playerAnimLib$getAnimatedAvatarsToTick()) { + state.playerAnimLib$getAnimManager().handleAnimations(state); + } + } + } +} diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java index 327727e..b124345 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/LivingEntityRendererMixin.java @@ -41,10 +41,10 @@ public class LivingEntityRendererMixin { @Inject(method = "submit(Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;Lnet/minecraft/client/renderer/state/CameraRenderState;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/LivingEntityRenderer;scale(Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;)V")) private void doTranslations(S livingEntityRenderState, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CameraRenderState cameraRenderState, CallbackInfo ci) { - if (livingEntityRenderState instanceof IAvatarAnimationState avatarRenderState) { - var animationPlayer = avatarRenderState.playerAnimLib$getAnimManager(); + if (livingEntityRenderState instanceof IAvatarAnimationState animationRenderState) { + var animationPlayer = ((IAvatarAnimationState)animationRenderState).playerAnimLib$getAnimManager(); if (animationPlayer != null && animationPlayer.isActive()) { - avatarRenderState.playerAnimLib$getAnimManager().handleAnimations(animationPlayer.getTickDelta(), false, avatarRenderState.playerAnimLib$isFirstPersonPass()); + animationPlayer.handleAnimations(animationRenderState); poseStack.scale(-1.0F, -1.0F, 1.0F); //These are additive properties diff --git a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java index caa415a..7d99ffa 100644 --- a/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java +++ b/minecraft/common/src/main/java/com/zigythebird/playeranim/mixin/firstPerson/LevelRendererMixin.java @@ -30,6 +30,7 @@ import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; import com.zigythebird.playeranim.accessors.IAvatarAnimationState; import com.zigythebird.playeranim.util.ClientUtil; +import com.zigythebird.playeranimcore.animation.AnimationData; import net.minecraft.client.Camera; import net.minecraft.client.DeltaTracker; import net.minecraft.client.renderer.LevelRenderer; @@ -56,7 +57,7 @@ private boolean fakeThirdPersonMode(boolean original, @Local(argsOnly = true) Ca @Inject(method = "extractVisibleEntities", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) private void setRenderStateToFirstPerson(Camera camera, Frustum frustum, DeltaTracker deltaTracker, LevelRenderState renderState, CallbackInfo ci, @Local Entity entity, @Local EntityRenderState entityRenderState, @Share("firstPerson") LocalBooleanRef isFirstPerson) { if (entity == camera.entity() && isFirstPerson.get()) { - ((IAvatarAnimationState) entityRenderState).playerAnimLib$setFirstPersonPass(true); + ((IAvatarAnimationState) entityRenderState).playerAnimLib$getAnimData().addData(AnimationData.IS_FIRST_PERSON_PASS, true); entityRenderState.shadowPieces.clear(); entityRenderState.shadowRadius = 0; } diff --git a/minecraft/common/src/main/resources/player_animation_library.mixins.json b/minecraft/common/src/main/resources/player_animation_library.mixins.json index 3a5f82e..f49c40b 100644 --- a/minecraft/common/src/main/resources/player_animation_library.mixins.json +++ b/minecraft/common/src/main/resources/player_animation_library.mixins.json @@ -10,6 +10,8 @@ "CapeModelAccessor", "ElytraLayerMixin", "ItemInHandLayerMixin", + "LevelRendererMixin", + "LevelRenderStateMixin", "LivingEntityRendererMixin", "PlayerCapeModelMixin", "PlayerModelMixin",