diff --git a/runelite-api/src/main/java/net/runelite/api/events/PlayerCompositionChanged.java b/runelite-api/src/main/java/net/runelite/api/events/PlayerCompositionChanged.java new file mode 100644 index 0000000000..cc21ba71f3 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/PlayerCompositionChanged.java @@ -0,0 +1,13 @@ +package net.runelite.api.events; + +import lombok.Value; +import net.runelite.api.Player; + +/** + * This will fire whenever the {@link net.runelite.api.PlayerComposition} hash changes. + */ +@Value +public class PlayerCompositionChanged +{ + Player player; +} \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index 34ef03e1bb..3a50ba0862 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -493,6 +493,9 @@ public enum WidgetInfo PVP_SKULL_CONTAINER(WidgetID.PVP_GROUP_ID, WidgetID.Pvp.SKULL_CONTAINER), PVP_WORLD_SAFE_ZONE(WidgetID.PVP_GROUP_ID, WidgetID.Pvp.SAFE_ZONE), + + PVP_FOG_OVERLAY(WidgetID.PVP_GROUP_ID, WidgetID.Pvp.FOG_OVERLAY), + PVP_CONTAINER(WidgetID.PVP_GROUP_ID, WidgetID.Pvp.PVP_WIDGET_CONTAINER), PVP_WILDERNESS_LEVEL(WidgetID.PVP_GROUP_ID, WidgetID.Pvp.WILDERNESS_LEVEL), PVP_BOUNTY_HUNTER_INFO(WidgetID.PVP_GROUP_ID, WidgetID.Pvp.BOUNTY_HUNTER_INFO), diff --git a/runelite-client/src/main/java/com/openosrs/client/events/AttackStyleChanged.java b/runelite-client/src/main/java/com/openosrs/client/events/AttackStyleChanged.java new file mode 100644 index 0000000000..9d4d898fde --- /dev/null +++ b/runelite-client/src/main/java/com/openosrs/client/events/AttackStyleChanged.java @@ -0,0 +1,33 @@ +package com.openosrs.client.events; + +import lombok.Value; +import net.runelite.api.Player; +import com.openosrs.client.game.AttackStyle; + +/** + * This will fire when {@link com.openosrs.client.game.PlayerManager} detects + * a change in the player appearance that resulted in the shifting of an attack style. + * For example, ranged str went to 0, but melee str went to 108. + */ +@Value +public class AttackStyleChanged +{ + /** + * The player that changed styles. + */ + Player player; + + /** + * Can be Unknown(nullable) + * + * @see com.openosrs.client.game.AttackStyle + */ + AttackStyle oldStyle; + + /** + * Can be Unknown(nullable) + * + * @see com.openosrs.client.game.AttackStyle + */ + AttackStyle newStyle; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/com/openosrs/client/game/AttackStyle.java b/runelite-client/src/main/java/com/openosrs/client/game/AttackStyle.java new file mode 100644 index 0000000000..a68e7a2edf --- /dev/null +++ b/runelite-client/src/main/java/com/openosrs/client/game/AttackStyle.java @@ -0,0 +1,47 @@ +/* + * 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 com.openosrs.client.game; + +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/com/openosrs/client/game/CombatStats.java b/runelite-client/src/main/java/com/openosrs/client/game/CombatStats.java new file mode 100644 index 0000000000..d6fa080c40 --- /dev/null +++ b/runelite-client/src/main/java/com/openosrs/client/game/CombatStats.java @@ -0,0 +1,24 @@ +package com.openosrs.client.game; + +import lombok.Value; + +@Value +public class CombatStats +{ + int magicAttack; + int magicDefence; + int magicStr; + int meleeAtkCrush; + int meleeAtkSlash; + int meleeAtkStab; + int meleeAttack; + int meleeDefCrush; + int meleeDefence; + int meleeDefSlash; + int meleeDefStab; + int meleeStr; + int rangeAttack; + int rangeDefence; + int rangeStr; + int speed; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/com/openosrs/client/game/ItemReclaimCost.java b/runelite-client/src/main/java/com/openosrs/client/game/ItemReclaimCost.java new file mode 100644 index 0000000000..02c25bdc76 --- /dev/null +++ b/runelite-client/src/main/java/com/openosrs/client/game/ItemReclaimCost.java @@ -0,0 +1,133 @@ +/* + * 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 com.openosrs.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 ItemReclaimCost +{ + // Capes + FIRE_CAPE(ItemID.FIRE_CAPE, 50000), + FIRE_MAX_CAPE(ItemID.FIRE_MAX_CAPE, 99000), + INFERNAL_CAPE(ItemID.INFERNAL_CAPE, 50000), + INFERNAL_MAX_CAPE(ItemID.INFERNAL_MAX_CAPE, 99000), + AVAS_ASSEMBLER(ItemID.AVAS_ASSEMBLER, 75000), + ASSEMBLER_MAX_CAPE(ItemID.ASSEMBLER_MAX_CAPE, 99000), + IMBUED_GUTHIX_CAPE(ItemID.IMBUED_GUTHIX_CAPE, 75000), + IMBUED_GUTHIX_MAX_CAPE(ItemID.GUTHIX_MAX_CAPE, 99000), + IMBUED_SARADOMIN_CAPE(ItemID.IMBUED_SARADOMIN_CAPE, 75000), + IMBUED_SARADOMIN_MAX_CAPE(ItemID.SARADOMIN_MAX_CAPE, 99000), + IMBUED_ZAMORAK_CAPE(ItemID.IMBUED_ZAMORAK_CAPE, 75000), + IMBUED_ZAMORAK_MAX_CAPE(ItemID.ZAMORAK_MAX_CAPE, 99000), + + // 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), + + // Granite Maul + GRANITE_MAUL(ItemID.GRANITE_MAUL_24225, 375000), + GRANITE_MAUL_OR(ItemID.GRANITE_MAUL_24227, 375000); + + private static final ImmutableMap idMap; + + static + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + + for (ItemReclaimCost items : values()) + { + builder.put(items.itemID, items); + } + + idMap = builder.build(); + } + + private final int itemID; + private final int value; + + @Nullable + public static ItemReclaimCost 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/com/openosrs/client/game/PlayerContainer.java b/runelite-client/src/main/java/com/openosrs/client/game/PlayerContainer.java new file mode 100644 index 0000000000..ecfb0ce55a --- /dev/null +++ b/runelite-client/src/main/java/com/openosrs/client/game/PlayerContainer.java @@ -0,0 +1,104 @@ +/* + * 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 com.openosrs.client.game; + +import java.util.LinkedHashMap; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import net.runelite.api.Player; +import net.runelite.http.api.hiscore.HiscoreResult; + +@Getter +@Setter +@ToString(exclude = "player") +public class PlayerContainer +{ + private AttackStyle attackStyle; + private AttackStyle weakness; + private HiscoreResult skills; + private LinkedHashMap gear; + private LinkedHashMap riskedGear; + private MeleeStyle meleeStyle; + private Player player; + private String location; + private String name; + private String targetString; + private CombatStats combatStats; + private boolean httpRetry; + private boolean hiscoresRequested; + private boolean scouted; + private boolean attacking; + private boolean friend; + private boolean clan; + private int hpLevel; + private int potionBoost; + private int prayerLevel; + private int risk; + private int scoutTimer; + private int shield; + private int timer; + private int weapon; + private int wildyLevel; + + PlayerContainer(Player player) + { + this.attackStyle = AttackStyle.UNKNOWN; + this.gear = new LinkedHashMap<>(); + this.hpLevel = 0; + this.location = "N/A"; + this.meleeStyle = MeleeStyle.STAB; + this.name = player.getName(); + this.player = player; + this.riskedGear = new LinkedHashMap<>(); + this.scoutTimer = 500; + this.scouted = false; + this.skills = null; + this.targetString = ""; + this.weakness = AttackStyle.UNKNOWN; + } + + void reset() + { + setMeleeStyle(MeleeStyle.NONE); + if (getTimer() > 0) + { + setTimer(getTimer() - 1); + if (getTimer() == 0) + { + setAttacking(false); + } + } + } + + @Getter(AccessLevel.PACKAGE) + enum MeleeStyle + { + CRUSH, + SLASH, + STAB, + NONE + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/com/openosrs/client/game/PlayerManager.java b/runelite-client/src/main/java/com/openosrs/client/game/PlayerManager.java new file mode 100644 index 0000000000..54efa1b89a --- /dev/null +++ b/runelite-client/src/main/java/com/openosrs/client/game/PlayerManager.java @@ -0,0 +1,551 @@ +package com.openosrs.client.game; + +import com.openosrs.client.events.AttackStyleChanged; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.Client; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemID; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.api.WorldType; +import net.runelite.api.events.AnimationChanged; +import net.runelite.api.events.PlayerCompositionChanged; +import net.runelite.api.events.PlayerDespawned; +import net.runelite.api.kit.KitType; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.FriendChatManager; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.ItemMapping; +import net.runelite.client.util.PvPUtil; +import net.runelite.http.api.hiscore.HiscoreClient; +import net.runelite.http.api.hiscore.HiscoreResult; +import net.runelite.http.api.item.ItemEquipmentStats; +import net.runelite.http.api.item.ItemStats; +import okhttp3.OkHttpClient; + +@Singleton +@Slf4j +@SuppressWarnings("unused") +public class PlayerManager +{ + private final HiscoreClient hiscoreClient; + private final Client client; + private final ItemManager itemManager; + private final EventBus eventBus; + private final FriendChatManager friendChatManager; + private final Map playerMap = new ConcurrentHashMap<>(); + private final Map resultCache = new ConcurrentHashMap<>(); + private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); + + @Inject + PlayerManager( + final Client client, + final EventBus eventBus, + final ItemManager itemManager, + final FriendChatManager friendChatManager, + final OkHttpClient okHttpClient + ) + { + this.client = client; + this.itemManager = itemManager; + this.eventBus = eventBus; + this.friendChatManager = friendChatManager; + this.hiscoreClient = new HiscoreClient(okHttpClient); + + eventBus.register(this); + } + + /** + * @return Collection of {@link PlayerContainer} that are attacking you, this can be empty. + */ + public Set getAllAttackers() + { + Set result = new HashSet<>(); + for (PlayerContainer playerContainer : playerMap.values()) + { + if (playerContainer.isAttacking()) + { + result.add(playerContainer); + } + } + return Collections.unmodifiableSet(result); + } + + /** + * @return Collection of {@link PlayerContainer}, this can be empty. + */ + public Collection getPlayerContainers() + { + return playerMap.values(); + } + + /** + * @param name Players name. + * @return {@link PlayerContainer} if provided with proper name, else null. + */ + @Nullable + public PlayerContainer getPlayer(String name) + { + return playerMap.get(name); + } + + /** + * @param player Player object. + * @return {@link PlayerContainer} if provided with proper name, else null. + */ + @Nullable + public PlayerContainer getPlayer(Player player) + { + if (player == null) + { + return null; + } + + return playerMap.get(player.getName()); + } + + /** + * This will keep submitting an http request until it successfully updates. + * + * @param name The player name you wish to update. + */ + public void updateStats(String name) + { + final PlayerContainer p = playerMap.get(name); + + if (p == null) + { + return; + } + + updateStats(p.getPlayer()); + } + + /** + * This will keep submitting an http request until it successfully updates. + * + * @param requestedPlayer The player object you wish to update. + */ + public void updateStats(Player requestedPlayer) + { + if (requestedPlayer == null) + { + return; + } + + final PlayerContainer player = playerMap.get(requestedPlayer.getName()); + + if (player == null) + { + return; + } + + if (resultCache.containsKey(player.getName())) + { + player.setSkills(resultCache.get(player.getName())); + player.setPrayerLevel(player.getSkills().getPrayer().getLevel()); + player.setHpLevel(player.getSkills().getHitpoints().getLevel()); + return; + } + + if (player.isHiscoresRequested() && !player.isHttpRetry()) + { + return; + } + + player.setHiscoresRequested(true); + + executorService.submit(() -> + { + int timeout = 0; + HiscoreResult result; + do + { + try + { + result = hiscoreClient.lookup(player.getName()); + } + catch (IOException ex) + { + if (timeout == 10) + { + log.error("HiScore Lookup timed out on: {}", player.getName()); + player.setHttpRetry(true); + return; + } + result = null; + timeout++; + try + { + Thread.sleep(1000); + } + catch (InterruptedException ignored) + { + } + } + } + while (result == null); + + resultCache.put(player.getName(), result); + player.setSkills(result); + player.setPrayerLevel(player.getSkills().getPrayer().getLevel()); + player.setHpLevel(player.getSkills().getHitpoints().getLevel()); + player.setHttpRetry(false); + player.setHiscoresRequested(false); + }); + } + + @Subscribe + private void onAppearenceChanged(PlayerCompositionChanged event) + { + PlayerContainer player = playerMap.computeIfAbsent(event.getPlayer().getName(), s -> new PlayerContainer(event.getPlayer())); + update(player); + player.setFriend(client.isFriended(player.getName(), false)); + player.setClan(friendChatManager.isMember(player.getName())); + } + + @Subscribe + private void onPlayerDespawned(PlayerDespawned event) + { + playerMap.remove(event.getPlayer().getName()); + } + + @Subscribe + private void onAnimationChanged(AnimationChanged event) + { + final Actor actor = event.getActor(); + + if (actor.getInteracting() != client.getLocalPlayer() || !(actor instanceof Player) || actor.getAnimation() == -1) + { + return; + } + + final PlayerContainer player = playerMap.get(actor.getName()); + + if (player == null) + { + return; + } + + assert player.getPlayer() == actor; + + if (player.getSkills() == null) + { + updateStats(player.getPlayer()); + } + + player.setAttacking(true); + player.setTimer(8); + } + + private void update(PlayerContainer player) + { + player.setRisk(0); + updatePlayerGear(player); + updateAttackStyle(player); + updateWeakness(player); + player.setLocation(WorldLocation.location(player.getPlayer().getWorldLocation())); + player.setWildyLevel(PvPUtil.getWildernessLevelFrom(player.getPlayer().getWorldLocation())); + player.setTargetString(targetStringBuilder(player)); + } + + private void updatePlayerGear(PlayerContainer player) + { + final Map prices = new HashMap<>(); + + if (player.getPlayer().getPlayerComposition() == null) + { + return; + } + + int magicAttack = 0, + magicDefence = 0, + magicStr = 0, + meleeAtkCrush = 0, + meleeAtkStab = 0, + meleeAtkSlash = 0, + meleeDefCrush = 0, + meleeDefStab = 0, + meleeDefSlash = 0, + meleeStr = 0, + rangeAttack = 0, + rangeDefence = 0, + rangeStr = 0, + speed = 0; + + for (KitType kitType : KitType.values()) + { + if (kitType.equals(KitType.RING) || kitType.equals(KitType.AMMUNITION)) + { + continue; + } + + final int id = player.getPlayer().getPlayerComposition().getEquipmentId(kitType); + + if (id == -1) + { + continue; + } + + if (kitType.equals(KitType.WEAPON)) + { + player.setWeapon(id); + + switch (id) + { + case ItemID.HEAVY_BALLISTA: + case ItemID.HEAVY_BALLISTA_23630: + case ItemID.LIGHT_BALLISTA: + rangeStr += 150; + break; + case ItemID.MAPLE_LONGBOW: + case ItemID.MAPLE_SHORTBOW: + rangeStr += 31; + break; + case ItemID.MAGIC_SHORTBOW: + case ItemID.MAGIC_SHORTBOW_20558: + case ItemID.MAGIC_SHORTBOW_I: + rangeStr += +55; + break; + case ItemID.DARK_BOW: + case ItemID.DARK_BOW_12765: + case ItemID.DARK_BOW_12766: + case ItemID.DARK_BOW_12767: + case ItemID.DARK_BOW_12768: + case ItemID.DARK_BOW_20408: + rangeStr += +60; + break; + case ItemID.RUNE_CROSSBOW: + case ItemID.RUNE_CROSSBOW_23601: + rangeStr += +117; + break; + case ItemID.DRAGON_CROSSBOW: + case ItemID.ARMADYL_CROSSBOW: + case ItemID.ARMADYL_CROSSBOW_23611: + rangeStr += +122; + break; + } + } + + final ItemStats item = itemManager.getItemStats(id, false); + final ItemComposition itemComposition = itemManager.getItemComposition(id); + + if (item == null) + { + log.debug("Item is null: {}", id); + continue; + } + + final ItemEquipmentStats stats = item.getEquipment(); + + speed += stats.getAspeed(); + meleeAtkCrush += stats.getAcrush(); + meleeAtkStab += stats.getAstab(); + meleeAtkSlash += stats.getAslash(); + meleeDefCrush += stats.getDcrush(); + meleeDefStab += stats.getDstab(); + meleeDefSlash += stats.getDslash(); + magicAttack += stats.getAmagic(); + rangeAttack += stats.getArange(); + magicDefence += stats.getDmagic(); + rangeDefence += stats.getDrange(); + rangeStr += stats.getRstr(); + meleeStr += stats.getStr(); + magicStr += stats.getMdmg(); + + if (ItemReclaimCost.breaksOnDeath(id)) + { + prices.put(id, itemManager.getRepairValue(id)); + log.debug("Item has a broken value: Id {}, Value {}", id, itemManager.getRepairValue(id)); + continue; + } + + if (!itemComposition.isTradeable() && !ItemMapping.isMapped(id)) + { + prices.put(id, itemComposition.getPrice()); + } + else if (itemComposition.isTradeable()) + { + prices.put(id, itemManager.getItemPrice(id, false)); + } + } + + player.setCombatStats(new CombatStats( + magicAttack, + magicDefence, + magicStr, + meleeAtkCrush, + meleeAtkSlash, + meleeAtkStab, + (meleeAtkCrush + meleeAtkSlash + meleeAtkStab) / 3, + meleeDefCrush, + (meleeDefCrush + meleeDefSlash + meleeDefStab) / 3, + meleeDefSlash, + meleeDefStab, + meleeStr, + rangeAttack, + rangeDefence, + rangeStr, + speed + )); + updateGear(player, prices); + updateMeleeStyle(player); + } + + private 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 (client.getWorldType().stream().noneMatch(x -> x == WorldType.HIGH_RISK)) + { + if (player.getPlayer().getSkullIcon() == null) + { + removeEntries(player.getRiskedGear(), player.getPrayerLevel() < 25 ? 3 : 4); + } + else + { + removeEntries(player.getRiskedGear(), player.getPrayerLevel() < 25 ? 0 : 1); + } + } + + int risk = 0; + for (int val : player.getRiskedGear().values()) + risk += val; + player.setRisk(risk); + } + + private void updateMeleeStyle(PlayerContainer player) + { + final CombatStats stats = player.getCombatStats(); + + if (stats.getMeleeAtkCrush() >= stats.getMeleeAtkSlash() && stats.getMeleeAtkCrush() >= stats.getMeleeAtkStab()) + { + player.setMeleeStyle(PlayerContainer.MeleeStyle.CRUSH); + } + else if (stats.getMeleeAtkSlash() >= stats.getMeleeAtkCrush() && stats.getMeleeAtkSlash() >= stats.getMeleeAtkStab()) + { + player.setMeleeStyle(PlayerContainer.MeleeStyle.SLASH); + } + else + { + player.setMeleeStyle(PlayerContainer.MeleeStyle.STAB); + } + } + + private void updateAttackStyle(PlayerContainer player) + { + final AttackStyle oldStyle = player.getAttackStyle(); + boolean staff = false; + + for (int id : player.getGear().keySet()) + { + ItemComposition def = itemManager.getItemComposition(id); + if (def.getName().toLowerCase().contains("staff")) + { + player.setAttackStyle(AttackStyle.MAGE); + if (oldStyle != player.getAttackStyle()) + { + eventBus.post(new AttackStyleChanged( + player.getPlayer(), oldStyle, player.getAttackStyle()) + ); + } + return; + } + } + + final CombatStats stats = player.getCombatStats(); + + if (stats.getMagicStr() >= stats.getRangeStr() && stats.getMagicStr() >= stats.getMeleeStr()) + { + player.setAttackStyle(AttackStyle.MAGE); + } + else if (stats.getRangeStr() >= stats.getMagicStr() && stats.getRangeStr() >= stats.getMeleeStr()) + { + player.setAttackStyle(AttackStyle.RANGE); + } + else + { + player.setAttackStyle(AttackStyle.MELEE); + } + + if (oldStyle != player.getAttackStyle()) + { + eventBus.post(new AttackStyleChanged( + player.getPlayer(), oldStyle, player.getAttackStyle()) + ); + } + } + + private void updateWeakness(PlayerContainer player) + { + final CombatStats stats = player.getCombatStats(); + + if (stats.getMagicDefence() <= stats.getRangeDefence() && stats.getMagicDefence() <= stats.getMeleeDefence()) + { + player.setWeakness(AttackStyle.MAGE); + } + else if (stats.getRangeDefence() <= stats.getMagicDefence() && stats.getRangeDefence() <= stats.getMeleeDefence()) + { + player.setWeakness(AttackStyle.RANGE); + } + else + { + player.setWeakness(AttackStyle.MELEE); + } + } + + private static void removeEntries(LinkedHashMap map, int quantity) + { + final Iterator> it = map.entrySet().iterator(); + for (int i = 0; it.hasNext() && i < quantity; i++) + { + it.next(); + it.remove(); // LinkedHashMap iterator supports this + } + } + + private 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"; + } +} \ No newline at end of file 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 d524a9a5e3..2d2ef2eb91 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 @@ -28,6 +28,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; +import com.openosrs.client.game.ItemReclaimCost; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.IOException; @@ -349,6 +350,41 @@ public class ItemManager return price; } + public int getAlchValue(ItemComposition composition) + { + if (composition.getId() == COINS_995) + { + return 1; + } + if (composition.getId() == PLATINUM_TOKEN) + { + return 1000; + } + + return Math.max(1, composition.getHaPrice()); + } + + public int getRepairValue(int itemId) + { + return getRepairValue(itemId, false); + } + + private int getRepairValue(int itemId, boolean fullValue) + { + final ItemReclaimCost b = ItemReclaimCost.of(itemId); + + if (b != null) + { + if (fullValue || b.getItemID() == GRANITE_MAUL_24225 || b.getItemID() == GRANITE_MAUL_24227) + { + return b.getValue(); + } + return (int) (b.getValue() * (75.0f / 100.0f)); + } + + return 0; + } + /** * Look up an item's stats * @param itemId item id diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java b/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java index c8694b7153..bf730e3f23 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java @@ -369,4 +369,9 @@ public enum ItemMapping return mapping; } + + public static boolean isMapped(int itemId) + { + return MAPPINGS.containsValue(itemId); + } }