diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/NpcExpModifier.java b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/NpcExpModifier.java new file mode 100644 index 0000000000..7d3ebbb237 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/NpcExpModifier.java @@ -0,0 +1,137 @@ +/* + * 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.plugins.experiencedrop; + +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; +import net.runelite.api.NpcID; + +/** + * The Experience received from damaging an NPC is actually based on the NPCs stats. + * For stronger NPCs this can cause you to gain more XP for the same damage dealt. + */ +@Getter +enum NpcExpModifier +{ + // TODO: Confirm all NPC ids, some are probably incorrect or just useless + // Common + DAGANNOTH_REX(255, 255, 255, 255, 255, 255, 255, 0, 0, NpcID.DAGANNOTH_REX), + DAGANNOTH_PRIME(255, 255, 255, 255, 255, 255, 255, 0, 0, NpcID.DAGANNOTH_PRIME), + DAGANNOTH_SUPREME(225, 255, 128, 255, 10, 10, 10, 0, 0, NpcID.DAGANNOTH_SUPREME), + GIANT_MOLE(200, 200, 200, 200, 60, 80, 100, 0, 0, NpcID.GIANT_MOLE), + KQ(300, 300, 300, 255, 50, 50, 10, 0, 0, NpcID.KALPHITE_QUEEN, NpcID.KALPHITE_QUEEN_963, NpcID.KALPHITE_QUEEN_965), + KQ_2(300, 300, 300, 255, 100, 100, 100, 0, 0, NpcID.KALPHITE_QUEEN_4303, NpcID.KALPHITE_QUEEN_4304), + // Other + VORKATH(560, 308, 214, 750, 26, 108, 108, 16, 0, NpcID.VORKATH, NpcID.VORKATH_8058, NpcID.VORKATH_8059, NpcID.VORKATH_8060, NpcID.VORKATH_8061), + // All Zulrah phase stats are the same for the values we care about + ZULRAH(1, 1, 300, 500, 0, 0, 0, 0, 0, NpcID.ZULRAH, NpcID.ZULRAH_2043, NpcID.ZULRAH_2044), + // Sporadic + OBOR(90, 100, 60, 120, 35, 40, 45, 100, 68, NpcID.OBOR), + BRYOPHYTA(130, 100, 100, 115, 0, 0, 0, 33, 31, NpcID.BRYOPHYTA), + SKOTIZO(240, 250, 200, 450, 80, 80, 80, 160, 31, NpcID.SKOTIZO), + // GWD + KREEARRA(300, 200, 260, 255, 180, 180, 180, 136, 12, NpcID.KREEARRA), + GENERAL_GRAARDOR(280, 350, 250, 255, 90, 90, 90, 120, 43, NpcID.GENERAL_GRAARDOR), + COMMANDER_ZILYANA(280, 196, 300, 255, 100, 100, 100, 195, 20, NpcID.COMMANDER_ZILYANA), + KRIL_TSUTSAROTH(340, 300, 270, 255, 80, 80, 80, 160, 31, NpcID.KRIL_TSUTSAROTH), + // Slayer + DUSK(200, 1400, 100, 450, 0, 0, 0, 0, 0, NpcID.DUSK, NpcID.DUSK_7851, NpcID.DUSK_7854, NpcID.DUSK_7855, + NpcID.DUSK_7882, NpcID.DUSK_7883, NpcID.DUSK_7886, NpcID.DUSK_7887, NpcID.DUSK_7888, NpcID.DUSK_7889), + DAWN(140, 140, 100, 450, 0, 0, 0, 0, 0, NpcID.DAWN, NpcID.DAWN_7852, NpcID.DAWN_7853, NpcID.DAWN_7884, NpcID.DAWN_7885), + ABYSSAL_SIRE(180, 136, 250, 350, 40, 60, 50, 65, 0, NpcID.ABYSSAL_SIRE, NpcID.ABYSSAL_SIRE_5908, + NpcID.ABYSSAL_SIRE_5887, NpcID.ABYSSAL_SIRE_5888, NpcID.ABYSSAL_SIRE_5889, NpcID.ABYSSAL_SIRE_5890, NpcID.ABYSSAL_SIRE_5891), + KRAKEN(1, 1, 1, 255, 0, 0, 0, 0, 0, NpcID.KRAKEN, NpcID.KRAKEN_6640, NpcID.KRAKEN_6656), + CERBERUS(220, 220, 100, 600, 50, 100, 25, 50, 0, NpcID.CERBERUS, NpcID.CERBERUS_5863, NpcID.CERBERUS_5866), + SMOKE_DEVIL(240, 220, 360, 240, 11, 4, 9, 0, 0, NpcID.THERMONUCLEAR_SMOKE_DEVIL), + // Theatre of Blood + MAIDEN(350, 350, 200, 3500, 0, 0, 0, 0, 0, + NpcID.THE_MAIDEN_OF_SUGADINTI, NpcID.THE_MAIDEN_OF_SUGADINTI_8361, NpcID.THE_MAIDEN_OF_SUGADINTI_8362, + NpcID.THE_MAIDEN_OF_SUGADINTI_8363, NpcID.THE_MAIDEN_OF_SUGADINTI_8364, NpcID.THE_MAIDEN_OF_SUGADINTI_8365), + BLOAT(250, 340, 100, 2000, 40, 20, 40, 82, 150, NpcID.PESTILENT_BLOAT), + NYLOCAS(400, 350, 50, 2500, 0, 0, 0, 60, 0, + NpcID.NYLOCAS_VASILIAS, NpcID.NYLOCAS_VASILIAS_8355, NpcID.NYLOCAS_VASILIAS_8356, NpcID.NYLOCAS_VASILIAS_8357), + SOTETSEG(250, 250, 200, 4000, 70, 70, 70, 49, 0, NpcID.SOTETSEG, NpcID.SOTETSEG_8388), + XARPUS(1, 1, 250, 5080, 0, 0, 0, 0, 0, NpcID.XARPUS, NpcID.XARPUS_8339, NpcID.XARPUS_8340, NpcID.XARPUS_8341), + VERZIK(400, 400, 20, 2000, 20, 20, 20, 0, 0, NpcID.VERZIK_VITUR_8370), + VERZIK_2(400, 400, 200, 3250, 100, 60, 100, 0, 0, NpcID.VERZIK_VITUR_8372), + VERZIK_3(400, 400, 150, 3250, 70, 30, 70, 30, 80, NpcID.VERZIK_VITUR_8374); + + private final int attack; + private final int strength; + private final int defence; + private final int hp; + private final int stab; + private final int slash; + private final int crush; + private final int bonusStrength; + private final int bonusAttack; + private final int[] npcIDs; + + private final static Map XP_MODIFIER_MAP = new HashMap<>(); + + static + { + for (net.runelite.client.plugins.experiencedrop.NpcExpModifier m : values()) + { + final double bonus = m.calculateXpModifier(); + for (int id : m.getNpcIDs()) + { + XP_MODIFIER_MAP.put(id, bonus); + } + } + } + + NpcExpModifier(int attack, int strength, int defence, int hp, int stab, int slash, int crush, int bonusStrength, int bonusAttack, int... npcIDs) + { + this.attack = attack; + this.strength = strength; + this.defence = defence; + this.hp = hp; + this.stab = stab; + this.slash = slash; + this.crush = crush; + this.bonusStrength = bonusStrength; + this.bonusAttack = bonusAttack; + this.npcIDs = npcIDs; + } + + /** + * Based off the formula found here: http://services.runescape.com/m=forum/c=PLuJ4cy6gtA/forums.ws?317,318,712,65587452,209,337584542#209 + * @return bonus XP modifier + */ + private double calculateXpModifier() + { + final double averageLevel = Math.floor((attack + strength + defence + hp) / 4); + final double averageDefBonus = Math.floor((stab + slash + crush) / 3); + + return (1 + Math.floor(averageLevel * (averageDefBonus + bonusStrength + bonusAttack) / 5120) / 40); + } + + public static double getByNpcId(final int npcID) + { + return XP_MODIFIER_MAP.getOrDefault(npcID, 1.0); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropOverlay.java index c3b9bf916b..b0fddcd62c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropOverlay.java @@ -51,7 +51,7 @@ class XpDropOverlay extends Overlay @Override public Dimension render(Graphics2D graphics) { - if (config.showDamage()) + if (config.showDamage() && plugin.getTickShow() > 0) { final Actor opponent = plugin.getLastOpponent(); if (opponent != null) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropPlugin.java index 50e64e1558..d8a4384300 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropPlugin.java @@ -25,8 +25,6 @@ package net.runelite.client.plugins.experiencedrop; import com.google.inject.Provides; -import java.time.Duration; -import java.time.Instant; import java.util.Arrays; import java.util.EnumMap; import java.util.Map; @@ -42,37 +40,21 @@ import net.runelite.api.Player; import static net.runelite.api.ScriptID.XPDROP_DISABLED; import net.runelite.api.Skill; import net.runelite.api.SpriteID; -import net.runelite.api.VarPlayer; import net.runelite.api.Varbits; +import net.runelite.api.WorldType; import net.runelite.api.events.ExperienceChanged; +import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; -import net.runelite.api.events.InteractingChanged; -import net.runelite.api.events.VarbitChanged; import net.runelite.api.events.WidgetHiddenChanged; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetID; import net.runelite.api.widgets.WidgetInfo; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.game.HiscoreManager; import net.runelite.client.game.NPCManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.plugins.attackstyles.AttackStyle; -import static net.runelite.client.plugins.attackstyles.AttackStyle.ACCURATE; -import static net.runelite.client.plugins.attackstyles.AttackStyle.AGGRESSIVE; -import static net.runelite.client.plugins.attackstyles.AttackStyle.CASTING; -import static net.runelite.client.plugins.attackstyles.AttackStyle.CONTROLLED; -import static net.runelite.client.plugins.attackstyles.AttackStyle.DEFENSIVE; -import static net.runelite.client.plugins.attackstyles.AttackStyle.DEFENSIVE_CASTING; -import static net.runelite.client.plugins.attackstyles.AttackStyle.LONGRANGE; -import static net.runelite.client.plugins.attackstyles.AttackStyle.OTHER; -import static net.runelite.client.plugins.attackstyles.AttackStyle.RANGING; -import net.runelite.client.plugins.attackstyles.WeaponType; import net.runelite.client.ui.overlay.OverlayManager; -import net.runelite.client.util.Text; -import net.runelite.http.api.hiscore.HiscoreEndpoint; -import net.runelite.http.api.hiscore.HiscoreResult; @PluginDescriptor( name = "XP Drop", @@ -82,7 +64,8 @@ import net.runelite.http.api.hiscore.HiscoreResult; public class XpDropPlugin extends Plugin { private static final int XPDROP_PADDING = 2; // space between xp drop icons - private static final Duration WAIT = Duration.ofSeconds(5); + private static final double HITPOINT_RATIO = 1.33; // Base rate of hp xp per point damage + private static final double DMM_MULTIPLIER_RATIO = 10; @Inject private Client client; @@ -90,29 +73,8 @@ public class XpDropPlugin extends Plugin @Inject private XpDropConfig config; - private int tickCounter = 0; - private int previousExpGained; - private boolean hasHit = false; - private boolean hasDropped = false; - private boolean correctPrayer; - private Skill lastSkill = null; - private Map previousSkillExpTable = new EnumMap<>(Skill.class); - private PrayerType currentTickPrayer; - private AttackStyle attackStyle; - private int attackStyleVarbit = -1; - private int equippedWeaponTypeVarbit = -1; - private int castingModeVarbit = -1; - private int opponentHealth = -1; - private int xpGains = 0; - private AttackStyle[] offensiveStyles = {ACCURATE, AGGRESSIVE, DEFENSIVE, CONTROLLED, RANGING, LONGRANGE, CASTING, DEFENSIVE_CASTING}; - - @Getter(AccessLevel.PACKAGE) - private int damage = 0; - - @Getter(AccessLevel.PACKAGE) - private Actor lastOpponent; - - private Instant lastTime; + @Inject + private NPCManager npcManager; @Inject private OverlayManager overlayManager; @@ -120,11 +82,25 @@ public class XpDropPlugin extends Plugin @Inject private XpDropOverlay overlay; - @Inject - private NPCManager npcManager; + @Getter(AccessLevel.PACKAGE) + private int damage = 0; - @Inject - private HiscoreManager hiscoreManager; + @Getter(AccessLevel.PACKAGE) + private int tickShow = 0; + + @Getter(AccessLevel.PACKAGE) + private Actor lastOpponent; + + private double hpExp = 0; + private boolean loginTick = false; + + private int tickCounter = 0; + private int previousExpGained; + private boolean hasDropped = false; + private boolean correctPrayer; + private Skill lastSkill = null; + private Map previousSkillExpTable = new EnumMap<>(Skill.class); + private PrayerType currentTickPrayer; @Provides XpDropConfig provideConfig(ConfigManager configManager) @@ -135,18 +111,25 @@ public class XpDropPlugin extends Plugin @Override protected void startUp() throws Exception { - lastOpponent = null; overlayManager.add(overlay); - if (client.getGameState() == GameState.LOGGED_IN) + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() == GameState.LOGIN_SCREEN) { - attackStyleVarbit = client.getVar(VarPlayer.ATTACK_STYLE); - equippedWeaponTypeVarbit = client.getVar(Varbits.EQUIPPED_WEAPON_TYPE); - castingModeVarbit = client.getVar(Varbits.DEFENSIVE_CASTING_MODE); - updateAttackStyle( - equippedWeaponTypeVarbit, - attackStyleVarbit, - castingModeVarbit); + loginTick = true; } + + damage = 0; + tickShow = 0; } @Subscribe @@ -277,40 +260,16 @@ public class XpDropPlugin extends Plugin @Subscribe public void onGameTick(GameTick tick) { - // Detect hitting a 0 - if (lastOpponent != null) + loginTick = false; + lastOpponent = client.getLocalPlayer().getInteracting(); + + if (tickShow > 0) { - int health = calculateHealth(lastOpponent); - if (health != -1 && opponentHealth != -1 && health == opponentHealth && hasHit) - { - damage = 0; - hasHit = false; - } + tickShow--; } - - // Handle getting XP gains - if (hasDropped) + else { - if (xpGains != 0 && attackStyle.getSkills().length > 1 && attackStyle != LONGRANGE) - { - damage = (int) (xpGains / (attackStyle.getSkills().length * 1.3)); - } - else if (xpGains != 0) - { - damage = xpGains / 4; - } - - xpGains = 0; - hasDropped = false; - } - - // Clear opponent - if (lastOpponent != null && lastTime != null && client.getLocalPlayer().getInteracting() == null) - { - if (Duration.between(lastTime, Instant.now()).compareTo(WAIT) > 0) - { - lastOpponent = null; - } + damage = 0; } currentTickPrayer = getActivePrayerType(); @@ -350,120 +309,67 @@ public class XpDropPlugin extends Plugin Integer previous = previousSkillExpTable.put(skill, xp); if (previous != null) { - opponentHealth = calculateHealth(lastOpponent); previousExpGained = xp - previous; - if (skill != Skill.HITPOINTS && Arrays.stream(offensiveStyles).anyMatch(attackStyle::equals)) - { - xpGains += previousExpGained; - } - hasDropped = true; - hasHit = true; } - } - private void updateAttackStyle(int equippedWeaponType, int attackStyleIndex, int castingMode) - { - AttackStyle[] attackStyles = WeaponType.getWeaponType(equippedWeaponType).getAttackStyles(); - if (attackStyleIndex < attackStyles.length) - { - attackStyle = attackStyles[attackStyleIndex]; - if (attackStyle == null) - { - attackStyle = OTHER; - } - else if ((attackStyle == CASTING) && (castingMode == 1)) - { - attackStyle = DEFENSIVE_CASTING; - } - } - } - - @Subscribe - public void onInteractingChanged(InteractingChanged event) - { - if (event.getSource() != client.getLocalPlayer()) + if (loginTick) { return; } - Actor opponent = event.getTarget(); - - if (opponent == null) + if (client.getGameState() != GameState.LOGGED_IN) { - lastTime = Instant.now(); - return; - } - else if (opponent.getName().equalsIgnoreCase("fishing spot")) - { - lastTime = Instant.now().minus(WAIT); + damage = 0; + tickShow = 0; return; } - damage = 0; - lastOpponent = opponent; - opponentHealth = calculateHealth(opponent); - } - - private int calculateHealth(Actor target) - { - if (target == null || target.getName() == null) + if (event.getSkill().equals(Skill.HITPOINTS)) { - return -1; - } + final double oldExp = hpExp; + hpExp = client.getSkillExperience(Skill.HITPOINTS); - final int healthScale = target.getHealth(); - final int healthRatio = target.getHealthRatio(); - final String targetName = Text.removeTags(target.getName()); - - Integer maxHealth = -1; - if (target instanceof NPC) - { - maxHealth = npcManager.getHealth(targetName, target.getCombatLevel()); - } - else if (target instanceof Player) - { - final HiscoreResult hiscoreResult = hiscoreManager.lookupAsync(targetName, HiscoreEndpoint.NORMAL); - if (hiscoreResult != null) + final double diff = hpExp - oldExp; + if (diff < 1) { - final int hp = hiscoreResult.getHitpoints().getLevel(); - if (hp > 0) - { - maxHealth = hp; - } + return; } - } - if (healthRatio < 0 || healthScale <= 0 || maxHealth == null) - { - return -1; + final double damageDealt = calculateDamageDealt(diff); + damage = (int) Math.rint(damageDealt); + tickShow = 3; } - - return (int) ((maxHealth * healthRatio / healthScale) + 0.5f); } - @Subscribe - public void onVarbitChanged(VarbitChanged event) + private double calculateDamageDealt(double diff) { - if (attackStyleVarbit == -1 || attackStyleVarbit != client.getVar(VarPlayer.ATTACK_STYLE)) + double damageDealt = diff / HITPOINT_RATIO; + // DeadMan mode has an XP modifier + if (client.getWorldType().contains(WorldType.DEADMAN)) { - attackStyleVarbit = client.getVar(VarPlayer.ATTACK_STYLE); - updateAttackStyle(client.getVar(Varbits.EQUIPPED_WEAPON_TYPE), attackStyleVarbit, - client.getVar(Varbits.DEFENSIVE_CASTING_MODE)); + damageDealt = damageDealt / DMM_MULTIPLIER_RATIO; } - if (equippedWeaponTypeVarbit == -1 || equippedWeaponTypeVarbit != client.getVar(Varbits.EQUIPPED_WEAPON_TYPE)) + // Some NPCs have an XP modifier, account for it here. + Actor a = client.getLocalPlayer().getInteracting(); + if (!(a instanceof NPC) && !(a instanceof Player)) { - equippedWeaponTypeVarbit = client.getVar(Varbits.EQUIPPED_WEAPON_TYPE); - updateAttackStyle(equippedWeaponTypeVarbit, client.getVar(VarPlayer.ATTACK_STYLE), - client.getVar(Varbits.DEFENSIVE_CASTING_MODE)); + // If we are interacting with nothing we may have clicked away at the perfect time fall back to last tick + if (!(lastOpponent instanceof NPC) && !(lastOpponent instanceof Player)) + { + return damageDealt; + } + + a = lastOpponent; } - if (castingModeVarbit == -1 || castingModeVarbit != client.getVar(Varbits.DEFENSIVE_CASTING_MODE)) + if (a instanceof Player) { - castingModeVarbit = client.getVar(Varbits.DEFENSIVE_CASTING_MODE); - updateAttackStyle(client.getVar(Varbits.EQUIPPED_WEAPON_TYPE), client.getVar(VarPlayer.ATTACK_STYLE), - castingModeVarbit); + return damageDealt; } + + NPC target = (NPC) a; + return damageDealt / NpcExpModifier.getByNpcId(target.getId()); } }