package software.bernie.geckolib.model; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import software.bernie.geckolib.GeckoLibConstants; import software.bernie.geckolib.animatable.GeoAnimatable; import software.bernie.geckolib.animatable.GeoReplacedEntity; import software.bernie.geckolib.animation.AnimatableManager; import software.bernie.geckolib.animation.Animation; import software.bernie.geckolib.animation.AnimationProcessor; import software.bernie.geckolib.animation.AnimationState; import software.bernie.geckolib.cache.GeckoLibCache; import software.bernie.geckolib.cache.object.BakedGeoModel; import software.bernie.geckolib.cache.object.GeoBone; import software.bernie.geckolib.constant.DataTickets; import software.bernie.geckolib.constant.dataticket.DataTicket; import software.bernie.geckolib.loading.object.BakedAnimations; import software.bernie.geckolib.renderer.GeoRenderer; import software.bernie.geckolib.util.RenderUtil; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.DoubleSupplier; import net.minecraft.class_1297; import net.minecraft.class_1921; import net.minecraft.class_2960; import net.minecraft.class_310; /** * Base class for all code-based model objects *

* All models registered to a {@link GeoRenderer} must be an instance of this or one of its subclasses * * @see GeckoLib Wiki - Models */ public abstract class GeoModel { private final AnimationProcessor processor = new AnimationProcessor<>(this); private BakedGeoModel currentModel = null; private double animTime; private double lastGameTickTime; private long lastRenderedInstance = -1; /** * Returns the resource path for the {@link BakedGeoModel} (model json file) to render based on the provided animatable */ public class_2960 getModelResource(T animatable, @Nullable GeoRenderer renderer) { return getModelResource(animatable); } /** * Returns the resource path for the {@link BakedGeoModel} (model json file) to render based on the provided animatable */ @Deprecated public abstract class_2960 getModelResource(T animatable); /** * Returns the resource path for the texture file to render based on the provided animatable */ public class_2960 getTextureResource(T animatable, @Nullable GeoRenderer renderer) { return getTextureResource(animatable); } /** * Returns the resource path for the texture file to render based on the provided animatable */ @Deprecated public abstract class_2960 getTextureResource(T animatable); /** * Returns the resource path for the {@link BakedAnimations} (animation json file) to use for animations based on the provided animatable */ public abstract class_2960 getAnimationResource(T animatable); /** * Returns the resource path for the {@link BakedAnimations} (animation json file) fallback locations in the event * your animation isn't present in the {@link #getAnimationResource(GeoAnimatable) primary resource}. *

* Should NOT be used as the primary animation resource path, and in general shouldn't be used * at all unless you know what you are doing */ public class_2960[] getAnimationResourceFallbacks(T animatable) { return new class_2960[0]; } /** * Override this and return true if Geckolib should crash when attempting to animate the model, but fails to find a bone *

* By default, GeckoLib will just gracefully ignore a missing bone, which might cause oddities with incorrect models or mismatching variables */ public boolean crashIfBoneMissing() { return false; } /** * Gets the default render type for this animatable, to be selected by default by the renderer using it * * @return Return the RenderType to use, or null to prevent the model rendering. Returning null will not prevent animation functions taking place */ @Nullable public class_1921 getRenderType(T animatable, class_2960 texture) { return class_1921.method_23578(texture); } /** * Get the baked geo model object used for rendering from the given resource path */ public BakedGeoModel getBakedModel(class_2960 location) { BakedGeoModel model = GeckoLibCache.getBakedModels().get(location); if (model == null) { if (!location.method_12832().contains("geo/")) throw GeckoLibConstants.exception(location, "Invalid model resource path provided - GeckoLib models must be placed in assets//geo/"); throw GeckoLibConstants.exception(location, "Unable to find model"); } if (model != this.currentModel) { this.processor.setActiveModel(model); this.currentModel = model; } return this.currentModel; } /** * Gets a bone from this model by name * * @param name The name of the bone * @return An {@link Optional} containing the {@link software.bernie.geckolib.cache.object.GeoBone} if one matches, otherwise an empty Optional */ public Optional getBone(String name) { return Optional.ofNullable(getAnimationProcessor().getBone(name)); } /** * Gets the loaded {@link Animation} for the given animation {@code name}, if it exists * * @param animatable The {@code GeoAnimatable} instance being referred to * @param name The name of the animation to retrieve * @return The {@code Animation} instance for the provided {@code name}, or null if none match */ @Nullable public Animation getAnimation(T animatable, String name) { class_2960 location = getAnimationResource(animatable); BakedAnimations bakedAnimations = GeckoLibCache.getBakedAnimations().get(location); Animation animation = bakedAnimations != null ? bakedAnimations.getAnimation(name) : null; if (animation != null) return animation; for (class_2960 fallbackLocation : getAnimationResourceFallbacks(animatable)) { bakedAnimations = GeckoLibCache.getBakedAnimations().get(location = fallbackLocation); animation = bakedAnimations != null ? bakedAnimations.getAnimation(name) : null; if (animation != null) return animation; } if (bakedAnimations == null) { if (!location.method_12832().contains("animations/")) throw GeckoLibConstants.exception(location, "Invalid animation resource path provided - GeckoLib animations must be placed in assets//animations/"); throw GeckoLibConstants.exception(location, "Unable to find animation file."); } return null; } /** * Gets the {@link AnimationProcessor} for this model. */ public AnimationProcessor getAnimationProcessor() { return this.processor; } /** * Add additional {@link DataTicket DataTickets} to the {@link AnimationState} to be handled by your animation handler at render time * * @param animatable The animatable instance currently being animated * @param instanceId The unique instance id of the animatable being animated * @param dataConsumer The DataTicket + data consumer to be added to the AnimationEvent */ public void addAdditionalStateData(T animatable, long instanceId, BiConsumer, T> dataConsumer) {} /** * This method is called once per render-frame for each {@link GeoAnimatable} being rendered *

* It is an internal method for automated animation parsing. Use {@link GeoModel#setCustomAnimations(GeoAnimatable, long, AnimationState)} for custom animation work */ @ApiStatus.Internal public void handleAnimations(T animatable, long instanceId, AnimationState animationState, float partialTick) { class_310 mc = class_310.method_1551(); AnimatableManager animatableManager = animatable.getAnimatableInstanceCache().getManagerForId(instanceId); Double currentTick = animationState.getData(DataTickets.TICK); if (currentTick == null) currentTick = animatable instanceof class_1297 entity ? (double)entity.field_6012 : RenderUtil.getCurrentTick(); if (animatableManager.getFirstTickTime() == -1) animatableManager.startedAt(currentTick + partialTick); double currentFrameTime = animatable instanceof class_1297 || animatable instanceof GeoReplacedEntity ? currentTick + partialTick : currentTick - animatableManager.getFirstTickTime(); boolean isReRender = !animatableManager.isFirstTick() && currentFrameTime == animatableManager.getLastUpdateTime(); if (isReRender && instanceId == this.lastRenderedInstance) return; if (!mc.method_1493() || animatable.shouldPlayAnimsWhileGamePaused()) { animatableManager.updatedAt(currentFrameTime); double lastUpdateTime = animatableManager.getLastUpdateTime(); this.animTime += lastUpdateTime - this.lastGameTickTime; this.lastGameTickTime = lastUpdateTime; } animationState.animationTick = this.animTime; this.lastRenderedInstance = instanceId; AnimationProcessor processor = getAnimationProcessor(); processor.preAnimationSetup(animationState, this.animTime); if (!processor.getRegisteredBones().isEmpty()) processor.tickAnimation(animatable, this, animatableManager, this.animTime, animationState, crashIfBoneMissing()); setCustomAnimations(animatable, instanceId, animationState); } /** * This method is called once per render-frame for each {@link GeoAnimatable} being rendered *

* Override to set custom animations (such as head rotation, etc) * * @param animatable The {@code GeoAnimatable} instance currently being rendered * @param instanceId The instance id of the {@code GeoAnimatable} * @param animationState An {@link AnimationState} instance created to hold animation data for the {@code animatable} for this method call */ public void setCustomAnimations(T animatable, long instanceId, AnimationState animationState) {} /** * This method is called once per render-frame for each {@link GeoAnimatable} being rendered *

* Use this method to set custom {@link software.bernie.geckolib.loading.math.value.Variable Variable} values via * {@link software.bernie.geckolib.loading.math.MathParser#setVariable(String, DoubleSupplier) MathParser.setVariable} * * @param animationState The AnimationState data for the current render frame * @param animTime The internal tick counter kept by the {@link AnimatableManager manager} for this animatable */ public void applyMolangQueries(AnimationState animationState, double animTime) {} }