playerscouter: adds a discord-webhook based Player Scouter (#1020)

* Add Player Scouter and all of its goodies.

* Unsubscribe from eventbus on shutdown.

* Add broken item value mapping, and implement to scouter.
This commit is contained in:
Ganom
2019-07-18 18:11:55 -04:00
committed by Kyleeld
parent 7d4d42f026
commit e8b4237893
8 changed files with 1496 additions and 1 deletions

View File

@@ -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
*

View File

@@ -0,0 +1,122 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/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.
* <p>
* 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<Integer, PvPValueBrokenItem> idMap;
static
{
ImmutableMap.Builder<Integer, PvPValueBrokenItem> 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);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2019, ganom <https://github.com/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;
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 2019, ganom <https://github.com/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;
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) 2019, ganom <https://github.com/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<Integer, Integer> gear;
private LinkedHashMap<Integer, Integer> 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;
}
}

View File

@@ -0,0 +1,334 @@
/*
* Copyright (c) 2019, ganom <https://github.com/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<WorldArea, String> 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> playerContainer = new HashSet<>();
@Getter(AccessLevel.PACKAGE)
private boolean overlayEnabled;
private boolean onlyWildy;
private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
private Map<String, Integer> blacklist = new HashMap<>();
private int reset;
private HttpUrl url;
private int minimumRisk;
private int minimumValue;
private int timeout;
private static Map<WorldArea, String> getLocationMap()
{
Map<WorldArea, String> 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());
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2019, ganom <https://github.com/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;
}
}

View File

@@ -0,0 +1,698 @@
/*
* Copyright (c) 2019, ganom <https://github.com/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<WorldArea, String> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> map)
{
if (!map.isEmpty())
{
Iterator<Map.Entry<Integer, Integer>> 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<FieldEmbed> 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<FieldEmbed> 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<WorldArea, String> map)
{
final WorldPoint wl = player.getPlayer().getWorldLocation();
int dist = 10000;
String s = "";
WorldArea closestArea = null;
for (Map.Entry<WorldArea, String> 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;
}
}