From 0305898935729d454548d3d37295f76ad57f3d4d Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 1 May 2019 21:23:39 +0200 Subject: [PATCH 1/2] Stop aggroarea from creating a timer if the duration is negative --- .../npcunaggroarea/NpcAggroAreaPlugin.java | 972 +++++++++--------- 1 file changed, 486 insertions(+), 486 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java index 10dd5752f9..3e583cd2b0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java @@ -1,486 +1,486 @@ -/* - * 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.npcunaggroarea; - -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.inject.Provides; -import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.geom.Area; -import java.awt.geom.GeneralPath; -import java.awt.image.BufferedImage; -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.List; -import javax.inject.Inject; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.Client; -import net.runelite.api.Constants; -import net.runelite.api.ItemID; -import net.runelite.api.NPC; -import net.runelite.api.NPCComposition; -import net.runelite.api.Perspective; -import net.runelite.api.coords.LocalPoint; -import net.runelite.api.coords.WorldArea; -import net.runelite.api.coords.WorldPoint; -import net.runelite.api.events.ConfigChanged; -import net.runelite.api.events.GameStateChanged; -import net.runelite.api.events.GameTick; -import net.runelite.api.events.NpcSpawned; -import net.runelite.api.geometry.Geometry; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.game.ItemManager; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; -import net.runelite.client.ui.overlay.infobox.InfoBoxManager; -import net.runelite.client.util.WildcardMatcher; - -@Slf4j -@PluginDescriptor( - name = "NPC Aggression Timer", - description = "Highlights the unaggressive area of NPCs nearby and timer until it becomes active", - tags = {"highlight", "lines", "unaggro", "aggro", "aggressive", "npcs", "area", "slayer"}, - enabledByDefault = false -) -public class NpcAggroAreaPlugin extends Plugin -{ - /* - How it works: The game remembers 2 tiles. When the player goes >10 steps - away from both tiles, the oldest one is moved to under the player and the - NPC aggression timer resets. - So to first figure out where the 2 tiles are, we wait until the player teleports - a long enough distance. At that point it's very likely that the player - moved out of the radius of both tiles, which resets one of them. The other - should reset shortly after as the player starts moving around. - */ - - private static final int SAFE_AREA_RADIUS = 10; - private static final int UNKNOWN_AREA_RADIUS = SAFE_AREA_RADIUS * 2; - private static final int AGGRESSIVE_TIME_SECONDS = 600; - private static final Splitter NAME_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults(); - private static final WorldArea WILDERNESS_ABOVE_GROUND = new WorldArea(2944, 3523, 448, 448, 0); - private static final WorldArea WILDERNESS_UNDERGROUND = new WorldArea(2944, 9918, 320, 442, 0); - - @Inject - private Client client; - - @Inject - private NpcAggroAreaConfig config; - - @Inject - private NpcAggroAreaOverlay overlay; - - @Inject - private NpcAggroAreaNotWorkingOverlay notWorkingOverlay; - - @Inject - private OverlayManager overlayManager; - - @Inject - private ItemManager itemManager; - - @Inject - private InfoBoxManager infoBoxManager; - - @Inject - private ConfigManager configManager; - - @Getter - private final WorldPoint[] safeCenters = new WorldPoint[2]; - - @Getter - private final GeneralPath[] linesToDisplay = new GeneralPath[Constants.MAX_Z]; - - @Getter - private boolean active; - - @Getter - private AggressionTimer currentTimer; - - private WorldPoint lastPlayerLocation; - private WorldPoint previousUnknownCenter; - private boolean loggingIn; - private List npcNamePatterns; - private boolean notWorkingOverlayShown = false; - - @Provides - NpcAggroAreaConfig provideConfig(ConfigManager configManager) - { - return configManager.getConfig(NpcAggroAreaConfig.class); - } - - @Override - protected void startUp() throws Exception - { - overlayManager.add(overlay); - if (config.showNotWorkingOverlay()) - { - overlayManager.add(notWorkingOverlay); - notWorkingOverlayShown = true; - } - - npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns()); - recheckActive(); - } - - @Override - protected void shutDown() throws Exception - { - removeTimer(); - overlayManager.remove(overlay); - if (notWorkingOverlayShown) - { - overlayManager.remove(notWorkingOverlay); - } - - Arrays.fill(safeCenters, null); - lastPlayerLocation = null; - currentTimer = null; - loggingIn = false; - npcNamePatterns = null; - active = false; - - Arrays.fill(linesToDisplay, null); - } - - private Area generateSafeArea() - { - final Area area = new Area(); - - for (WorldPoint wp : safeCenters) - { - if (wp == null) - { - continue; - } - - Polygon poly = new Polygon(); - poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() - SAFE_AREA_RADIUS); - poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() + SAFE_AREA_RADIUS + 1); - poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() + SAFE_AREA_RADIUS + 1); - poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() - SAFE_AREA_RADIUS); - area.add(new Area(poly)); - } - - return area; - } - - private void transformWorldToLocal(float[] coords) - { - final LocalPoint lp = LocalPoint.fromWorld(client, (int)coords[0], (int)coords[1]); - coords[0] = lp.getX() - Perspective.LOCAL_TILE_SIZE / 2f; - coords[1] = lp.getY() - Perspective.LOCAL_TILE_SIZE / 2f; - } - - private void reevaluateActive() - { - if (currentTimer != null) - { - currentTimer.setVisible(active && config.showTimer()); - } - - calculateLinesToDisplay(); - } - - private void calculateLinesToDisplay() - { - if (!active || !config.showAreaLines()) - { - Arrays.fill(linesToDisplay, null); - return; - } - - Rectangle sceneRect = new Rectangle( - client.getBaseX() + 1, client.getBaseY() + 1, - Constants.SCENE_SIZE - 2, Constants.SCENE_SIZE - 2); - - for (int i = 0; i < linesToDisplay.length; i++) - { - GeneralPath lines = new GeneralPath(generateSafeArea()); - lines = Geometry.clipPath(lines, sceneRect); - lines = Geometry.splitIntoSegments(lines, 1); - lines = Geometry.transformPath(lines, this::transformWorldToLocal); - linesToDisplay[i] = lines; - } - } - - private void removeTimer() - { - infoBoxManager.removeInfoBox(currentTimer); - currentTimer = null; - } - - private void createTimer(Duration duration) - { - removeTimer(); - BufferedImage image = itemManager.getImage(ItemID.ENSOULED_DEMON_HEAD); - currentTimer = new AggressionTimer(duration, image, this, active && config.showTimer()); - infoBoxManager.addInfoBox(currentTimer); - } - - private void resetTimer() - { - createTimer(Duration.ofSeconds(AGGRESSIVE_TIME_SECONDS)); - } - - private static boolean isInWilderness(WorldPoint location) - { - return WILDERNESS_ABOVE_GROUND.distanceTo2D(location) == 0 || WILDERNESS_UNDERGROUND.distanceTo2D(location) == 0; - } - - private boolean isNpcMatch(NPC npc) - { - NPCComposition composition = npc.getTransformedComposition(); - if (composition == null) - { - return false; - } - - if (Strings.isNullOrEmpty(composition.getName())) - { - return false; - } - - // Most NPCs stop aggroing when the player has more than double - // its combat level. - int playerLvl = client.getLocalPlayer().getCombatLevel(); - int npcLvl = composition.getCombatLevel(); - String npcName = composition.getName().toLowerCase(); - if (npcLvl > 0 && playerLvl > npcLvl * 2 && !isInWilderness(npc.getWorldLocation())) - { - return false; - } - - for (String pattern : npcNamePatterns) - { - if (WildcardMatcher.matches(pattern, npcName)) - { - return true; - } - } - - return false; - } - - private void checkAreaNpcs(final NPC... npcs) - { - for (NPC npc : npcs) - { - if (npc == null) - { - continue; - } - - if (isNpcMatch(npc)) - { - active = true; - break; - } - } - - reevaluateActive(); - } - - private void recheckActive() - { - active = config.alwaysActive(); - checkAreaNpcs(client.getCachedNPCs()); - } - - @Subscribe - public void onNpcSpawned(NpcSpawned event) - { - if (config.alwaysActive()) - { - return; - } - - checkAreaNpcs(event.getNpc()); - } - - @Subscribe - public void onGameTick(GameTick event) - { - WorldPoint newLocation = client.getLocalPlayer().getWorldLocation(); - if (lastPlayerLocation != null) - { - if (safeCenters[1] == null && newLocation.distanceTo2D(lastPlayerLocation) > SAFE_AREA_RADIUS * 4) - { - safeCenters[0] = null; - safeCenters[1] = newLocation; - resetTimer(); - calculateLinesToDisplay(); - - // We don't know where the previous area was, so if the player e.g. - // entered a dungeon and then goes back out, he/she may enter the previous - // area which is unknown and would make the plugin inaccurate - previousUnknownCenter = lastPlayerLocation; - } - } - - if (safeCenters[0] == null && previousUnknownCenter != null && - previousUnknownCenter.distanceTo2D(newLocation) <= UNKNOWN_AREA_RADIUS) - { - // Player went back to their previous unknown area before the 2nd - // center point was found, which means we don't know where it is again. - safeCenters[1] = null; - removeTimer(); - calculateLinesToDisplay(); - } - - if (safeCenters[1] != null) - { - if (Arrays.stream(safeCenters).noneMatch( - x -> x != null && x.distanceTo2D(newLocation) <= SAFE_AREA_RADIUS)) - { - safeCenters[0] = safeCenters[1]; - safeCenters[1] = newLocation; - resetTimer(); - calculateLinesToDisplay(); - previousUnknownCenter = null; - } - } - - lastPlayerLocation = newLocation; - } - - @Subscribe - public void onConfigChanged(ConfigChanged event) - { - String key = event.getKey(); - switch (key) - { - case "npcUnaggroAlwaysActive": - recheckActive(); - break; - case "npcUnaggroShowTimer": - if (currentTimer != null) - { - currentTimer.setVisible(active && config.showTimer()); - } - break; - case "npcUnaggroCollisionDetection": - case "npcUnaggroShowAreaLines": - calculateLinesToDisplay(); - break; - case "npcUnaggroNames": - npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns()); - recheckActive(); - break; - } - } - - private void loadConfig() - { - safeCenters[0] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, WorldPoint.class); - safeCenters[1] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, WorldPoint.class); - lastPlayerLocation = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, WorldPoint.class); - - Duration timeLeft = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.class); - if (timeLeft != null) - { - createTimer(timeLeft); - } - } - - private void resetConfig() - { - configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1); - configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2); - configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION); - configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION); - configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_NOT_WORKING_OVERLAY); - } - - private void saveConfig() - { - if (safeCenters[0] == null || safeCenters[1] == null || lastPlayerLocation == null || currentTimer == null) - { - resetConfig(); - } - else - { - configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, safeCenters[0]); - configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, safeCenters[1]); - configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, lastPlayerLocation); - configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.between(Instant.now(), currentTimer.getEndTime())); - } - } - - private void onLogin() - { - loadConfig(); - resetConfig(); - - WorldPoint newLocation = client.getLocalPlayer().getWorldLocation(); - assert newLocation != null; - - // If the player isn't at the location he/she logged out at, - // the safe unaggro area probably changed, and should be disposed. - if (lastPlayerLocation == null || newLocation.distanceTo(lastPlayerLocation) != 0) - { - safeCenters[0] = null; - safeCenters[1] = null; - lastPlayerLocation = newLocation; - } - } - - @Subscribe - public void onGameStateChanged(GameStateChanged event) - { - switch (event.getGameState()) - { - case LOGGED_IN: - if (loggingIn) - { - loggingIn = false; - onLogin(); - } - - recheckActive(); - break; - - case LOGGING_IN: - loggingIn = true; - break; - - case LOGIN_SCREEN: - if (lastPlayerLocation != null) - { - saveConfig(); - } - - safeCenters[0] = null; - safeCenters[1] = null; - lastPlayerLocation = null; - break; - } - } -} +/* + * 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.npcunaggroarea; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.inject.Provides; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.image.BufferedImage; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import javax.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.Constants; +import net.runelite.api.ItemID; +import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; +import net.runelite.api.Perspective; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldArea; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.NpcSpawned; +import net.runelite.api.geometry.Geometry; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.client.util.WildcardMatcher; + +@Slf4j +@PluginDescriptor( + name = "NPC Aggression Timer", + description = "Highlights the unaggressive area of NPCs nearby and timer until it becomes active", + tags = {"highlight", "lines", "unaggro", "aggro", "aggressive", "npcs", "area", "slayer"}, + enabledByDefault = false +) +public class NpcAggroAreaPlugin extends Plugin +{ + /* + How it works: The game remembers 2 tiles. When the player goes >10 steps + away from both tiles, the oldest one is moved to under the player and the + NPC aggression timer resets. + So to first figure out where the 2 tiles are, we wait until the player teleports + a long enough distance. At that point it's very likely that the player + moved out of the radius of both tiles, which resets one of them. The other + should reset shortly after as the player starts moving around. + */ + + private static final int SAFE_AREA_RADIUS = 10; + private static final int UNKNOWN_AREA_RADIUS = SAFE_AREA_RADIUS * 2; + private static final int AGGRESSIVE_TIME_SECONDS = 600; + private static final Splitter NAME_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults(); + private static final WorldArea WILDERNESS_ABOVE_GROUND = new WorldArea(2944, 3523, 448, 448, 0); + private static final WorldArea WILDERNESS_UNDERGROUND = new WorldArea(2944, 9918, 320, 442, 0); + + @Inject + private Client client; + + @Inject + private NpcAggroAreaConfig config; + + @Inject + private NpcAggroAreaOverlay overlay; + + @Inject + private NpcAggroAreaNotWorkingOverlay notWorkingOverlay; + + @Inject + private OverlayManager overlayManager; + + @Inject + private ItemManager itemManager; + + @Inject + private InfoBoxManager infoBoxManager; + + @Inject + private ConfigManager configManager; + + @Getter + private final WorldPoint[] safeCenters = new WorldPoint[2]; + + @Getter + private final GeneralPath[] linesToDisplay = new GeneralPath[Constants.MAX_Z]; + + @Getter + private boolean active; + + @Getter + private AggressionTimer currentTimer; + + private WorldPoint lastPlayerLocation; + private WorldPoint previousUnknownCenter; + private boolean loggingIn; + private List npcNamePatterns; + private boolean notWorkingOverlayShown = false; + + @Provides + NpcAggroAreaConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(NpcAggroAreaConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + if (config.showNotWorkingOverlay()) + { + overlayManager.add(notWorkingOverlay); + notWorkingOverlayShown = true; + } + + npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns()); + recheckActive(); + } + + @Override + protected void shutDown() throws Exception + { + removeTimer(); + overlayManager.remove(overlay); + if (notWorkingOverlayShown) + { + overlayManager.remove(notWorkingOverlay); + } + + Arrays.fill(safeCenters, null); + lastPlayerLocation = null; + currentTimer = null; + loggingIn = false; + npcNamePatterns = null; + active = false; + + Arrays.fill(linesToDisplay, null); + } + + private Area generateSafeArea() + { + final Area area = new Area(); + + for (WorldPoint wp : safeCenters) + { + if (wp == null) + { + continue; + } + + Polygon poly = new Polygon(); + poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() - SAFE_AREA_RADIUS); + poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() + SAFE_AREA_RADIUS + 1); + poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() + SAFE_AREA_RADIUS + 1); + poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() - SAFE_AREA_RADIUS); + area.add(new Area(poly)); + } + + return area; + } + + private void transformWorldToLocal(float[] coords) + { + final LocalPoint lp = LocalPoint.fromWorld(client, (int)coords[0], (int)coords[1]); + coords[0] = lp.getX() - Perspective.LOCAL_TILE_SIZE / 2f; + coords[1] = lp.getY() - Perspective.LOCAL_TILE_SIZE / 2f; + } + + private void reevaluateActive() + { + if (currentTimer != null) + { + currentTimer.setVisible(active && config.showTimer()); + } + + calculateLinesToDisplay(); + } + + private void calculateLinesToDisplay() + { + if (!active || !config.showAreaLines()) + { + Arrays.fill(linesToDisplay, null); + return; + } + + Rectangle sceneRect = new Rectangle( + client.getBaseX() + 1, client.getBaseY() + 1, + Constants.SCENE_SIZE - 2, Constants.SCENE_SIZE - 2); + + for (int i = 0; i < linesToDisplay.length; i++) + { + GeneralPath lines = new GeneralPath(generateSafeArea()); + lines = Geometry.clipPath(lines, sceneRect); + lines = Geometry.splitIntoSegments(lines, 1); + lines = Geometry.transformPath(lines, this::transformWorldToLocal); + linesToDisplay[i] = lines; + } + } + + private void removeTimer() + { + infoBoxManager.removeInfoBox(currentTimer); + currentTimer = null; + } + + private void createTimer(Duration duration) + { + removeTimer(); + BufferedImage image = itemManager.getImage(ItemID.ENSOULED_DEMON_HEAD); + currentTimer = new AggressionTimer(duration, image, this, active && config.showTimer()); + infoBoxManager.addInfoBox(currentTimer); + } + + private void resetTimer() + { + createTimer(Duration.ofSeconds(AGGRESSIVE_TIME_SECONDS)); + } + + private static boolean isInWilderness(WorldPoint location) + { + return WILDERNESS_ABOVE_GROUND.distanceTo2D(location) == 0 || WILDERNESS_UNDERGROUND.distanceTo2D(location) == 0; + } + + private boolean isNpcMatch(NPC npc) + { + NPCComposition composition = npc.getTransformedComposition(); + if (composition == null) + { + return false; + } + + if (Strings.isNullOrEmpty(composition.getName())) + { + return false; + } + + // Most NPCs stop aggroing when the player has more than double + // its combat level. + int playerLvl = client.getLocalPlayer().getCombatLevel(); + int npcLvl = composition.getCombatLevel(); + String npcName = composition.getName().toLowerCase(); + if (npcLvl > 0 && playerLvl > npcLvl * 2 && !isInWilderness(npc.getWorldLocation())) + { + return false; + } + + for (String pattern : npcNamePatterns) + { + if (WildcardMatcher.matches(pattern, npcName)) + { + return true; + } + } + + return false; + } + + private void checkAreaNpcs(final NPC... npcs) + { + for (NPC npc : npcs) + { + if (npc == null) + { + continue; + } + + if (isNpcMatch(npc)) + { + active = true; + break; + } + } + + reevaluateActive(); + } + + private void recheckActive() + { + active = config.alwaysActive(); + checkAreaNpcs(client.getCachedNPCs()); + } + + @Subscribe + public void onNpcSpawned(NpcSpawned event) + { + if (config.alwaysActive()) + { + return; + } + + checkAreaNpcs(event.getNpc()); + } + + @Subscribe + public void onGameTick(GameTick event) + { + WorldPoint newLocation = client.getLocalPlayer().getWorldLocation(); + if (lastPlayerLocation != null) + { + if (safeCenters[1] == null && newLocation.distanceTo2D(lastPlayerLocation) > SAFE_AREA_RADIUS * 4) + { + safeCenters[0] = null; + safeCenters[1] = newLocation; + resetTimer(); + calculateLinesToDisplay(); + + // We don't know where the previous area was, so if the player e.g. + // entered a dungeon and then goes back out, he/she may enter the previous + // area which is unknown and would make the plugin inaccurate + previousUnknownCenter = lastPlayerLocation; + } + } + + if (safeCenters[0] == null && previousUnknownCenter != null && + previousUnknownCenter.distanceTo2D(newLocation) <= UNKNOWN_AREA_RADIUS) + { + // Player went back to their previous unknown area before the 2nd + // center point was found, which means we don't know where it is again. + safeCenters[1] = null; + removeTimer(); + calculateLinesToDisplay(); + } + + if (safeCenters[1] != null) + { + if (Arrays.stream(safeCenters).noneMatch( + x -> x != null && x.distanceTo2D(newLocation) <= SAFE_AREA_RADIUS)) + { + safeCenters[0] = safeCenters[1]; + safeCenters[1] = newLocation; + resetTimer(); + calculateLinesToDisplay(); + previousUnknownCenter = null; + } + } + + lastPlayerLocation = newLocation; + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + String key = event.getKey(); + switch (key) + { + case "npcUnaggroAlwaysActive": + recheckActive(); + break; + case "npcUnaggroShowTimer": + if (currentTimer != null) + { + currentTimer.setVisible(active && config.showTimer()); + } + break; + case "npcUnaggroCollisionDetection": + case "npcUnaggroShowAreaLines": + calculateLinesToDisplay(); + break; + case "npcUnaggroNames": + npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns()); + recheckActive(); + break; + } + } + + private void loadConfig() + { + safeCenters[0] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, WorldPoint.class); + safeCenters[1] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, WorldPoint.class); + lastPlayerLocation = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, WorldPoint.class); + + Duration timeLeft = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.class); + if (timeLeft != null && !timeLeft.isNegative()) + { + createTimer(timeLeft); + } + } + + private void resetConfig() + { + configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1); + configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2); + configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION); + configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION); + configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_NOT_WORKING_OVERLAY); + } + + private void saveConfig() + { + if (safeCenters[0] == null || safeCenters[1] == null || lastPlayerLocation == null || currentTimer == null) + { + resetConfig(); + } + else + { + configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, safeCenters[0]); + configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, safeCenters[1]); + configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, lastPlayerLocation); + configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.between(Instant.now(), currentTimer.getEndTime())); + } + } + + private void onLogin() + { + loadConfig(); + resetConfig(); + + WorldPoint newLocation = client.getLocalPlayer().getWorldLocation(); + assert newLocation != null; + + // If the player isn't at the location he/she logged out at, + // the safe unaggro area probably changed, and should be disposed. + if (lastPlayerLocation == null || newLocation.distanceTo(lastPlayerLocation) != 0) + { + safeCenters[0] = null; + safeCenters[1] = null; + lastPlayerLocation = newLocation; + } + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + switch (event.getGameState()) + { + case LOGGED_IN: + if (loggingIn) + { + loggingIn = false; + onLogin(); + } + + recheckActive(); + break; + + case LOGGING_IN: + loggingIn = true; + break; + + case LOGIN_SCREEN: + if (lastPlayerLocation != null) + { + saveConfig(); + } + + safeCenters[0] = null; + safeCenters[1] = null; + lastPlayerLocation = null; + break; + } + } +} From 3f241ed65811d7debdcc5aca3f325936b2485b30 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 1 May 2019 21:29:09 +0200 Subject: [PATCH 2/2] use lf line endings --- .../npcunaggroarea/NpcAggroAreaPlugin.java | 972 +++++++++--------- 1 file changed, 486 insertions(+), 486 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java index 3e583cd2b0..6d10bdba62 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java @@ -1,486 +1,486 @@ -/* - * 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.npcunaggroarea; - -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.inject.Provides; -import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.geom.Area; -import java.awt.geom.GeneralPath; -import java.awt.image.BufferedImage; -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.List; -import javax.inject.Inject; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.Client; -import net.runelite.api.Constants; -import net.runelite.api.ItemID; -import net.runelite.api.NPC; -import net.runelite.api.NPCComposition; -import net.runelite.api.Perspective; -import net.runelite.api.coords.LocalPoint; -import net.runelite.api.coords.WorldArea; -import net.runelite.api.coords.WorldPoint; -import net.runelite.api.events.ConfigChanged; -import net.runelite.api.events.GameStateChanged; -import net.runelite.api.events.GameTick; -import net.runelite.api.events.NpcSpawned; -import net.runelite.api.geometry.Geometry; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.game.ItemManager; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; -import net.runelite.client.ui.overlay.infobox.InfoBoxManager; -import net.runelite.client.util.WildcardMatcher; - -@Slf4j -@PluginDescriptor( - name = "NPC Aggression Timer", - description = "Highlights the unaggressive area of NPCs nearby and timer until it becomes active", - tags = {"highlight", "lines", "unaggro", "aggro", "aggressive", "npcs", "area", "slayer"}, - enabledByDefault = false -) -public class NpcAggroAreaPlugin extends Plugin -{ - /* - How it works: The game remembers 2 tiles. When the player goes >10 steps - away from both tiles, the oldest one is moved to under the player and the - NPC aggression timer resets. - So to first figure out where the 2 tiles are, we wait until the player teleports - a long enough distance. At that point it's very likely that the player - moved out of the radius of both tiles, which resets one of them. The other - should reset shortly after as the player starts moving around. - */ - - private static final int SAFE_AREA_RADIUS = 10; - private static final int UNKNOWN_AREA_RADIUS = SAFE_AREA_RADIUS * 2; - private static final int AGGRESSIVE_TIME_SECONDS = 600; - private static final Splitter NAME_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults(); - private static final WorldArea WILDERNESS_ABOVE_GROUND = new WorldArea(2944, 3523, 448, 448, 0); - private static final WorldArea WILDERNESS_UNDERGROUND = new WorldArea(2944, 9918, 320, 442, 0); - - @Inject - private Client client; - - @Inject - private NpcAggroAreaConfig config; - - @Inject - private NpcAggroAreaOverlay overlay; - - @Inject - private NpcAggroAreaNotWorkingOverlay notWorkingOverlay; - - @Inject - private OverlayManager overlayManager; - - @Inject - private ItemManager itemManager; - - @Inject - private InfoBoxManager infoBoxManager; - - @Inject - private ConfigManager configManager; - - @Getter - private final WorldPoint[] safeCenters = new WorldPoint[2]; - - @Getter - private final GeneralPath[] linesToDisplay = new GeneralPath[Constants.MAX_Z]; - - @Getter - private boolean active; - - @Getter - private AggressionTimer currentTimer; - - private WorldPoint lastPlayerLocation; - private WorldPoint previousUnknownCenter; - private boolean loggingIn; - private List npcNamePatterns; - private boolean notWorkingOverlayShown = false; - - @Provides - NpcAggroAreaConfig provideConfig(ConfigManager configManager) - { - return configManager.getConfig(NpcAggroAreaConfig.class); - } - - @Override - protected void startUp() throws Exception - { - overlayManager.add(overlay); - if (config.showNotWorkingOverlay()) - { - overlayManager.add(notWorkingOverlay); - notWorkingOverlayShown = true; - } - - npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns()); - recheckActive(); - } - - @Override - protected void shutDown() throws Exception - { - removeTimer(); - overlayManager.remove(overlay); - if (notWorkingOverlayShown) - { - overlayManager.remove(notWorkingOverlay); - } - - Arrays.fill(safeCenters, null); - lastPlayerLocation = null; - currentTimer = null; - loggingIn = false; - npcNamePatterns = null; - active = false; - - Arrays.fill(linesToDisplay, null); - } - - private Area generateSafeArea() - { - final Area area = new Area(); - - for (WorldPoint wp : safeCenters) - { - if (wp == null) - { - continue; - } - - Polygon poly = new Polygon(); - poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() - SAFE_AREA_RADIUS); - poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() + SAFE_AREA_RADIUS + 1); - poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() + SAFE_AREA_RADIUS + 1); - poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() - SAFE_AREA_RADIUS); - area.add(new Area(poly)); - } - - return area; - } - - private void transformWorldToLocal(float[] coords) - { - final LocalPoint lp = LocalPoint.fromWorld(client, (int)coords[0], (int)coords[1]); - coords[0] = lp.getX() - Perspective.LOCAL_TILE_SIZE / 2f; - coords[1] = lp.getY() - Perspective.LOCAL_TILE_SIZE / 2f; - } - - private void reevaluateActive() - { - if (currentTimer != null) - { - currentTimer.setVisible(active && config.showTimer()); - } - - calculateLinesToDisplay(); - } - - private void calculateLinesToDisplay() - { - if (!active || !config.showAreaLines()) - { - Arrays.fill(linesToDisplay, null); - return; - } - - Rectangle sceneRect = new Rectangle( - client.getBaseX() + 1, client.getBaseY() + 1, - Constants.SCENE_SIZE - 2, Constants.SCENE_SIZE - 2); - - for (int i = 0; i < linesToDisplay.length; i++) - { - GeneralPath lines = new GeneralPath(generateSafeArea()); - lines = Geometry.clipPath(lines, sceneRect); - lines = Geometry.splitIntoSegments(lines, 1); - lines = Geometry.transformPath(lines, this::transformWorldToLocal); - linesToDisplay[i] = lines; - } - } - - private void removeTimer() - { - infoBoxManager.removeInfoBox(currentTimer); - currentTimer = null; - } - - private void createTimer(Duration duration) - { - removeTimer(); - BufferedImage image = itemManager.getImage(ItemID.ENSOULED_DEMON_HEAD); - currentTimer = new AggressionTimer(duration, image, this, active && config.showTimer()); - infoBoxManager.addInfoBox(currentTimer); - } - - private void resetTimer() - { - createTimer(Duration.ofSeconds(AGGRESSIVE_TIME_SECONDS)); - } - - private static boolean isInWilderness(WorldPoint location) - { - return WILDERNESS_ABOVE_GROUND.distanceTo2D(location) == 0 || WILDERNESS_UNDERGROUND.distanceTo2D(location) == 0; - } - - private boolean isNpcMatch(NPC npc) - { - NPCComposition composition = npc.getTransformedComposition(); - if (composition == null) - { - return false; - } - - if (Strings.isNullOrEmpty(composition.getName())) - { - return false; - } - - // Most NPCs stop aggroing when the player has more than double - // its combat level. - int playerLvl = client.getLocalPlayer().getCombatLevel(); - int npcLvl = composition.getCombatLevel(); - String npcName = composition.getName().toLowerCase(); - if (npcLvl > 0 && playerLvl > npcLvl * 2 && !isInWilderness(npc.getWorldLocation())) - { - return false; - } - - for (String pattern : npcNamePatterns) - { - if (WildcardMatcher.matches(pattern, npcName)) - { - return true; - } - } - - return false; - } - - private void checkAreaNpcs(final NPC... npcs) - { - for (NPC npc : npcs) - { - if (npc == null) - { - continue; - } - - if (isNpcMatch(npc)) - { - active = true; - break; - } - } - - reevaluateActive(); - } - - private void recheckActive() - { - active = config.alwaysActive(); - checkAreaNpcs(client.getCachedNPCs()); - } - - @Subscribe - public void onNpcSpawned(NpcSpawned event) - { - if (config.alwaysActive()) - { - return; - } - - checkAreaNpcs(event.getNpc()); - } - - @Subscribe - public void onGameTick(GameTick event) - { - WorldPoint newLocation = client.getLocalPlayer().getWorldLocation(); - if (lastPlayerLocation != null) - { - if (safeCenters[1] == null && newLocation.distanceTo2D(lastPlayerLocation) > SAFE_AREA_RADIUS * 4) - { - safeCenters[0] = null; - safeCenters[1] = newLocation; - resetTimer(); - calculateLinesToDisplay(); - - // We don't know where the previous area was, so if the player e.g. - // entered a dungeon and then goes back out, he/she may enter the previous - // area which is unknown and would make the plugin inaccurate - previousUnknownCenter = lastPlayerLocation; - } - } - - if (safeCenters[0] == null && previousUnknownCenter != null && - previousUnknownCenter.distanceTo2D(newLocation) <= UNKNOWN_AREA_RADIUS) - { - // Player went back to their previous unknown area before the 2nd - // center point was found, which means we don't know where it is again. - safeCenters[1] = null; - removeTimer(); - calculateLinesToDisplay(); - } - - if (safeCenters[1] != null) - { - if (Arrays.stream(safeCenters).noneMatch( - x -> x != null && x.distanceTo2D(newLocation) <= SAFE_AREA_RADIUS)) - { - safeCenters[0] = safeCenters[1]; - safeCenters[1] = newLocation; - resetTimer(); - calculateLinesToDisplay(); - previousUnknownCenter = null; - } - } - - lastPlayerLocation = newLocation; - } - - @Subscribe - public void onConfigChanged(ConfigChanged event) - { - String key = event.getKey(); - switch (key) - { - case "npcUnaggroAlwaysActive": - recheckActive(); - break; - case "npcUnaggroShowTimer": - if (currentTimer != null) - { - currentTimer.setVisible(active && config.showTimer()); - } - break; - case "npcUnaggroCollisionDetection": - case "npcUnaggroShowAreaLines": - calculateLinesToDisplay(); - break; - case "npcUnaggroNames": - npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns()); - recheckActive(); - break; - } - } - - private void loadConfig() - { - safeCenters[0] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, WorldPoint.class); - safeCenters[1] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, WorldPoint.class); - lastPlayerLocation = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, WorldPoint.class); - - Duration timeLeft = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.class); - if (timeLeft != null && !timeLeft.isNegative()) - { - createTimer(timeLeft); - } - } - - private void resetConfig() - { - configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1); - configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2); - configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION); - configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION); - configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_NOT_WORKING_OVERLAY); - } - - private void saveConfig() - { - if (safeCenters[0] == null || safeCenters[1] == null || lastPlayerLocation == null || currentTimer == null) - { - resetConfig(); - } - else - { - configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, safeCenters[0]); - configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, safeCenters[1]); - configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, lastPlayerLocation); - configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.between(Instant.now(), currentTimer.getEndTime())); - } - } - - private void onLogin() - { - loadConfig(); - resetConfig(); - - WorldPoint newLocation = client.getLocalPlayer().getWorldLocation(); - assert newLocation != null; - - // If the player isn't at the location he/she logged out at, - // the safe unaggro area probably changed, and should be disposed. - if (lastPlayerLocation == null || newLocation.distanceTo(lastPlayerLocation) != 0) - { - safeCenters[0] = null; - safeCenters[1] = null; - lastPlayerLocation = newLocation; - } - } - - @Subscribe - public void onGameStateChanged(GameStateChanged event) - { - switch (event.getGameState()) - { - case LOGGED_IN: - if (loggingIn) - { - loggingIn = false; - onLogin(); - } - - recheckActive(); - break; - - case LOGGING_IN: - loggingIn = true; - break; - - case LOGIN_SCREEN: - if (lastPlayerLocation != null) - { - saveConfig(); - } - - safeCenters[0] = null; - safeCenters[1] = null; - lastPlayerLocation = null; - break; - } - } -} +/* + * 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.npcunaggroarea; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.inject.Provides; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.image.BufferedImage; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import javax.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.Constants; +import net.runelite.api.ItemID; +import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; +import net.runelite.api.Perspective; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldArea; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.NpcSpawned; +import net.runelite.api.geometry.Geometry; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.client.util.WildcardMatcher; + +@Slf4j +@PluginDescriptor( + name = "NPC Aggression Timer", + description = "Highlights the unaggressive area of NPCs nearby and timer until it becomes active", + tags = {"highlight", "lines", "unaggro", "aggro", "aggressive", "npcs", "area", "slayer"}, + enabledByDefault = false +) +public class NpcAggroAreaPlugin extends Plugin +{ + /* + How it works: The game remembers 2 tiles. When the player goes >10 steps + away from both tiles, the oldest one is moved to under the player and the + NPC aggression timer resets. + So to first figure out where the 2 tiles are, we wait until the player teleports + a long enough distance. At that point it's very likely that the player + moved out of the radius of both tiles, which resets one of them. The other + should reset shortly after as the player starts moving around. + */ + + private static final int SAFE_AREA_RADIUS = 10; + private static final int UNKNOWN_AREA_RADIUS = SAFE_AREA_RADIUS * 2; + private static final int AGGRESSIVE_TIME_SECONDS = 600; + private static final Splitter NAME_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults(); + private static final WorldArea WILDERNESS_ABOVE_GROUND = new WorldArea(2944, 3523, 448, 448, 0); + private static final WorldArea WILDERNESS_UNDERGROUND = new WorldArea(2944, 9918, 320, 442, 0); + + @Inject + private Client client; + + @Inject + private NpcAggroAreaConfig config; + + @Inject + private NpcAggroAreaOverlay overlay; + + @Inject + private NpcAggroAreaNotWorkingOverlay notWorkingOverlay; + + @Inject + private OverlayManager overlayManager; + + @Inject + private ItemManager itemManager; + + @Inject + private InfoBoxManager infoBoxManager; + + @Inject + private ConfigManager configManager; + + @Getter + private final WorldPoint[] safeCenters = new WorldPoint[2]; + + @Getter + private final GeneralPath[] linesToDisplay = new GeneralPath[Constants.MAX_Z]; + + @Getter + private boolean active; + + @Getter + private AggressionTimer currentTimer; + + private WorldPoint lastPlayerLocation; + private WorldPoint previousUnknownCenter; + private boolean loggingIn; + private List npcNamePatterns; + private boolean notWorkingOverlayShown = false; + + @Provides + NpcAggroAreaConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(NpcAggroAreaConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + if (config.showNotWorkingOverlay()) + { + overlayManager.add(notWorkingOverlay); + notWorkingOverlayShown = true; + } + + npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns()); + recheckActive(); + } + + @Override + protected void shutDown() throws Exception + { + removeTimer(); + overlayManager.remove(overlay); + if (notWorkingOverlayShown) + { + overlayManager.remove(notWorkingOverlay); + } + + Arrays.fill(safeCenters, null); + lastPlayerLocation = null; + currentTimer = null; + loggingIn = false; + npcNamePatterns = null; + active = false; + + Arrays.fill(linesToDisplay, null); + } + + private Area generateSafeArea() + { + final Area area = new Area(); + + for (WorldPoint wp : safeCenters) + { + if (wp == null) + { + continue; + } + + Polygon poly = new Polygon(); + poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() - SAFE_AREA_RADIUS); + poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() + SAFE_AREA_RADIUS + 1); + poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() + SAFE_AREA_RADIUS + 1); + poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() - SAFE_AREA_RADIUS); + area.add(new Area(poly)); + } + + return area; + } + + private void transformWorldToLocal(float[] coords) + { + final LocalPoint lp = LocalPoint.fromWorld(client, (int)coords[0], (int)coords[1]); + coords[0] = lp.getX() - Perspective.LOCAL_TILE_SIZE / 2f; + coords[1] = lp.getY() - Perspective.LOCAL_TILE_SIZE / 2f; + } + + private void reevaluateActive() + { + if (currentTimer != null) + { + currentTimer.setVisible(active && config.showTimer()); + } + + calculateLinesToDisplay(); + } + + private void calculateLinesToDisplay() + { + if (!active || !config.showAreaLines()) + { + Arrays.fill(linesToDisplay, null); + return; + } + + Rectangle sceneRect = new Rectangle( + client.getBaseX() + 1, client.getBaseY() + 1, + Constants.SCENE_SIZE - 2, Constants.SCENE_SIZE - 2); + + for (int i = 0; i < linesToDisplay.length; i++) + { + GeneralPath lines = new GeneralPath(generateSafeArea()); + lines = Geometry.clipPath(lines, sceneRect); + lines = Geometry.splitIntoSegments(lines, 1); + lines = Geometry.transformPath(lines, this::transformWorldToLocal); + linesToDisplay[i] = lines; + } + } + + private void removeTimer() + { + infoBoxManager.removeInfoBox(currentTimer); + currentTimer = null; + } + + private void createTimer(Duration duration) + { + removeTimer(); + BufferedImage image = itemManager.getImage(ItemID.ENSOULED_DEMON_HEAD); + currentTimer = new AggressionTimer(duration, image, this, active && config.showTimer()); + infoBoxManager.addInfoBox(currentTimer); + } + + private void resetTimer() + { + createTimer(Duration.ofSeconds(AGGRESSIVE_TIME_SECONDS)); + } + + private static boolean isInWilderness(WorldPoint location) + { + return WILDERNESS_ABOVE_GROUND.distanceTo2D(location) == 0 || WILDERNESS_UNDERGROUND.distanceTo2D(location) == 0; + } + + private boolean isNpcMatch(NPC npc) + { + NPCComposition composition = npc.getTransformedComposition(); + if (composition == null) + { + return false; + } + + if (Strings.isNullOrEmpty(composition.getName())) + { + return false; + } + + // Most NPCs stop aggroing when the player has more than double + // its combat level. + int playerLvl = client.getLocalPlayer().getCombatLevel(); + int npcLvl = composition.getCombatLevel(); + String npcName = composition.getName().toLowerCase(); + if (npcLvl > 0 && playerLvl > npcLvl * 2 && !isInWilderness(npc.getWorldLocation())) + { + return false; + } + + for (String pattern : npcNamePatterns) + { + if (WildcardMatcher.matches(pattern, npcName)) + { + return true; + } + } + + return false; + } + + private void checkAreaNpcs(final NPC... npcs) + { + for (NPC npc : npcs) + { + if (npc == null) + { + continue; + } + + if (isNpcMatch(npc)) + { + active = true; + break; + } + } + + reevaluateActive(); + } + + private void recheckActive() + { + active = config.alwaysActive(); + checkAreaNpcs(client.getCachedNPCs()); + } + + @Subscribe + public void onNpcSpawned(NpcSpawned event) + { + if (config.alwaysActive()) + { + return; + } + + checkAreaNpcs(event.getNpc()); + } + + @Subscribe + public void onGameTick(GameTick event) + { + WorldPoint newLocation = client.getLocalPlayer().getWorldLocation(); + if (lastPlayerLocation != null) + { + if (safeCenters[1] == null && newLocation.distanceTo2D(lastPlayerLocation) > SAFE_AREA_RADIUS * 4) + { + safeCenters[0] = null; + safeCenters[1] = newLocation; + resetTimer(); + calculateLinesToDisplay(); + + // We don't know where the previous area was, so if the player e.g. + // entered a dungeon and then goes back out, he/she may enter the previous + // area which is unknown and would make the plugin inaccurate + previousUnknownCenter = lastPlayerLocation; + } + } + + if (safeCenters[0] == null && previousUnknownCenter != null && + previousUnknownCenter.distanceTo2D(newLocation) <= UNKNOWN_AREA_RADIUS) + { + // Player went back to their previous unknown area before the 2nd + // center point was found, which means we don't know where it is again. + safeCenters[1] = null; + removeTimer(); + calculateLinesToDisplay(); + } + + if (safeCenters[1] != null) + { + if (Arrays.stream(safeCenters).noneMatch( + x -> x != null && x.distanceTo2D(newLocation) <= SAFE_AREA_RADIUS)) + { + safeCenters[0] = safeCenters[1]; + safeCenters[1] = newLocation; + resetTimer(); + calculateLinesToDisplay(); + previousUnknownCenter = null; + } + } + + lastPlayerLocation = newLocation; + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + String key = event.getKey(); + switch (key) + { + case "npcUnaggroAlwaysActive": + recheckActive(); + break; + case "npcUnaggroShowTimer": + if (currentTimer != null) + { + currentTimer.setVisible(active && config.showTimer()); + } + break; + case "npcUnaggroCollisionDetection": + case "npcUnaggroShowAreaLines": + calculateLinesToDisplay(); + break; + case "npcUnaggroNames": + npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns()); + recheckActive(); + break; + } + } + + private void loadConfig() + { + safeCenters[0] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, WorldPoint.class); + safeCenters[1] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, WorldPoint.class); + lastPlayerLocation = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, WorldPoint.class); + + Duration timeLeft = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.class); + if (timeLeft != null && !timeLeft.isNegative()) + { + createTimer(timeLeft); + } + } + + private void resetConfig() + { + configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1); + configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2); + configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION); + configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION); + configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_NOT_WORKING_OVERLAY); + } + + private void saveConfig() + { + if (safeCenters[0] == null || safeCenters[1] == null || lastPlayerLocation == null || currentTimer == null) + { + resetConfig(); + } + else + { + configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, safeCenters[0]); + configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, safeCenters[1]); + configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, lastPlayerLocation); + configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.between(Instant.now(), currentTimer.getEndTime())); + } + } + + private void onLogin() + { + loadConfig(); + resetConfig(); + + WorldPoint newLocation = client.getLocalPlayer().getWorldLocation(); + assert newLocation != null; + + // If the player isn't at the location he/she logged out at, + // the safe unaggro area probably changed, and should be disposed. + if (lastPlayerLocation == null || newLocation.distanceTo(lastPlayerLocation) != 0) + { + safeCenters[0] = null; + safeCenters[1] = null; + lastPlayerLocation = newLocation; + } + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + switch (event.getGameState()) + { + case LOGGED_IN: + if (loggingIn) + { + loggingIn = false; + onLogin(); + } + + recheckActive(); + break; + + case LOGGING_IN: + loggingIn = true; + break; + + case LOGIN_SCREEN: + if (lastPlayerLocation != null) + { + saveConfig(); + } + + safeCenters[0] = null; + safeCenters[1] = null; + lastPlayerLocation = null; + break; + } + } +}