diff --git a/runelite-api/src/main/java/net/runelite/api/Actor.java b/runelite-api/src/main/java/net/runelite/api/Actor.java index 2655efc236..097908311d 100644 --- a/runelite-api/src/main/java/net/runelite/api/Actor.java +++ b/runelite-api/src/main/java/net/runelite/api/Actor.java @@ -28,6 +28,7 @@ import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.image.BufferedImage; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; public interface Actor extends Renderable @@ -75,4 +76,6 @@ public interface Actor extends Renderable int getLogicalHeight(); Polygon getConvexHull(); + + WorldArea getWorldArea(); } diff --git a/runelite-api/src/main/java/net/runelite/api/AnimationID.java b/runelite-api/src/main/java/net/runelite/api/AnimationID.java index d387270973..bed8bc49cc 100644 --- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java +++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java @@ -113,6 +113,12 @@ public final class AnimationID public static final int LOOKING_INTO = 832; public static final int DIG = 830; public static final int VENGEANCE_OTHER = 4411; + public static final int DEMONIC_GORILLA_MAGIC_ATTACK = 7225; + public static final int DEMONIC_GORILLA_MELEE_ATTACK = 7226; + public static final int DEMONIC_GORILLA_RANGED_ATTACK = 7227; + public static final int DEMONIC_GORILLA_AOE_ATTACK = 7228; + public static final int DEMONIC_GORILLA_PRAYER_SWITCH = 7228; + public static final int DEMONIC_GORILLA_DEFEND = 7224; // NPC animations public static final int TZTOK_JAD_MAGIC_ATTACK = 2656; 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 4cceee7c05..ce45ddc6a5 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -413,4 +413,6 @@ public interface Client extends GameEngine void setAttackersHidden(boolean state); void setProjectilesHidden(boolean state); + + CollisionData[] getCollisionMaps(); } diff --git a/runelite-api/src/main/java/net/runelite/api/CollisionData.java b/runelite-api/src/main/java/net/runelite/api/CollisionData.java new file mode 100644 index 0000000000..1201c2c889 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/CollisionData.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018, Woox + * 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; + +public interface CollisionData +{ + int[][] getFlags(); +} \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/CollisionDataFlag.java b/runelite-api/src/main/java/net/runelite/api/CollisionDataFlag.java new file mode 100644 index 0000000000..ea0887f7fe --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/CollisionDataFlag.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018, Woox + * 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; + +public final class CollisionDataFlag +{ + public static final int BLOCK_MOVEMENT_NORTH_WEST = 0x1; + public static final int BLOCK_MOVEMENT_NORTH = 0x2; + public static final int BLOCK_MOVEMENT_NORTH_EAST = 0x4; + public static final int BLOCK_MOVEMENT_EAST = 0x8; + public static final int BLOCK_MOVEMENT_SOUTH_EAST = 0x10; + public static final int BLOCK_MOVEMENT_SOUTH = 0x20; + public static final int BLOCK_MOVEMENT_SOUTH_WEST = 0x40; + public static final int BLOCK_MOVEMENT_WEST = 0x80; + + public static final int BLOCK_MOVEMENT_OBJECT = 0x100; + public static final int BLOCK_MOVEMENT_FLOOR_DECORATION = 0x40000; + public static final int BLOCK_MOVEMENT_FLOOR = 0x200000; // Eg. water + public static final int BLOCK_MOVEMENT_FULL = + BLOCK_MOVEMENT_OBJECT | + BLOCK_MOVEMENT_FLOOR_DECORATION | + BLOCK_MOVEMENT_FLOOR; + + public static final int BLOCK_LINE_OF_SIGHT_NORTH = BLOCK_MOVEMENT_NORTH << 9; // 0x400 + public static final int BLOCK_LINE_OF_SIGHT_EAST = BLOCK_MOVEMENT_EAST << 9; // 0x1000 + public static final int BLOCK_LINE_OF_SIGHT_SOUTH = BLOCK_MOVEMENT_SOUTH << 9; // 0x4000 + public static final int BLOCK_LINE_OF_SIGHT_WEST = BLOCK_MOVEMENT_WEST << 9; // 0x10000 + public static final int BLOCK_LINE_OF_SIGHT_FULL = 0x20000; +} \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/Hitsplat.java b/runelite-api/src/main/java/net/runelite/api/Hitsplat.java new file mode 100644 index 0000000000..af73dc1cab --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/Hitsplat.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018, Woox + * 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 lombok.Getter; + +public class Hitsplat +{ + public enum HitsplatType + { + BLOCK, // Blue + DAMAGE, // Red + POISON, // Green + VENOM, // Darkgreen + DISEASE, // Yellow + HEAL; // Purple + + public static HitsplatType fromInteger(int type) + { + switch (type) + { + case 0: return BLOCK; + case 1: return DAMAGE; + case 2: return POISON; + case 4: return DISEASE; + case 5: return VENOM; + case 6: return HEAL; + } + return null; + } + } + + @Getter + private HitsplatType hitsplatType; + + @Getter + private int amount; + + @Getter + private int disappearsOnGameCycle; + + public Hitsplat(HitsplatType hitsplatType, int amount, int disappearsOnGameCycle) + { + this.hitsplatType = hitsplatType; + this.amount = amount; + this.disappearsOnGameCycle = disappearsOnGameCycle; + } +} \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/NPCComposition.java b/runelite-api/src/main/java/net/runelite/api/NPCComposition.java index 06fe5f5139..3601e78030 100644 --- a/runelite-api/src/main/java/net/runelite/api/NPCComposition.java +++ b/runelite-api/src/main/java/net/runelite/api/NPCComposition.java @@ -41,8 +41,12 @@ public interface NPCComposition int getId(); int getCombatLevel(); - + int[] getConfigs(); NPCComposition transform(); + + int getSize(); + + int getOverheadIcon(); } diff --git a/runelite-api/src/main/java/net/runelite/api/Player.java b/runelite-api/src/main/java/net/runelite/api/Player.java index 6ea285b3d1..f0158e0047 100644 --- a/runelite-api/src/main/java/net/runelite/api/Player.java +++ b/runelite-api/src/main/java/net/runelite/api/Player.java @@ -40,4 +40,6 @@ public interface Player extends Actor boolean isClanMember(); boolean isFriend(); + + int getOverheadIcon(); } diff --git a/runelite-api/src/main/java/net/runelite/api/ProjectileID.java b/runelite-api/src/main/java/net/runelite/api/ProjectileID.java index 163629020c..9347d66453 100644 --- a/runelite-api/src/main/java/net/runelite/api/ProjectileID.java +++ b/runelite-api/src/main/java/net/runelite/api/ProjectileID.java @@ -60,6 +60,10 @@ public class ProjectileID public static final int WINTERTODT_SNOW_FALL_AOE = 501; + public static final int DEMONIC_GORILLA_RANGED = 1302; + public static final int DEMONIC_GORILLA_MAGIC = 1304; + public static final int DEMONIC_GORILLA_BOULDER = 856; + /** * missing: marble gargoyle, superior dark beast */ diff --git a/runelite-api/src/main/java/net/runelite/api/Tile.java b/runelite-api/src/main/java/net/runelite/api/Tile.java index 5ac718d27a..0099690098 100644 --- a/runelite-api/src/main/java/net/runelite/api/Tile.java +++ b/runelite-api/src/main/java/net/runelite/api/Tile.java @@ -55,4 +55,6 @@ public interface Tile LocalPoint getLocalLocation(); int getPlane(); + + boolean hasLineOfSightTo(Tile other); } diff --git a/runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java b/runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java new file mode 100644 index 0000000000..18c65e88d9 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java @@ -0,0 +1,630 @@ +/* + * Copyright (c) 2018, Woox + * 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.coords; + +import java.util.function.Predicate; +import lombok.Getter; +import net.runelite.api.Client; +import net.runelite.api.CollisionData; +import net.runelite.api.CollisionDataFlag; +import net.runelite.api.Constants; +import net.runelite.api.Point; +import net.runelite.api.Tile; + +public class WorldArea +{ + /** + * The western most point of the area + */ + @Getter + private int x; + + /** + * The southern most point of the area + */ + @Getter + private int y; + + /** + * The width of the area + */ + @Getter + private int width; + + /** + * The height of the area + */ + @Getter + private int height; + + /** + * The plane the area is on + */ + @Getter + private int plane; + + public WorldArea(int x, int y, int width, int height, int plane) + { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.plane = plane; + } + + public WorldArea(WorldPoint location, int width, int height) + { + this.x = location.getX(); + this.y = location.getY(); + this.plane = location.getPlane(); + this.width = width; + this.height = height; + } + + /** + * Get the shortest distance to another WorldArea for both x and y axis + * @param other The WorldArea to get the distance to + * @return Returns a Point with the shortest distance + */ + private Point getAxisDistances(WorldArea other) + { + Point p1 = this.getComparisonPoint(other); + Point p2 = other.getComparisonPoint(this); + return new Point(Math.abs(p1.getX() - p2.getX()), Math.abs(p1.getY() - p2.getY())); + } + + /** + * Get the shortest distance to another WorldArea + * + * @param other The other area + * @return Returns the distance + */ + public int distanceTo(WorldArea other) + { + if (this.getPlane() != other.getPlane()) + { + return Integer.MAX_VALUE; + } + + Point distances = getAxisDistances(other); + return Math.max(distances.getX(), distances.getY()); + } + + /** + * Get the shortest distance to another WorldPoint + * + * @param other The other worldpoint + * @return Returns the distance + */ + public int distanceTo(WorldPoint other) + { + return distanceTo(new WorldArea(other, 1, 1)); + } + + /** + * Determines if this WorldArea is within melee distance of another WorldArea + * + * @param other The other world area to compare with + * @return Returns true if it is in melee distance + */ + public boolean isInMeleeDistance(WorldArea other) + { + if (other == null || this.getPlane() != other.getPlane()) + { + return false; + } + + Point distances = getAxisDistances(other); + return distances.getX() + distances.getY() == 1; + } + + /** + * Determines if this WorldArea is within melee distance of another WorldPoint + * + * @param other The world pint to compare with + * @return Returns true if it is in melee distance + */ + public boolean isInMeleeDistance(WorldPoint other) + { + return isInMeleeDistance(new WorldArea(other, 1, 1)); + } + + /** + * Determines if a WorldArea intersects with another WorldArea + * + * @param other The other WorldArea to compare with + * @return Returns true if the areas intersect + */ + public boolean intersectsWith(WorldArea other) + { + if (this.getPlane() != other.getPlane()) + { + return false; + } + + Point distances = getAxisDistances(other); + return distances.getX() + distances.getY() == 0; + } + + /** + * Determines if the area can travel in one of the 8 directions + * by using the standard collision detection algorithm. + * Note that this method does not consider other actors as + * a collision, but most non-boss NPCs do check for collision + * with some actors. + * + * @param client The client to test in + * @param dx The x direction to test against + * @param dy The y direction to test against + * @return Returns true if it's possible to travel in specified direction + */ + public boolean canTravelInDirection(Client client, int dx, int dy) + { + return canTravelInDirection(client, dx, dy, x -> true); + } + + /** + * Determines if the area can travel in one of the 8 directions + * by using the standard collision detection algorithm. + * Note that this method does not consider other actors as + * a collision, but most non-boss NPCs do check for collision + * with some actors. + * + * @param client The client to test in + * @param dx The x direction to test against + * @param dy The y direction to test against + * @param extraCondition Additional check for if movement is allowed through specific + * tiles, which may be used if movement should be disabled through other actors + * @return Returns true if it's possible to travel in specified direction + */ + public boolean canTravelInDirection(Client client, int dx, int dy, + Predicate extraCondition) + { + dx = Integer.signum(dx); + dy = Integer.signum(dy); + + if (dx == 0 && dy == 0) + { + return true; + } + + LocalPoint lp = LocalPoint.fromWorld(client, x, y); + + int startX = lp.getRegionX() + dx; + int startY = lp.getRegionY() + dy; + int checkX = startX + (dx > 0 ? width - 1 : 0); + int checkY = startY + (dy > 0 ? height - 1 : 0); + int endX = startX + width - 1; + int endY = startY + height - 1; + + int xFlags = CollisionDataFlag.BLOCK_MOVEMENT_FULL; + int yFlags = CollisionDataFlag.BLOCK_MOVEMENT_FULL; + int xyFlags = CollisionDataFlag.BLOCK_MOVEMENT_FULL; + int xWallFlagsSouth = CollisionDataFlag.BLOCK_MOVEMENT_FULL; + int xWallFlagsNorth = CollisionDataFlag.BLOCK_MOVEMENT_FULL; + int yWallFlagsWest = CollisionDataFlag.BLOCK_MOVEMENT_FULL; + int yWallFlagsEast = CollisionDataFlag.BLOCK_MOVEMENT_FULL; + + if (dx < 0) + { + xFlags |= CollisionDataFlag.BLOCK_MOVEMENT_EAST; + xWallFlagsSouth |= CollisionDataFlag.BLOCK_MOVEMENT_SOUTH | + CollisionDataFlag.BLOCK_MOVEMENT_SOUTH_EAST; + xWallFlagsNorth |= CollisionDataFlag.BLOCK_MOVEMENT_NORTH | + CollisionDataFlag.BLOCK_MOVEMENT_NORTH_EAST; + } + if (dx > 0) + { + xFlags |= CollisionDataFlag.BLOCK_MOVEMENT_WEST; + xWallFlagsSouth |= CollisionDataFlag.BLOCK_MOVEMENT_SOUTH | + CollisionDataFlag.BLOCK_MOVEMENT_SOUTH_WEST; + xWallFlagsNorth |= CollisionDataFlag.BLOCK_MOVEMENT_NORTH | + CollisionDataFlag.BLOCK_MOVEMENT_NORTH_WEST; + } + if (dy < 0) + { + yFlags |= CollisionDataFlag.BLOCK_MOVEMENT_NORTH; + yWallFlagsWest |= CollisionDataFlag.BLOCK_MOVEMENT_WEST | + CollisionDataFlag.BLOCK_MOVEMENT_NORTH_WEST; + yWallFlagsEast |= CollisionDataFlag.BLOCK_MOVEMENT_EAST | + CollisionDataFlag.BLOCK_MOVEMENT_NORTH_EAST; + } + if (dy > 0) + { + yFlags |= CollisionDataFlag.BLOCK_MOVEMENT_SOUTH; + yWallFlagsWest |= CollisionDataFlag.BLOCK_MOVEMENT_WEST | + CollisionDataFlag.BLOCK_MOVEMENT_SOUTH_WEST; + yWallFlagsEast |= CollisionDataFlag.BLOCK_MOVEMENT_EAST | + CollisionDataFlag.BLOCK_MOVEMENT_SOUTH_EAST; + } + if (dx < 0 && dy < 0) + { + xyFlags |= CollisionDataFlag.BLOCK_MOVEMENT_NORTH_EAST; + } + if (dx < 0 && dy > 0) + { + xyFlags |= CollisionDataFlag.BLOCK_MOVEMENT_SOUTH_EAST; + } + if (dx > 0 && dy < 0) + { + xyFlags |= CollisionDataFlag.BLOCK_MOVEMENT_NORTH_WEST; + } + if (dx > 0 && dy > 0) + { + xyFlags |= CollisionDataFlag.BLOCK_MOVEMENT_SOUTH_WEST; + } + + CollisionData[] collisionData = client.getCollisionMaps(); + int[][] collisionDataFlags = collisionData[plane].getFlags(); + + if (dx != 0) + { + // Check that the area doesn't bypass a wall + for (int y = startY; y <= endY; y++) + { + if ((collisionDataFlags[checkX][y] & xFlags) != 0 || + !extraCondition.test(WorldPoint.fromRegion(client, checkX, y, plane))) + { + // Collision while attempting to travel along the x axis + return false; + } + } + + // Check that the new area tiles don't contain a wall + for (int y = startY + 1; y <= endY; y++) + { + if ((collisionDataFlags[checkX][y] & xWallFlagsSouth) != 0) + { + // The new area tiles contains a wall + return false; + } + } + for (int y = endY - 1; y >= startY; y--) + { + if ((collisionDataFlags[checkX][y] & xWallFlagsNorth) != 0) + { + // The new area tiles contains a wall + return false; + } + } + } + if (dy != 0) + { + // Check that the area tiles don't bypass a wall + for (int x = startX; x <= endX; x++) + { + if ((collisionDataFlags[x][checkY] & yFlags) != 0 || + !extraCondition.test(WorldPoint.fromRegion(client, x, checkY, client.getPlane()))) + { + // Collision while attempting to travel along the y axis + return false; + } + } + + // Check that the new area tiles don't contain a wall + for (int x = startX + 1; x <= endX; x++) + { + if ((collisionDataFlags[x][checkY] & yWallFlagsWest) != 0) + { + // The new area tiles contains a wall + return false; + } + } + for (int x = endX - 1; x >= startX; x--) + { + if ((collisionDataFlags[x][checkY] & yWallFlagsEast) != 0) + { + // The new area tiles contains a wall + return false; + } + } + } + if (dx != 0 && dy != 0) + { + if ((collisionDataFlags[checkX][checkY] & xyFlags) != 0 || + !extraCondition.test(WorldPoint.fromRegion(client, checkX, checkY, client.getPlane()))) + { + // Collision while attempting to travel diagonally + return false; + } + + // When the areas edge size is 1 and it attempts to travel + // diagonally, a collision check is done for respective + // x and y axis as well. + if (width == 1) + { + if ((collisionDataFlags[checkX][checkY - dy] & xFlags) != 0 && + extraCondition.test(WorldPoint.fromRegion(client, checkX, startY, client.getPlane()))) + { + return false; + } + } + if (height == 1) + { + if ((collisionDataFlags[checkX - dx][checkY] & yFlags) != 0 && + extraCondition.test(WorldPoint.fromRegion(client, startX, checkY, client.getPlane()))) + { + return false; + } + } + } + + return true; + } + + /** + * Retrieves the Point within this WorldArea which is the closest to another WorldArea + * + * @param other The other WorldArea to compare to + * @return Returns the closest Point + */ + private Point getComparisonPoint(WorldArea other) + { + int x, y; + if (other.x <= this.x) + { + x = this.x; + } + else if (other.x >= this.x + this.width - 1) + { + x = this.x + this.width - 1; + } + else + { + x = other.x; + } + if (other.y <= this.y) + { + y = this.y; + } + else if (other.y >= this.y + this.height - 1) + { + y = this.y + this.height - 1; + } + else + { + y = other.y; + } + return new Point(x, y); + } + + /** + * Calculates the next area that will be occupied if this area + * attempts to move toward it by using the normal NPC travelling + * pattern. + * + * @param client The client to calculate with + * @param target The target area + * @param stopAtMeleeDistance Determine if it should stop at melee distance to the target + * @return Returns the next occupied area + */ + public WorldArea calculateNextTravellingPoint(Client client, WorldArea target, + boolean stopAtMeleeDistance) + { + return calculateNextTravellingPoint(client, target, stopAtMeleeDistance, x -> true); + } + + /** + * Calculates the next area that will be occupied if this area + * attempts to move toward it by using the normal NPC travelling + * pattern. + * + * @param client The client to calculate with + * @param target The target area + * @param stopAtMeleeDistance Determine if it should stop at melee distance to the target + * @param extraCondition Additional check for if movement is allowed through specific + * tiles, which may be used if movement should be disabled through other actors + * @return Returns the next occupied area + */ + public WorldArea calculateNextTravellingPoint(Client client, WorldArea target, + boolean stopAtMeleeDistance, Predicate extraCondition) + { + if (plane != target.getPlane()) + { + return null; + } + + if (this.intersectsWith(target)) + { + if (stopAtMeleeDistance) + { + // Movement is unpredictable when the NPC and actor stand on top of each other + return null; + } + else + { + return this; + } + } + + int dx = target.x - this.x; + int dy = target.y - this.y; + Point axisDistances = getAxisDistances(target); + if (stopAtMeleeDistance && axisDistances.getX() + axisDistances.getY() == 1) + { + // NPC is in melee distance of target, so no movement is done + return this; + } + + LocalPoint lp = LocalPoint.fromWorld(client, x, y); + if (lp == null || + lp.getRegionX() + dx < 0 || lp.getRegionX() + dy >= Constants.REGION_SIZE || + lp.getRegionY() + dx < 0 || lp.getRegionY() + dy >= Constants.REGION_SIZE) + { + // NPC is travelling out of region, so collision data isn't available + return null; + } + + int dxSig = Integer.signum(dx); + int dySig = Integer.signum(dy); + if (stopAtMeleeDistance && axisDistances.getX() == 1 && axisDistances.getY() == 1) + { + // When it needs to stop at melee distance, it will only attempt + // to travel along the x axis when it is standing diagonally + // from the target + if (this.canTravelInDirection(client, dxSig, 0, extraCondition)) + { + return new WorldArea(x + dxSig, y, width, height, plane); + } + } + else + { + if (this.canTravelInDirection(client, dxSig, dySig, extraCondition)) + { + return new WorldArea(x + dxSig, y + dySig, width, height, plane); + } + else if (dx != 0 && this.canTravelInDirection(client, dxSig, 0, extraCondition)) + { + return new WorldArea(x + dxSig, y, width, height, plane); + } + else if (dy != 0 && Math.max(Math.abs(dx), Math.abs(dy)) > 1 && + this.canTravelInDirection(client, 0, dy, extraCondition)) + { + // Note that NPCs don't attempts to travel along the y-axis + // if the target is <= 1 tile distance away + return new WorldArea(x, y + dySig, width, height, plane); + } + } + + // The NPC is stuck + return this; + } + + /** + * Determine if this WorldArea has line of sight to another WorldArea. + * Note that the reverse isn't necessarily true, meaning this can return true + * while the other WorldArea does not have line of sight to this WorldArea. + * + * @param client The client to compare in + * @param other The other WorldArea to compare with + * @return Returns true if this WorldArea has line of sight to the other + */ + public boolean hasLineOfSightTo(Client client, WorldArea other) + { + if (plane != other.getPlane()) + { + return false; + } + + LocalPoint sourceLp = LocalPoint.fromWorld(client, x, y); + LocalPoint targetLp = LocalPoint.fromWorld(client, other.getX(), other.getY()); + if (sourceLp == null || targetLp == null) + { + return false; + } + + int thisX = sourceLp.getRegionX(); + int thisY = sourceLp.getRegionY(); + int otherX = targetLp.getRegionX(); + int otherY = targetLp.getRegionY(); + + int cmpThisX, cmpThisY, cmpOtherX, cmpOtherY; + + // Determine which position to compare with for this NPC + if (otherX <= thisX) + { + cmpThisX = thisX; + } + else if (otherX >= thisX + width - 1) + { + cmpThisX = thisX + width - 1; + } + else + { + cmpThisX = otherX; + } + if (otherY <= thisY) + { + cmpThisY = thisY; + } + else if (otherY >= thisY + height - 1) + { + cmpThisY = thisY + height - 1; + } + else + { + cmpThisY = otherY; + } + + // Determine which position to compare for the other actor + if (thisX <= otherX) + { + cmpOtherX = otherX; + } + else if (thisX >= otherX + other.getWidth() - 1) + { + cmpOtherX = otherX + other.getWidth() - 1; + } + else + { + cmpOtherX = thisX; + } + if (thisY <= otherY) + { + cmpOtherY = otherY; + } + else if (thisY >= otherY + other.getHeight() - 1) + { + cmpOtherY = otherY + other.getHeight() - 1; + } + else + { + cmpOtherY = thisY; + } + + Tile[][][] tiles = client.getRegion().getTiles(); + Tile sourceTile = tiles[plane][cmpThisX][cmpThisY]; + Tile targetTile = tiles[other.getPlane()][cmpOtherX][cmpOtherY]; + if (sourceTile == null || targetTile == null) + { + return false; + } + return sourceTile.hasLineOfSightTo(targetTile); + } + + /** + * Determine if this WorldArea has line of sight to another WorldArea. + * Note that the reverse isn't necessarily true, meaning this can return true + * while the other WorldArea does not have line of sight to this WorldArea. + * + * @param client The client to compare in + * @param other The other WorldPoint to compare with + * @return Returns true if this WorldPoint has line of sight to the WorldPoint + */ + public boolean hasLineOfSightTo(Client client, WorldPoint other) + { + return hasLineOfSightTo(client, new WorldArea(other, 1, 1)); + } + + /** + * Retrieves the southwestern most point of this WorldArea + * + * @return Returns the southwestern most WorldPoint in the area + */ + public WorldPoint toWorldPoint() + { + return new WorldPoint(x, y, plane); + } +} \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java b/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java index 73d2aa29f4..e82f22429a 100644 --- a/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java +++ b/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java @@ -119,6 +119,17 @@ public class WorldPoint ); } + /** + * Find the shortest distance from this point to a WorldArea + * + * @param other The WorldArea to find the distance to + * @return Returns the shortest distance + */ + public int distanceTo(WorldArea other) + { + return new WorldArea(this, 1, 1).distanceTo(other); + } + /** * Find the distance from this point to another point. Returns Integer.MAX_VALUE if other is on * a different plane. diff --git a/runelite-api/src/main/java/net/runelite/api/events/HitsplatApplied.java b/runelite-api/src/main/java/net/runelite/api/events/HitsplatApplied.java new file mode 100644 index 0000000000..18f02e584e --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/HitsplatApplied.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018, Woox + * 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.events; + +import lombok.Data; +import net.runelite.api.Actor; +import net.runelite.api.Hitsplat; + +@Data +public class HitsplatApplied +{ + private Actor actor; + private Hitsplat hitsplat; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java index 0905c8a694..69d5eef6c3 100644 --- a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java +++ b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java @@ -39,6 +39,7 @@ import java.awt.RenderingHints; import net.runelite.api.Actor; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; +import net.runelite.api.Hitsplat; import net.runelite.api.ItemComposition; import net.runelite.api.KeyFocusListener; import net.runelite.api.MainBufferProvider; @@ -55,6 +56,7 @@ import net.runelite.api.events.ActorDeath; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.FocusChanged; import net.runelite.api.events.GameTick; +import net.runelite.api.events.HitsplatApplied; import net.runelite.api.events.MenuOpened; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.PostItemComposition; @@ -455,4 +457,29 @@ public class Hooks event.setMenuEntries(client.getMenuEntries()); eventBus.post(event); } + + /** + * Called after a hitsplat has been processed on an actor. + * Note that this event runs even if the hitsplat didn't show up, + * i.e. the actor already had 4 visible hitsplats. + * + * @param actor The actor the hitsplat was applied to + * @param type The hitsplat type (i.e. color) + * @param value The value of the hitsplat (i.e. how high the hit was) + * @param var3 + * @param var4 + * @param gameCycle The gamecycle the hitsplat was applied on + * @param duration The amount of gamecycles the hitsplat will last for + */ + public static void onActorHitsplat(Actor actor, int type, int value, int var3, int var4, + int gameCycle, int duration) + { + Hitsplat hitsplat = new Hitsplat(Hitsplat.HitsplatType.fromInteger(type), value, + gameCycle + duration); + + HitsplatApplied event = new HitsplatApplied(); + event.setActor(actor); + event.setHitsplat(hitsplat); + eventBus.post(event); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorilla.java b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorilla.java new file mode 100644 index 0000000000..52e4409a9a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorilla.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2018, Woox + * 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.demonicgorilla; + +import java.util.Arrays; +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.Actor; +import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; +import net.runelite.api.coords.WorldArea; + +public class DemonicGorilla +{ + static final int MAX_ATTACK_RANGE = 10; // Needs <= 10 tiles to reach target + static final int ATTACK_RATE = 5; // 5 ticks between each attack + static final int ATTACKS_PER_SWITCH = 3; // 3 unsuccessful attacks per style switch + + static final int PROJECTILE_MAGIC_SPEED = 8; // Travels 8 tiles per tick + static final int PROJECTILE_RANGED_SPEED = 6; // Travels 6 tiles per tick + static final int PROJECTILE_MAGIC_DELAY = 12; // Requires an extra 12 tiles + static final int PROJECTILE_RANGED_DELAY = 9; // Requires an extra 9 tiles + + public static final AttackStyle[] ALL_REGULAR_ATTACK_STYLES = + { + AttackStyle.MELEE, + AttackStyle.RANGED, + AttackStyle.MAGIC + }; + + enum AttackStyle + { + MAGIC, + RANGED, + MELEE, + BOULDER + } + + @Getter + private NPC npc; + + @Getter + @Setter + private List nextPosibleAttackStyles; + + @Getter + @Setter + private int attacksUntilSwitch; + + @Getter + @Setter + private int nextAttackTick; + + @Getter + @Setter + private int lastTickAnimation; + + @Getter + @Setter + private WorldArea lastWorldArea; + + @Getter + @Setter + private boolean initiatedCombat; + + @Getter + @Setter + private Actor lastTickInteracting; + + @Getter + @Setter + private boolean takenDamageRecently; + + @Getter + @Setter + private int recentProjectileId; + + @Getter + @Setter + private boolean changedPrayerThisTick; + + @Getter + @Setter + private boolean changedAttackStyleThisTick; + + @Getter + @Setter + private boolean changedAttackStyleLastTick; + + @Getter + @Setter + private int lastTickOverheadIcon; + + @Getter + @Setter + private int disabledMeleeMovementForTicks; + + public DemonicGorilla(NPC npc) + { + this.npc = npc; + this.nextPosibleAttackStyles = Arrays.asList(ALL_REGULAR_ATTACK_STYLES); + this.nextAttackTick = -100; + this.attacksUntilSwitch = ATTACKS_PER_SWITCH; + this.recentProjectileId = -1; + this.lastTickOverheadIcon = -1; + } + + public int getOverheadIcon() + { + NPCComposition composition = this.npc.getComposition(); + if (composition != null) + { + return composition.getOverheadIcon(); + } + return -1; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaOverlay.java new file mode 100644 index 0000000000..bc23adf01f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaOverlay.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2018, Woox + * 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.demonicgorilla; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.geom.Arc2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Perspective; +import net.runelite.api.Player; +import net.runelite.api.Point; +import net.runelite.api.Skill; +import net.runelite.api.coords.LocalPoint; +import net.runelite.client.game.SkillIconManager; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; + +public class DemonicGorillaOverlay extends Overlay +{ + private static final Color COLOR_ICON_BACKGROUND = new Color(0, 0, 0, 128); + private static final Color COLOR_ICON_BORDER = new Color(0, 0, 0, 255); + private static final Color COLOR_ICON_BORDER_FILL = new Color(219, 175, 0, 255); + private static final int OVERLAY_ICON_DISTANCE = 50; + private static final int OVERLAY_ICON_MARGIN = 8; + + private Client client; + private DemonicGorillaPlugin plugin; + + @Inject + private SkillIconManager iconManager; + + @Inject + public DemonicGorillaOverlay(Client client, DemonicGorillaPlugin plugin) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + this.client = client; + this.plugin = plugin; + } + + private BufferedImage getIcon(DemonicGorilla.AttackStyle attackStyle) + { + switch (attackStyle) + { + case MELEE: return iconManager.getSkillImage(Skill.ATTACK); + case RANGED: return iconManager.getSkillImage(Skill.RANGED); + case MAGIC: return iconManager.getSkillImage(Skill.MAGIC); + } + return null; + } + + @Override + public Dimension render(Graphics2D graphics) + { + Player player = client.getLocalPlayer(); + + for (DemonicGorilla gorilla : plugin.getGorillas().values()) + { + if (gorilla.getNpc().getInteracting() == null) + { + continue; + } + + LocalPoint lp = gorilla.getNpc().getLocalLocation(); + if (lp != null) + { + Point point = Perspective.worldToCanvas(client, lp.getX(), lp.getY(), client.getPlane(), + gorilla.getNpc().getLogicalHeight() + 16); + if (point != null) + { + List attackStyles = gorilla.getNextPosibleAttackStyles(); + List icons = new ArrayList<>(); + int totalWidth = (attackStyles.size() - 1) * OVERLAY_ICON_MARGIN; + for (DemonicGorilla.AttackStyle attackStyle : attackStyles) + { + BufferedImage icon = getIcon(attackStyle); + icons.add(icon); + totalWidth += icon.getWidth(); + } + + int bgPadding = 4; + int currentPosX = 0; + for (BufferedImage icon : icons) + { + graphics.setStroke(new BasicStroke(2)); + graphics.setColor(COLOR_ICON_BACKGROUND); + graphics.fillOval( + point.getX() - totalWidth / 2 + currentPosX - bgPadding, + point.getY() - icon.getHeight() / 2 - OVERLAY_ICON_DISTANCE - bgPadding, + icon.getWidth() + bgPadding * 2, + icon.getHeight() + bgPadding * 2); + + graphics.setColor(COLOR_ICON_BORDER); + graphics.drawOval( + point.getX() - totalWidth / 2 + currentPosX - bgPadding, + point.getY() - icon.getHeight() / 2 - OVERLAY_ICON_DISTANCE - bgPadding, + icon.getWidth() + bgPadding * 2, + icon.getHeight() + bgPadding * 2); + + graphics.drawImage( + icon, + point.getX() - totalWidth / 2 + currentPosX, + point.getY() - icon.getHeight() / 2 - OVERLAY_ICON_DISTANCE, + null); + + graphics.setColor(COLOR_ICON_BORDER_FILL); + Arc2D.Double arc = new Arc2D.Double( + point.getX() - totalWidth / 2 + currentPosX - bgPadding, + point.getY() - icon.getHeight() / 2 - OVERLAY_ICON_DISTANCE - bgPadding, + icon.getWidth() + bgPadding * 2, + icon.getHeight() + bgPadding * 2, + 90.0, + -360.0 * (DemonicGorilla.ATTACKS_PER_SWITCH - + gorilla.getAttacksUntilSwitch()) / DemonicGorilla.ATTACKS_PER_SWITCH, + Arc2D.OPEN); + graphics.draw(arc); + + currentPosX += icon.getWidth() + OVERLAY_ICON_MARGIN; + } + } + } + } + + return null; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaPlugin.java new file mode 100644 index 0000000000..43c7f5cc03 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaPlugin.java @@ -0,0 +1,695 @@ +/* + * Copyright (c) 2018, Woox + * 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.demonicgorilla; + +import com.google.common.eventbus.Subscribe; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.AnimationID; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Hitsplat; +import net.runelite.api.NPC; +import net.runelite.api.NpcID; +import net.runelite.api.Player; +import net.runelite.api.Projectile; +import net.runelite.api.ProjectileID; +import net.runelite.api.coords.WorldArea; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.NpcDespawned; +import net.runelite.api.events.NpcSpawned; +import net.runelite.api.events.PlayerDespawned; +import net.runelite.api.events.PlayerSpawned; +import net.runelite.api.events.ProjectileMoved; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.Overlay; + +@PluginDescriptor( + name = "Demonic gorillas" +) +@Slf4j +public class DemonicGorillaPlugin extends Plugin +{ + static final int OVERHEAD_ICON_MELEE = 0; + static final int OVERHEAD_ICON_RANGED = 1; + static final int OVERHEAD_ICON_MAGIC = 2; + + @Inject + private Client client; + + @Inject + private DemonicGorillaOverlay overlay; + + @Getter + private Map gorillas; + + private int tickCounter; + + private List recentBoulders; + + private List pendingAttacks; + + private Map memorizedPlayers; + + @Override + protected void startUp() throws Exception + { + tickCounter = 0; + gorillas = new HashMap<>(); + recentBoulders = new ArrayList<>(); + pendingAttacks = new ArrayList<>(); + memorizedPlayers = new HashMap<>(); + } + + @Override + protected void shutDown() throws Exception + { + gorillas = null; + recentBoulders = null; + pendingAttacks = null; + memorizedPlayers = null; + } + + @Override + public Overlay getOverlay() + { + return overlay; + } + + private void clear() + { + recentBoulders.clear(); + pendingAttacks.clear(); + gorillas.clear(); + memorizedPlayers.clear(); + } + + private void resetPlayers() + { + memorizedPlayers.clear(); + for (Player player : client.getPlayers()) + { + memorizedPlayers.put(player, new MemorizedPlayer(player)); + } + } + + public static boolean isNpcGorilla(int npcId) + { + return npcId == NpcID.DEMONIC_GORILLA || + npcId == NpcID.DEMONIC_GORILLA_7145 || + npcId == NpcID.DEMONIC_GORILLA_7146 || + npcId == NpcID.DEMONIC_GORILLA_7147 || + npcId == NpcID.DEMONIC_GORILLA_7148 || + npcId == NpcID.DEMONIC_GORILLA_7149; + } + + private void checkGorillaAttackStyleSwitch(DemonicGorilla gorilla, + final DemonicGorilla.AttackStyle... protectedStyles) + { + if (gorilla.getAttacksUntilSwitch() <= 0 || + gorilla.getNextPosibleAttackStyles().size() == 0) + { + gorilla.setNextPosibleAttackStyles(Arrays + .stream(DemonicGorilla.ALL_REGULAR_ATTACK_STYLES) + .filter(x -> !Arrays.stream(protectedStyles).anyMatch(y -> x == y)) + .collect(Collectors.toList())); + gorilla.setAttacksUntilSwitch(DemonicGorilla.ATTACKS_PER_SWITCH); + gorilla.setChangedAttackStyleThisTick(true); + } + } + + private DemonicGorilla.AttackStyle getProtectedStyle(Player player) + { + if (player.getOverheadIcon() == OVERHEAD_ICON_MELEE) + { + return DemonicGorilla.AttackStyle.MELEE; + } + else if (player.getOverheadIcon() == OVERHEAD_ICON_RANGED) + { + return DemonicGorilla.AttackStyle.RANGED; + } + else if (player.getOverheadIcon() == OVERHEAD_ICON_MAGIC) + { + return DemonicGorilla.AttackStyle.MAGIC; + } + return null; + } + + private void onGorillaAttack(DemonicGorilla gorilla, final DemonicGorilla.AttackStyle attackStyle) + { + gorilla.setInitiatedCombat(true); + + Player target = (Player)gorilla.getNpc().getInteracting(); + + DemonicGorilla.AttackStyle protectedStyle = null; + if (target != null) + { + protectedStyle = getProtectedStyle(target); + } + boolean correctPrayer = + target == null || // If player is out of memory, assume prayer was correct + attackStyle == protectedStyle; + + if (attackStyle == DemonicGorilla.AttackStyle.BOULDER) + { + // The gorilla can't throw boulders when it's meleeing + gorilla.setNextPosibleAttackStyles(gorilla + .getNextPosibleAttackStyles() + .stream() + .filter(x -> x != DemonicGorilla.AttackStyle.MELEE) + .collect(Collectors.toList())); + } + else + { + if (correctPrayer) + { + gorilla.setAttacksUntilSwitch(gorilla.getAttacksUntilSwitch() - 1); + } + else + { + // We're not sure if the attack will hit a 0 or not, + // so we don't know if we should decrease the counter or not, + // so we keep track of the attack here until the damage splat + // has appeared on the player. + + int damagesOnTick = tickCounter; + if (attackStyle == DemonicGorilla.AttackStyle.MAGIC) + { + MemorizedPlayer mp = memorizedPlayers.get(target); + WorldArea lastPlayerArea = mp.getLastWorldArea(); + if (lastPlayerArea != null) + { + int dist = gorilla.getNpc().getWorldArea().distanceTo(lastPlayerArea); + damagesOnTick += (dist + DemonicGorilla.PROJECTILE_MAGIC_DELAY) / + DemonicGorilla.PROJECTILE_MAGIC_SPEED; + } + } + else if (attackStyle == DemonicGorilla.AttackStyle.RANGED) + { + MemorizedPlayer mp = memorizedPlayers.get(target); + WorldArea lastPlayerArea = mp.getLastWorldArea(); + if (lastPlayerArea != null) + { + int dist = gorilla.getNpc().getWorldArea().distanceTo(lastPlayerArea); + damagesOnTick += (dist + DemonicGorilla.PROJECTILE_RANGED_DELAY) / + DemonicGorilla.PROJECTILE_RANGED_SPEED; + } + } + pendingAttacks.add(new PendingGorillaAttack(gorilla, attackStyle, target, damagesOnTick)); + } + + gorilla.setNextPosibleAttackStyles(gorilla + .getNextPosibleAttackStyles() + .stream() + .filter(x -> x == attackStyle) + .collect(Collectors.toList())); + + if (gorilla.getNextPosibleAttackStyles().size() == 0) + { + // Sometimes the gorilla can switch attack style before it's supposed to + // if someone was fighting it earlier and then left, so we just + // reset the counter in that case. + + gorilla.setNextPosibleAttackStyles(Arrays + .stream(DemonicGorilla.ALL_REGULAR_ATTACK_STYLES) + .filter(x -> x == attackStyle) + .collect(Collectors.toList())); + gorilla.setAttacksUntilSwitch(DemonicGorilla.ATTACKS_PER_SWITCH - + (correctPrayer ? 1 : 0)); + } + } + + checkGorillaAttackStyleSwitch(gorilla, protectedStyle); + + gorilla.setNextAttackTick(tickCounter + DemonicGorilla.ATTACK_RATE); + } + + private void checkGorillaAttacks() + { + for (DemonicGorilla gorilla : gorillas.values()) + { + Player interacting = (Player)gorilla.getNpc().getInteracting(); + MemorizedPlayer mp = memorizedPlayers.get(interacting); + + if (gorilla.getLastTickInteracting() != null && interacting == null) + { + gorilla.setInitiatedCombat(false); + } + else if (mp != null && mp.getLastWorldArea() != null && + !gorilla.isInitiatedCombat() && + tickCounter < gorilla.getNextAttackTick() && + gorilla.getNpc().getWorldArea().isInMeleeDistance(mp.getLastWorldArea())) + { + gorilla.setInitiatedCombat(true); + gorilla.setNextAttackTick(tickCounter + 1); + } + + int animationId = gorilla.getNpc().getAnimation(); + + if (gorilla.isTakenDamageRecently() && + tickCounter >= gorilla.getNextAttackTick() + 4) + { + // The gorilla was flinched, so its next attack gets delayed + gorilla.setNextAttackTick(tickCounter + DemonicGorilla.ATTACK_RATE / 2); + gorilla.setInitiatedCombat(true); + + if (mp != null && mp.getLastWorldArea() != null && + !gorilla.getNpc().getWorldArea().isInMeleeDistance(mp.getLastWorldArea()) && + !gorilla.getNpc().getWorldArea().intersectsWith(mp.getLastWorldArea())) + { + // Gorillas stop meleeing when they get flinched + // and the target isn't in melee distance + gorilla.setNextPosibleAttackStyles(gorilla + .getNextPosibleAttackStyles() + .stream() + .filter(x -> x != DemonicGorilla.AttackStyle.MELEE) + .collect(Collectors.toList())); + checkGorillaAttackStyleSwitch(gorilla, DemonicGorilla.AttackStyle.MELEE, + getProtectedStyle(interacting)); + } + } + else if (animationId != gorilla.getLastTickAnimation()) + { + if (animationId == AnimationID.DEMONIC_GORILLA_MELEE_ATTACK) + { + onGorillaAttack(gorilla, DemonicGorilla.AttackStyle.MELEE); + } + else if (animationId == AnimationID.DEMONIC_GORILLA_MAGIC_ATTACK) + { + onGorillaAttack(gorilla, DemonicGorilla.AttackStyle.MAGIC); + } + else if (animationId == AnimationID.DEMONIC_GORILLA_RANGED_ATTACK) + { + onGorillaAttack(gorilla, DemonicGorilla.AttackStyle.RANGED); + } + else if (animationId == AnimationID.DEMONIC_GORILLA_AOE_ATTACK && interacting != null) + { + // Note that AoE animation is the same as prayer switch animation + // so we need to check if the prayer was switched or not. + // It also does this animation when it spawns, so + // we need the interacting != null check. + + if (gorilla.getOverheadIcon() == gorilla.getLastTickOverheadIcon()) + { + // Confirmed, the gorilla used the AoE attack + onGorillaAttack(gorilla, DemonicGorilla.AttackStyle.BOULDER); + } + else + { + if (tickCounter >= gorilla.getNextAttackTick()) + { + gorilla.setChangedPrayerThisTick(true); + + // This part is more complicated because the gorilla may have + // used an attack, but the prayer switch animation takes + // priority over normal attack animations. + + int projectileId = gorilla.getRecentProjectileId(); + if (projectileId == ProjectileID.DEMONIC_GORILLA_MAGIC) + { + onGorillaAttack(gorilla, DemonicGorilla.AttackStyle.MAGIC); + } + else if (projectileId == ProjectileID.DEMONIC_GORILLA_RANGED) + { + onGorillaAttack(gorilla, DemonicGorilla.AttackStyle.RANGED); + } + else if (mp != null) + { + WorldArea lastPlayerArea = mp.getLastWorldArea(); + if (lastPlayerArea != null && + interacting != null && recentBoulders.stream() + .anyMatch(x -> x.distanceTo(lastPlayerArea) == 0)) + { + // A boulder started falling on the gorillas target, + // so we assume it was the gorilla who shot it + onGorillaAttack(gorilla, DemonicGorilla.AttackStyle.BOULDER); + } + else if (mp.getRecentHitsplats().size() > 0) + { + // It wasn't any of the three other attacks, + // but the player took damage, so we assume + // it's a melee attack + onGorillaAttack(gorilla, DemonicGorilla.AttackStyle.MELEE); + } + } + } + + // The next attack tick is always delayed if the + // gorilla switched prayer + gorilla.setNextAttackTick(tickCounter + DemonicGorilla.ATTACK_RATE); + gorilla.setChangedPrayerThisTick(true); + } + } + } + + if (gorilla.getDisabledMeleeMovementForTicks() > 0) + { + gorilla.setDisabledMeleeMovementForTicks(gorilla.getDisabledMeleeMovementForTicks() - 1); + } + else if (gorilla.isInitiatedCombat() && + gorilla.getNpc().getInteracting() != null && + !gorilla.isChangedAttackStyleThisTick() && + gorilla.getNextPosibleAttackStyles().size() >= 2 && + gorilla.getNextPosibleAttackStyles().stream() + .anyMatch(x -> x == DemonicGorilla.AttackStyle.MELEE)) + { + // If melee is a possibility, we can check if the gorilla + // is or isn't moving toward the player to determine if + // it is actually attempting to melee or not. + // We only run this check if the gorilla is in combat + // because otherwise it attempts to travel to melee + // distance before attacking its target. + + if (mp != null && mp.getLastWorldArea() != null && gorilla.getLastWorldArea() != null) + { + WorldArea predictedNewArea = gorilla.getLastWorldArea().calculateNextTravellingPoint( + client, mp.getLastWorldArea(), true, x -> + { + // Gorillas can't normally walk through other gorillas + // or other players + final WorldArea area1 = new WorldArea(x, 1, 1); + return area1 != null && + !gorillas.values().stream().anyMatch(y -> + { + if (y == gorilla) + { + return false; + } + final WorldArea area2 = + y.getNpc().getIndex() < gorilla.getNpc().getIndex() ? + y.getNpc().getWorldArea() : y.getLastWorldArea(); + return area2 != null && area1.intersectsWith(area2); + }) && + !memorizedPlayers.values().stream().anyMatch(y -> + { + final WorldArea area2 = y.getLastWorldArea(); + return area2 != null && area1.intersectsWith(area2); + }); + + // There is a special case where if a player walked through + // a gorilla, or a player walked through another player, + // the tiles that were walked through becomes + // walkable, but I didn't feel like it's necessary to handle + // that special case as it should rarely happen. + }); + if (predictedNewArea != null) + { + int distance = gorilla.getNpc().getWorldArea().distanceTo(mp.getLastWorldArea()); + WorldPoint predictedMovement = predictedNewArea.toWorldPoint(); + if (distance <= DemonicGorilla.MAX_ATTACK_RANGE && + mp != null && + mp.getLastWorldArea().hasLineOfSightTo(client, gorilla.getLastWorldArea())) + { + if (predictedMovement.distanceTo(gorilla.getLastWorldArea().toWorldPoint()) != 0) + { + if (predictedMovement.distanceTo(gorilla.getNpc().getWorldLocation()) == 0) + { + gorilla.setNextPosibleAttackStyles(gorilla + .getNextPosibleAttackStyles() + .stream() + .filter(x -> x == DemonicGorilla.AttackStyle.MELEE) + .collect(Collectors.toList())); + } + else + { + gorilla.setNextPosibleAttackStyles(gorilla + .getNextPosibleAttackStyles() + .stream() + .filter(x -> x != DemonicGorilla.AttackStyle.MELEE) + .collect(Collectors.toList())); + } + } + else if (tickCounter >= gorilla.getNextAttackTick() && + gorilla.getRecentProjectileId() == -1 && + !recentBoulders.stream().anyMatch(x -> x.distanceTo(mp.getLastWorldArea()) == 0)) + { + gorilla.setNextPosibleAttackStyles(gorilla + .getNextPosibleAttackStyles() + .stream() + .filter(x -> x == DemonicGorilla.AttackStyle.MELEE) + .collect(Collectors.toList())); + } + } + } + } + } + + if (gorilla.isTakenDamageRecently()) + { + gorilla.setInitiatedCombat(true); + } + + if (gorilla.getOverheadIcon() != gorilla.getLastTickOverheadIcon()) + { + if (gorilla.isChangedAttackStyleLastTick() || + gorilla.isChangedAttackStyleThisTick()) + { + // Apparently if it changes attack style and changes + // prayer on the same tick or 1 tick apart, it won't + // be able to move for the next 2 ticks if it attempts + // to melee + gorilla.setDisabledMeleeMovementForTicks(2); + } + else + { + // If it didn't change attack style lately, + // it's only for the next 1 tick + gorilla.setDisabledMeleeMovementForTicks(1); + } + } + gorilla.setLastTickAnimation(gorilla.getNpc().getAnimation()); + gorilla.setLastWorldArea(gorilla.getNpc().getWorldArea()); + gorilla.setLastTickInteracting(gorilla.getNpc().getInteracting()); + gorilla.setTakenDamageRecently(false); + gorilla.setChangedPrayerThisTick(false); + gorilla.setChangedAttackStyleLastTick(gorilla.isChangedAttackStyleThisTick()); + gorilla.setChangedAttackStyleThisTick(false); + gorilla.setLastTickOverheadIcon(gorilla.getOverheadIcon()); + gorilla.setRecentProjectileId(-1); + } + } + + @Subscribe + public void onProjectile(ProjectileMoved event) + { + Projectile projectile = event.getProjectile(); + int projectileId = projectile.getId(); + if (projectileId != ProjectileID.DEMONIC_GORILLA_RANGED && + projectileId != ProjectileID.DEMONIC_GORILLA_MAGIC && + projectileId != ProjectileID.DEMONIC_GORILLA_BOULDER) + { + return; + } + + // The event fires once before the projectile starts moving, + // and we only want to check each projectile once + if (client.getGameCycle() >= projectile.getStartMovementCycle()) + { + return; + } + + if (projectileId == ProjectileID.DEMONIC_GORILLA_BOULDER) + { + recentBoulders.add(WorldPoint.fromLocal(client, event.getPosition())); + } + else if (projectileId == ProjectileID.DEMONIC_GORILLA_MAGIC || + projectileId == ProjectileID.DEMONIC_GORILLA_RANGED) + { + WorldPoint projectileSourcePosition = WorldPoint.fromLocal( + client, projectile.getX1(), projectile.getY1(), client.getPlane()); + for (DemonicGorilla gorilla : gorillas.values()) + { + if (gorilla.getNpc().getWorldLocation().distanceTo(projectileSourcePosition) == 0) + { + gorilla.setRecentProjectileId(projectile.getId()); + } + } + } + } + + private void checkPendingAttacks() + { + Iterator it = pendingAttacks.iterator(); + while (it.hasNext()) + { + PendingGorillaAttack attack = it.next(); + if (tickCounter >= attack.getFinishesOnTick()) + { + boolean shouldDecreaseCounter = false; + DemonicGorilla gorilla = attack.getAttacker(); + MemorizedPlayer target = memorizedPlayers.get(attack.getTarget()); + if (target == null) + { + // Player went out of memory, so assume the hit was a 0 + shouldDecreaseCounter = true; + } + else if (target.getRecentHitsplats().size() == 0) + { + // No hitsplats was applied. This may happen in some cases + // where the player was out of memory while the + // projectile was travelling. So we assume the hit was a 0. + shouldDecreaseCounter = true; + } + else if (target.getRecentHitsplats().stream() + .anyMatch(x -> x.getHitsplatType() == Hitsplat.HitsplatType.BLOCK)) + { + // A blue hitsplat appeared, so we assume the gorilla hit a 0 + shouldDecreaseCounter = true; + } + + if (shouldDecreaseCounter) + { + gorilla.setAttacksUntilSwitch(gorilla.getAttacksUntilSwitch() - 1); + checkGorillaAttackStyleSwitch(gorilla); + } + + it.remove(); + } + } + } + + private void updatePlayers() + { + for (MemorizedPlayer mp : memorizedPlayers.values()) + { + mp.setLastWorldArea(mp.getPlayer().getWorldArea()); + mp.getRecentHitsplats().clear(); + } + } + + @Subscribe + public void onHitsplat(HitsplatApplied event) + { + if (gorillas.isEmpty()) + { + return; + } + + if (event.getActor() instanceof Player) + { + Player player = (Player)event.getActor(); + MemorizedPlayer mp = memorizedPlayers.get(player); + if (mp != null) + { + mp.getRecentHitsplats().add(event.getHitsplat()); + } + } + else if (event.getActor() instanceof NPC) + { + DemonicGorilla gorilla = gorillas.get(event.getActor()); + Hitsplat.HitsplatType hitsplatType = event.getHitsplat().getHitsplatType(); + if (gorilla != null && (hitsplatType == Hitsplat.HitsplatType.BLOCK || + hitsplatType == Hitsplat.HitsplatType.DAMAGE)) + { + gorilla.setTakenDamageRecently(true); + } + } + } + + @Subscribe + public void onGameState(GameStateChanged event) + { + GameState gs = event.getGameState(); + if (gs == GameState.LOGGING_IN || + gs == GameState.CONNECTION_LOST || + gs == GameState.HOPPING) + { + clear(); + } + } + + @Subscribe + public void onPlayerSpawned(PlayerSpawned event) + { + if (gorillas.isEmpty()) + { + return; + } + + Player player = event.getPlayer(); + memorizedPlayers.put(player, new MemorizedPlayer(player)); + } + + @Subscribe + public void onPlayerDespawned(PlayerDespawned event) + { + if (gorillas.isEmpty()) + { + return; + } + + memorizedPlayers.remove(event.getPlayer()); + } + + @Subscribe + public void onNpcSpawned(NpcSpawned event) + { + NPC npc = event.getNpc(); + if (isNpcGorilla(npc.getId())) + { + if (gorillas.isEmpty()) + { + // Players are not kept track of when there are no gorillas in + // memory, so we need to add the players that were already in memory. + resetPlayers(); + } + + gorillas.put(npc, new DemonicGorilla(npc)); + } + } + + @Subscribe + public void onNpcDespawned(NpcDespawned event) + { + if (gorillas.remove(event.getNpc()) != null && gorillas.isEmpty()) + { + clear(); + } + } + + @Subscribe + public void onGameTick(GameTick event) + { + checkGorillaAttacks(); + checkPendingAttacks(); + updatePlayers(); + recentBoulders.clear(); + + tickCounter++; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/MemorizedPlayer.java b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/MemorizedPlayer.java new file mode 100644 index 0000000000..7dd0058cd6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/MemorizedPlayer.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018, Woox + * 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.demonicgorilla; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.Hitsplat; +import net.runelite.api.Player; +import net.runelite.api.coords.WorldArea; + +public class MemorizedPlayer +{ + @Getter + private Player player; + + @Getter + @Setter + private WorldArea lastWorldArea; + + @Getter + private List recentHitsplats; + + public MemorizedPlayer(Player player) + { + this.player = player; + this.recentHitsplats = new ArrayList<>(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/PendingGorillaAttack.java b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/PendingGorillaAttack.java new file mode 100644 index 0000000000..6f3faff8d2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/PendingGorillaAttack.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018, Woox + * 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.demonicgorilla; + +import lombok.Getter; +import net.runelite.api.Player; + +public class PendingGorillaAttack +{ + @Getter + private DemonicGorilla attacker; + + @Getter + private DemonicGorilla.AttackStyle attackStyle; + + @Getter + private Player target; + + @Getter + private int finishesOnTick; + + public PendingGorillaAttack(DemonicGorilla attacker, DemonicGorilla.AttackStyle attackStyle, + Player target, int finishesOnTick) + { + this.attacker = attacker; + this.attackStyle = attackStyle; + this.target = target; + this.finishesOnTick = finishesOnTick; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java index 559cbee86f..b18c99ed5e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java @@ -180,6 +180,22 @@ public class DevToolsPanel extends PluginPanel }); container.add(mapSquaresBtn); + final JButton validMovementBtn = new JButton("Valid Moves"); + validMovementBtn.addActionListener(e -> + { + highlightButton(validMovementBtn); + plugin.toggleValidMovement(); + }); + container.add(validMovementBtn); + + final JButton lineOfSightBtn = new JButton("Line of Sight"); + lineOfSightBtn.addActionListener(e -> + { + highlightButton(lineOfSightBtn); + plugin.toggleLineOfSight(); + }); + container.add(lineOfSightBtn); + return container; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java index ca61efec6e..deba2b4f9a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java @@ -67,7 +67,7 @@ public class DevToolsPlugin extends Plugin private LocationOverlay locationOverlay; @Inject - private BorderOverlay borderOverlay; + private SceneOverlay sceneOverlay; @Inject private EventBus eventBus; @@ -84,6 +84,8 @@ public class DevToolsPlugin extends Plugin private boolean toggleLocation; private boolean toggleChunkBorders; private boolean toggleMapSquares; + private boolean toggleValidMovement; + private boolean toggleLineOfSight; Widget currentWidget; int itemIndex = -1; @@ -129,7 +131,7 @@ public class DevToolsPlugin extends Plugin @Override public Collection getOverlays() { - return Arrays.asList(overlay, locationOverlay, borderOverlay); + return Arrays.asList(overlay, locationOverlay, sceneOverlay); } @Subscribe @@ -223,6 +225,16 @@ public class DevToolsPlugin extends Plugin toggleMapSquares = !toggleMapSquares; } + void toggleValidMovement() + { + toggleValidMovement = !toggleValidMovement; + } + + void toggleLineOfSight() + { + toggleLineOfSight = !toggleLineOfSight; + } + boolean isTogglePlayers() { return togglePlayers; @@ -282,4 +294,14 @@ public class DevToolsPlugin extends Plugin { return toggleMapSquares; } + + boolean isToggleValidMovement() + { + return toggleValidMovement; + } + + boolean isToggleLineOfSight() + { + return toggleLineOfSight; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/BorderOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/SceneOverlay.java similarity index 55% rename from runelite-client/src/main/java/net/runelite/client/plugins/devtools/BorderOverlay.java rename to runelite-client/src/main/java/net/runelite/client/plugins/devtools/SceneOverlay.java index e2ee297881..cc8a703d2a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/BorderOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/SceneOverlay.java @@ -28,33 +28,44 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; +import java.awt.Polygon; import java.awt.geom.GeneralPath; +import java.util.List; import javax.inject.Inject; +import net.runelite.api.Actor; import net.runelite.api.Client; +import net.runelite.api.NPC; import net.runelite.api.Perspective; +import net.runelite.api.Player; import net.runelite.api.Point; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayUtil; -public class BorderOverlay extends Overlay +public class SceneOverlay extends Overlay { private static final Color MAP_SQUARE_COLOR = Color.GREEN; private static final Color CHUNK_BORDER_COLOR = Color.BLUE; + private static final Color LOCAL_VALID_MOVEMENT_COLOR = new Color(141, 220, 26); + private static final Color VALID_MOVEMENT_COLOR = new Color(73, 122, 18); + private static final Color LINE_OF_SIGHT_COLOR = new Color(204, 42, 219); private static final int LOCAL_TILE_SIZE = Perspective.LOCAL_TILE_SIZE; private static final int CHUNK_SIZE = 8; private static final int MAP_SQUARE_SIZE = CHUNK_SIZE * CHUNK_SIZE; // 64 - private static final int CULL_RANGE = 16; + private static final int CULL_CHUNK_BORDERS_RANGE = 16; private static final int STROKE_WIDTH = 4; + private static final int CULL_LINE_OF_SIGHT_RANGE = 10; private final Client client; private final DevToolsPlugin plugin; @Inject - public BorderOverlay(Client client, DevToolsPlugin plugin) + public SceneOverlay(Client client, DevToolsPlugin plugin) { setPosition(OverlayPosition.DYNAMIC); setLayer(OverlayLayer.ABOVE_SCENE); @@ -75,16 +86,26 @@ public class BorderOverlay extends Overlay renderMapSquares(graphics); } + if (plugin.isToggleLineOfSight()) + { + renderLineOfSight(graphics); + } + + if (plugin.isToggleValidMovement()) + { + renderValidMovement(graphics); + } + return null; } private void renderChunkBorders(Graphics2D graphics) { WorldPoint wp = client.getLocalPlayer().getWorldLocation(); - int startX = (wp.getX() - CULL_RANGE + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE; - int startY = (wp.getY() - CULL_RANGE + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE; - int endX = (wp.getX() + CULL_RANGE) / CHUNK_SIZE * CHUNK_SIZE; - int endY = (wp.getY() + CULL_RANGE) / CHUNK_SIZE * CHUNK_SIZE; + int startX = (wp.getX() - CULL_CHUNK_BORDERS_RANGE + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE; + int startY = (wp.getY() - CULL_CHUNK_BORDERS_RANGE + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE; + int endX = (wp.getX() + CULL_CHUNK_BORDERS_RANGE) / CHUNK_SIZE * CHUNK_SIZE; + int endY = (wp.getY() + CULL_CHUNK_BORDERS_RANGE) / CHUNK_SIZE * CHUNK_SIZE; graphics.setStroke(new BasicStroke(STROKE_WIDTH)); graphics.setColor(CHUNK_BORDER_COLOR); @@ -92,8 +113,8 @@ public class BorderOverlay extends Overlay GeneralPath path = new GeneralPath(); for (int x = startX; x <= endX; x += CHUNK_SIZE) { - LocalPoint lp1 = LocalPoint.fromWorld(client, x, wp.getY() - CULL_RANGE); - LocalPoint lp2 = LocalPoint.fromWorld(client, x, wp.getY() + CULL_RANGE); + LocalPoint lp1 = LocalPoint.fromWorld(client, x, wp.getY() - CULL_CHUNK_BORDERS_RANGE); + LocalPoint lp2 = LocalPoint.fromWorld(client, x, wp.getY() + CULL_CHUNK_BORDERS_RANGE); boolean first = true; for (int y = lp1.getY(); y <= lp2.getY(); y += LOCAL_TILE_SIZE) @@ -118,8 +139,8 @@ public class BorderOverlay extends Overlay } for (int y = startY; y <= endY; y += CHUNK_SIZE) { - LocalPoint lp1 = LocalPoint.fromWorld(client, wp.getX() - CULL_RANGE, y); - LocalPoint lp2 = LocalPoint.fromWorld(client, wp.getX() + CULL_RANGE, y); + LocalPoint lp1 = LocalPoint.fromWorld(client, wp.getX() - CULL_CHUNK_BORDERS_RANGE, y); + LocalPoint lp2 = LocalPoint.fromWorld(client, wp.getX() + CULL_CHUNK_BORDERS_RANGE, y); boolean first = true; for (int x = lp1.getX(); x <= lp2.getX(); x += LOCAL_TILE_SIZE) @@ -148,10 +169,10 @@ public class BorderOverlay extends Overlay private void renderMapSquares(Graphics2D graphics) { WorldPoint wp = client.getLocalPlayer().getWorldLocation(); - int startX = (wp.getX() - CULL_RANGE + MAP_SQUARE_SIZE - 1) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE; - int startY = (wp.getY() - CULL_RANGE + MAP_SQUARE_SIZE - 1) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE; - int endX = (wp.getX() + CULL_RANGE) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE; - int endY = (wp.getY() + CULL_RANGE) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE; + int startX = (wp.getX() - CULL_CHUNK_BORDERS_RANGE + MAP_SQUARE_SIZE - 1) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE; + int startY = (wp.getY() - CULL_CHUNK_BORDERS_RANGE + MAP_SQUARE_SIZE - 1) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE; + int endX = (wp.getX() + CULL_CHUNK_BORDERS_RANGE) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE; + int endY = (wp.getY() + CULL_CHUNK_BORDERS_RANGE) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE; graphics.setStroke(new BasicStroke(STROKE_WIDTH)); graphics.setColor(MAP_SQUARE_COLOR); @@ -159,8 +180,8 @@ public class BorderOverlay extends Overlay GeneralPath path = new GeneralPath(); for (int x = startX; x <= endX; x += MAP_SQUARE_SIZE) { - LocalPoint lp1 = LocalPoint.fromWorld(client, x, wp.getY() - CULL_RANGE); - LocalPoint lp2 = LocalPoint.fromWorld(client, x, wp.getY() + CULL_RANGE); + LocalPoint lp1 = LocalPoint.fromWorld(client, x, wp.getY() - CULL_CHUNK_BORDERS_RANGE); + LocalPoint lp2 = LocalPoint.fromWorld(client, x, wp.getY() + CULL_CHUNK_BORDERS_RANGE); boolean first = true; for (int y = lp1.getY(); y <= lp2.getY(); y += LOCAL_TILE_SIZE) @@ -185,8 +206,8 @@ public class BorderOverlay extends Overlay } for (int y = startY; y <= endY; y += MAP_SQUARE_SIZE) { - LocalPoint lp1 = LocalPoint.fromWorld(client, wp.getX() - CULL_RANGE, y); - LocalPoint lp2 = LocalPoint.fromWorld(client, wp.getX() + CULL_RANGE, y); + LocalPoint lp1 = LocalPoint.fromWorld(client, wp.getX() - CULL_CHUNK_BORDERS_RANGE, y); + LocalPoint lp2 = LocalPoint.fromWorld(client, wp.getX() + CULL_CHUNK_BORDERS_RANGE, y); boolean first = true; for (int x = lp1.getX(); x <= lp2.getX(); x += LOCAL_TILE_SIZE) @@ -212,4 +233,125 @@ public class BorderOverlay extends Overlay graphics.draw(path); } + private void renderTileIfValidForMovement(Graphics2D graphics, Actor actor, int dx, int dy) + { + WorldArea area = actor.getWorldArea(); + if (area == null) + { + return; + } + + if (area.canTravelInDirection(client, dx, dy)) + { + LocalPoint lp = actor.getLocalLocation(); + if (lp == null) + { + return; + } + + lp = new LocalPoint( + lp.getX() + dx * Perspective.LOCAL_TILE_SIZE + dx * Perspective.LOCAL_TILE_SIZE * (area.getWidth() - 1) / 2, + lp.getY() + dy * Perspective.LOCAL_TILE_SIZE + dy * Perspective.LOCAL_TILE_SIZE * (area.getHeight() - 1) / 2); + if (lp == null) + { + return; + } + + Polygon poly = Perspective.getCanvasTilePoly(client, lp); + if (poly == null) + { + return; + } + + if (actor == client.getLocalPlayer()) + { + OverlayUtil.renderPolygon(graphics, poly, LOCAL_VALID_MOVEMENT_COLOR); + } + else + { + OverlayUtil.renderPolygon(graphics, poly, VALID_MOVEMENT_COLOR); + } + } + } + + private void renderValidMovement(Graphics2D graphics) + { + Player player = client.getLocalPlayer(); + List npcs = client.getNpcs(); + for (NPC npc : npcs) + { + if (player.getInteracting() != npc && npc.getInteracting() != player) + { + continue; + } + for (int dx = -1; dx <= 1; dx++) + { + for (int dy = -1; dy <= 1; dy++) + { + if (dx == 0 && dy == 0) + { + continue; + } + renderTileIfValidForMovement(graphics, npc, dx, dy); + } + } + } + + for (int dx = -1; dx <= 1; dx++) + { + for (int dy = -1; dy <= 1; dy++) + { + if (dx == 0 && dy == 0) + { + continue; + } + renderTileIfValidForMovement(graphics, player, dx, dy); + } + } + } + + private void renderTileIfHasLineOfSight(Graphics2D graphics, WorldArea start, int targetX, int targetY) + { + WorldPoint targetLocation = new WorldPoint(targetX, targetY, start.getPlane()); + if (targetLocation == null) + { + return; + } + + // Running the line of sight algorithm 100 times per frame doesn't + // seem to use much CPU time, however rendering 100 tiles does + if (start.hasLineOfSightTo(client, targetLocation)) + { + LocalPoint lp = LocalPoint.fromWorld(client, targetLocation); + if (lp == null) + { + return; + } + + Polygon poly = Perspective.getCanvasTilePoly(client, lp); + if (poly == null) + { + return; + } + + OverlayUtil.renderPolygon(graphics, poly, LINE_OF_SIGHT_COLOR); + } + } + + private void renderLineOfSight(Graphics2D graphics) + { + WorldArea area = client.getLocalPlayer().getWorldArea(); + for (int x = area.getX() - CULL_LINE_OF_SIGHT_RANGE; x <= area.getX() + CULL_LINE_OF_SIGHT_RANGE; x++) + { + for (int y = area.getY() - CULL_LINE_OF_SIGHT_RANGE; y <= area.getY() + CULL_LINE_OF_SIGHT_RANGE; y++) + { + if (x == area.getX() && y == area.getY()) + { + continue; + } + renderTileIfHasLineOfSight(graphics, area, x, y); + } + } + } + } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java index 6dff4fcf43..43791932ee 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java @@ -29,11 +29,13 @@ import java.awt.Polygon; import java.awt.image.BufferedImage; import net.runelite.api.Actor; import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; import net.runelite.api.Perspective; import net.runelite.api.Player; import net.runelite.api.Point; import net.runelite.api.SpritePixels; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.AnimationChanged; import net.runelite.api.events.GraphicChanged; @@ -204,4 +206,25 @@ public abstract class RSActorMixin implements RSActor } return model.getConvexHull(getX(), getY(), getOrientation()); } + + @Inject + @Override + public WorldArea getWorldArea() + { + int size = 1; + if (this instanceof NPC) + { + NPCComposition composition = ((NPC)this).getComposition(); + if (composition != null && composition.getConfigs() != null) + { + composition = composition.transform(); + } + if (composition != null) + { + size = composition.getSize(); + } + } + + return new WorldArea(this.getWorldLocation(), size, size); + } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java index 1cedf4f298..31dad819fb 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java @@ -25,11 +25,13 @@ package net.runelite.mixins; import net.runelite.api.Actor; +import net.runelite.api.CollisionDataFlag; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.GameState; import net.runelite.api.GroundObject; import net.runelite.api.Point; +import net.runelite.api.Tile; import net.runelite.api.WallObject; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; @@ -52,6 +54,7 @@ import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import static net.runelite.client.callback.Hooks.eventBus; import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSCollisionData; import net.runelite.rs.api.RSGameObject; import net.runelite.rs.api.RSTile; @@ -272,4 +275,115 @@ public abstract class RSTileMixin implements RSTile ItemLayerChanged itemLayerChanged = new ItemLayerChanged(this); eventBus.post(itemLayerChanged); } + + @Inject + @Override + public boolean hasLineOfSightTo(Tile other) + { + // Thanks to Henke for this method :) + + if (this.getPlane() != other.getPlane()) + { + return false; + } + + RSCollisionData[] collisionData = client.getCollisionMaps(); + int z = this.getPlane(); + int[][] collisionDataFlags = collisionData[z].getFlags(); + + Point p1 = this.getRegionLocation(); + Point p2 = other.getRegionLocation(); + if (p1.getX() == p2.getX() && p1.getY() == p2.getY()) + { + return true; + } + + int dx = p2.getX() - p1.getX(); + int dy = p2.getY() - p1.getY(); + int dxAbs = Math.abs(dx); + int dyAbs = Math.abs(dy); + + int xFlags = CollisionDataFlag.BLOCK_LINE_OF_SIGHT_FULL; + int yFlags = CollisionDataFlag.BLOCK_LINE_OF_SIGHT_FULL; + if (dx < 0) + { + xFlags |= CollisionDataFlag.BLOCK_LINE_OF_SIGHT_EAST; + } + else + { + xFlags |= CollisionDataFlag.BLOCK_LINE_OF_SIGHT_WEST; + } + if (dy < 0) + { + yFlags |= CollisionDataFlag.BLOCK_LINE_OF_SIGHT_NORTH; + } + else + { + yFlags |= CollisionDataFlag.BLOCK_LINE_OF_SIGHT_SOUTH; + } + + if (dxAbs > dyAbs) + { + int x = p1.getX(); + int yBig = p1.getY() << 16; // The y position is represented as a bigger number to handle rounding + int slope = (dy << 16) / dxAbs; + yBig += 0x8000; // Add half of a tile + if (dy < 0) + { + yBig--; // For correct rounding + } + int direction = dx < 0 ? -1 : 1; + + while (x != p2.getX()) + { + x += direction; + int y = yBig >>> 16; + if ((collisionDataFlags[x][y] & xFlags) != 0) + { + // Collision while traveling on the x axis + return false; + } + yBig += slope; + int nextY = yBig >>> 16; + if (nextY != y && (collisionDataFlags[x][nextY] & yFlags) != 0) + { + // Collision while traveling on the y axis + return false; + } + } + } + else + { + int y = p1.getY(); + int xBig = p1.getX() << 16; // The x position is represented as a bigger number to handle rounding + int slope = (dx << 16) / dyAbs; + xBig += 0x8000; // Add half of a tile + if (dx < 0) + { + xBig--; // For correct rounding + } + int direction = dy < 0 ? -1 : 1; + + while (y != p2.getY()) + { + y += direction; + int x = xBig >>> 16; + if ((collisionDataFlags[x][y] & yFlags) != 0) + { + // Collision while traveling on the y axis + return false; + } + xBig += slope; + int nextX = xBig >>> 16; + if (nextX != x && (collisionDataFlags[nextX][y] & xFlags) != 0) + { + // Collision while traveling on the x axis + return false; + } + } + } + + // No collision + return true; + } } 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 2c68afbbc6..45ba0ecb59 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 @@ -92,4 +92,13 @@ public interface RSActor extends RSRenderable, Actor @Import("spotAnimFrameCycle") int getSpotAnimFrameCycle(); + + @Import("hitsplatValues") + int[] getHitsplatValues(); + + @Import("hitsplatTypes") + int[] getHitsplatTypes(); + + @Import("hitsplatCycles") + int[] getHitsplatCycles(); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSCollisionData.java b/runescape-api/src/main/java/net/runelite/rs/api/RSCollisionData.java index 5363333740..8df6236650 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSCollisionData.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSCollisionData.java @@ -24,9 +24,10 @@ */ package net.runelite.rs.api; +import net.runelite.api.CollisionData; import net.runelite.mapping.Import; -public interface RSCollisionData +public interface RSCollisionData extends CollisionData { @Import("flags") int[][] getFlags(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSNPCComposition.java b/runescape-api/src/main/java/net/runelite/rs/api/RSNPCComposition.java index 535b60b4e1..dbfd50a291 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSNPCComposition.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSNPCComposition.java @@ -68,4 +68,12 @@ public interface RSNPCComposition extends NPCComposition @Import("transform") @Override RSNPCComposition transform(); + + @Import("size") + @Override + int getSize(); + + @Import("headIcon") + @Override + int getOverheadIcon(); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSPlayer.java b/runescape-api/src/main/java/net/runelite/rs/api/RSPlayer.java index 1e693a0e94..ddfa78c75a 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSPlayer.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSPlayer.java @@ -57,4 +57,8 @@ public interface RSPlayer extends RSActor, Player @Import("isFriend") @Override boolean isFriend(); + + @Import("overheadIcon") + @Override + int getOverheadIcon(); }