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 71bc00f829..34a95e613a 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 @@ -285,7 +285,7 @@ public class ItemManager /** * Look up an item's price * - * @param itemID item id + * @param itemID item id * @param ignoreUntradeableMap should the price returned ignore the {@link UntradeableItemMapping} * @return item price */ @@ -350,6 +350,18 @@ public class ItemManager return (int) Math.max(1, getItemDefinition(itemID).getPrice() * HIGH_ALCHEMY_MULTIPLIER); } + public int getBrokenValue(int itemId) + { + PvPValueBrokenItem b = PvPValueBrokenItem.of(itemId); + + if (b != null) + { + return (int) (b.getValue() * (75.0f / 100.0f)); + } + + return 0; + } + /** * Look up an item's stats * diff --git a/runelite-client/src/main/java/net/runelite/client/game/PvPValueBrokenItem.java b/runelite-client/src/main/java/net/runelite/client/game/PvPValueBrokenItem.java new file mode 100644 index 0000000000..e84354f4fc --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/game/PvPValueBrokenItem.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018, TheStonedTurtle + * 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.game; + +import com.google.common.collect.ImmutableMap; +import javax.annotation.Nullable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.ItemID; + +/** + * Some non tradeable items are kept on death inside low level wilderness (1-20) but are turned into a broken variant. + *

+ * The non-broken variant will be shown inside the interface. + */ +@AllArgsConstructor +@Getter +public enum PvPValueBrokenItem +{ + // Capes + FIRE_CAPE(ItemID.FIRE_CAPE, 50000), + FIRE_MAX_CAPE(ItemID.FIRE_MAX_CAPE, 50000), + INFERNAL_CAPE(ItemID.INFERNAL_CAPE, 50000), + INFERNAL_MAX_CAPE(ItemID.INFERNAL_MAX_CAPE, 50000), + AVAS_ASSEMBLER(ItemID.AVAS_ASSEMBLER, 75000), + ASSEMBLER_MAX_CAPE(ItemID.ASSEMBLER_MAX_CAPE, 75000), + + // Defenders + BRONZE_DEFENDER(ItemID.BRONZE_DEFENDER, 1000), + IRON_DEFENDER(ItemID.IRON_DEFENDER, 2000), + STEEL_DEFENDER(ItemID.STEEL_DEFENDER, 2500), + BLACK_DEFENDER(ItemID.BLACK_DEFENDER, 5000), + MITHRIL_DEFENDER(ItemID.MITHRIL_DEFENDER, 15000), + ADAMANT_DEFENDER(ItemID.ADAMANT_DEFENDER, 25000), + RUNE_DEFENDER(ItemID.RUNE_DEFENDER, 35000), + DRAGON_DEFENDER(ItemID.DRAGON_DEFENDER, 40000), + AVERNIC_DEFENDER(ItemID.AVERNIC_DEFENDER, 1000000), + + // Void + VOID_MAGE_HELM(ItemID.VOID_MAGE_HELM, 40000), + VOID_RANGER_HELM(ItemID.VOID_RANGER_HELM, 40000), + VOID_MELEE_HELM(ItemID.VOID_MELEE_HELM, 40000), + VOID_KNIGHT_TOP(ItemID.VOID_KNIGHT_TOP, 45000), + VOID_KNIGHT_ROBE(ItemID.VOID_KNIGHT_ROBE, 45000), + VOID_KNIGHT_GLOVES(ItemID.VOID_KNIGHT_GLOVES, 30000), + ELITE_VOID_TOP(ItemID.ELITE_VOID_TOP, 50000), + ELITE_VOID_ROBE(ItemID.ELITE_VOID_ROBE, 50000), + + // Barb Assault + FIGHTER_HAT(ItemID.FIGHTER_HAT, 45000), + RANGER_HAT(ItemID.RANGER_HAT, 45000), + HEALER_HAT(ItemID.HEALER_HAT, 45000), + FIGHTER_TORSO(ItemID.FIGHTER_TORSO, 50000), + PENANCE_SKIRT(ItemID.PENANCE_SKIRT, 20000), + + // Castle Wars + SARADOMIN_HALO(ItemID.SARADOMIN_HALO, 25000), + ZAMORAK_HALO(ItemID.ZAMORAK_HALO, 25000), + GUTHIX_HALO(ItemID.GUTHIX_HALO, 25000), + DECORATIVE_MAGIC_HAT(ItemID.DECORATIVE_ARMOUR_11898, 5000), + DECORATIVE_MAGIC_ROBE_TOP(ItemID.DECORATIVE_ARMOUR_11896, 5000), + DECORATIVE_MAGIC_ROBE_LEGS(ItemID.DECORATIVE_ARMOUR_11897, 5000), + DECORATIVE_RANGE_TOP(ItemID.DECORATIVE_ARMOUR_11899, 5000), + DECORATIVE_RANGE_BOTTOM(ItemID.DECORATIVE_ARMOUR_11900, 5000), + DECORATIVE_RANGE_QUIVER(ItemID.DECORATIVE_ARMOUR_11901, 5000), + GOLD_DECORATIVE_HELM(ItemID.DECORATIVE_HELM_4511, 5000), + GOLD_DECORATIVE_BODY(ItemID.DECORATIVE_ARMOUR_4509, 5000), + GOLD_DECORATIVE_LEGS(ItemID.DECORATIVE_ARMOUR_4510, 5000), + GOLD_DECORATIVE_SKIRT(ItemID.DECORATIVE_ARMOUR_11895, 5000), + GOLD_DECORATIVE_SHIELD(ItemID.DECORATIVE_SHIELD_4512, 5000), + GOLD_DECORATIVE_SWORD(ItemID.DECORATIVE_SWORD_4508, 5000); + + private static final ImmutableMap idMap; + + static + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + + for (PvPValueBrokenItem items : values()) + { + builder.put(items.itemID, items); + } + + idMap = builder.build(); + } + + private final int itemID; + private final int value; + + @Nullable + public static PvPValueBrokenItem of(int itemId) + { + return idMap.get(itemId); + } + + public static boolean breaksOnDeath(int itemId) + { + return idMap.containsKey(itemId); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/AttackStyle.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/AttackStyle.java new file mode 100644 index 0000000000..14dd4b7be5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/AttackStyle.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019, ganom + * 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.playerscouter; + +import java.awt.Color; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.Prayer; + +@AllArgsConstructor +@Getter +public enum AttackStyle +{ + MAGE("Mage", Color.CYAN, Prayer.PROTECT_FROM_MAGIC), + RANGE("Range", Color.GREEN, Prayer.PROTECT_FROM_MISSILES), + MELEE("Melee", Color.RED, Prayer.PROTECT_FROM_MELEE), + UNKNOWN("Unknown", Color.WHITE, null); + + private String name; + private Color color; + private Prayer prayer; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/AttackerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/AttackerOverlay.java new file mode 100644 index 0000000000..12e055918c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/AttackerOverlay.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019, ganom + * 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.playerscouter; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.client.graphics.ModelOutlineRenderer; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; + +@Singleton +public class AttackerOverlay extends Overlay +{ + private static final Color TRANSPARENT = new Color(0, 0, 0, 0); + + private final PlayerScouter plugin; + private final ModelOutlineRenderer outlineRenderer; + + @Inject + public AttackerOverlay(final PlayerScouter plugin, final ModelOutlineRenderer outlineRenderer) + { + this.plugin = plugin; + this.outlineRenderer = outlineRenderer; + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!plugin.isOverlayEnabled() || plugin.getPlayerContainer().isEmpty()) + { + return null; + } + + plugin.getPlayerContainer().forEach(player -> + { + if (!player.isTarget()) + { + return; + } + + final AttackStyle attackStyle = player.getAttackStyle(); + + if (attackStyle.getPrayer() == null) + { + return; + } + + outlineRenderer.drawOutline(player.getPlayer(), 2, attackStyle.getColor(), TRANSPARENT); + }); + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/PlayerContainer.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/PlayerContainer.java new file mode 100644 index 0000000000..c98f24f465 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/PlayerContainer.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019, ganom + * 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.playerscouter; + +import java.util.LinkedHashMap; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import net.runelite.api.Player; +import net.runelite.api.Prayer; +import net.runelite.http.api.hiscore.Skill; + +/* +You may be asking, why in the fuck is there so much information +being gathered? The answer is, why not. Always plan for the future. +Better to have too much than to have too little. + */ +@Getter(AccessLevel.PACKAGE) +@Setter(AccessLevel.PACKAGE) +@ToString(exclude = "player") +class PlayerContainer +{ + private AttackStyle attackStyle; + private AttackStyle weakness; + private boolean attacking; + private boolean logging; + private boolean scouted; + private boolean target; + private double drainRate; + private double estimatedPrayer; + private int magicAttack; + private int magicDefence; + private int meleeAttack; + private int meleeDefence; + private int prayerBonus; + private int rangeAttack; + private int rangeDefence; + private int risk; + private int scoutTimer; + private int timer; + private int weapon; + private int wildyLevel; + private LinkedHashMap gear; + private LinkedHashMap riskedGear; + private Player player; + private Prayer overhead; + private Prayer predictedPrayer; + private Skill prayer; + private String estimatedPrayerString; + private String location; + private String name; + private String targetString; + + PlayerContainer(Player player, Skill prayer) + { + this.attacking = false; + this.attackStyle = AttackStyle.UNKNOWN; + this.drainRate = 0; + this.estimatedPrayer = 0; + this.estimatedPrayerString = ""; + this.gear = new LinkedHashMap<>(); + this.location = "N/A"; + this.logging = false; + this.magicAttack = 0; + this.magicDefence = 0; + this.meleeAttack = 0; + this.meleeDefence = 0; + this.name = player.getName(); + this.overhead = null; + this.player = player; + this.prayer = prayer; + this.prayerBonus = 0; + this.predictedPrayer = null; + this.rangeAttack = 0; + this.rangeDefence = 0; + this.risk = 0; + this.riskedGear = new LinkedHashMap<>(); + this.scouted = false; + this.scoutTimer = 500; + this.target = false; + this.targetString = ""; + this.timer = 0; + this.weakness = AttackStyle.UNKNOWN; + this.weapon = 0; + this.wildyLevel = 0; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/PlayerScouter.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/PlayerScouter.java new file mode 100644 index 0000000000..9488875557 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/PlayerScouter.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2019, ganom + * 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.playerscouter; + +import com.google.inject.Provides; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Player; +import net.runelite.api.Varbits; +import net.runelite.api.WorldType; +import net.runelite.api.coords.WorldArea; +import net.runelite.api.events.AnimationChanged; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.InteractingChanged; +import net.runelite.api.events.PlayerDespawned; +import net.runelite.api.events.PlayerSpawned; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.PluginType; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.PvPUtil; +import net.runelite.client.util.WildernessLocation; +import net.runelite.http.api.discord.DiscordClient; +import net.runelite.http.api.hiscore.HiscoreClient; +import net.runelite.http.api.hiscore.HiscoreSkill; +import net.runelite.http.api.hiscore.SingleHiscoreSkillResult; +import okhttp3.HttpUrl; + +@PluginDescriptor( + name = "Player Scouter", + description = "Scout players and output them to your discord channel!", + type = PluginType.PVP +) +@Slf4j +public class PlayerScouter extends Plugin +{ + private static final DiscordClient DISCORD_CLIENT = new DiscordClient(); + private static final String ICONBASEURL = "https://www.osrsbox.com/osrsbox-db/items-icons/"; // Add item id + ".png" + private static final HiscoreClient HISCORE_CLIENT = new HiscoreClient(); + private static final Map WILD_LOCS = getLocationMap(); + @Inject + private Client client; + @Inject + private ItemManager itemManager; + @Inject + private AttackerOverlay attackerOverlay; + @Inject + private OverlayManager overlayManager; + @Inject + private ClientThread clientThread; + @Inject + private PlayerScouterConfig config; + @Inject + private EventBus eventBus; + @Getter(AccessLevel.PACKAGE) + private Set playerContainer = new HashSet<>(); + @Getter(AccessLevel.PACKAGE) + private boolean overlayEnabled; + private boolean onlyWildy; + private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); + private Map blacklist = new HashMap<>(); + private int reset; + private HttpUrl url; + private int minimumRisk; + private int minimumValue; + private int timeout; + + private static Map getLocationMap() + { + Map hashMap = new HashMap<>(); + Arrays.stream(WildernessLocation.values()).forEach(wildernessLocation -> + hashMap.put(wildernessLocation.getWorldArea(), wildernessLocation.getName())); + return hashMap; + } + + @Provides + PlayerScouterConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(PlayerScouterConfig.class); + } + + @Override + protected void startUp() + { + overlayManager.add(attackerOverlay); + blacklist.clear(); + updateConfig(); + addSubscriptions(); + } + + @Override + protected void shutDown() + { + overlayManager.remove(attackerOverlay); + playerContainer.clear(); + blacklist.clear(); + eventBus.unregister(this); + } + + private void addSubscriptions() + { + eventBus.subscribe(ConfigChanged.class, this, this::onConfigChanged); + eventBus.subscribe(GameStateChanged.class, this, this::onGameStateChanged); + eventBus.subscribe(InteractingChanged.class, this, this::onInteractingChanged); + eventBus.subscribe(AnimationChanged.class, this, this::onAnimationChanged); + eventBus.subscribe(PlayerSpawned.class, this, this::onPlayerSpawned); + eventBus.subscribe(PlayerDespawned.class, this, this::onPlayerDespawned); + eventBus.subscribe(GameTick.class, this, this::onGameTick); + } + + private void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() == GameState.LOGGED_IN) + { + return; + } + + blacklist.clear(); + } + + private void onConfigChanged(ConfigChanged event) + { + if (!event.getGroup().equals("playerscouter")) + { + return; + } + updateConfig(); + } + + private void onInteractingChanged(InteractingChanged event) + { + if ((event.getSource() instanceof Player) && (event.getTarget() instanceof Player)) + { + final Player source = (Player) event.getSource(); + final Player target = (Player) event.getTarget(); + + if (source == client.getLocalPlayer()) + { + if (!PvPUtil.isAttackable(client, target)) + { + return; + } + + playerContainer.forEach(player -> + { + if (player.getPlayer() == target) + { + player.setTimer(16); + player.setTarget(true); + } + }); + } + else if (target == client.getLocalPlayer()) + { + if (!PvPUtil.isAttackable(client, source)) + { + return; + } + playerContainer.forEach(player -> + { + if (player.getPlayer() == source) + { + player.setTimer(16); + player.setTarget(true); + } + }); + } + } + } + + private void onAnimationChanged(AnimationChanged event) + { + final Actor actor = event.getActor(); + + if (actor.getInteracting() != client.getLocalPlayer()) + { + return; + } + + if (!(actor instanceof Player)) + { + return; + } + + if (PvPUtil.isAttackable(client, (Player) actor) && actor.getAnimation() != -1) + { + playerContainer.forEach(player -> + { + if (player.getPlayer() == actor) + { + player.setTimer(16); + player.setTarget(true); + player.setAttacking(true); + } + }); + } + } + + private void onPlayerSpawned(PlayerSpawned event) + { + final Player player = event.getPlayer(); + + if (player == client.getLocalPlayer()) + { + return; + } + + if (!blacklist.isEmpty() && blacklist.keySet().contains(player.getName())) + { + return; + } + + executorService.submit(() -> + { + SingleHiscoreSkillResult result; + + try + { + result = HISCORE_CLIENT.lookup(player.getName(), HiscoreSkill.PRAYER); + } + catch (IOException ex) + { + log.warn("Error fetching Hiscore data " + ex.getMessage()); + return; + } + + playerContainer.add(new PlayerContainer(player, result.getSkill())); + blacklist.put(player.getName(), client.getTickCount() + this.timeout); + }); + } + + private void onPlayerDespawned(PlayerDespawned event) + { + final Player player = event.getPlayer(); + + playerContainer.removeIf(p -> p.getPlayer() == player); + } + + private void onGameTick(GameTick event) + { + resetBlacklist(); + + if (!checkWildy() || playerContainer.isEmpty()) + { + return; + } + + playerContainer.forEach(player -> + { + Utils.reset(player); + Utils.update(player, itemManager, 6, WILD_LOCS); + + if (player.getRisk() > this.minimumRisk) + { + Utils.scoutPlayer(player, url, DISCORD_CLIENT, itemManager, client, this.minimumValue); + } + }); + } + + private void resetBlacklist() + { + if (blacklist.isEmpty()) + { + return; + } + + blacklist.forEach((k, v) -> + { + if (v == client.getTickCount()) + { + blacklist.remove(k, v); + } + }); + } + + private void updateConfig() + { + this.url = HttpUrl.parse(config.webhook()); + this.minimumRisk = config.minimumRisk(); + this.minimumValue = config.minimumValue(); + this.overlayEnabled = config.overlayEnabled(); + this.timeout = config.timeout(); + this.onlyWildy = config.onlyWildy(); + } + + private boolean checkWildy() + { + if (!this.onlyWildy) + { + return true; + } + return client.getVar(Varbits.IN_WILDERNESS) == 1 || WorldType.isPvpWorld(client.getWorldType()); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/PlayerScouterConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/PlayerScouterConfig.java new file mode 100644 index 0000000000..7c7b9faffb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/PlayerScouterConfig.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019, ganom + * 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.playerscouter; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("playerscouter") +public interface PlayerScouterConfig extends Config +{ + @ConfigItem( + keyName = "webhook", + name = "Webhook Url", + description = "Input the url for your webhook.", + position = 0 + ) + default String webhook() + { + return ""; + } + + @ConfigItem( + keyName = "overlayEnabled", + name = "Attacker Overlay", + description = "This will highlight your attacker.", + position = 1 + ) + default boolean overlayEnabled() + { + return true; + } + + @ConfigItem( + keyName = "onlyWildy", + name = "Only Scout in Wildy", + description = "This will only scout players in the wilderness.", + position = 1 + ) + default boolean onlyWildy() + { + return true; + } + + @ConfigItem( + keyName = "minimumRisk", + name = "Minimum Risk", + description = "Minimum risk for the player to be scouted.", + position = 2 + ) + default int minimumRisk() + { + return 1; + } + + @ConfigItem( + keyName = "minimumValue", + name = "Minimum Value", + description = "Minimum value for the item to be posted on discord.", + position = 3 + ) + default int minimumValue() + { + return 1000; + } + + @ConfigItem( + keyName = "timeout", + name = "Timeout", + description = "Minimum amount of ticks before the player can be scouted again. (1 tick = 600ms)", + position = 4 + ) + default int timeout() + { + return 500; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/Utils.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/Utils.java new file mode 100644 index 0000000000..d63f98bf64 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerscouter/Utils.java @@ -0,0 +1,698 @@ +/* + * Copyright (c) 2019, ganom + * 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.playerscouter; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.Client; +import net.runelite.api.ItemDefinition; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.api.Prayer; +import net.runelite.api.coords.WorldArea; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.kit.KitType; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.PvPValueBrokenItem; +import net.runelite.client.util.StackFormatter; +import net.runelite.http.api.discord.DiscordClient; +import net.runelite.http.api.discord.DiscordEmbed; +import net.runelite.http.api.discord.DiscordMessage; +import net.runelite.http.api.discord.embed.AuthorEmbed; +import net.runelite.http.api.discord.embed.FieldEmbed; +import net.runelite.http.api.discord.embed.FooterEmbed; +import net.runelite.http.api.discord.embed.ThumbnailEmbed; +import net.runelite.http.api.item.ItemEquipmentStats; +import net.runelite.http.api.item.ItemStats; +import okhttp3.HttpUrl; + +/* +This class is pretty useless, its not called anywhere else, +but I mainly have it so its pretty obvious whats happening +in the main class. Pretty much stuffing the ugly in here. + */ + +@Slf4j +class Utils +{ + private static final double INCREASE = 0.60; + private static final String ICONBASEURL = "https://www.osrsbox.com/osrsbox-db/items-icons/"; // Add item id + ".png" + private static final SimpleDateFormat SDF = new SimpleDateFormat("MMM dd h:mm a z"); + + static void reset(PlayerContainer player) + { + player.setMeleeAttack(0); + player.setMagicAttack(0); + player.setRangeAttack(0); + player.setMeleeDefence(0); + player.setMagicDefence(0); + player.setRangeDefence(0); + player.setRisk(0); + player.setPrayerBonus(0); + player.setDrainRate(0); + player.setOverhead(Utils.iconToPrayer(player.getPlayer())); + player.setAttackStyle(AttackStyle.UNKNOWN); + } + + static void update(PlayerContainer player, ItemManager itemManager, int restores, Map map) + { + updatePlayerGear(player, itemManager); + updateAttackStyle(player); + updateWeakness(player); + player.setPredictedPrayer(predictOffensivePrayer(player.getPrayer().getLevel(), player.getAttackStyle())); + updatePrayerDrainRate(player); + updateEstimatedPrayer(player, restores); + player.setLocation(location(player, map)); + player.setWildyLevel(getWildernessLevelFrom(player.getPlayer().getWorldLocation())); + player.setTargetString(targetStringBuilder(player)); + if (player.isScouted()) + { + player.setScoutTimer(player.getScoutTimer() - 1); + + if (player.getScoutTimer() <= 0) + { + player.setScouted(false); + player.setScoutTimer(500); + } + } + log.debug(player.toString()); + } + + //pvputil is private, so atm i can't grab from it. + private static int getWildernessLevelFrom(WorldPoint point) + { + int y = point.getY(); + + int underLevel = ((y - 9920) / 8) + 1; + int upperLevel = ((y - 3520) / 8) + 1; + + return y > 6400 ? underLevel : upperLevel; + } + + private static void updatePlayerGear(PlayerContainer player, ItemManager itemManager) + { + Map prices = new HashMap<>(); + + if (player.getPlayer().getPlayerAppearance() != null) + { + for (KitType kitType : KitType.values()) + { + if (kitType.equals(KitType.RING) || kitType.equals(KitType.AMMUNITION)) + { + continue; + } + + final int id = player.getPlayer().getPlayerAppearance().getEquipmentId(kitType); + + if (id == -1) + { + continue; + } + + if (kitType.equals(KitType.WEAPON)) + { + player.setWeapon(id); + } + + final ItemStats item = itemManager.getItemStats(id, false); + final ItemDefinition itemDefinition = itemManager.getItemDefinition(id); + + if (item == null) + { + log.debug("Item is null: {}", id); + continue; + } + + if (itemDefinition == null) + { + log.debug("Item Def is null: {}", id); + continue; + } + + if (PvPValueBrokenItem.breaksOnDeath(id)) + { + prices.put(id, itemManager.getBrokenValue(id)); + log.debug("Item has a broken value: Id {}, Value {}", id, itemManager.getBrokenValue(id)); + } + + if (!itemDefinition.isTradeable() && !PvPValueBrokenItem.breaksOnDeath(id)) + { + prices.put(id, itemDefinition.getPrice()); + } + else if (itemDefinition.isTradeable()) + { + prices.put(id, itemManager.getItemPrice(id, false)); + } + + ItemEquipmentStats stats = item.getEquipment(); + + if (stats == null) + { + log.debug("Stats are null: {}", item); + continue; + } + + player.setMeleeAttack(player.getMeleeAttack() + ((stats.getAcrush() + stats.getAslash() + stats.getAstab()) / 3)); + player.setMagicAttack(player.getMagicAttack() + stats.getAmagic()); + player.setRangeAttack(player.getRangeAttack() + stats.getArange()); + player.setMeleeDefence(player.getMeleeDefence() + ((stats.getDcrush() + stats.getDslash() + stats.getDstab()) / 3)); + player.setMagicDefence(player.getMagicDefence() + stats.getDmagic()); + player.setRangeDefence(player.getRangeDefence() + stats.getDrange()); + player.setPrayerBonus(player.getPrayerBonus() + stats.getPrayer()); + } + updateGear(player, prices); + } + } + + static void updateTarget(PlayerContainer player) + { + if (player.isTarget()) + { + if (player.getTimer() > 0) + { + player.setTimer(player.getTimer() - 1); + } + else if (player.getTimer() == 0) + { + player.setTarget(false); + } + } + } + + static void updateDefence(PlayerContainer player) + { + if (player.getOverhead() != null) + { + //yeah i know this is a shit way to do it :shrug: + switch (player.getOverhead()) + { + case PROTECT_FROM_MELEE: + player.setMeleeDefence((int) (player.getMeleeDefence() / INCREASE)); + log.debug("Melee Overhead, Defence Increased"); + break; + case PROTECT_FROM_MAGIC: + player.setMagicDefence((int) (player.getMagicDefence() / INCREASE)); + log.debug("Magic Overhead, Defence Increased"); + break; + case PROTECT_FROM_MISSILES: + player.setRangeDefence((int) (player.getRangeDefence() / INCREASE)); + log.debug("Range Overhead, Defence Increased"); + break; + } + } + } + + private static void updateGear(PlayerContainer player, Map prices) + { + player.setGear(prices.entrySet() + .stream() + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new))); + + player.setRiskedGear(prices.entrySet() + .stream() + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new))); + + if (player.getPlayer().getSkullIcon() == null) + { + removeEntries(player.getRiskedGear(), player.getPrayer().getLevel() <= 25 ? 3 : 4); + } + else + { + removeEntries(player.getRiskedGear(), player.getPrayer().getLevel() <= 25 ? 0 : 1); + } + + player.getRiskedGear().values().forEach(price -> player.setRisk(player.getRisk() + price)); + prices.clear(); + } + + private static void updateAttackStyle(PlayerContainer player) + { + if (player.getMagicAttack() >= player.getRangeAttack() && player.getMagicAttack() >= player.getMeleeAttack()) + { + player.setAttackStyle(AttackStyle.MAGE); + } + else if (player.getRangeAttack() >= player.getMagicAttack() && player.getRangeAttack() >= player.getMeleeAttack()) + { + player.setAttackStyle(AttackStyle.RANGE); + } + else if (player.getMeleeAttack() >= player.getMagicAttack() && player.getMeleeAttack() >= player.getRangeAttack()) + { + player.setAttackStyle(AttackStyle.MELEE); + } + } + + private static void updateWeakness(PlayerContainer player) + { + if (player.getMagicDefence() <= player.getRangeDefence() && player.getMagicDefence() <= player.getMeleeDefence()) + { + player.setWeakness(AttackStyle.MAGE); + } + else if (player.getRangeDefence() <= player.getMagicDefence() && player.getRangeDefence() <= player.getMeleeDefence()) + { + player.setWeakness(AttackStyle.RANGE); + } + else if (player.getMeleeAttack() <= player.getRangeDefence() && player.getMeleeAttack() <= player.getMagicDefence()) + { + player.setWeakness(AttackStyle.MELEE); + } + } + + private static void updateEstimatedPrayer(PlayerContainer player, int restores) + { + player.setEstimatedPrayerString(getEstimatedPrayerRemaining(player, restores)); + + if (player.getOverhead() == null) + { + return; + } + + if (player.getEstimatedPrayer() >= 0) + { + player.setEstimatedPrayer(player.getEstimatedPrayer() - player.getDrainRate()); + } + } + + private static void updatePrayerDrainRate(PlayerContainer player) + { + double drainRate = 0.0; + + if (player.getOverhead() != null) + { + drainRate += player.getOverhead().getDrainRate(); + } + if (player.getPredictedPrayer() != null) + { + drainRate += player.getPredictedPrayer().getDrainRate(); + } + if (player.getPrayer().getLevel() >= 25) + { + drainRate += Prayer.PROTECT_ITEM.getDrainRate(); + } + drainRate = (((drainRate / 100)) / (1.0 + (player.getPrayerBonus() / 30.0))); + + player.setDrainRate(drainRate); + } + + private static String getEstimatedPrayerRemaining(PlayerContainer player, int restores) + { + final double drainRate = player.getDrainRate(); + + if (drainRate == 0) + { + return "N/A"; + } + + log.debug("Drain Rate: " + drainRate); + + final int prayerLevel = player.getPrayer().getLevel(); + final int restoreValue = (int) (4 * (prayerLevel * 0.25) + 8); + final double estimatedTotalPrayer = prayerLevel + (restoreValue * restores); + final double estimatedPrayer = player.getEstimatedPrayer(); + final int restoreValueLeft = (int) Math.round(estimatedPrayer - player.getPrayer().getLevel()); + + if (player.getEstimatedPrayer() == 0) + { + player.setEstimatedPrayer(estimatedTotalPrayer); + } + + if (restoreValueLeft > 0) + { + return player.getPrayer().getLevel() + "(" + restoreValueLeft + ")"; + } + + return Integer.toString((int) Math.round(estimatedPrayer)); + } + + private static void removeEntries(LinkedHashMap map, int quantity) + { + if (map.size() < quantity) + { + log.debug("Size is lower than removal quantity."); + } + for (int i = 0; i < quantity; i++) + { + if (!map.entrySet().iterator().hasNext()) + { + log.debug("Attempted to remove entries, but there was not enough to remove."); + return; + } + log.debug("Entry Removed: " + map.entrySet().iterator().next()); + map.entrySet().remove(map.entrySet().iterator().next()); + } + } + + private static Map.Entry getEntry(LinkedHashMap map) + { + if (!map.isEmpty()) + { + Iterator> entry = map.entrySet().iterator(); + + for (int i = 0; i < 1; i++) + { + entry.next(); + } + + return entry.next(); + } + return null; + } + + private static Prayer iconToPrayer(Player player) + { + if (player.getOverheadIcon() != null) + { + switch (player.getOverheadIcon()) + { + case RANGED: + return Prayer.PROTECT_FROM_MISSILES; + case MAGIC: + return Prayer.PROTECT_FROM_MAGIC; + case MELEE: + return Prayer.PROTECT_FROM_MELEE; + case SMITE: + return Prayer.SMITE; + case REDEMPTION: + return Prayer.REDEMPTION; + case RETRIBUTION: + return Prayer.RETRIBUTION; + default: + return null; + } + } + return null; + } + + private static Prayer predictOffensivePrayer(int prayerLevel, AttackStyle attackStyle) + { + switch (attackStyle) + { + case MELEE: + if (prayerLevel <= 12 && prayerLevel >= 1) + { + return Prayer.BURST_OF_STRENGTH; + } + else if (prayerLevel <= 30 && prayerLevel >= 13) + { + return Prayer.SUPERHUMAN_STRENGTH; + } + else if (prayerLevel <= 59 && prayerLevel >= 31) + { + return Prayer.ULTIMATE_STRENGTH; + } + else if (prayerLevel <= 69 && prayerLevel >= 60) + { + return Prayer.CHIVALRY; + } + else if (prayerLevel >= 70) + { + return Prayer.PIETY; + } + case RANGE: + if (prayerLevel <= 8 && prayerLevel >= 1) + { + return Prayer.SHARP_EYE; + } + else if (prayerLevel <= 43 && prayerLevel >= 26) + { + return Prayer.HAWK_EYE; + } + else if (prayerLevel <= 73 && prayerLevel >= 44) + { + return Prayer.EAGLE_EYE; + } + else if (prayerLevel >= 74) + { + return Prayer.RIGOUR; + } + case MAGE: + if (prayerLevel <= 26 && prayerLevel >= 9) + { + return Prayer.MYSTIC_WILL; + } + else if (prayerLevel <= 44 && prayerLevel >= 27) + { + return Prayer.MYSTIC_LORE; + } + else if (prayerLevel <= 76 && prayerLevel >= 45) + { + return Prayer.MYSTIC_MIGHT; + } + else if (prayerLevel >= 77) + { + return Prayer.AUGURY; + } + default: + return Prayer.PROTECT_ITEM; + } + } + + private static String targetStringBuilder(PlayerContainer player) + { + if (player.getPlayer().getInteracting() != null) + { + Actor actor = player.getPlayer().getInteracting(); + if (actor instanceof Player) + { + return "(Player) " + actor.getName(); + } + else if (actor instanceof NPC) + { + return "(NPC) " + actor.getName(); + } + } + return "No Target Detected"; + } + + static void scoutPlayer(PlayerContainer player, HttpUrl url, DiscordClient discordClient, ItemManager itemManager, Client client, int minimumValue) + { + if (player.isScouted()) + { + return; + } + + List fieldList = new ArrayList<>(); + //green + String color = "8388352"; + + if (player.getRisk() < 1000000 && player.getRisk() > 150000) + { + //blue + color = "32767"; + } + else if (player.getRisk() > 1000000) + { + //orange + color = "16744448"; + } + + ThumbnailEmbed image = ThumbnailEmbed.builder() + .url("https://oldschool.runescape.wiki/images/a/a1/Skull_(status)_icon.png") + .height(50) + .width(50) + .build(); + + if (player.getPlayer().getSkullIcon() == null) + { + image = ThumbnailEmbed.builder() + .url(ICONBASEURL + player.getWeapon() + ".png") + .height(100) + .width(100) + .build(); + } + + fieldList.add(FieldEmbed.builder() + .name("Risk") + .value(StackFormatter.quantityToRSDecimalStack(player.getRisk())) + .inline(true) + .build()); + + fieldList.add(FieldEmbed.builder() + .name("World") + .value(Integer.toString(client.getWorld())) + .inline(true) + .build()); + + fieldList.add(FieldEmbed.builder() + .name("Combat Level") + .value(Integer.toString(player.getPlayer().getCombatLevel())) + .inline(true) + .build()); + + fieldList.add(FieldEmbed.builder() + .name("Wildy Level") + .value(Integer.toString(player.getWildyLevel())) + .inline(true) + .build()); + + fieldList.add(FieldEmbed.builder() + .name("Location") + .value(player.getLocation()) + .inline(true) + .build()); + + fieldList.add(FieldEmbed.builder() + .name("Target") + .value(player.getTargetString()) + .inline(true) + .build()); + + fieldList.add(FieldEmbed.builder() + .name("Risked Items Sorted by Value") + .value("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + .build()); + + final int[] items = {0}; + + player.getRiskedGear().forEach((gear, value) -> + { + if (value <= 0 || value <= minimumValue) + { + items[0]++; + return; + } + + ItemStats item = itemManager.getItemStats(gear, false); + + if (item == null) + { + log.error("Item is Null: {}", gear); + return; + } + + fieldList.add(FieldEmbed.builder() + .name(item.getName()) + .value("Value: " + StackFormatter.quantityToRSDecimalStack(value)) + .inline(true) + .build()); + }); + + if (items[0] > 0) + { + fieldList.add(FieldEmbed.builder() + .name("Items below value: " + minimumValue) + .value(Integer.toString(items[0])) + .inline(true) + .build()); + } + + message(player.getPlayer().getName(), " ", ICONBASEURL + Objects.requireNonNull(getEntry(player.getGear())).getKey() + ".png", image, fieldList, url, discordClient, color); + player.setScouted(true); + fieldList.clear(); + } + + private static void message(String name, String description, String iconUrl, ThumbnailEmbed thumbnail, List fields, HttpUrl url, DiscordClient discordClient, String color) + { + log.debug("Message Contents: {}, {}, {}, {}, {}", name, description, thumbnail, Arrays.toString(fields.toArray()), url); + log.debug("Fields: {}", fields); + + if (name.isEmpty() || fields.isEmpty()) + { + log.error("Discord message will fail with a missing name/description/field"); + return; + } + + final Date currentTime = new Date(System.currentTimeMillis()); + + DiscordEmbed discordEmbed = DiscordEmbed.builder() + .author(AuthorEmbed.builder() + .icon_url(iconUrl) // Icon of npc / player + .name(name) + .build()) + .thumbnail(thumbnail) + .description(description) + .fields(fields) + .footer(FooterEmbed.builder() + .icon_url("https://raw.githubusercontent.com/runelite/runelite/master/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman.png") + .text("Gabon Scouter | Time: " + SDF.format(currentTime)) + .build()) + .color(color) + .build(); + + DiscordMessage discordMessage = new DiscordMessage("Gabon Scouter", "", "https://i.imgur.com/2A6dr7q.png"); + discordMessage.getEmbeds().add(discordEmbed); + discordClient.message(url, discordMessage); + fields.clear(); + } + + private static String location(PlayerContainer player, Map map) + { + final WorldPoint wl = player.getPlayer().getWorldLocation(); + int dist = 10000; + String s = ""; + WorldArea closestArea = null; + for (Map.Entry entry : map.entrySet()) + { + WorldArea worldArea = entry.getKey(); + + if (worldArea.toWorldPointList().contains(wl)) + { + s = entry.getValue(); + return s; + } + int distTo = worldArea.distanceTo(wl); + if (distTo < dist) + { + dist = distTo; + closestArea = worldArea; + } + } + if (wl.getY() > (Objects.requireNonNull(closestArea).toWorldPoint().getY() + closestArea.getHeight())) + { + s = s + "N"; + } + if (wl.getY() < closestArea.toWorldPoint().getY()) + { + s = s + "S"; + } + if (wl.getX() < closestArea.toWorldPoint().getX()) + { + s = s + "W"; + } + if (wl.getX() > (closestArea.toWorldPoint().getX() + closestArea.getWidth())) + { + s = s + "E"; + } + s = s + " of "; + s = s + map.get(closestArea); + if (s.startsWith(" of ")) + { + s = s.substring(3); + } + return s; + } +} \ No newline at end of file