Skip to content

Commit

Permalink
api: add RuneLiteObjectController
Browse files Browse the repository at this point in the history
  • Loading branch information
LlemonDuck authored and Adam- committed Jan 1, 2025
1 parent 5346024 commit f654e1c
Show file tree
Hide file tree
Showing 5 changed files with 520 additions and 44 deletions.
22 changes: 22 additions & 0 deletions runelite-api/src/main/java/net/runelite/api/Animation.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public interface Animation
*/
int getId();

/**
* Is this animation a newer-style "maya" animation
*/
boolean isMayaAnim();

/**
* Get how many distinct frames this animation has.
*
Expand All @@ -53,4 +58,21 @@ public interface Animation
* @see #getRestartMode()
*/
void setRestartMode(int restartMode);

/**
* How many frames the animation lasts
*/
int getDuration();

/**
* How many frames to go back when looping
*/
int getFrameStep();

/**
* How many ticks each frame is.
*
* {@code null} for {@link #isMayaAnim()} animations
*/
int[] getFrameLengths();
}
185 changes: 185 additions & 0 deletions runelite-api/src/main/java/net/runelite/api/AnimationController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright (c) 2024, LlemonDuck <napkinorton@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.api;

import java.util.function.Consumer;
import java.util.function.IntPredicate;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.experimental.Accessors;

@Accessors(chain = true)
public class AnimationController
{
private final Client client;

@Getter
@Nullable
private Animation animation;

@Setter
@NonNull
private Consumer<AnimationController> onFinished = AnimationController::loop;

@Getter
@Setter
private int frame;

@Getter
@Setter
private int elapsedTicksThisFrame;

public AnimationController(Client client, int animationID)
{
this(client, client.loadAnimation(animationID));
}

public AnimationController(Client client, Animation animation)
{
this.client = client;
setAnimation(animation);
}

public void setAnimation(@Nullable Animation animation)
{
this.animation = animation;
reset();
}

public void reset()
{
frame = 0;
elapsedTicksThisFrame = 0;
}

public void loop()
{
if (animation == null)
{
return;
}

frame -= animation.getFrameStep();
if (frame < 0 || frame >= animation.getDuration())
{
frame = 0;
}
}

public void tick(int ticks)
{
outer:
for (; ; )
{
if (animation == null)
{
return;
}

if (animation.isMayaAnim())
{
frame += ticks;
ticks = 0;
if (frame >= animation.getDuration())
{
onFinished.accept(this);
if (animation != null && frame < animation.getDuration())
{
continue;
}
}
}
else
{
int[] frameLengths = animation.getFrameLengths();
if (frameLengths == null)
{
return;
}

elapsedTicksThisFrame += ticks;
ticks = 0;

for (; ; )
{
if (frame >= frameLengths.length)
{
onFinished.accept(this);
continue outer;
}

if (elapsedTicksThisFrame > frameLengths[frame])
{
elapsedTicksThisFrame -= frameLengths[frame];
frame++;
}
else
{
break;
}
}
}

return;
}
}

public Model animate(Model model)
{
return animate(model, null);
}

public Model animate(Model model, @Nullable AnimationController other)
{
if (other != null)
{
return client.applyTransformations(model, animation, getPackedFrame(), other.animation, other.getPackedFrame());
}
else
{
return client.applyTransformations(model, animation, getPackedFrame(), null, 0);
}
}

private int getPackedFrame()
{
if (animation == null)
{
return 0;
}

IntPredicate interpFilter = client.getAnimationInterpolationFilter();
if (interpFilter == null || !interpFilter.test(animation.getId()))
{
return frame;
}

return Integer.MIN_VALUE
| elapsedTicksThisFrame << 16
| frame;
}
}
23 changes: 23 additions & 0 deletions runelite-api/src/main/java/net/runelite/api/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,21 @@ public interface Client extends OAuthApi, GameEngine
*/
RuneLiteObject createRuneLiteObject();

/**
* Registers a new {@link RuneLiteObjectController} to its corresponding {@link WorldView}.
*/
void registerRuneLiteObject(RuneLiteObjectController controller);

/**
* Removes a new {@link RuneLiteObjectController} from its corresponding {@link WorldView}.
*/
void removeRuneLiteObject(RuneLiteObjectController controller);

/**
* Checks whether a {@link RuneLiteObjectController} is registered to any {@link WorldView}.
*/
boolean isRuneLiteObjectRegistered(RuneLiteObjectController controller);

/**
* Loads an unlit model from the cache. The returned model shares
* data such as faces, face colors, face transparencies, and vertex points with
Expand Down Expand Up @@ -2357,4 +2372,12 @@ default Tile getSelectedSceneTile()
{
return getTopLevelWorldView().getSelectedSceneTile();
}

/**
* Applies an animation to a Model. The returned model is shared and shouldn't be used
* after any other call to applyTransformations, including calls made by the client internally.
* Vertices are cloned from the source model. Face transparencies are copied if either animation
* animates transparency, otherwise it will share a reference. All other fields share a reference.
*/
Model applyTransformations(Model model, @Nullable Animation animA, int frameA, @Nullable Animation animB, int frameB);
}
Loading

0 comments on commit f654e1c

Please sign in to comment.