From 6234f209e9cab5b7880b76d69fdefc73d24e1bef Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 30 Sep 2019 09:01:25 -0600 Subject: [PATCH 01/23] runelite-client: Remove Demonic Gorilla Plugin as per Jagex's wishes --- .../demonicgorilla/DemonicGorilla.java | 140 ---- .../demonicgorilla/DemonicGorillaOverlay.java | 154 ---- .../demonicgorilla/DemonicGorillaPlugin.java | 716 ------------------ .../demonicgorilla/MemorizedPlayer.java | 52 -- .../demonicgorilla/PendingGorillaAttack.java | 52 -- 5 files changed, 1114 deletions(-) delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorilla.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaOverlay.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaPlugin.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/MemorizedPlayer.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/PendingGorillaAttack.java 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 deleted file mode 100644 index 0a96a49635..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorilla.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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.HeadIcon; -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 HeadIcon 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; - } - - public HeadIcon getOverheadIcon() - { - NPCComposition composition = this.npc.getComposition(); - if (composition != null) - { - return composition.getOverheadIcon(); - } - return null; - } -} \ 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 deleted file mode 100644 index 2fa191d5ba..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaOverlay.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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.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) - { - for (DemonicGorilla gorilla : plugin.getGorillas().values()) - { - if (gorilla.getNpc().getInteracting() == null) - { - continue; - } - - LocalPoint lp = gorilla.getNpc().getLocalLocation(); - if (lp != null) - { - Point point = Perspective.localToCanvas(client, lp, client.getPlane(), - gorilla.getNpc().getLogicalHeight() + 16); - if (point != null) - { - point = new Point(point.getX(), point.getY()); - - 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 deleted file mode 100644 index 0e9660b918..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaPlugin.java +++ /dev/null @@ -1,716 +0,0 @@ -/* - * 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.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 net.runelite.api.AnimationID; -import net.runelite.api.Client; -import net.runelite.api.GameState; -import net.runelite.api.HeadIcon; -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.callback.ClientThread; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; - -@PluginDescriptor( - name = "Demonic Gorillas", - description = "Count demonic gorilla attacks and display their next possible attack styles", - tags = {"combat", "overlay", "pve", "pvm"} -) -public class DemonicGorillaPlugin extends Plugin -{ - @Inject - private Client client; - - @Inject - private OverlayManager overlayManager; - - @Inject - private DemonicGorillaOverlay overlay; - - @Inject - private ClientThread clientThread; - - @Getter - private Map gorillas; - - private List recentBoulders; - - private List pendingAttacks; - - private Map memorizedPlayers; - - @Override - protected void startUp() throws Exception - { - overlayManager.add(overlay); - gorillas = new HashMap<>(); - recentBoulders = new ArrayList<>(); - pendingAttacks = new ArrayList<>(); - memorizedPlayers = new HashMap<>(); - clientThread.invoke(this::reset); // Updates the list of gorillas and players - } - - @Override - protected void shutDown() throws Exception - { - overlayManager.remove(overlay); - gorillas = null; - recentBoulders = null; - pendingAttacks = null; - memorizedPlayers = null; - } - - private void clear() - { - recentBoulders.clear(); - pendingAttacks.clear(); - memorizedPlayers.clear(); - gorillas.clear(); - } - - private void reset() - { - recentBoulders.clear(); - pendingAttacks.clear(); - resetGorillas(); - resetPlayers(); - } - - private void resetGorillas() - { - gorillas.clear(); - for (NPC npc : client.getNpcs()) - { - if (isNpcGorilla(npc.getId())) - { - gorillas.put(npc, new DemonicGorilla(npc)); - } - } - } - - 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().isEmpty()) - { - gorilla.setNextPosibleAttackStyles(Arrays - .stream(DemonicGorilla.ALL_REGULAR_ATTACK_STYLES) - .filter(x -> Arrays.stream(protectedStyles).noneMatch(y -> x == y)) - .collect(Collectors.toList())); - gorilla.setAttacksUntilSwitch(DemonicGorilla.ATTACKS_PER_SWITCH); - gorilla.setChangedAttackStyleThisTick(true); - } - } - - private DemonicGorilla.AttackStyle getProtectedStyle(Player player) - { - HeadIcon headIcon = player.getOverheadIcon(); - if (headIcon == null) - { - return null; - } - switch (headIcon) - { - case MELEE: - return DemonicGorilla.AttackStyle.MELEE; - case RANGED: - return DemonicGorilla.AttackStyle.RANGED; - case MAGIC: - return DemonicGorilla.AttackStyle.MAGIC; - default: - 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 = client.getTickCount(); - 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().isEmpty()) - { - // 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); - - int tickCounter = client.getTickCount(); - gorilla.setNextAttackTick(tickCounter + DemonicGorilla.ATTACK_RATE); - } - - private void checkGorillaAttacks() - { - int tickCounter = client.getTickCount(); - 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().isEmpty()) - { - // 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().noneMatch(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().noneMatch(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().noneMatch(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 onProjectileMoved(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(); - int tickCounter = client.getTickCount(); - 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().isEmpty()) - { - // 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 onHitsplatApplied(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 onGameStateChanged(GameStateChanged event) - { - GameState gs = event.getGameState(); - if (gs == GameState.LOGGING_IN || - gs == GameState.CONNECTION_LOST || - gs == GameState.HOPPING) - { - reset(); - } - } - - @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(); - } -} \ 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 deleted file mode 100644 index 7dd0058cd6..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/MemorizedPlayer.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 deleted file mode 100644 index 6f3faff8d2..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/PendingGorillaAttack.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 From 3819994572a21fbaf2e796323915b0b30b88b046 Mon Sep 17 00:00:00 2001 From: dekvall Date: Tue, 1 Oct 2019 14:05:51 +0200 Subject: [PATCH 02/23] runelite-api: add convexhull to wallobject --- .../src/main/java/net/runelite/api/WallObject.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/WallObject.java b/runelite-api/src/main/java/net/runelite/api/WallObject.java index ddc4a734cd..c9016b4289 100644 --- a/runelite-api/src/main/java/net/runelite/api/WallObject.java +++ b/runelite-api/src/main/java/net/runelite/api/WallObject.java @@ -24,6 +24,8 @@ */ package net.runelite.api; +import java.awt.Polygon; + /** * Represents one or two walls on a tile */ @@ -55,6 +57,15 @@ public interface WallObject extends TileObject */ int getConfig(); + /** + * Gets the convex hull of the objects model. + * + * @return the convex hull + * @see net.runelite.api.model.Jarvis + */ + Polygon getConvexHull(); + Polygon getConvexHull2(); + Renderable getRenderable1(); Renderable getRenderable2(); } From 047861d8cd3ae3c44015db47201123d42d192e2a Mon Sep 17 00:00:00 2001 From: dekvall Date: Tue, 1 Oct 2019 14:28:39 +0200 Subject: [PATCH 03/23] object indicators: add ability to mark wall objects --- .../ObjectIndicatorsOverlay.java | 6 ++++ .../ObjectIndicatorsPlugin.java | 32 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java index f111a6f94f..509b3f5bde 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java @@ -32,6 +32,7 @@ import net.runelite.api.Client; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.TileObject; +import net.runelite.api.WallObject; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; @@ -72,6 +73,11 @@ class ObjectIndicatorsOverlay extends Overlay { polygon = ((GameObject) object).getConvexHull(); } + else if (object instanceof WallObject) + { + polygon = ((WallObject) object).getConvexHull(); + polygon2 = ((WallObject) object).getConvexHull2(); + } else if (object instanceof DecorativeObject) { polygon = ((DecorativeObject) object).getConvexHull(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index e305c76ff3..c41e81c6c6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -51,6 +51,7 @@ import net.runelite.api.ObjectComposition; import net.runelite.api.Scene; import net.runelite.api.Tile; import net.runelite.api.TileObject; +import net.runelite.api.WallObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.FocusChanged; import net.runelite.api.events.GameObjectDespawned; @@ -60,6 +61,9 @@ import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.DecorativeObjectSpawned; import net.runelite.api.events.DecorativeObjectDespawned; +import net.runelite.api.events.WallObjectChanged; +import net.runelite.api.events.WallObjectDespawned; +import net.runelite.api.events.WallObjectSpawned; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.input.KeyListener; @@ -157,6 +161,28 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener } } + @Subscribe + public void onWallObjectSpawned(WallObjectSpawned event) + { + checkObjectPoints(event.getWallObject()); + } + + @Subscribe + public void onWallObjectChanged(WallObjectChanged event) + { + WallObject previous = event.getPrevious(); + WallObject wallObject = event.getWallObject(); + + objects.remove(previous); + checkObjectPoints(wallObject); + } + + @Subscribe + public void onWallObjectDespawned(WallObjectDespawned event) + { + objects.remove(event.getWallObject()); + } + @Subscribe public void onGameObjectSpawned(GameObjectSpawned event) { @@ -296,6 +322,12 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener final GameObject[] tileGameObjects = tile.getGameObjects(); final DecorativeObject tileDecorativeObject = tile.getDecorativeObject(); + final WallObject tileWallObject = tile.getWallObject(); + + if (tileWallObject != null && tileWallObject.getId() == id) + { + return tileWallObject; + } if (tileDecorativeObject != null && tileDecorativeObject.getId() == id) { From a4cfbc3b423516d488ab4d9cf3a21eb4172bdb98 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 9 Sep 2019 08:19:28 -0600 Subject: [PATCH 04/23] runelite-api: Use more general Shape for clickboxes and convex hulls --- .../src/main/java/net/runelite/api/Actor.java | 3 ++- .../main/java/net/runelite/api/DecorativeObject.java | 6 +++--- .../src/main/java/net/runelite/api/GameObject.java | 6 +++--- .../src/main/java/net/runelite/api/TileObject.java | 4 ++-- .../src/main/java/net/runelite/api/WallObject.java | 6 +++--- .../client/plugins/agility/AgilityOverlay.java | 4 ++-- .../blastfurnace/BlastFurnaceClickBoxOverlay.java | 4 ++-- .../client/plugins/devtools/DevToolsOverlay.java | 11 ++++++----- .../client/plugins/herbiboars/HerbiboarOverlay.java | 6 +++--- .../client/plugins/npchighlight/NpcSceneOverlay.java | 5 +++-- .../objectindicators/ObjectIndicatorsOverlay.java | 6 +++--- .../client/plugins/roguesden/RoguesDenOverlay.java | 7 ++++--- .../client/plugins/runecraft/AbyssOverlay.java | 4 ++-- .../client/plugins/slayer/TargetClickboxOverlay.java | 4 ++-- .../net/runelite/client/ui/overlay/OverlayUtil.java | 10 +++++----- 15 files changed, 45 insertions(+), 41 deletions(-) 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 2b35102832..a523555648 100644 --- a/runelite-api/src/main/java/net/runelite/api/Actor.java +++ b/runelite-api/src/main/java/net/runelite/api/Actor.java @@ -26,6 +26,7 @@ package net.runelite.api; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Shape; import java.awt.image.BufferedImage; import javax.annotation.Nullable; import net.runelite.api.annotations.VisibleForDevtools; @@ -218,7 +219,7 @@ public interface Actor extends Renderable * @return the convex hull * @see net.runelite.api.model.Jarvis */ - Polygon getConvexHull(); + Shape getConvexHull(); /** * Gets the world area that the actor occupies. diff --git a/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java b/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java index 145531fa5d..7dba1a3e65 100644 --- a/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java +++ b/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java @@ -24,7 +24,7 @@ */ package net.runelite.api; -import java.awt.Polygon; +import java.awt.Shape; /** * Represents a decorative object, such as an object on a wall. @@ -37,8 +37,8 @@ public interface DecorativeObject extends TileObject * @return the convex hull * @see net.runelite.api.model.Jarvis */ - Polygon getConvexHull(); - Polygon getConvexHull2(); + Shape getConvexHull(); + Shape getConvexHull2(); Renderable getRenderable(); Renderable getRenderable2(); diff --git a/runelite-api/src/main/java/net/runelite/api/GameObject.java b/runelite-api/src/main/java/net/runelite/api/GameObject.java index c721f35067..2dbf259795 100644 --- a/runelite-api/src/main/java/net/runelite/api/GameObject.java +++ b/runelite-api/src/main/java/net/runelite/api/GameObject.java @@ -24,7 +24,7 @@ */ package net.runelite.api; -import java.awt.Polygon; +import java.awt.Shape; import net.runelite.api.coords.Angle; /** @@ -54,12 +54,12 @@ public interface GameObject extends TileObject Point getSceneMaxLocation(); /** - * Gets the convex hull of the actors model. + * Gets the convex hull of the object's model. * * @return the convex hull * @see net.runelite.api.model.Jarvis */ - Polygon getConvexHull(); + Shape getConvexHull(); /** * Gets the orientation of the object. diff --git a/runelite-api/src/main/java/net/runelite/api/TileObject.java b/runelite-api/src/main/java/net/runelite/api/TileObject.java index e9012d29e4..7c6c606756 100644 --- a/runelite-api/src/main/java/net/runelite/api/TileObject.java +++ b/runelite-api/src/main/java/net/runelite/api/TileObject.java @@ -26,7 +26,7 @@ package net.runelite.api; import java.awt.Graphics2D; import java.awt.Polygon; -import java.awt.geom.Area; +import java.awt.Shape; import javax.annotation.Nullable; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; @@ -132,5 +132,5 @@ public interface TileObject * @return the clickable area */ @Nullable - Area getClickbox(); + Shape getClickbox(); } diff --git a/runelite-api/src/main/java/net/runelite/api/WallObject.java b/runelite-api/src/main/java/net/runelite/api/WallObject.java index c9016b4289..99e8236942 100644 --- a/runelite-api/src/main/java/net/runelite/api/WallObject.java +++ b/runelite-api/src/main/java/net/runelite/api/WallObject.java @@ -24,7 +24,7 @@ */ package net.runelite.api; -import java.awt.Polygon; +import java.awt.Shape; /** * Represents one or two walls on a tile @@ -63,8 +63,8 @@ public interface WallObject extends TileObject * @return the convex hull * @see net.runelite.api.model.Jarvis */ - Polygon getConvexHull(); - Polygon getConvexHull2(); + Shape getConvexHull(); + Shape getConvexHull2(); Renderable getRenderable1(); Renderable getRenderable2(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java index e9c205f64f..c012b945dc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java @@ -29,7 +29,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; -import java.awt.geom.Area; +import java.awt.Shape; import java.util.List; import javax.inject.Inject; import net.runelite.api.Client; @@ -90,7 +90,7 @@ class AgilityOverlay extends Overlay } return; } - Area objectClickbox = object.getClickbox(); + Shape objectClickbox = object.getClickbox(); if (objectClickbox != null) { AgilityShortcut agilityShortcut = obstacle.getShortcut(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BlastFurnaceClickBoxOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BlastFurnaceClickBoxOverlay.java index 9dba17df30..ea09b36e63 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BlastFurnaceClickBoxOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BlastFurnaceClickBoxOverlay.java @@ -27,7 +27,7 @@ package net.runelite.client.plugins.blastfurnace; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.geom.Area; +import java.awt.Shape; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.EquipmentInventorySlot; @@ -110,7 +110,7 @@ class BlastFurnaceClickBoxOverlay extends Overlay if (localLocation.distanceTo(location) <= MAX_DISTANCE) { - Area objectClickbox = object.getClickbox(); + Shape objectClickbox = object.getClickbox(); if (objectClickbox != null) { if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY())) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java index 457a86b766..aa2f55bd27 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java @@ -32,6 +32,7 @@ import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.Rectangle; +import java.awt.Shape; import java.awt.geom.Rectangle2D; import java.util.List; import javax.inject.Inject; @@ -286,10 +287,10 @@ class DevToolsOverlay extends Overlay // Draw a polygon around the convex hull // of the model vertices - Polygon p = gameObject.getConvexHull(); + Shape p = gameObject.getConvexHull(); if (p != null) { - graphics.drawPolygon(p); + graphics.draw(p); } } } @@ -330,16 +331,16 @@ class DevToolsOverlay extends Overlay OverlayUtil.renderTileOverlay(graphics, decorObject, "ID: " + decorObject.getId(), DEEP_PURPLE); } - Polygon p = decorObject.getConvexHull(); + Shape p = decorObject.getConvexHull(); if (p != null) { - graphics.drawPolygon(p); + graphics.draw(p); } p = decorObject.getConvexHull2(); if (p != null) { - graphics.drawPolygon(p); + graphics.draw(p); } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarOverlay.java index 653228cdb3..a97f5d9d49 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/herbiboars/HerbiboarOverlay.java @@ -28,7 +28,7 @@ import com.google.inject.Inject; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.geom.Area; +import java.awt.Shape; import java.util.Set; import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; @@ -103,7 +103,7 @@ class HerbiboarOverlay extends Overlay { if (config.showClickBoxes()) { - Area clickbox = object.getClickbox(); + Shape clickbox = object.getClickbox(); if (clickbox != null) { graphics.setColor(config.getObjectColor()); @@ -129,7 +129,7 @@ class HerbiboarOverlay extends Overlay { if (config.showClickBoxes()) { - Area clickbox = object.getClickbox(); + Shape clickbox = object.getClickbox(); if (clickbox != null) { Color col = config.getObjectColor(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcSceneOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcSceneOverlay.java index ae252aef93..fef1b03c8a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcSceneOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcSceneOverlay.java @@ -30,6 +30,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Shape; import java.text.DecimalFormat; import java.text.NumberFormat; import java.time.Instant; @@ -167,7 +168,7 @@ public class NpcSceneOverlay extends Overlay break; case HULL: - Polygon objectClickbox = actor.getConvexHull(); + Shape objectClickbox = actor.getConvexHull(); renderPoly(graphics, color, objectClickbox); break; @@ -185,7 +186,7 @@ public class NpcSceneOverlay extends Overlay } } - private void renderPoly(Graphics2D graphics, Color color, Polygon polygon) + private void renderPoly(Graphics2D graphics, Color color, Shape polygon) { if (polygon != null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java index 509b3f5bde..a61aa526dc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java @@ -26,7 +26,7 @@ package net.runelite.client.plugins.objectindicators; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.Polygon; +import java.awt.Shape; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.DecorativeObject; @@ -66,8 +66,8 @@ class ObjectIndicatorsOverlay extends Overlay continue; } - final Polygon polygon; - Polygon polygon2 = null; + final Shape polygon; + Shape polygon2 = null; if (object instanceof GameObject) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/roguesden/RoguesDenOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/roguesden/RoguesDenOverlay.java index 4b5cc234e1..1dc0aa0bc6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/roguesden/RoguesDenOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/roguesden/RoguesDenOverlay.java @@ -28,6 +28,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Shape; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.coords.LocalPoint; @@ -65,11 +66,11 @@ public class RoguesDenOverlay extends Overlay { if (tile.getPlane() == client.getPlane() && obstacle.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE) { - Polygon p = tile.getGameObjects()[0].getConvexHull(); + Shape p = tile.getGameObjects()[0].getConvexHull(); if (p != null) { graphics.setColor(Color.CYAN); - graphics.drawPolygon(p); + graphics.draw(p); } } }); @@ -82,7 +83,7 @@ public class RoguesDenOverlay extends Overlay if (p != null) { graphics.setColor(Color.CYAN); - graphics.drawPolygon(p); + graphics.draw(p); } } }); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssOverlay.java index b4d3df3e18..0981628dc9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssOverlay.java @@ -26,7 +26,7 @@ package net.runelite.client.plugins.runecraft; import java.awt.Color; import java.awt.Polygon; -import java.awt.geom.Area; +import java.awt.Shape; import com.google.inject.Inject; import java.awt.Dimension; import java.awt.Graphics2D; @@ -105,7 +105,7 @@ class AbyssOverlay extends Overlay } Point mousePosition = client.getMouseCanvasPosition(); - Area objectClickbox = object.getClickbox(); + Shape objectClickbox = object.getClickbox(); if (objectClickbox != null) { if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY())) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java index cc52cf9ac1..52b5b31865 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java @@ -30,7 +30,7 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.Polygon; +import java.awt.Shape; import java.util.List; import javax.inject.Inject; import net.runelite.api.NPC; @@ -71,7 +71,7 @@ public class TargetClickboxOverlay extends Overlay private void renderTargetOverlay(Graphics2D graphics, NPC actor, Color color) { - Polygon objectClickbox = actor.getConvexHull(); + Shape objectClickbox = actor.getConvexHull(); if (objectClickbox != null) { graphics.setColor(color); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java index f15bc0124e..912abab8f4 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java @@ -31,8 +31,8 @@ import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.RenderingHints; +import java.awt.Shape; import java.awt.Stroke; -import java.awt.geom.Area; import java.awt.image.BufferedImage; import net.runelite.api.Actor; import net.runelite.api.Client; @@ -50,14 +50,14 @@ public class OverlayUtil private static final int MINIMAP_DOT_RADIUS = 4; private static final double UNIT = Math.PI / 1024.0d; - public static void renderPolygon(Graphics2D graphics, Polygon poly, Color color) + public static void renderPolygon(Graphics2D graphics, Shape poly, Color color) { graphics.setColor(color); final Stroke originalStroke = graphics.getStroke(); graphics.setStroke(new BasicStroke(2)); - graphics.drawPolygon(poly); + graphics.draw(poly); graphics.setColor(new Color(0, 0, 0, 50)); - graphics.fillPolygon(poly); + graphics.fill(poly); graphics.setStroke(originalStroke); } @@ -175,7 +175,7 @@ public class OverlayUtil renderImageLocation(client, graphics, localLocation, image, 0); } - public static void renderHoverableArea(Graphics2D graphics, Area area, net.runelite.api.Point mousePosition, Color fillColor, Color borderColor, Color borderHoverColor) + public static void renderHoverableArea(Graphics2D graphics, Shape area, net.runelite.api.Point mousePosition, Color fillColor, Color borderColor, Color borderHoverColor) { if (area != null) { From 293b9cdf7ca8725a46ec770e79390ca8d19dd8dc Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 11 Sep 2019 22:20:28 -0600 Subject: [PATCH 05/23] runelite-api: Add simple Shape implementations --- .../net/runelite/api/geometry/Shapes.java | 212 ++++++++ .../runelite/api/geometry/SimplePolygon.java | 496 ++++++++++++++++++ 2 files changed, 708 insertions(+) create mode 100644 runelite-api/src/main/java/net/runelite/api/geometry/Shapes.java create mode 100644 runelite-api/src/main/java/net/runelite/api/geometry/SimplePolygon.java diff --git a/runelite-api/src/main/java/net/runelite/api/geometry/Shapes.java b/runelite-api/src/main/java/net/runelite/api/geometry/Shapes.java new file mode 100644 index 0000000000..2e02a1cf62 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/geometry/Shapes.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2019 Abex + * 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.geometry; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Shapes implements Shape +{ + public Shapes(T ...shape) + { + this(Arrays.asList(shape)); + } + + @Getter + private final List shapes; + + @Override + public Rectangle getBounds() + { + int + minX = Integer.MAX_VALUE, + minY = Integer.MAX_VALUE, + maxX = Integer.MIN_VALUE, + maxY = Integer.MIN_VALUE; + + for (Shape shape : shapes) + { + Rectangle bounds = shape.getBounds(); + minX = Math.min(bounds.x, minX); + minY = Math.min(bounds.y, minY); + maxX = Math.max(bounds.x + bounds.width, maxX); + maxY = Math.max(bounds.y + bounds.height, maxY); + } + + return new Rectangle(minX, minY, maxX - minX, maxY - minY); + } + + @Override + public Rectangle2D getBounds2D() + { + double + minX = Double.MAX_VALUE, + minY = Double.MAX_VALUE, + maxX = Double.MIN_VALUE, + maxY = Double.MIN_VALUE; + + for (Shape shape : shapes) + { + Rectangle2D bounds = shape.getBounds2D(); + minX = Math.min(bounds.getX(), minX); + minY = Math.min(bounds.getY(), minY); + maxX = Math.max(bounds.getMaxX(), maxX); + maxY = Math.max(bounds.getMaxY(), maxY); + } + + return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY); + } + + @Override + public boolean contains(double x, double y) + { + return shapes.stream().anyMatch(s -> s.contains(x, y)); + } + + @Override + public boolean contains(Point2D p) + { + return shapes.stream().anyMatch(s -> s.contains(p)); + } + + @Override + public boolean intersects(double x, double y, double w, double h) + { + return shapes.stream().anyMatch(s -> s.intersects(x, y, w, h)); + } + + @Override + public boolean intersects(Rectangle2D r) + { + return shapes.stream().anyMatch(s -> s.intersects(r)); + } + + @Override + public boolean contains(double x, double y, double w, double h) + { + return shapes.stream().anyMatch(s -> s.contains(x, y, w, h)); + } + + @Override + public boolean contains(Rectangle2D r) + { + return shapes.stream().anyMatch(s -> s.contains(r)); + } + + @Override + public PathIterator getPathIterator(AffineTransform at) + { + return new ShapeIterator(shapes.stream() + .map(s -> s.getPathIterator(at)) + .iterator()); + } + + @Override + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return new ShapeIterator(shapes.stream() + .map(s -> s.getPathIterator(at, flatness)) + .iterator()); + } + + private static class ShapeIterator implements PathIterator + { + private final Iterator iter; + private PathIterator current = null; + private final int windingRule; + + ShapeIterator(Iterator iter) + { + this.iter = iter; + if (iter.hasNext()) + { + current = iter.next(); + windingRule = current.getWindingRule(); + checkDone(); + } + else + { + windingRule = 0; + } + } + + @Override + public int getWindingRule() + { + return windingRule; + } + + @Override + public boolean isDone() + { + return current == null; + } + + @Override + public void next() + { + current.next(); + checkDone(); + } + + private void checkDone() + { + for (; current != null && current.isDone(); ) + { + if (iter.hasNext()) + { + current = iter.next(); + assert windingRule == current.getWindingRule(); + } + else + { + current = null; + } + } + } + + @Override + public int currentSegment(float[] coords) + { + return current.currentSegment(coords); + } + + @Override + public int currentSegment(double[] coords) + { + return current.currentSegment(coords); + } + } +} diff --git a/runelite-api/src/main/java/net/runelite/api/geometry/SimplePolygon.java b/runelite-api/src/main/java/net/runelite/api/geometry/SimplePolygon.java new file mode 100644 index 0000000000..bfd136d948 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/geometry/SimplePolygon.java @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2019 Abex + * 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.geometry; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.Point; + +/** + * A simple list of vertices that can be append or prepended to + */ +@AllArgsConstructor +@Getter +@Setter +public class SimplePolygon implements Shape +{ + private static final int GROW = 16; + + protected int[] x, y; + protected int left, right; + + public SimplePolygon() + { + this(new int[32], new int[32], 16, 15); + } + + public SimplePolygon(int[] x, int[] y, int length) + { + this(x, y, 0, length - 1); + } + + public void pushLeft(int xCoord, int yCoord) + { + left--; + if (left < 0) + { + expandLeft(GROW); + } + x[left] = xCoord; + y[left] = yCoord; + } + + public void popLeft() + { + left++; + } + + protected void expandLeft(int grow) + { + int[] nx = new int[x.length + grow]; + System.arraycopy(x, 0, nx, grow, x.length); + x = nx; + int[] ny = new int[nx.length]; + System.arraycopy(y, 0, ny, grow, y.length); + y = ny; + left += grow; + right += grow; + } + + public void pushRight(int xCoord, int yCoord) + { + right++; + if (right >= x.length) + { + expandRight(GROW); + } + x[right] = xCoord; + y[right] = yCoord; + } + + public void popRight() + { + right--; + } + + protected void expandRight(int grow) + { + int[] nx = new int[x.length + grow]; + System.arraycopy(x, 0, nx, 0, x.length); + x = nx; + int[] ny = new int[nx.length]; + System.arraycopy(y, 0, ny, 0, y.length); + y = ny; + } + + public int getX(int index) + { + return x[left + index]; + } + + public int getY(int index) + { + return y[left + index]; + } + + public int size() + { + return right - left + 1; + } + + public List toRuneLitePointList() + { + List out = new ArrayList<>(size()); + for (int i = left; i <= right; i++) + { + out.add(new Point(x[i], y[i])); + } + return out; + } + + public void copyTo(int[] xDest, int[] yDest, int offset) + { + System.arraycopy(x, left, xDest, offset, size()); + System.arraycopy(y, left, yDest, offset, size()); + } + + public void appendTo(SimplePolygon other) + { + int size = size(); + if (size <= 0) + { + return; + } + other.expandRight(size); + copyTo(other.x, other.y, other.right + 1); + other.right += size; + } + + public void reverse() + { + int half = size() / 2; + for (int i = 0; i < half; i++) + { + int li = left + i; + int ri = right - i; + int tx = x[li]; + int ty = y[li]; + x[li] = x[ri]; + y[li] = y[ri]; + x[ri] = tx; + y[ri] = ty; + } + } + + /** + * Clips the polygon with the passed convex polygon + */ + public void intersectWithConvex(SimplePolygon convex) + { + // Sutherland-Hodgman + int[] tx = new int[size()]; + int[] ty = new int[tx.length]; + + int cx1 = convex.x[convex.right]; + int cy1 = convex.y[convex.right]; + for (int ci = convex.left; ci <= convex.right; ci++) + { + if (size() < 3) + { + return; + } + + int tRight = this.right; + int tLeft = this.left; + + int[] tmpX = x; + int[] tmpY = y; + + this.x = tx; + this.y = ty; + this.left = 0; + this.right = -1; + tx = tmpX; + ty = tmpY; + + int cx2 = convex.x[ci]; + int cy2 = convex.y[ci]; + + int tx1 = tx[tRight]; + int ty1 = ty[tRight]; + + for (int ti = tLeft; ti <= tRight; ti++) + { + int tx2 = tx[ti]; + int ty2 = ty[ti]; + + int p1 = (cx2 - cx1) * (ty1 - cy1) - (cy2 - cy1) * (tx1 - cx1); + int p2 = (cx2 - cx1) * (ty2 - cy1) - (cy2 - cy1) * (tx2 - cx1); + + if (p1 < 0 && p2 < 0) + { + pushRight(tx2, ty2); + } + else if (p1 >= 0 != p2 >= 0) + { + long nota = cx1 * cy2 - cy1 * cx2; + long clue = tx1 * ty2 - ty1 * tx2; + long div = ((cx1 - cx2) * (ty1 - ty2) - (cy1 - cy2) * (tx1 - tx2)); + pushRight((int) ((nota * (tx1 - tx2) - (cx1 - cx2) * clue) / div), + (int) ((nota * (ty1 - ty2) - (cy1 - cy2) * clue) / div)); + + if (p1 >= 0) + { + pushRight(tx2, ty2); + } + } + + tx1 = tx2; + ty1 = ty2; + } + + cx1 = cx2; + cy1 = cy2; + } + } + + @Override + public Rectangle getBounds() + { + int + minX = Integer.MAX_VALUE, + minY = Integer.MAX_VALUE, + maxX = Integer.MIN_VALUE, + maxY = Integer.MIN_VALUE; + + for (int i = left; i <= right; i++) + { + final int xs = x[i]; + final int ys = y[i]; + + if (xs < minX) + { + minX = xs; + } + if (xs > maxX) + { + maxX = xs; + } + if (ys < minY) + { + minY = ys; + } + if (ys > maxY) + { + maxY = ys; + } + } + + return new Rectangle(minX, minY, maxX - minX, maxY - minY); + } + + @Override + public Rectangle2D getBounds2D() + { + Rectangle b = getBounds(); + return new Rectangle2D.Float(b.x, b.y, b.width, b.height); + } + + @Override + public boolean contains(double cx, double cy) + { + if (size() < 3) + { + return false; + } + + return (crossings(cx, cy, false) & 1) != 0; + } + + private int crossings(double cx, double cy, boolean swap) + { + int collisions = 0; + + int[] x = this.x; + int[] y = this.y; + if (swap) + { + y = this.x; + x = this.y; + } + + for (int x0 = x[right], y0 = y[right], x1, y1, i = left; i <= right; i++, x0 = x1, y0 = y1) + { + x1 = x[i]; + y1 = y[i]; + + if (y0 == y1) + { + continue; + } + + double dy0 = y0, dy1 = y1; + + if (cy <= dy0 == cy <= dy1) + { + continue; + } + + double dx0 = x0, dx1 = x1; + + boolean left = cx < dx0; + if (left == cx < dx1) + { + if (!left) + { + collisions++; + } + continue; + } + + if ((dx1 - dx0) * (cy - dy0) - (cx - dx0) * (dy1 - dy0) > 0 == dy0 > dy1) + { + collisions++; + } + } + return collisions; + } + + @Override + public boolean contains(Point2D p) + { + return contains(p.getX(), p.getY()); + } + + @Override + public boolean intersects(double x0, double y0, double w, double h) + { + // this is horribly inefficient, but I don't think it will be called anywhere + + double x1 = x0 + w; + double y1 = y0 + h; + + return crossings(x0, y0, false) != crossings(x1, y0, false) // top + || crossings(x0, y1, false) != crossings(x1, y1, false) // bottom + || crossings(x0, y0, true) != crossings(x0, y1, true) // left + || crossings(x1, y0, true) != crossings(x1, y1, true); // right + + } + + @Override + public boolean intersects(Rectangle2D r) + { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override + public boolean contains(double x, double y, double w, double h) + { + if (!getBounds().contains(x, y, w, h)) + { + return false; + } + + return !intersects(x, y, w, h); + } + + @Override + public boolean contains(Rectangle2D r) + { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + + @Override + public PathIterator getPathIterator(AffineTransform at) + { + if (at == null) + { + return new SimpleIterator(); + } + return new TransformIterator(at); + } + + @Override + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return getPathIterator(at); + } + + private class SimpleIterator implements PathIterator + { + private int i = -1; + + @Override + public int getWindingRule() + { + return WIND_EVEN_ODD; + } + + @Override + public boolean isDone() + { + return size() == 0 || i > right; + } + + @Override + public void next() + { + if (i == -1) + { + i = left; + } + else + { + i++; + } + } + + @Override + public int currentSegment(float[] coords) + { + if (i == -1) + { + coords[0] = x[right]; + coords[1] = y[right]; + return SEG_MOVETO; + } + + coords[0] = x[i]; + coords[1] = y[i]; + return SEG_LINETO; + } + + @Override + public int currentSegment(double[] coords) + { + if (i == -1) + { + coords[0] = x[right]; + coords[1] = y[right]; + return SEG_MOVETO; + } + + coords[0] = x[i]; + coords[1] = y[i]; + return SEG_LINETO; + } + } + + private class TransformIterator extends SimpleIterator + { + private final AffineTransform transform; + + TransformIterator(AffineTransform transform) + { + this.transform = transform; + } + + @Override + public int currentSegment(float[] coords) + { + int v = super.currentSegment(coords); + transform.transform(coords, 0, coords, 0, 2); + return v; + } + + @Override + public int currentSegment(double[] coords) + { + int v = super.currentSegment(coords); + transform.transform(coords, 0, coords, 0, 2); + return v; + } + } +} From b77461a3e0e363355f2c1013c97ef62b8ce687de Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 9 Sep 2019 10:33:49 -0600 Subject: [PATCH 06/23] Perspective: Add modelToCanvas This has significantly less overhead than repeatedly calling localToCanvas, and may be vectorized in the future by a smarter jit --- .../java/net/runelite/api/Perspective.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/Perspective.java b/runelite-api/src/main/java/net/runelite/api/Perspective.java index 45469cc00c..e414e89c0d 100644 --- a/runelite-api/src/main/java/net/runelite/api/Perspective.java +++ b/runelite-api/src/main/java/net/runelite/api/Perspective.java @@ -148,7 +148,74 @@ public class Perspective } return null; + } + /** + * Translates a model's vertices into 2d space + */ + public static void modelToCanvas(Client client, int end, int x3dCenter, int y3dCenter, int z3dCenter, int rotate, int[] x3d, int[] y3d, int[] z3d, int[] x2d, int[] y2d) + { + final int + cameraPitch = client.getCameraPitch(), + cameraYaw = client.getCameraYaw(), + + pitchSin = SINE[cameraPitch], + pitchCos = COSINE[cameraPitch], + yawSin = SINE[cameraYaw], + yawCos = COSINE[cameraYaw], + rotateSin = SINE[rotate], + rotateCos = COSINE[rotate], + + cx = x3dCenter - client.getCameraX(), + cy = y3dCenter - client.getCameraY(), + cz = z3dCenter - client.getCameraZ(), + + viewportXMiddle = client.getViewportWidth() / 2, + viewportYMiddle = client.getViewportHeight() / 2, + viewportXOffset = client.getViewportXOffset(), + viewportYOffset = client.getViewportYOffset(), + + zoom3d = client.getScale(); + + for (int i = 0; i < end; i++) + { + int x = x3d[i]; + int y = y3d[i]; + int z = z3d[i]; + + if (rotate != 0) + { + int x0 = x; + x = x0 * rotateCos + y * rotateSin >> 16; + y = y * rotateCos - x0 * rotateSin >> 16; + } + + x += cx; + y += cy; + z += cz; + + final int + x1 = x * yawCos + y * yawSin >> 16, + y1 = y * yawCos - x * yawSin >> 16, + y2 = z * pitchCos - y1 * pitchSin >> 16, + z1 = y1 * pitchCos + z * pitchSin >> 16; + + int viewX, viewY; + + if (z1 < 50) + { + viewX = Integer.MIN_VALUE; + viewY = Integer.MIN_VALUE; + } + else + { + viewX = (viewportXMiddle + x1 * zoom3d / z1) + viewportXOffset; + viewY = (viewportYMiddle + y2 * zoom3d / z1) + viewportYOffset; + } + + x2d[i] = viewX; + y2d[i] = viewY; + } } /** From 80709f1bfa692dfcb0038a80f7fc5c65861290fd Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 9 Sep 2019 10:38:27 -0600 Subject: [PATCH 07/23] Jarvis: use primitive arrays This creates much less garbage and allows use with modelToCanvas --- .../java/net/runelite/api/model/Jarvis.java | 137 ++++++++++++------ 1 file changed, 96 insertions(+), 41 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/model/Jarvis.java b/runelite-api/src/main/java/net/runelite/api/model/Jarvis.java index ba29c86b6d..0c60b84462 100644 --- a/runelite-api/src/main/java/net/runelite/api/model/Jarvis.java +++ b/runelite-api/src/main/java/net/runelite/api/model/Jarvis.java @@ -24,9 +24,9 @@ */ package net.runelite.api.model; -import java.util.ArrayList; import java.util.List; import net.runelite.api.Point; +import net.runelite.api.geometry.SimplePolygon; /** * Provides utility methods for computing the convex hull of a list of @@ -41,92 +41,147 @@ public class Jarvis /** * Computes and returns the convex hull of the passed points. *

- * The size of the list must be at least 4, otherwise this method will + * The size of the list must be at least 3, otherwise this method will * return null. * * @param points list of points * @return list containing the points part of the convex hull */ + @Deprecated public static List convexHull(List points) { - if (points.size() < 3) + int[] xs = new int[points.size()]; + int[] ys = new int[xs.length]; + for (int i = 0; i < xs.length; i++) + { + Point p = points.get(i); + xs[i] = p.getX(); + ys[i] = p.getY(); + } + + SimplePolygon poly = convexHull(xs, ys); + if (poly == null) { return null; } - List ch = new ArrayList<>(); + return poly.toRuneLitePointList(); + } + + /** + * Computes and returns the convex hull of the passed points. + *

+ * The size of the list must be at least 3, otherwise this method will + * return null. + * + * @return a shape the points part of the convex hull + */ + public static SimplePolygon convexHull(int[] xs, int[] ys) + { + int length = xs.length; + + // remove any invalid entries + { + int i = 0, offset = 0; + for (; i < length; i++) + { + if (xs[i] == Integer.MIN_VALUE) + { + offset++; + i++; + break; + } + } + for (; i < length; i++) + { + if (xs[i] == Integer.MIN_VALUE) + { + offset++; + continue; + } + xs[i - offset] = xs[i]; + ys[i - offset] = ys[i]; + } + length -= offset; + } + + if (length < 3) + { + return null; + } // find the left most point - Point left = findLeftMost(points); + int left = findLeftMost(xs, ys, length); // current point we are on - Point current = left; + int current = left; + + SimplePolygon out = new SimplePolygon(new int[16], new int[16], 0); do { - ch.add(current); - assert ch.size() <= points.size() : "hull has more points than graph"; - if (ch.size() > points.size()) + int cx = xs[current]; + int cy = ys[current]; + out.pushRight(cx, cy); + + if (out.size() > length) { - // Just to make sure we never somehow get stuck in this loop return null; } // the next point - all points are to the right of the // line between current and next - Point next = null; + int next = 0; + int nx = xs[next]; + int ny = ys[next]; - for (Point p : points) + for (int i = 1; i < length; i++) { - if (next == null) + long cp = crossProduct(cx, cy, xs[i], ys[i], nx, ny); + if (cp > 0 || (cp == 0 && square(cx - xs[i]) + square(cy - ys[i]) > square(cx - nx) + square(cy - ny))) { - next = p; - continue; + next = i; + nx = xs[next]; + ny = ys[next]; } - - long cp = crossProduct(current, p, next); - if (cp > 0 || (cp == 0 && current.distanceTo(p) > current.distanceTo(next))) - { - next = p; - } - } - - // Points can be null if they are behind or very close to the camera. - if (next == null) - { - return null; } current = next; } while (current != left); - return ch; + return out; } - private static Point findLeftMost(List points) + private static int square(int x) { - Point left = null; + return x * x; + } - for (Point p : points) + private static int findLeftMost(int[] xs, int[] ys, int length) + { + int idx = 0; + int x = xs[idx]; + int y = ys[idx]; + + for (int i = 1; i < length; i++) { - if (left == null || p.getX() < left.getX()) + int ix = xs[i]; + if (ix < x || ix == x && ys[i] < y) { - left = p; - } - else if (p.getX() == left.getX() && p.getY() < left.getY()) - { - left = p; + idx = i; + x = xs[idx]; + y = ys[idx]; } } - return left; + return idx; } - private static long crossProduct(Point p, Point q, Point r) + private static long crossProduct(int px, int py, int qx, int qy, int rx, int ry) { - long val = (long)(q.getY() - p.getY()) * (r.getX() - q.getX()) - - (long)(q.getX() - p.getX()) * (r.getY() - q.getY()); + long val = (long) (qy - py) * (rx - qx) + - (long) (qx - px) * (ry - qy); return val; } } From f16cd53d0920f88d6edeb08c2b9c7f005e910f15 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 9 Sep 2019 10:46:42 -0600 Subject: [PATCH 08/23] runelite-api: Optimize getClickbox - Use the pre-calculated center/extreme xyz fields for the aabb - use modelToCanvas and reduce indirection - Use a specialized union that only does axis-aligned rectangles instead of the Area class - Use a specialized intersection that only does convex polygons, again to avoid Area --- runelite-api/pom.xml | 6 + .../src/main/java/net/runelite/api/Model.java | 1 + .../java/net/runelite/api/Perspective.java | 369 +++++++--------- .../runelite/api/geometry/RectangleUnion.java | 415 ++++++++++++++++++ .../api/geometry/RectangleUnionTest.java | 115 +++++ 5 files changed, 688 insertions(+), 218 deletions(-) create mode 100644 runelite-api/src/main/java/net/runelite/api/geometry/RectangleUnion.java create mode 100644 runelite-api/src/test/java/net/runelite/api/geometry/RectangleUnionTest.java diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml index bc24d72ca6..8f2c250c66 100644 --- a/runelite-api/pom.xml +++ b/runelite-api/pom.xml @@ -57,5 +57,11 @@ 4.12 test + + org.slf4j + slf4j-simple + 1.7.12 + test + diff --git a/runelite-api/src/main/java/net/runelite/api/Model.java b/runelite-api/src/main/java/net/runelite/api/Model.java index c287b2afd1..d229a073f7 100644 --- a/runelite-api/src/main/java/net/runelite/api/Model.java +++ b/runelite-api/src/main/java/net/runelite/api/Model.java @@ -103,4 +103,5 @@ public interface Model extends Renderable int getExtremeZ(); int getXYZMag(); + boolean isClickable(); } diff --git a/runelite-api/src/main/java/net/runelite/api/Perspective.java b/runelite-api/src/main/java/net/runelite/api/Perspective.java index e414e89c0d..a248f9e0bb 100644 --- a/runelite-api/src/main/java/net/runelite/api/Perspective.java +++ b/runelite-api/src/main/java/net/runelite/api/Perspective.java @@ -27,20 +27,19 @@ package net.runelite.api; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.geom.Area; +import java.awt.Shape; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; import static net.runelite.api.Constants.TILE_FLAG_BRIDGE; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.geometry.RectangleUnion; +import net.runelite.api.geometry.Shapes; +import net.runelite.api.geometry.SimplePolygon; import net.runelite.api.model.Jarvis; -import net.runelite.api.model.Triangle; -import net.runelite.api.model.Vertex; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; @@ -562,244 +561,178 @@ public class Perspective * Get the on-screen clickable area of {@code model} as though it's for the * object on the tile at ({@code localX}, {@code localY}) and rotated to * angle {@code orientation}. - * - * @param client the game client - * @param model the model to calculate a clickbox for + * @param client the game client + * @param model the model to calculate a clickbox for * @param orientation the orientation of the model (0-2048, where 0 is north) - * @param point the coordinate of the tile + * @param point the coordinate of the tile * @return the clickable area of the model */ - public static @Nullable Area getClickbox(@Nonnull Client client, Model model, int orientation, @Nonnull LocalPoint point) + @Nullable + public static Shape getClickbox(@Nonnull Client client, Model model, int orientation, LocalPoint point) { if (model == null) { return null; } - List triangles = model.getTriangles().stream() - .map(triangle -> triangle.rotate(orientation)) - .collect(Collectors.toList()); + int x = point.getX(); + int y = point.getY(); + int z = getTileHeight(client, point, client.getPlane()); - List vertices = model.getVertices().stream() - .map(v -> v.rotate(orientation)) - .collect(Collectors.toList()); + SimplePolygon bounds = calculateAABB(client, model, orientation, x, y, z); - Area clickBox = get2DGeometry(client, triangles, point); - Area visibleAABB = getAABB(client, vertices, point); - - if (visibleAABB == null) + if (bounds == null) { return null; } - clickBox.intersect(visibleAABB); - return clickBox; - } - - /** - * Determine if a given point is off-screen. - * - * @param client - * @param point - * @return - */ - private static boolean isOffscreen(@Nonnull Client client, @Nonnull Point point) - { - return (point.getX() < 0 || point.getX() >= client.getViewportWidth()) - && (point.getY() < 0 || point.getY() >= client.getViewportHeight()); - } - - private static @Nonnull Area get2DGeometry( - @Nonnull Client client, - @Nonnull List triangles, - @Nonnull LocalPoint point - ) - { - int radius = 5; - Area geometry = new Area(); - - final int tileHeight = getTileHeight(client, point, client.getPlane()); - - for (Triangle triangle : triangles) + if (model.isClickable()) { - Vertex _a = triangle.getA(); - Point a = localToCanvas(client, - point.getX() - _a.getX(), - point.getY() - _a.getZ(), - tileHeight + _a.getY()); - if (a == null) - { - continue; - } - - Vertex _b = triangle.getB(); - Point b = localToCanvas(client, - point.getX() - _b.getX(), - point.getY() - _b.getZ(), - tileHeight + _b.getY()); - if (b == null) - { - continue; - } - - Vertex _c = triangle.getC(); - Point c = localToCanvas(client, - point.getX() - _c.getX(), - point.getY() - _c.getZ(), - tileHeight + _c.getY()); - if (c == null) - { - continue; - } - - if (isOffscreen(client, a) && isOffscreen(client, b) && isOffscreen(client, c)) - { - continue; - } - - int minX = Math.min(Math.min(a.getX(), b.getX()), c.getX()); - int minY = Math.min(Math.min(a.getY(), b.getY()), c.getY()); - - // For some reason, this calculation is always 4 pixels short of the actual in-client one - int maxX = Math.max(Math.max(a.getX(), b.getX()), c.getX()) + 4; - int maxY = Math.max(Math.max(a.getY(), b.getY()), c.getY()) + 4; - - Rectangle clickableRect = new Rectangle( - minX - radius, minY - radius, - maxX - minX + radius, maxY - minY + radius - ); - - if (geometry.contains(clickableRect)) - { - continue; - } - - geometry.add(new Area(clickableRect)); + return bounds; } - return geometry; - } - - private static Area getAABB( - @Nonnull Client client, - @Nonnull List vertices, - @Nonnull LocalPoint point - ) - { - int maxX = 0; - int minX = 0; - int maxY = 0; - int minY = 0; - int maxZ = 0; - int minZ = 0; - - for (Vertex vertex : vertices) - { - int x = vertex.getX(); - int y = vertex.getY(); - int z = vertex.getZ(); - - if (x > maxX) - { - maxX = x; - } - if (x < minX) - { - minX = x; - } - - if (y > maxY) - { - maxY = y; - } - if (y < minY) - { - minY = y; - } - - if (z > maxZ) - { - maxZ = z; - } - if (z < minZ) - { - minZ = z; - } - } - - int centerX = (minX + maxX) / 2; - int centerY = (minY + maxY) / 2; - int centerZ = (minZ + maxZ) / 2; - - int extremeX = (maxX - minX + 1) / 2; - int extremeY = (maxY - minY + 1) / 2; - int extremeZ = (maxZ - minZ + 1) / 2; - - if (extremeX < 32) - { - extremeX = 32; - } - - if (extremeZ < 32) - { - extremeZ = 32; - } - - int x1 = point.getX() - (centerX - extremeX); - int y1 = centerY - extremeY; - int z1 = point.getY() - (centerZ - extremeZ); - - int x2 = point.getX() - (centerX + extremeX); - int y2 = centerY + extremeY; - int z2 = point.getY() - (centerZ + extremeZ); - - final int tileHeight = getTileHeight(client, point, client.getPlane()); - - Point p1 = localToCanvas(client, x1, z1, tileHeight + y1); - Point p2 = localToCanvas(client, x1, z2, tileHeight + y1); - Point p3 = localToCanvas(client, x2, z2, tileHeight + y1); - - Point p4 = localToCanvas(client, x2, z1, tileHeight + y1); - Point p5 = localToCanvas(client, x1, z1, tileHeight + y2); - Point p6 = localToCanvas(client, x1, z2, tileHeight + y2); - Point p7 = localToCanvas(client, x2, z2, tileHeight + y2); - Point p8 = localToCanvas(client, x2, z1, tileHeight + y2); - - List points = new ArrayList<>(8); - points.add(p1); - points.add(p2); - points.add(p3); - points.add(p4); - points.add(p5); - points.add(p6); - points.add(p7); - points.add(p8); - - try - { - points = Jarvis.convexHull(points); - } - catch (NullPointerException e) - { - // No non-null screen points for this AABB e.g. for an way off-screen model - return null; - } - - if (points == null) + Shapes bounds2d = calculate2DBounds(client, model, orientation, x, y, z); + if (bounds2d == null) { return null; } - Polygon hull = new Polygon(); - for (Point p : points) + for (SimplePolygon poly : bounds2d.getShapes()) { - if (p != null) - { - hull.addPoint(p.getX(), p.getY()); - } + poly.intersectWithConvex(bounds); } - return new Area(hull); + return bounds2d; + } + + private static SimplePolygon calculateAABB(Client client, Model m, int jauOrient, int x, int y, int z) + { + int ex = m.getExtremeX(); + if (ex == -1) + { + // dynamic models don't get stored when they render where this normally happens + m.calculateBoundsCylinder(); + m.calculateExtreme(0); + ex = m.getExtremeX(); + } + + int x1 = m.getCenterX(); + int y1 = m.getCenterZ(); + int z1 = m.getCenterY(); + + int ey = m.getExtremeZ(); + int ez = m.getExtremeY(); + + int x2 = x1 + ex; + int y2 = y1 + ey; + int z2 = z1 + ez; + + x1 -= ex; + y1 -= ey; + z1 -= ez; + + int[] xa = new int[]{ + x1, x2, x1, x2, + x1, x2, x1, x2 + }; + int[] ya = new int[]{ + y1, y1, y2, y2, + y1, y1, y2, y2 + }; + int[] za = new int[]{ + z1, z1, z1, z1, + z2, z2, z2, z2 + }; + + int[] x2d = new int[8]; + int[] y2d = new int[8]; + + modelToCanvas(client, 8, x, y, z, jauOrient, xa, ya, za, x2d, y2d); + + return Jarvis.convexHull(x2d, y2d); + } + + private static Shapes calculate2DBounds(Client client, Model m, int jauOrient, int x, int y, int z) + { + int[] x2d = new int[m.getVerticesCount()]; + int[] y2d = new int[m.getVerticesCount()]; + + Perspective.modelToCanvas(client, + m.getVerticesCount(), + x, y, z, + jauOrient, + m.getVerticesX(), m.getVerticesZ(), m.getVerticesY(), + x2d, y2d); + + final int radius = 5; + + int[][] tris = new int[][]{ + m.getTrianglesX(), + m.getTrianglesY(), + m.getTrianglesZ() + }; + + int vpX1 = client.getViewportXOffset(); + int vpY1 = client.getViewportXOffset(); + int vpX2 = vpX1 + client.getViewportWidth(); + int vpY2 = vpY1 + client.getViewportHeight(); + + List rects = new ArrayList<>(m.getTrianglesCount()); + + nextTri: + for (int tri = 0; tri < m.getTrianglesCount(); tri++) + { + int + minX = Integer.MAX_VALUE, + minY = Integer.MAX_VALUE, + maxX = Integer.MIN_VALUE, + maxY = Integer.MIN_VALUE; + + for (int[] vertex : tris) + { + final int idx = vertex[tri]; + final int xs = x2d[idx]; + final int ys = y2d[idx]; + + if (xs == Integer.MIN_VALUE || ys == Integer.MIN_VALUE) + { + continue nextTri; + } + + if (xs < minX) + { + minX = xs; + } + if (xs > maxX) + { + maxX = xs; + } + if (ys < minY) + { + minY = ys; + } + if (ys > maxY) + { + maxY = ys; + } + } + + minX -= radius; + minY -= radius; + maxX += radius; + maxY += radius; + + if (vpX1 > maxX || vpX2 < minX || vpY1 > maxY || vpY2 < minY) + { + continue; + } + + RectangleUnion.Rectangle r = new RectangleUnion.Rectangle(minX, minY, maxX, maxY); + + rects.add(r); + } + + return RectangleUnion.union(rects); } /** diff --git a/runelite-api/src/main/java/net/runelite/api/geometry/RectangleUnion.java b/runelite-api/src/main/java/net/runelite/api/geometry/RectangleUnion.java new file mode 100644 index 0000000000..e71f80c6b1 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/geometry/RectangleUnion.java @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2019 Abex + * 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.geometry; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import javax.annotation.Nullable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class RectangleUnion +{ + private RectangleUnion() + { + } + + @RequiredArgsConstructor + @Getter + @ToString + public static class Rectangle + { + private final int x1, y1, x2, y2; + } + + /** + * Returns a polygon representing the union of all of the passed rectangles. + * the passed List will be modified + */ + @Nullable + public static Shapes union(List lefts) + { + // https://stackoverflow.com/a/35362615/2977136 + if (lefts.size() == 0) + { + return null; + } + + boolean trace = log.isTraceEnabled(); + + // Sort all of the rectangles so they are ordered by their left edge + lefts.sort(Comparator.comparing(Rectangle::getX1)); + + // Again, but for the right edge + // this should be relatively fast if the rectangles are similar sizes because timsort deals with partially + // presorted data well + List rights = new ArrayList<>(lefts); + rights.sort(Comparator.comparing(Rectangle::getX2)); + + // ranges of our scan line with how many rectangles it is occluding + Segments segments = new Segments(); + Shapes out = new Shapes<>(new ArrayList<>()); + ChangingState cs = new ChangingState(out); + + // Walk a beam left to right, colliding with any vertical edges of rectangles + for (int l = 0, r = 0; ; ) + { + Rectangle lr = null, rr = null; + if (l < lefts.size()) + { + lr = lefts.get(l); + } + if (r < rights.size()) + { + rr = rights.get(r); + } + if (lr == null && rr == null) + { + break; + } + + // get the next edge, preferring + edges + Rectangle rect; + boolean remove = lr == null || (rr != null && rr.x2 < lr.x1); + if (remove) + { + cs.delta = -1; + cs.x = rr.x2; + r++; + rect = rr; + } + else + { + cs.delta = 1; + cs.x = lr.x1; + l++; + rect = lr; + } + if (trace) + { + log.trace("{}{}", remove ? "-" : "+", rect); + } + + int y1 = rect.y1; + int y2 = rect.y2; + + // Find or create the y1 edge + Segment n = segments.findLE(y1); + if (n == null) + { + n = segments.insertAfter(null, y1); + } + if (n.y != y1) + { + n = segments.insertAfter(n, y1); + n.value = n.previous.value; + } + + for (; ; ) + { + // create the y2 edge if the next edge is past + if (n.next == null || n.next.y > y2) + { + segments.insertAfter(n, y2); + } + cs.touch(n); + n = n.next; + if (n.y == y2) + { + cs.finish(n); + + if (trace) + { + for (Segment s = segments.first; s != null; s = s.next) + { + String chunk = ""; + if (s.chunk != null) + { + chunk = (s.left ? ">" : "[") + System.identityHashCode(s.chunk) + (s.left ? "]" : "<"); + } + log.trace("{} = {} {}", s.y, s.value, chunk); + } + log.trace(""); + } + break; + } + } + } + + assert segments.allZero(); + + return out; + } + + @RequiredArgsConstructor + private static class ChangingState + { + final Shapes out; + + int x; + int delta; + + Segment first; + + void touch(Segment s) + { + int oldValue = s.value; + s.value += delta; + if (oldValue <= 0 ^ s.value <= 0) + { + if (first == null) + { + first = s; + } + } + else + { + finish(s); + } + } + + void finish(Segment s) + { + if (first == null) + { + return; + } + + if (first.chunk != null && s.chunk != null) + { + push(first); + push(s); + + if (first.chunk == s.chunk) + { + Chunk c = first.chunk; + first.chunk = null; + s.chunk = null; + c.left = null; + c.right = null; + out.getShapes().add(c); + } + else + { + Chunk leftChunk, rightChunk; + if (!s.left) + { + leftChunk = s.chunk; + rightChunk = first.chunk; + } + else + { + leftChunk = first.chunk; + rightChunk = s.chunk; + } + + log.trace("Joining {} onto {}", System.identityHashCode(rightChunk), System.identityHashCode(leftChunk)); + if (first.left == s.left) + { + log.trace("reverse"); + if (first.left) + { + leftChunk.reverse(); + } + else + { + rightChunk.reverse(); + } + } + log.trace("{} {}", first.y, s.y); + rightChunk.appendTo(leftChunk); + + first.chunk = null; + s.chunk = null; + leftChunk.right.chunk = null; + rightChunk.left.chunk = null; + leftChunk.right = rightChunk.right; + leftChunk.left.chunk = leftChunk; + leftChunk.right.chunk = leftChunk; + } + } + else if (first.chunk == null && s.chunk == null) + { + first.chunk = new Chunk(); + first.chunk.right = first; + first.left = false; + s.chunk = first.chunk; + first.chunk.left = s; + s.left = true; + + push(first); + push(s); + } + else if (first.chunk == null) + { + push(s); + move(first, s); + push(first); + } + else + { + push(first); + move(s, first); + push(s); + } + + first = null; + } + + private void move(Segment dst, Segment src) + { + dst.chunk = src.chunk; + dst.left = src.left; + src.chunk = null; + if (dst.left) + { + assert dst.chunk.left == src; + dst.chunk.left = dst; + } + else + { + assert dst.chunk.right == src; + dst.chunk.right = dst; + } + } + + private void push(Segment s) + { + if (s.left) + { + s.chunk.pushLeft(x, s.y); + assert s.chunk.left == s; + } + else + { + s.chunk.pushRight(x, s.y); + assert s.chunk.right == s; + } + } + } + + @NoArgsConstructor + private static class Segment + { + Segment next, previous; + + Chunk chunk; + boolean left; + int y; + int value; + } + + @NoArgsConstructor + private static class Segments + { + Segment first; + + Segment findLE(int y) + { + Segment s = first; + if (s == null || s.y > y) + { + return null; + } + for (; ; ) + { + if (s.y == y) + { + return s; + } + + Segment n = s.next; + if (n == null || n.y > y) + { + return s; + } + + s = n; + } + } + + Segment insertAfter(Segment before, int y) + { + Segment n = new Segment(); + n.y = y; + if (before != null) + { + if (before.next != null) + { + n.next = before.next; + n.next.previous = n; + } + n.value = before.value; + before.next = n; + n.previous = before; + } + else + { + if (first != null) + { + n.next = first; + first.previous = n; + } + first = n; + } + return n; + } + + boolean allZero() + { + for (Segment s = first; s != null; s = s.next) + { + if (s.value != 0 || s.chunk != null) + { + return false; + } + } + return true; + } + } + + private static class Chunk extends SimplePolygon + { + Segment left, right; + + @Override + public void reverse() + { + super.reverse(); + assert right.left == false; + assert left.left == true; + Segment tr = left; + left = right; + right = tr; + right.left = false; + left.left = true; + } + } +} diff --git a/runelite-api/src/test/java/net/runelite/api/geometry/RectangleUnionTest.java b/runelite-api/src/test/java/net/runelite/api/geometry/RectangleUnionTest.java new file mode 100644 index 0000000000..f8d714180b --- /dev/null +++ b/runelite-api/src/test/java/net/runelite/api/geometry/RectangleUnionTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019 Abex + * 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.geometry; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import javax.imageio.ImageIO; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; + +@Slf4j +public class RectangleUnionTest +{ + private static final int ITERATIONS = 100; + private static final int WIDTH = 1000; + private static final int MAX_RECTS = 50; + + // @Test + public void test() throws IOException + { + for (int count = 1; count < MAX_RECTS; count++) + { + for (int r = 0; r < ITERATIONS; r++) + { + Random rand = new Random(count << 16 | r); + String id = count + "rects_iteration" + r; + log.info(id); + BufferedImage wanted = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_BYTE_BINARY); + BufferedImage got = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_BYTE_BINARY); + + Graphics2D wg = wanted.createGraphics(); + wg.setColor(Color.WHITE); + Graphics2D gg = got.createGraphics(); + gg.setColor(Color.WHITE); + + List rects = new ArrayList<>(count); + + for (int i = 0; i < count; i++) + { + int x1, y1, x2, y2; + + do + { + x1 = rand.nextInt(WIDTH); + x2 = rand.nextInt(WIDTH); + } + while (x1 >= x2); + + do + { + y1 = rand.nextInt(WIDTH); + y2 = rand.nextInt(WIDTH); + } + while (y1 >= y2); + + RectangleUnion.Rectangle rect = new RectangleUnion.Rectangle(x1, y1, x2, y2); + log.trace("{}", rect); + rects.add(rect); + + wg.fillRect(x1, y1, x2 - x1, y2 - y1); + } + + Shape union = RectangleUnion.union(rects); + + gg.fill(union); + + loop: + for (int x = 0; x < WIDTH; x++) + { + for (int y = 0; y < WIDTH; y++) + { + if (wanted.getRGB(x, y) != got.getRGB(x, y)) + { + File tmp = new File(System.getProperty("java.io.tmpdir")); + ImageIO.write(wanted, "png", new File(tmp, id + "_wanted.png")); + ImageIO.write(got, "png", new File(tmp, id + "_got.png")); + + Assert.fail(id); + break loop; + } + } + } + } + } + } +} \ No newline at end of file From 9e696ac3f2e6099460df51bee4079e636c0830a1 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 9 Sep 2019 19:31:57 -0600 Subject: [PATCH 09/23] runelite-api: Remove Triangle and Vertex classes --- .../src/main/java/net/runelite/api/Model.java | 18 ----- .../java/net/runelite/api/model/Triangle.java | 54 --------------- .../java/net/runelite/api/model/Vertex.java | 65 ------------------- 3 files changed, 137 deletions(-) delete mode 100644 runelite-api/src/main/java/net/runelite/api/model/Triangle.java delete mode 100644 runelite-api/src/main/java/net/runelite/api/model/Vertex.java diff --git a/runelite-api/src/main/java/net/runelite/api/Model.java b/runelite-api/src/main/java/net/runelite/api/Model.java index d229a073f7..4e5332e2f2 100644 --- a/runelite-api/src/main/java/net/runelite/api/Model.java +++ b/runelite-api/src/main/java/net/runelite/api/Model.java @@ -24,29 +24,11 @@ */ package net.runelite.api; -import java.util.List; -import net.runelite.api.model.Triangle; -import net.runelite.api.model.Vertex; - /** * Represents the model of an object. */ public interface Model extends Renderable { - /** - * Gets a list of all vertices of the model. - * - * @return the vertices - */ - List getVertices(); - - /** - * Gets a list of all triangles of the model. - * - * @return the triangle - */ - List getTriangles(); - int getVerticesCount(); int[] getVerticesX(); diff --git a/runelite-api/src/main/java/net/runelite/api/model/Triangle.java b/runelite-api/src/main/java/net/runelite/api/model/Triangle.java deleted file mode 100644 index daf59c2489..0000000000 --- a/runelite-api/src/main/java/net/runelite/api/model/Triangle.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2017, 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.api.model; - -import lombok.Value; - -/** - * Represents 3 vertices as a three-dimensional Triangle. - */ -@Value -public class Triangle -{ - private final Vertex a; - private final Vertex b; - private final Vertex c; - - /** - * Rotates the triangle by the given orientation. - * - * @param orientation passed orientation - * @return new instance - */ - public Triangle rotate(int orientation) - { - return new Triangle( - a.rotate(orientation), - b.rotate(orientation), - c.rotate(orientation) - ); - } - -} diff --git a/runelite-api/src/main/java/net/runelite/api/model/Vertex.java b/runelite-api/src/main/java/net/runelite/api/model/Vertex.java deleted file mode 100644 index b59a7d7891..0000000000 --- a/runelite-api/src/main/java/net/runelite/api/model/Vertex.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2017, 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.api.model; - -import lombok.Value; -import net.runelite.api.Perspective; - -/** - * Represents a point in a three-dimensional space. - */ -@Value -public class Vertex -{ - private final int x; - private final int y; - private final int z; - - /** - * Rotates the triangle by the given orientation. - * - * @param orientation passed orientation - * @return new instance - */ - public Vertex rotate(int orientation) - { - // models are orientated north (1024) and there are 2048 angles total - orientation = (orientation + 1024) % 2048; - - if (orientation == 0) - { - return this; - } - - int sin = Perspective.SINE[orientation]; - int cos = Perspective.COSINE[orientation]; - - return new Vertex( - x * cos + z * sin >> 16, - y, - z * cos - x * sin >> 16 - ); - } -} From b15bf1c22118d6f8d9c9efbd47ce686a0f862d40 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 1 Oct 2019 21:53:48 -0400 Subject: [PATCH 10/23] world hopper: add ping to tags --- .../runelite/client/plugins/worldhopper/WorldHopperPlugin.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java index a02401e795..96aad43f11 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java @@ -93,7 +93,8 @@ import org.apache.commons.lang3.ArrayUtils; @PluginDescriptor( name = "World Hopper", - description = "Allows you to quickly hop worlds" + description = "Allows you to quickly hop worlds", + tags = {"ping"} ) @Slf4j public class WorldHopperPlugin extends Plugin From 1549bc63d79aa07a1c0e86d528524f7098921cf2 Mon Sep 17 00:00:00 2001 From: Runelite auto updater Date: Wed, 2 Oct 2019 12:51:41 +0000 Subject: [PATCH 11/23] [maven-release-plugin] prepare release runelite-parent-1.5.35 --- cache-client/pom.xml | 2 +- cache-updater/pom.xml | 2 +- cache/pom.xml | 2 +- http-api/pom.xml | 2 +- http-service/pom.xml | 2 +- pom.xml | 4 ++-- protocol-api/pom.xml | 2 +- protocol/pom.xml | 2 +- runelite-api/pom.xml | 2 +- runelite-client/pom.xml | 2 +- runelite-script-assembler-plugin/pom.xml | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cache-client/pom.xml b/cache-client/pom.xml index 88d32c2985..a55293f657 100644 --- a/cache-client/pom.xml +++ b/cache-client/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 cache-client diff --git a/cache-updater/pom.xml b/cache-updater/pom.xml index 7d42489f5b..4ed7f2d2e9 100644 --- a/cache-updater/pom.xml +++ b/cache-updater/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 Cache Updater diff --git a/cache/pom.xml b/cache/pom.xml index 77756e056c..0e1ecb0f26 100644 --- a/cache/pom.xml +++ b/cache/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 cache diff --git a/http-api/pom.xml b/http-api/pom.xml index 00ce6f4808..c7207f80c4 100644 --- a/http-api/pom.xml +++ b/http-api/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 Web API diff --git a/http-service/pom.xml b/http-service/pom.xml index 3d129ddac7..47d16414fb 100644 --- a/http-service/pom.xml +++ b/http-service/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 Web Service diff --git a/pom.xml b/pom.xml index b41e6948c1..bfc2c9310b 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 pom RuneLite @@ -59,7 +59,7 @@ https://github.com/runelite/runelite scm:git:git://github.com/runelite/runelite scm:git:git@github.com:runelite/runelite - HEAD + runelite-parent-1.5.35 diff --git a/protocol-api/pom.xml b/protocol-api/pom.xml index f54c525380..234a7841a7 100644 --- a/protocol-api/pom.xml +++ b/protocol-api/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 protocol-api diff --git a/protocol/pom.xml b/protocol/pom.xml index 84c404c3f5..d20bc19839 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 protocol diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml index bc24d72ca6..8fa47f73ea 100644 --- a/runelite-api/pom.xml +++ b/runelite-api/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 runelite-api diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index 70adbefb09..29111ace0e 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 client diff --git a/runelite-script-assembler-plugin/pom.xml b/runelite-script-assembler-plugin/pom.xml index 6a73a3a31b..e051b7a81b 100644 --- a/runelite-script-assembler-plugin/pom.xml +++ b/runelite-script-assembler-plugin/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35-SNAPSHOT + 1.5.35 script-assembler-plugin From 03c4422628b94160ef3970bfa11317369f1c8698 Mon Sep 17 00:00:00 2001 From: Runelite auto updater Date: Wed, 2 Oct 2019 12:51:48 +0000 Subject: [PATCH 12/23] [maven-release-plugin] prepare for next development iteration --- cache-client/pom.xml | 2 +- cache-updater/pom.xml | 2 +- cache/pom.xml | 2 +- http-api/pom.xml | 2 +- http-service/pom.xml | 2 +- pom.xml | 4 ++-- protocol-api/pom.xml | 2 +- protocol/pom.xml | 2 +- runelite-api/pom.xml | 2 +- runelite-client/pom.xml | 2 +- runelite-script-assembler-plugin/pom.xml | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cache-client/pom.xml b/cache-client/pom.xml index a55293f657..9e2da34d4c 100644 --- a/cache-client/pom.xml +++ b/cache-client/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT cache-client diff --git a/cache-updater/pom.xml b/cache-updater/pom.xml index 4ed7f2d2e9..c8c34e2059 100644 --- a/cache-updater/pom.xml +++ b/cache-updater/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT Cache Updater diff --git a/cache/pom.xml b/cache/pom.xml index 0e1ecb0f26..e82ec87614 100644 --- a/cache/pom.xml +++ b/cache/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT cache diff --git a/http-api/pom.xml b/http-api/pom.xml index c7207f80c4..b9c5bd1aab 100644 --- a/http-api/pom.xml +++ b/http-api/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT Web API diff --git a/http-service/pom.xml b/http-service/pom.xml index 47d16414fb..05b3b911f7 100644 --- a/http-service/pom.xml +++ b/http-service/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT Web Service diff --git a/pom.xml b/pom.xml index bfc2c9310b..90b3bd9b60 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT pom RuneLite @@ -59,7 +59,7 @@ https://github.com/runelite/runelite scm:git:git://github.com/runelite/runelite scm:git:git@github.com:runelite/runelite - runelite-parent-1.5.35 + HEAD diff --git a/protocol-api/pom.xml b/protocol-api/pom.xml index 234a7841a7..469eeffed9 100644 --- a/protocol-api/pom.xml +++ b/protocol-api/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT protocol-api diff --git a/protocol/pom.xml b/protocol/pom.xml index d20bc19839..d548031afa 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT protocol diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml index 8fa47f73ea..fe99f48577 100644 --- a/runelite-api/pom.xml +++ b/runelite-api/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT runelite-api diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index 29111ace0e..a87e7f816b 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT client diff --git a/runelite-script-assembler-plugin/pom.xml b/runelite-script-assembler-plugin/pom.xml index e051b7a81b..da0cc3fc3e 100644 --- a/runelite-script-assembler-plugin/pom.xml +++ b/runelite-script-assembler-plugin/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.35 + 1.5.36-SNAPSHOT script-assembler-plugin From 1e58a77683de2598d1907b0cac57d4e2a156ef80 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 27 Sep 2019 22:15:46 -0400 Subject: [PATCH 13/23] item controller: add etag to bulk prices --- .../http/service/item/ItemController.java | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java index 9d1ed5710e..8db56ef2ff 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java @@ -28,6 +28,9 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; import java.time.Instant; import java.util.Arrays; import java.util.List; @@ -53,20 +56,39 @@ public class ItemController private static final String RUNELITE_CACHE = "RuneLite-Cache"; private static final int MAX_BATCH_LOOKUP = 1024; + private static class MemoizedPrices + { + final ItemPrice[] prices; + final String hash; + + MemoizedPrices(ItemPrice[] prices) + { + this.prices = prices; + + Hasher hasher = Hashing.sha256().newHasher(); + for (ItemPrice itemPrice : prices) + { + hasher.putInt(itemPrice.getId()).putInt(itemPrice.getPrice()); + } + HashCode code = hasher.hash(); + hash = code.toString(); + } + } + private final Cache cachedEmpty = CacheBuilder.newBuilder() .maximumSize(1024L) .build(); private final ItemService itemService; - private final Supplier memorizedPrices; + private final Supplier memoizedPrices; @Autowired public ItemController(ItemService itemService) { this.itemService = itemService; - memorizedPrices = Suppliers.memoizeWithExpiration(() -> itemService.fetchPrices().stream() + memoizedPrices = Suppliers.memoizeWithExpiration(() -> new MemoizedPrices(itemService.fetchPrices().stream() .map(priceEntry -> { ItemPrice itemPrice = new ItemPrice(); @@ -76,7 +98,7 @@ public class ItemController itemPrice.setTime(priceEntry.getTime()); return itemPrice; }) - .toArray(ItemPrice[]::new), 30, TimeUnit.MINUTES); + .toArray(ItemPrice[]::new)), 30, TimeUnit.MINUTES); } @GetMapping("/{itemId}") @@ -220,8 +242,10 @@ public class ItemController @GetMapping("/prices") public ResponseEntity prices() { + MemoizedPrices memorizedPrices = this.memoizedPrices.get(); return ResponseEntity.ok() + .eTag(memorizedPrices.hash) .cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic()) - .body(memorizedPrices.get()); + .body(memorizedPrices.prices); } } From cade74c18cf965ec79dd13fe0cf4de0e09a314b8 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 27 Sep 2019 22:16:02 -0400 Subject: [PATCH 14/23] feed controller: add etag --- .../http/service/feed/FeedController.java | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java b/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java index c679cd319f..480478c9d0 100644 --- a/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java +++ b/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java @@ -24,6 +24,9 @@ */ package net.runelite.http.service.feed; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -51,7 +54,26 @@ public class FeedController private final TwitterService twitterService; private final OSRSNewsService osrsNewsService; - private FeedResult feedResult; + private static class MemoizedFeed + { + final FeedResult feedResult; + final String hash; + + MemoizedFeed(FeedResult feedResult) + { + this.feedResult = feedResult; + + Hasher hasher = Hashing.sha256().newHasher(); + for (FeedItem itemPrice : feedResult.getItems()) + { + hasher.putBytes(itemPrice.getTitle().getBytes()).putBytes(itemPrice.getContent().getBytes()); + } + HashCode code = hasher.hash(); + hash = code.toString(); + } + } + + private MemoizedFeed memoizedFeed; @Autowired public FeedController(BlogService blogService, TwitterService twitterService, OSRSNewsService osrsNewsService) @@ -93,20 +115,21 @@ public class FeedController log.warn(e.getMessage()); } - feedResult = new FeedResult(items); + memoizedFeed = new MemoizedFeed(new FeedResult(items)); } @GetMapping public ResponseEntity getFeed() { - if (feedResult == null) + if (memoizedFeed == null) { return ResponseEntity.notFound() .build(); } return ResponseEntity.ok() + .eTag(memoizedFeed.hash) .cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic()) - .body(feedResult); + .body(memoizedFeed.feedResult); } } From d6041bdc55d9290f645e93115200b750eb5fa9b3 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 27 Sep 2019 22:18:45 -0400 Subject: [PATCH 15/23] client: use own okhttp client with cache --- .../src/main/java/net/runelite/client/RuneLiteModule.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java index 525a0f23b6..5d9bbf1531 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java @@ -28,6 +28,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.name.Names; import java.applet.Applet; +import java.io.File; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Supplier; @@ -49,12 +50,15 @@ import net.runelite.client.task.Scheduler; import net.runelite.client.util.DeferredEventBus; import net.runelite.client.util.ExecutorServiceExceptionLogger; import net.runelite.http.api.RuneLiteAPI; +import okhttp3.Cache; import okhttp3.OkHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RuneLiteModule extends AbstractModule { + private static final int MAX_OKHTTP_CACHE_SIZE = 20 * 1024 * 1024; // 20mb + private final Supplier clientLoader; private final boolean developerMode; @@ -69,7 +73,9 @@ public class RuneLiteModule extends AbstractModule { bindConstant().annotatedWith(Names.named("developerMode")).to(developerMode); bind(ScheduledExecutorService.class).toInstance(new ExecutorServiceExceptionLogger(Executors.newSingleThreadScheduledExecutor())); - bind(OkHttpClient.class).toInstance(RuneLiteAPI.CLIENT); + bind(OkHttpClient.class).toInstance(RuneLiteAPI.CLIENT.newBuilder() + .cache(new Cache(new File(RuneLite.RUNELITE_DIR, "cache" + File.separator + "okhttp"), MAX_OKHTTP_CACHE_SIZE)) + .build()); bind(MenuManager.class); bind(ChatMessageManager.class); bind(ItemManager.class); From 1fa840926583b37d1d6cccafd07afe31ba9f1ebc Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 28 Sep 2019 10:11:34 -0400 Subject: [PATCH 16/23] http-api: add javax.inject dependency --- http-api/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/http-api/pom.xml b/http-api/pom.xml index b9c5bd1aab..a0b31501c9 100644 --- a/http-api/pom.xml +++ b/http-api/pom.xml @@ -63,6 +63,11 @@ commons-csv 1.4 + + javax.inject + javax.inject + 1 + junit From 2af98eacfc84c61f3ed2dc7b8298f63c7e1e41f4 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 27 Sep 2019 22:19:46 -0400 Subject: [PATCH 17/23] feed: use caching okhttp client --- .../java/net/runelite/http/api/feed/FeedClient.java | 12 +++++++++++- .../net/runelite/client/plugins/feed/FeedPlugin.java | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/http-api/src/main/java/net/runelite/http/api/feed/FeedClient.java b/http-api/src/main/java/net/runelite/http/api/feed/FeedClient.java index 6b05ad20ba..bf4698315c 100644 --- a/http-api/src/main/java/net/runelite/http/api/feed/FeedClient.java +++ b/http-api/src/main/java/net/runelite/http/api/feed/FeedClient.java @@ -28,8 +28,10 @@ import com.google.gson.JsonParseException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import javax.inject.Inject; import net.runelite.http.api.RuneLiteAPI; import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; @@ -39,6 +41,14 @@ public class FeedClient { private static final Logger logger = LoggerFactory.getLogger(FeedClient.class); + private final OkHttpClient client; + + @Inject + public FeedClient(OkHttpClient client) + { + this.client = client; + } + public FeedResult lookupFeed() throws IOException { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() @@ -51,7 +61,7 @@ public class FeedClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java index 8a34a73cf1..89c7ef3c5a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java @@ -64,10 +64,12 @@ public class FeedPlugin extends Plugin @Inject private ScheduledExecutorService executorService; + @Inject + private FeedClient feedClient; + private FeedPanel feedPanel; private NavigationButton navButton; - private FeedClient feedClient = new FeedClient(); private Supplier feedSupplier = Suppliers.memoizeWithExpiration(() -> { try From 9c5b36cec7db880394d8b4b7375ff0b12c1d22d8 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 27 Sep 2019 22:21:42 -0400 Subject: [PATCH 18/23] item manager: use caching okhttp client --- .../runelite/http/api/item/ItemClient.java | 22 ++++++++++++++----- .../net/runelite/client/game/ItemManager.java | 6 +++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java b/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java index 17784dd67b..d1262e32c6 100644 --- a/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java @@ -34,8 +34,10 @@ import java.lang.reflect.Type; import java.util.Arrays; import java.util.Map; import javax.imageio.ImageIO; +import javax.inject.Inject; import net.runelite.http.api.RuneLiteAPI; import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; @@ -45,6 +47,14 @@ public class ItemClient { private static final Logger logger = LoggerFactory.getLogger(ItemClient.class); + private final OkHttpClient client; + + @Inject + public ItemClient(OkHttpClient client) + { + this.client = client; + } + public ItemPrice lookupItemPrice(int itemId) throws IOException { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() @@ -59,7 +69,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { @@ -95,7 +105,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { @@ -126,7 +136,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { @@ -156,7 +166,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { @@ -187,7 +197,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { @@ -219,7 +229,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java index e758d66969..88cb65b466 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java @@ -84,7 +84,7 @@ public class ItemManager private final ScheduledExecutorService scheduledExecutorService; private final ClientThread clientThread; - private final ItemClient itemClient = new ItemClient(); + private final ItemClient itemClient; private Map itemPrices = Collections.emptyMap(); private Map itemStats = Collections.emptyMap(); private final LoadingCache itemImages; @@ -155,11 +155,13 @@ public class ItemManager build(); @Inject - public ItemManager(Client client, ScheduledExecutorService executor, ClientThread clientThread) + public ItemManager(Client client, ScheduledExecutorService executor, ClientThread clientThread, + ItemClient itemClient) { this.client = client; this.scheduledExecutorService = executor; this.clientThread = clientThread; + this.itemClient = itemClient; scheduledExecutorService.scheduleWithFixedDelay(this::loadPrices, 0, 30, TimeUnit.MINUTES); scheduledExecutorService.submit(this::loadStats); From f49670cd5b494daf6b59e916b9ae0f3aa52394b5 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 27 Sep 2019 23:40:56 -0400 Subject: [PATCH 19/23] Use caching okhttp client for worldclient --- .../runelite/http/api/worlds/WorldClient.java | 19 ++++++++++++++----- .../defaultworld/DefaultWorldPlugin.java | 6 ++++-- .../worldhopper/WorldHopperPlugin.java | 5 ++++- .../net/runelite/client/rs/HostSupplier.java | 6 ++++-- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java b/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java index 668e088404..8e08d0c9b1 100644 --- a/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java +++ b/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java @@ -26,21 +26,30 @@ package net.runelite.http.api.worlds; import com.google.gson.JsonParseException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import javax.inject.Inject; import net.runelite.http.api.RuneLiteAPI; import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - public class WorldClient { private static final Logger logger = LoggerFactory.getLogger(WorldClient.class); + private final OkHttpClient client; + + @Inject + public WorldClient(OkHttpClient client) + { + this.client = client; + } + public WorldResult lookupWorlds() throws IOException { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() @@ -53,7 +62,7 @@ public class WorldClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java index d68791d289..ef987c2cc7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java @@ -31,9 +31,9 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.events.GameStateChanged; -import net.runelite.client.events.SessionOpen; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.SessionOpen; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.util.WorldUtil; @@ -55,7 +55,9 @@ public class DefaultWorldPlugin extends Plugin @Inject private DefaultWorldConfig config; - private final WorldClient worldClient = new WorldClient(); + @Inject + private WorldClient worldClient; + private int worldCache; private boolean worldChangeRequired; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java index 96aad43f11..09fa7b747b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java @@ -140,6 +140,9 @@ public class WorldHopperPlugin extends Plugin @Inject private WorldHopperPingOverlay worldHopperOverlay; + @Inject + private WorldClient worldClient; + private ScheduledExecutorService hopperExecutorService; private NavigationButton navButton; @@ -508,7 +511,7 @@ public class WorldHopperPlugin extends Plugin try { - WorldResult worldResult = new WorldClient().lookupWorlds(); + WorldResult worldResult = worldClient.lookupWorlds(); if (worldResult != null) { diff --git a/runelite-client/src/main/java/net/runelite/client/rs/HostSupplier.java b/runelite-client/src/main/java/net/runelite/client/rs/HostSupplier.java index b92f9bb074..cd75157ac1 100644 --- a/runelite-client/src/main/java/net/runelite/client/rs/HostSupplier.java +++ b/runelite-client/src/main/java/net/runelite/client/rs/HostSupplier.java @@ -33,6 +33,8 @@ import java.util.Random; import java.util.function.Supplier; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.worlds.World; import net.runelite.http.api.worlds.WorldClient; @Slf4j @@ -51,11 +53,11 @@ class HostSupplier implements Supplier try { - List newHosts = new WorldClient() + List newHosts = new WorldClient(RuneLiteAPI.CLIENT) .lookupWorlds() .getWorlds() .stream() - .map(i -> i.getAddress()) + .map(World::getAddress) .collect(Collectors.toList()); Collections.shuffle(newHosts, random); From d6772a3be4f228a6e686ac1e3f5036cdba69d2e6 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 2 Oct 2019 18:59:05 -0400 Subject: [PATCH 20/23] tile indicators: fix storing names for multilocs This was storing "null" for multilocs whose base name is "null", which caused a lot of unintentional matches against objects on the tile with no names. --- .../ObjectIndicatorsPlugin.java | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index c41e81c6c6..7b91196592 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -37,9 +37,11 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import javax.inject.Inject; import lombok.AccessLevel; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import static net.runelite.api.Constants.REGION_SIZE; import net.runelite.api.DecorativeObject; @@ -78,6 +80,7 @@ import net.runelite.client.ui.overlay.OverlayManager; tags = {"overlay", "objects", "mark", "marker"}, enabledByDefault = false ) +@Slf4j public class ObjectIndicatorsPlugin extends Plugin implements KeyListener { private static final String CONFIG_GROUP = "objectindicators"; @@ -279,9 +282,13 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener return; } - ObjectComposition objectDefinition = client.getObjectDefinition(object.getId()); + // object.getId() is always the base object id, getObjectComposition transforms it to + // the correct object we see + ObjectComposition objectDefinition = getObjectComposition(object.getId()); String name = objectDefinition.getName(); - if (Strings.isNullOrEmpty(name)) + // Name is probably never "null" - however prevent adding it if it is, as it will + // become ambiguous as objects with no name are assigned name "null" + if (Strings.isNullOrEmpty(name) || name.equals("null")) { return; } @@ -304,8 +311,10 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener if ((worldPoint.getX() & (REGION_SIZE - 1)) == objectPoint.getRegionX() && (worldPoint.getY() & (REGION_SIZE - 1)) == objectPoint.getRegionY()) { - if (objectPoint.getName().equals(client.getObjectDefinition(object.getId()).getName())) + // Transform object to get the name which matches against what we've stored + if (objectPoint.getName().equals(getObjectComposition(object.getId()).getName())) { + log.debug("Marking object {} due to matching {}", object, objectPoint); objects.add(object); break; } @@ -346,7 +355,8 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener return object; } - // Check impostors + // Menu action EXAMINE_OBJECT sends the transformed object id, not the base id, unlike + // all of the GAME_OBJECT_OPTION actions, so check the id against the impostor ids final ObjectComposition comp = client.getObjectDefinition(object.getId()); if (comp.getImpostorIds() != null) @@ -386,11 +396,13 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener { objectPoints.remove(point); objects.remove(object); + log.debug("Unmarking object: {}", point); } else { objectPoints.add(point); objects.add(object); + log.debug("Marking object: {}", point); } savePoints(regionId, objectPoints); @@ -418,8 +430,20 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener return null; } - return GSON.fromJson(json, new TypeToken>() + Set points = GSON.fromJson(json, new TypeToken>() { }.getType()); + // Prior to multiloc support the plugin would mark objects named "null", which breaks + // in most cases due to the specific object being identified being ambiguous, so remove + // them + return points.stream() + .filter(point -> !point.getName().equals("null")) + .collect(Collectors.toSet()); + } + + private ObjectComposition getObjectComposition(int id) + { + ObjectComposition objectComposition = client.getObjectDefinition(id); + return objectComposition.getImpostorIds() == null ? objectComposition : objectComposition.getImpostor(); } } \ No newline at end of file From b22fc6b30176bcb176d63d6c61a602f83a4ed938 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 2 Oct 2019 19:31:27 -0400 Subject: [PATCH 21/23] object indicators: support non-gameobject multilocs --- .../ObjectIndicatorsPlugin.java | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index 7b91196592..c3739650f5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -333,47 +333,57 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener final DecorativeObject tileDecorativeObject = tile.getDecorativeObject(); final WallObject tileWallObject = tile.getWallObject(); - if (tileWallObject != null && tileWallObject.getId() == id) + if (objectIdEquals(tileWallObject, id)) { return tileWallObject; } - if (tileDecorativeObject != null && tileDecorativeObject.getId() == id) + if (objectIdEquals(tileDecorativeObject, id)) { return tileDecorativeObject; } for (GameObject object : tileGameObjects) { - if (object == null) - { - continue; - } - - if (object.getId() == id) + if (objectIdEquals(object, id)) { return object; } - - // Menu action EXAMINE_OBJECT sends the transformed object id, not the base id, unlike - // all of the GAME_OBJECT_OPTION actions, so check the id against the impostor ids - final ObjectComposition comp = client.getObjectDefinition(object.getId()); - - if (comp.getImpostorIds() != null) - { - for (int impostorId : comp.getImpostorIds()) - { - if (impostorId == id) - { - return object; - } - } - } } return null; } + private boolean objectIdEquals(TileObject tileObject, int id) + { + if (tileObject == null) + { + return false; + } + + if (tileObject.getId() == id) + { + return true; + } + + // Menu action EXAMINE_OBJECT sends the transformed object id, not the base id, unlike + // all of the GAME_OBJECT_OPTION actions, so check the id against the impostor ids + final ObjectComposition comp = client.getObjectDefinition(tileObject.getId()); + + if (comp.getImpostorIds() != null) + { + for (int impostorId : comp.getImpostorIds()) + { + if (impostorId == id) + { + return true; + } + } + } + + return false; + } + private void markObject(String name, final TileObject object) { if (object == null) From 5f745be26de01207cffa357cac24c91acee9c67b Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 2 Oct 2019 20:55:47 -0400 Subject: [PATCH 22/23] api: add getConvexHull to GroundObject --- .../src/main/java/net/runelite/api/GroundObject.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/GroundObject.java b/runelite-api/src/main/java/net/runelite/api/GroundObject.java index 8a974cd752..8971aa88bf 100644 --- a/runelite-api/src/main/java/net/runelite/api/GroundObject.java +++ b/runelite-api/src/main/java/net/runelite/api/GroundObject.java @@ -24,10 +24,20 @@ */ package net.runelite.api; +import java.awt.Shape; + /** * Represents an object on the ground of a tile. */ public interface GroundObject extends TileObject { Renderable getRenderable(); + + /** + * Gets the convex hull of the objects model. + * + * @return the convex hull + * @see net.runelite.api.model.Jarvis + */ + Shape getConvexHull(); } From ad5514acdefe657b31326c2f6776c66884daf060 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 2 Oct 2019 21:10:57 -0400 Subject: [PATCH 23/23] object indicator: add support for ground objects --- .../ObjectIndicatorsOverlay.java | 5 ++++ .../ObjectIndicatorsPlugin.java | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java index a61aa526dc..21d0a4692d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java @@ -31,6 +31,7 @@ import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; +import net.runelite.api.GroundObject; import net.runelite.api.TileObject; import net.runelite.api.WallObject; import net.runelite.client.ui.overlay.Overlay; @@ -83,6 +84,10 @@ class ObjectIndicatorsOverlay extends Overlay polygon = ((DecorativeObject) object).getConvexHull(); polygon2 = ((DecorativeObject) object).getConvexHull2(); } + else if (object instanceof GroundObject) + { + polygon = ((GroundObject) object).getConvexHull(); + } else { polygon = object.getCanvasTilePoly(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index c3739650f5..34386b52c4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -47,6 +47,7 @@ import static net.runelite.api.Constants.REGION_SIZE; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.GameState; +import net.runelite.api.GroundObject; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; import net.runelite.api.ObjectComposition; @@ -55,14 +56,16 @@ import net.runelite.api.Tile; import net.runelite.api.TileObject; import net.runelite.api.WallObject; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.DecorativeObjectDespawned; +import net.runelite.api.events.DecorativeObjectSpawned; import net.runelite.api.events.FocusChanged; import net.runelite.api.events.GameObjectDespawned; import net.runelite.api.events.GameObjectSpawned; import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GroundObjectDespawned; +import net.runelite.api.events.GroundObjectSpawned; import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuOptionClicked; -import net.runelite.api.events.DecorativeObjectSpawned; -import net.runelite.api.events.DecorativeObjectDespawned; import net.runelite.api.events.WallObjectChanged; import net.runelite.api.events.WallObjectDespawned; import net.runelite.api.events.WallObjectSpawned; @@ -212,6 +215,20 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener objects.remove(event.getDecorativeObject()); } + @Subscribe + public void onGroundObjectSpawned(GroundObjectSpawned groundObjectSpawned) + { + final GroundObject groundObject = groundObjectSpawned.getGroundObject(); + checkObjectPoints(groundObject); + } + + @Subscribe + public void onGroundObjectDespawned(GroundObjectDespawned groundObjectDespawned) + { + GroundObject groundObject = groundObjectDespawned.getGroundObject(); + objects.remove(groundObject); + } + @Subscribe public void onGameStateChanged(GameStateChanged gameStateChanged) { @@ -332,6 +349,7 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener final GameObject[] tileGameObjects = tile.getGameObjects(); final DecorativeObject tileDecorativeObject = tile.getDecorativeObject(); final WallObject tileWallObject = tile.getWallObject(); + final GroundObject groundObject = tile.getGroundObject(); if (objectIdEquals(tileWallObject, id)) { @@ -343,6 +361,11 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener return tileDecorativeObject; } + if (objectIdEquals(groundObject, id)) + { + return groundObject; + } + for (GameObject object : tileGameObjects) { if (objectIdEquals(object, id))