diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 59948a5806..69e654b618 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -369,4 +369,16 @@ public interface Client extends GameEngine void setHintArrow(Player player); void setHintArrow(NPC npc); + + boolean isInterpolatePlayerAnimations(); + + void setInterpolatePlayerAnimations(boolean interpolate); + + boolean isInterpolateNpcAnimations(); + + void setInterpolateNpcAnimations(boolean interpolate); + + boolean isInterpolateObjectAnimations(); + + void setInterpolateObjectAnimations(boolean interpolate); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingConfig.java new file mode 100644 index 0000000000..7c4218658c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingConfig.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018, Adam + * 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.client.plugins.animsmoothing; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup( + keyName = AnimationSmoothingPlugin.CONFIG_GROUP, + name = "Smooth Animations", + description = "Configuration for the smooth animations plugin" +) +public interface AnimationSmoothingConfig extends Config +{ + + @ConfigItem( + keyName = "smoothPlayerAnimations", + name = "Smooth Player Animations", + description = "Configures whether the player animations are smooth or not", + position = 1 + ) + default boolean smoothPlayerAnimations() + { + return true; + } + + @ConfigItem( + keyName = "smoothNpcAnimations", + name = "Smooth NPC Animations", + description = "Configures whether the npc animations are smooth or not", + position = 2 + ) + default boolean smoothNpcAnimations() + { + return true; + } + + @ConfigItem( + keyName = "smoothObjectAnimations", + name = "Smooth Object Animations", + description = "Configures whether the object animations are smooth or not", + position = 3 + ) + default boolean smoothObjectAnimations() + { + return true; + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java new file mode 100644 index 0000000000..d5b994638d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018, Adam + * 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.client.plugins.animsmoothing; + +import com.google.common.eventbus.Subscribe; +import com.google.inject.Provides; +import net.runelite.api.Client; +import net.runelite.api.events.ConfigChanged; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import javax.inject.Inject; + +@PluginDescriptor( + name = "Animation Smoothing", + enabledByDefault = false +) +public class AnimationSmoothingPlugin extends Plugin +{ + static final String CONFIG_GROUP = "animationSmoothing"; + + @Inject + private Client client; + + @Inject + private AnimationSmoothingConfig config; + + @Provides + AnimationSmoothingConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(AnimationSmoothingConfig.class); + } + + @Override + protected void startUp() throws Exception + { + update(); + } + + @Override + protected void shutDown() throws Exception + { + client.setInterpolatePlayerAnimations(false); + client.setInterpolateNpcAnimations(false); + client.setInterpolateObjectAnimations(false); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals(CONFIG_GROUP)) + { + update(); + } + } + + private void update() + { + client.setInterpolatePlayerAnimations(config.smoothPlayerAnimations()); + client.setInterpolateNpcAnimations(config.smoothNpcAnimations()); + client.setInterpolateObjectAnimations(config.smoothObjectAnimations()); + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index 532d0e44c0..9167f2f0a0 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -95,6 +95,57 @@ public abstract class RSClientMixin implements RSClient @Shadow("clientInstance") private static RSClient client; + @Inject + private static boolean interpolatePlayerAnimations; + + @Inject + private static boolean interpolateNpcAnimations; + + @Inject + private static boolean interpolateObjectAnimations; + + @Inject + @Override + public boolean isInterpolatePlayerAnimations() + { + return interpolatePlayerAnimations; + } + + @Inject + @Override + public void setInterpolatePlayerAnimations(boolean interpolate) + { + interpolatePlayerAnimations = interpolate; + } + + @Inject + @Override + public boolean isInterpolateNpcAnimations() + { + return interpolateNpcAnimations; + } + + @Inject + @Override + public void setInterpolateNpcAnimations(boolean interpolate) + { + interpolateNpcAnimations = interpolate; + } + + @Inject + @Override + public boolean isInterpolateObjectAnimations() + { + return interpolateObjectAnimations; + } + + @Inject + @Override + public void setInterpolateObjectAnimations(boolean interpolate) + { + interpolateObjectAnimations = interpolate; + } + @Inject @Override public List getPlayers() diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSDynamicObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSDynamicObjectMixin.java new file mode 100644 index 0000000000..f9639b6309 --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSDynamicObjectMixin.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018, Adam + * 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.mixins; + +import net.runelite.api.mixins.Copy; +import net.runelite.api.mixins.FieldHook; +import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Replace; +import net.runelite.api.mixins.Shadow; +import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSDynamicObject; +import net.runelite.rs.api.RSModel; + +@Mixin(RSDynamicObject.class) +public abstract class RSDynamicObjectMixin implements RSDynamicObject +{ + @Shadow("clientInstance") + private static RSClient client; + + @Copy("getModel") + public abstract RSModel rs$getModel(); + + @Replace("getModel") + public RSModel rl$getModel() + { + try + { + // reset frame because it may have been set from the constructor + // it should be set again inside the getModel method + int animFrame = getAnimFrame(); + if (animFrame < 0) + { + setAnimFrame((animFrame ^ Integer.MIN_VALUE) & 0xFFFF); + } + return rs$getModel(); + } + finally + { + int animFrame = getAnimFrame(); + if (animFrame < 0) + { + setAnimFrame((animFrame ^ Integer.MIN_VALUE) & 0xFFFF); + } + } + } + + @FieldHook("animCycleCount") + @Inject + public void onAnimCycleCountChanged(int idx) + { + if (client.isInterpolateObjectAnimations()) + { + // sets the packed anim frame with the frame cycle + int objectFrameCycle = client.getGameCycle() - getAnimCycleCount(); + setAnimFrame(Integer.MIN_VALUE | objectFrameCycle << 16 | getAnimFrame()); + } + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java index 003591e200..93e9961476 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java @@ -36,6 +36,9 @@ import net.runelite.api.model.Jarvis; import net.runelite.api.model.Triangle; import net.runelite.api.model.Vertex; import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSFrame; +import net.runelite.rs.api.RSFrameMap; +import net.runelite.rs.api.RSFrames; import net.runelite.rs.api.RSModel; @Mixin(RSModel.class) @@ -95,6 +98,140 @@ public abstract class RSModelMixin implements RSModel return triangles; } + @Inject + public void interpolateFrames(RSFrames frames, int frameId, RSFrames nextFrames, int nextFrameId, int interval, + int intervalCount) + { + if (getVertexGroups() != null) + { + if (frameId != -1) + { + RSFrame frame = frames.getFrames()[frameId]; + RSFrameMap skin = frame.getSkin(); + RSFrame nextFrame = null; + if (nextFrames != null) + { + nextFrame = nextFrames.getFrames()[nextFrameId]; + if (nextFrame.getSkin() != skin) + { + nextFrame = null; + } + } + + client.setAnimOffsetX(0); + client.setAnimOffsetY(0); + client.setAnimOffsetZ(0); + + interpolateFrames(skin, frame, nextFrame, interval, intervalCount); + resetBounds(); + } + } + } + + @Inject + public void interpolateFrames(RSFrameMap skin, RSFrame frame, RSFrame nextFrame, int interval, int intervalCount) + { + if (nextFrame == null || interval == 0) + { + // if there is no next frame or interval then animate the model as we normally would + for (int i = 0; i < frame.getTransformCount(); i++) + { + int type = frame.getTransformTypes()[i]; + this.animate(skin.getTypes()[type], skin.getList()[type], frame.getTranslatorX()[i], + frame.getTranslatorY()[i], frame.getTranslatorZ()[i]); + } + } + else + { + int transformIndex = 0; + int nextTransformIndex = 0; + for (int i = 0; i < skin.getCount(); i++) + { + boolean frameValid = false; + if (transformIndex < frame.getTransformCount() + && frame.getTransformTypes()[transformIndex] == i) + { + frameValid = true; + } + boolean nextFrameValid = false; + if (nextTransformIndex < nextFrame.getTransformCount() + && nextFrame.getTransformTypes()[nextTransformIndex] == i) + { + nextFrameValid = true; + } + if (frameValid || nextFrameValid) + { + int staticFrame = 0; + int type = skin.getTypes()[i]; + if (type == 3 || type == 10) + { + staticFrame = 128; + } + int currentTranslateX = staticFrame; + int currentTranslateY = staticFrame; + int currentTranslateZ = staticFrame; + if (frameValid) + { + currentTranslateX = frame.getTranslatorX()[transformIndex]; + currentTranslateY = frame.getTranslatorY()[transformIndex]; + currentTranslateZ = frame.getTranslatorZ()[transformIndex]; + transformIndex++; + } + int nextTranslateX = staticFrame; + int nextTranslateY = staticFrame; + int nextTranslateZ = staticFrame; + if (nextFrameValid) + { + nextTranslateX = nextFrame.getTranslatorX()[nextTransformIndex]; + nextTranslateY = nextFrame.getTranslatorY()[nextTransformIndex]; + nextTranslateZ = nextFrame.getTranslatorZ()[nextTransformIndex]; + nextTransformIndex++; + } + int translateX; + int translateY; + int translateZ; + if (type == 2) + { + int deltaX = nextTranslateX - currentTranslateX & 0x3fff; + int deltaY = nextTranslateY - currentTranslateY & 0x3fff; + int deltaZ = nextTranslateZ - currentTranslateZ & 0x3fff; + if (deltaX >= 8192) + { + deltaX -= 16384; + } + if (deltaY >= 8192) + { + deltaY -= 16384; + } + if (deltaZ >= 8192) + { + deltaZ -= 16384; + } + translateX = currentTranslateX + deltaX * interval / intervalCount & 0x3fff; + translateY = currentTranslateY + deltaY * interval / intervalCount & 0x3fff; + translateZ = currentTranslateZ + deltaZ * interval / intervalCount & 0x3fff; + } + else if (type == 5) + { + // don't interpolate alpha transformations + // alpha + translateX = currentTranslateX; + translateY = 0; + translateZ = 0; + } + else + { + translateX = currentTranslateX + (nextTranslateX - currentTranslateX) * interval / intervalCount; + translateY = currentTranslateY + (nextTranslateY - currentTranslateY) * interval / intervalCount; + translateZ = currentTranslateZ + (nextTranslateZ - currentTranslateZ) * interval / intervalCount; + } + // use interpolated translations to animate + animate(type, skin.getList()[i], translateX, translateY, translateZ); + } + } + } + } + @Override @Inject public Polygon getConvexHull(int localX, int localY, int orientation) diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java index d052b8c6e2..649d4ef6dc 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java @@ -24,14 +24,22 @@ */ package net.runelite.mixins; +import net.runelite.api.mixins.Copy; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Replace; +import net.runelite.api.mixins.Shadow; +import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSModel; import net.runelite.rs.api.RSNPC; import net.runelite.rs.api.RSNPCComposition; @Mixin(RSNPC.class) public abstract class RSNPCMixin implements RSNPC { + @Shadow("clientInstance") + private static RSClient client; + @Inject private int npcIndex; @@ -72,4 +80,35 @@ public abstract class RSNPCMixin implements RSNPC { npcIndex = id; } + + @Copy("getModel") + public abstract RSModel rs$getModel(); + + @Replace("getModel") + public RSModel rl$getModel() + { + if (!client.isInterpolateNpcAnimations()) + { + return rs$getModel(); + } + int actionFrame = getActionFrame(); + int poseFrame = getPoseFrame(); + int spotAnimFrame = getSpotAnimFrame(); + try + { + // combine the frames with the frame cycle so we can access this information in the sequence methods + // without having to change method calls + setActionFrame(Integer.MIN_VALUE | getActionFrameCycle() << 16 | actionFrame); + setPoseFrame(Integer.MIN_VALUE | getPoseFrameCycle() << 16 | poseFrame); + setSpotAnimFrame(Integer.MIN_VALUE | getSpotAnimFrameCycle() << 16 | spotAnimFrame); + return rs$getModel(); + } + finally + { + // reset frames + setActionFrame(actionFrame); + setPoseFrame(poseFrame); + setSpotAnimFrame(spotAnimFrame); + } + } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java index 69a33d0674..020be2b8cc 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java @@ -30,12 +30,15 @@ import java.util.List; import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.Point; +import net.runelite.api.mixins.Copy; +import net.runelite.api.mixins.Replace; import net.runelite.api.model.Triangle; import net.runelite.api.model.Vertex; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSModel; import net.runelite.rs.api.RSName; import net.runelite.rs.api.RSPlayer; @@ -158,4 +161,35 @@ public abstract class RSPlayerMixin implements RSPlayer { this.playerIndex = index; } + + @Copy("getModel") + public abstract RSModel rs$getModel(); + + @Replace("getModel") + public RSModel rl$getModel() + { + if (!client.isInterpolatePlayerAnimations()) + { + return rs$getModel(); + } + int actionFrame = getActionFrame(); + int poseFrame = getPoseFrame(); + int spotAnimFrame = getSpotAnimFrame(); + try + { + // combine the frames with the frame cycle so we can access this information in the sequence methods + // without having to change method calls + setActionFrame(Integer.MIN_VALUE | getActionFrameCycle() << 16 | actionFrame); + setPoseFrame(Integer.MIN_VALUE | getPoseFrameCycle() << 16 | poseFrame); + setSpotAnimFrame(Integer.MIN_VALUE | getSpotAnimFrameCycle() << 16 | spotAnimFrame); + return rs$getModel(); + } + finally + { + // reset frames + setActionFrame(actionFrame); + setPoseFrame(poseFrame); + setSpotAnimFrame(spotAnimFrame); + } + } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSSequenceMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSSequenceMixin.java new file mode 100644 index 0000000000..563d5fce17 --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSSequenceMixin.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2018, Adam + * 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.mixins; + +import net.runelite.api.mixins.Copy; +import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Replace; +import net.runelite.api.mixins.Shadow; +import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSFrames; +import net.runelite.rs.api.RSModel; +import net.runelite.rs.api.RSSequence; + +@Mixin(RSSequence.class) +public abstract class RSSequenceMixin implements RSSequence +{ + @Shadow("clientInstance") + private static RSClient client; + + @Copy("applyTransformations") + public abstract RSModel rs$applyTransformations(RSModel model, int actionFrame, RSSequence poseSeq, int poseFrame); + + @Replace("applyTransformations") + public RSModel rl$applyTransformations(RSModel model, int actionFrame, RSSequence poseSeq, int poseFrame) + { + // reset frame ids because we're not interpolating this + if (actionFrame < 0) + { + int packed = actionFrame ^ Integer.MIN_VALUE; + actionFrame = packed & 0xFFFF; + } + if (poseFrame < 0) + { + int packed = poseFrame ^ Integer.MIN_VALUE; + poseFrame = packed & 0xFFFF; + } + return rs$applyTransformations(model, actionFrame, poseSeq, poseFrame); + } + + @Copy("transformActorModel") + public abstract RSModel rs$transformActorModel(RSModel model, int frameIdx); + + @Replace("transformActorModel") + public RSModel rl$transformActorModel(RSModel model, int frame) + { + // check if the frame has been modified + if (frame < 0) + { + // remove flag to check if the frame has been modified + int packed = frame ^ Integer.MIN_VALUE; + int interval = packed >> 16; + frame = packed & 0xFFFF; + int nextFrame = frame + 1; + if (nextFrame >= getFrameIDs().length) + { + // dont interpolate last frame + nextFrame = -1; + } + int[] frameIds = getFrameIDs(); + int frameId = frameIds[frame]; + RSFrames frames = client.getFrames(frameId >> 16); + int frameIdx = frameId & 0xFFFF; + + int nextFrameIdx = -1; + RSFrames nextFrames = null; + if (nextFrame != -1) + { + int nextFrameId = frameIds[nextFrame]; + nextFrames = client.getFrames(nextFrameId >> 16); + nextFrameIdx = nextFrameId & 0xFFFF; + } + + if (frames == null) + { + // not sure what toSharedModel does but it is needed + return model.toSharedModel(true); + } + else + { + RSModel animatedModel = model.toSharedModel(!frames.getFrames()[frameIdx].isShowing()); + animatedModel.interpolateFrames(frames, frameIdx, nextFrames, nextFrameIdx, interval, + getFrameLenths()[frame]); + return animatedModel; + } + } + else + { + return rs$transformActorModel(model, frame); + } + } + + @Copy("transformObjectModel") + public abstract RSModel rs$transformObjectModel(RSModel model, int frame, int rotation); + + @Replace("transformObjectModel") + public RSModel rl$transformObjectModel(RSModel model, int frame, int rotation) + { + // check if the frame has been modified + if (frame < 0) + { + // remove flag to check if the frame has been modified + int packed = frame ^ Integer.MIN_VALUE; + int interval = packed >> 16; + frame = packed & 0xFFFF; + + int nextFrame = frame + 1; + if (nextFrame >= getFrameIDs().length) + { + // dont interpolate last frame + nextFrame = -1; + } + int[] frameIds = getFrameIDs(); + int frameId = frameIds[frame]; + RSFrames frames = client.getFrames(frameId >> 16); + int frameIdx = frameId & 0xFFFF; + + int nextFrameIdx = -1; + RSFrames nextFrames = null; + if (nextFrame != -1) + { + int nextFrameId = frameIds[nextFrame]; + nextFrames = client.getFrames(nextFrameId >> 16); + nextFrameIdx = nextFrameId & 0xFFFF; + } + + if (frames == null) + { + return model.toSharedModel(true); + } + else + { + RSModel animatedModel = model.toSharedModel(!frames.getFrames()[frameIdx].isShowing()); + // reset rotation before animating + rotation &= 3; + if (rotation == 1) + { + animatedModel.rotateY270Ccw(); + } + else if (rotation == 2) + { + animatedModel.rotateY180Ccw(); + } + else if (rotation == 3) + { + animatedModel.rotateY90Ccw(); + } + animatedModel.interpolateFrames(frames, frameIdx, nextFrames, nextFrameIdx, interval, + getFrameLenths()[frame]); + // reapply rotation after animating + if (rotation == 1) + { + animatedModel.rotateY90Ccw(); + } + else if (rotation == 2) + { + animatedModel.rotateY180Ccw(); + } + else if (rotation == 3) + { + animatedModel.rotateY270Ccw(); + } + return animatedModel; + } + } + else + { + return rs$transformObjectModel(model, frame, rotation); + } + } + + @Copy("transformSpotAnimModel") + public abstract RSModel rs$transformSpotAnimModel(RSModel model, int frame); + + @Replace("transformSpotAnimModel") + public RSModel rl$transformSpotAnimModel(RSModel model, int frame) + { + // check if the frame has been modified + if (frame < 0) + { + // remove flag to check if the frame has been modified + int packed = frame ^ Integer.MIN_VALUE; + int interval = packed >> 16; + frame = packed & 0xFFFF; + int nextFrame = frame + 1; + if (nextFrame >= getFrameIDs().length) + { + // dont interpolate last frame + nextFrame = -1; + } + int[] frameIds = getFrameIDs(); + int frameId = frameIds[frame]; + RSFrames frames = client.getFrames(frameId >> 16); + int frameIdx = frameId & 0xFFFF; + + int nextFrameIdx = -1; + RSFrames nextFrames = null; + if (nextFrame != -1) + { + int nextFrameId = frameIds[nextFrame]; + nextFrames = client.getFrames(nextFrameId >> 16); + nextFrameIdx = nextFrameId & 0xFFFF; + } + + if (frames == null) + { + return model.toSharedSpotAnimModel(true); + } + else + { + RSModel animatedModel = model.toSharedSpotAnimModel(!frames.getFrames()[frameIdx].isShowing()); + animatedModel.interpolateFrames(frames, frameIdx, nextFrames, nextFrameIdx, interval, + getFrameLenths()[frame]); + return animatedModel; + } + } + else + { + return rs$transformSpotAnimModel(model, frame); + } + } +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java b/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java index c9854f024a..2c68afbbc6 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java @@ -65,4 +65,31 @@ public interface RSActor extends RSRenderable, Actor @Import("logicalHeight") @Override int getLogicalHeight(); + + @Import("actionFrame") + int getActionFrame(); + + @Import("actionFrame") + void setActionFrame(int frame); + + @Import("actionFrameCycle") + int getActionFrameCycle(); + + @Import("poseFrame") + int getPoseFrame(); + + @Import("poseFrame") + void setPoseFrame(int frame); + + @Import("poseFrameCycle") + int getPoseFrameCycle(); + + @Import("spotAnimFrame") + int getSpotAnimFrame(); + + @Import("spotAnimFrame") + void setSpotAnimFrame(int frame); + + @Import("spotAnimFrameCycle") + int getSpotAnimFrameCycle(); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java index 079a13d326..8be9264b5a 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java @@ -550,6 +550,18 @@ public interface RSClient extends RSGameEngine, Client @Override RSWorld createWorld(); + @Import("animOffsetX") + void setAnimOffsetX(int animOffsetX); + + @Import("animOffsetY") + void setAnimOffsetY(int animOffsetY); + + @Import("animOffsetZ") + void setAnimOffsetZ(int animOffsetZ); + + @Import("getFrames") + RSFrames getFrames(int frameId); + @Import("minimapSprite") RSSpritePixels getMinimapSprite(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSDynamicObject.java b/runescape-api/src/main/java/net/runelite/rs/api/RSDynamicObject.java new file mode 100644 index 0000000000..4d18bdd26a --- /dev/null +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSDynamicObject.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018, Adam + * 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.rs.api; + +import net.runelite.api.Renderable; +import net.runelite.mapping.Import; + +public interface RSDynamicObject extends Renderable, RSRenderable +{ + @Import("id") + int getId(); + + @Import("animFrame") + int getAnimFrame(); + + @Import("animFrame") + void setAnimFrame(int frame); + + @Import("animCycleCount") + int getAnimCycleCount(); +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSFrame.java b/runescape-api/src/main/java/net/runelite/rs/api/RSFrame.java new file mode 100644 index 0000000000..6169518500 --- /dev/null +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSFrame.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018, Adam + * 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.rs.api; + +import net.runelite.mapping.Import; + +public interface RSFrame +{ + @Import("skin") + RSFrameMap getSkin(); + + @Import("transformCount") + int getTransformCount(); + + @Import("transformTypes") + int[] getTransformTypes(); + + @Import("translator_x") + int[] getTranslatorX(); + + @Import("translator_y") + int[] getTranslatorY(); + + @Import("translator_z") + int[] getTranslatorZ(); + + @Import("showing") + boolean isShowing(); +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSFrameMap.java b/runescape-api/src/main/java/net/runelite/rs/api/RSFrameMap.java new file mode 100644 index 0000000000..215f3be17b --- /dev/null +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSFrameMap.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018, Adam + * 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.rs.api; + +import net.runelite.mapping.Import; + +public interface RSFrameMap extends RSNode +{ + @Import("count") + int getCount(); + + @Import("types") + int[] getTypes(); + + @Import("list") + int[][] getList(); +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSFrames.java b/runescape-api/src/main/java/net/runelite/rs/api/RSFrames.java new file mode 100644 index 0000000000..947549248d --- /dev/null +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSFrames.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018, Adam + * 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.rs.api; + +import net.runelite.mapping.Import; + +public interface RSFrames extends RSCacheableNode +{ + @Import("skeletons") + RSFrame[] getFrames(); +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java b/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java index 7e10c56061..8f4072f581 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java @@ -54,6 +54,33 @@ public interface RSModel extends RSRenderable, Model @Import("indices3") int[] getTrianglesZ(); + @Import("vertexGroups") + int[][] getVertexGroups(); + + @Import("animate") + void animate(int type, int[] list, int x, int y, int z); + + @Import("resetBounds") + void resetBounds(); + + @Import("toSharedModel") + RSModel toSharedModel(boolean b); + + @Import("toSharedSpotAnimModel") + RSModel toSharedSpotAnimModel(boolean b); + + @Import("rotateY90Ccw") + void rotateY90Ccw(); + + @Import("rotateY180Ccw") + void rotateY180Ccw(); + + @Import("rotateY270Ccw") + void rotateY270Ccw(); + + void interpolateFrames(RSFrames frames, int frameId, RSFrames nextFrames, int nextFrameId, int interval, + int intervalCount); + /** * Compute the convex hull of this model * @param localX diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSSequence.java b/runescape-api/src/main/java/net/runelite/rs/api/RSSequence.java index 1672347d13..0bea230793 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSSequence.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSSequence.java @@ -42,4 +42,10 @@ public interface RSSequence @Import("interleaveLeave") int[] getInterleaveLeave(); + + @Import("frameIDs") + int[] getFrameIDs(); + + @Import("frameLengths") + int[] getFrameLenths(); }