diff --git a/runelite-client/src/main/java/net/runelite/client/input/MouseManager.java b/runelite-client/src/main/java/net/runelite/client/input/MouseManager.java index ba1e3b80e8..d444f93fe6 100644 --- a/runelite-client/src/main/java/net/runelite/client/input/MouseManager.java +++ b/runelite-client/src/main/java/net/runelite/client/input/MouseManager.java @@ -33,6 +33,9 @@ import javax.inject.Singleton; @Singleton public class MouseManager { + // Button numbers greater than BUTTON3 have no constant identifier + private static final int MOUSE_BUTTON_4 = 4; + private final List mouseListeners = new CopyOnWriteArrayList<>(); private final List mouseWheelListeners = new CopyOnWriteArrayList<>(); @@ -74,6 +77,7 @@ public class MouseManager public MouseEvent processMousePressed(MouseEvent mouseEvent) { + checkExtraMouseButtons(mouseEvent); for (MouseListener mouseListener : mouseListeners) { mouseEvent = mouseListener.mousePressed(mouseEvent); @@ -83,6 +87,7 @@ public class MouseManager public MouseEvent processMouseReleased(MouseEvent mouseEvent) { + checkExtraMouseButtons(mouseEvent); for (MouseListener mouseListener : mouseListeners) { mouseEvent = mouseListener.mouseReleased(mouseEvent); @@ -92,6 +97,7 @@ public class MouseManager public MouseEvent processMouseClicked(MouseEvent mouseEvent) { + checkExtraMouseButtons(mouseEvent); for (MouseListener mouseListener : mouseListeners) { mouseEvent = mouseListener.mouseClicked(mouseEvent); @@ -99,6 +105,17 @@ public class MouseManager return mouseEvent; } + private void checkExtraMouseButtons(MouseEvent mouseEvent) + { + // Prevent extra mouse buttins from being passed into the client, + // as it treats them all as left click + int button = mouseEvent.getButton(); + if (button >= MOUSE_BUTTON_4) + { + mouseEvent.consume(); + } + } + public MouseEvent processMouseEntered(MouseEvent mouseEvent) { for (MouseListener mouseListener : mouseListeners) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsConfig.java new file mode 100644 index 0000000000..5720f57016 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsConfig.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 Adam + * 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.dpscounter; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("dpscounter") +public interface DpsConfig extends Config +{ + @ConfigItem( + position = 0, + keyName = "showDamage", + name = "Show damage", + description = "Show total damage instead of DPS" + ) + default boolean showDamage() + { + return false; + } + + @ConfigItem( + position = 1, + keyName = "autopause", + name = "Auto pause", + description = "Pause the DPS tracker when a boss dies" + ) + default boolean autopause() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsCounterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsCounterPlugin.java new file mode 100644 index 0000000000..186c52d561 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsCounterPlugin.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2020 Adam + * 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.dpscounter; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Provides; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +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.Hitsplat; +import net.runelite.api.NPC; +import static net.runelite.api.NpcID.*; +import net.runelite.api.Player; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.NpcDespawned; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.OverlayMenuClicked; +import net.runelite.client.events.PartyChanged; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ws.PartyMember; +import net.runelite.client.ws.PartyService; +import net.runelite.client.ws.WSClient; + +@PluginDescriptor( + name = "DPS Counter", + description = "Counts damage (per second) by a party", + enabledByDefault = false +) +@Slf4j +public class DpsCounterPlugin extends Plugin +{ + private static final ImmutableSet BOSSES = ImmutableSet.of( + ABYSSAL_SIRE, ABYSSAL_SIRE_5887, ABYSSAL_SIRE_5888, ABYSSAL_SIRE_5889, ABYSSAL_SIRE_5890, ABYSSAL_SIRE_5891, ABYSSAL_SIRE_5908, + CALLISTO, CALLISTO_6609, + CERBERUS, CERBERUS_5863, CERBERUS_5866, + CHAOS_ELEMENTAL, CHAOS_ELEMENTAL_6505, + CORPOREAL_BEAST, + GENERAL_GRAARDOR, GENERAL_GRAARDOR_6494, + GIANT_MOLE, GIANT_MOLE_6499, + KALPHITE_QUEEN, KALPHITE_QUEEN_963, KALPHITE_QUEEN_965, KALPHITE_QUEEN_4303, KALPHITE_QUEEN_4304, KALPHITE_QUEEN_6500, KALPHITE_QUEEN_6501, + KING_BLACK_DRAGON, KING_BLACK_DRAGON_2642, KING_BLACK_DRAGON_6502, + KRIL_TSUTSAROTH, KRIL_TSUTSAROTH_6495, + SARACHNIS, + VENENATIS, VENENATIS_6610, + VETION, VETION_REBORN, + + // ToB + THE_MAIDEN_OF_SUGADINTI, THE_MAIDEN_OF_SUGADINTI_8361, THE_MAIDEN_OF_SUGADINTI_8362, THE_MAIDEN_OF_SUGADINTI_8363, THE_MAIDEN_OF_SUGADINTI_8364, THE_MAIDEN_OF_SUGADINTI_8365, + PESTILENT_BLOAT, + NYLOCAS_VASILIAS, NYLOCAS_VASILIAS_8355, NYLOCAS_VASILIAS_8356, NYLOCAS_VASILIAS_8357, + SOTETSEG, SOTETSEG_8388, + XARPUS_8340, XARPUS_8341, + VERZIK_VITUR_8370, + VERZIK_VITUR_8372, + VERZIK_VITUR_8374, + + // CoX + TEKTON, TEKTON_7541, TEKTON_7542, TEKTON_ENRAGED, TEKTON_ENRAGED_7544, TEKTON_7545, + VESPULA, VESPULA_7531, VESPULA_7532, ABYSSAL_PORTAL, + VANGUARD, VANGUARD_7526, VANGUARD_7527, VANGUARD_7528, VANGUARD_7529, + GREAT_OLM, GREAT_OLM_LEFT_CLAW, GREAT_OLM_RIGHT_CLAW_7553, GREAT_OLM_7554, GREAT_OLM_LEFT_CLAW_7555, + DEATHLY_RANGER, DEATHLY_MAGE, + MUTTADILE, MUTTADILE_7562, MUTTADILE_7563, + VASA_NISTIRIO, VASA_NISTIRIO_7567, + GUARDIAN, GUARDIAN_7570, GUARDIAN_7571, GUARDIAN_7572, + LIZARDMAN_SHAMAN_7573, LIZARDMAN_SHAMAN_7574, + ICE_DEMON, ICE_DEMON_7585, + SKELETAL_MYSTIC, SKELETAL_MYSTIC_7605, SKELETAL_MYSTIC_7606, + + THE_NIGHTMARE, THE_NIGHTMARE_9426, THE_NIGHTMARE_9427, THE_NIGHTMARE_9428, THE_NIGHTMARE_9429, THE_NIGHTMARE_9430, THE_NIGHTMARE_9431, THE_NIGHTMARE_9432, THE_NIGHTMARE_9433 + ); + + @Inject + private Client client; + + @Inject + private OverlayManager overlayManager; + + @Inject + private PartyService partyService; + + @Inject + private WSClient wsClient; + + @Inject + private DpsOverlay dpsOverlay; + + @Inject + private DpsConfig dpsConfig; + + @Getter(AccessLevel.PACKAGE) + private final Map members = new ConcurrentHashMap<>(); + @Getter(AccessLevel.PACKAGE) + private final DpsMember total = new DpsMember("Total"); + + @Provides + DpsConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(DpsConfig.class); + } + + @Override + protected void startUp() + { + total.reset(); + overlayManager.add(dpsOverlay); + wsClient.registerMessage(DpsUpdate.class); + } + + @Override + protected void shutDown() + { + wsClient.unregisterMessage(DpsUpdate.class); + overlayManager.remove(dpsOverlay); + members.clear(); + } + + @Subscribe + public void onPartyChanged(PartyChanged partyChanged) + { + members.clear(); + } + + @Subscribe + public void onHitsplatApplied(HitsplatApplied hitsplatApplied) + { + Player player = client.getLocalPlayer(); + Actor actor = hitsplatApplied.getActor(); + if (!(actor instanceof NPC)) + { + return; + } + + Hitsplat hitsplat = hitsplatApplied.getHitsplat(); + + switch (hitsplat.getHitsplatType()) + { + case DAMAGE_ME: + int hit = hitsplat.getAmount(); + // Update local member + PartyMember localMember = partyService.getLocalMember(); + // If not in a party, user local player name + final String name = localMember == null ? player.getName() : localMember.getName(); + DpsMember dpsMember = members.computeIfAbsent(name, DpsMember::new); + dpsMember.addDamage(hit); + + // broadcast damage + if (localMember != null) + { + final DpsUpdate specialCounterUpdate = new DpsUpdate(hit); + specialCounterUpdate.setMemberId(localMember.getMemberId()); + wsClient.send(specialCounterUpdate); + } + // apply to total + break; + case DAMAGE_OTHER: + final int npcId = ((NPC) actor).getId(); + boolean isBoss = BOSSES.contains(npcId); + if (actor != player.getInteracting() && !isBoss) + { + // only track damage to npcs we are attacking, or is a nearby common boss + return; + } + // apply to total + break; + default: + return; + } + + unpause(); + total.addDamage(hitsplat.getAmount()); + } + + @Subscribe + public void onDpsUpdate(DpsUpdate dpsUpdate) + { + if (partyService.getLocalMember().getMemberId().equals(dpsUpdate.getMemberId())) + { + return; + } + + String name = partyService.getMemberById(dpsUpdate.getMemberId()).getName(); + if (name == null) + { + return; + } + + unpause(); + + DpsMember dpsMember = members.computeIfAbsent(name, DpsMember::new); + dpsMember.addDamage(dpsUpdate.getHit()); + } + + @Subscribe + public void onOverlayMenuClicked(OverlayMenuClicked event) + { + if (event.getEntry() == DpsOverlay.RESET_ENTRY) + { + members.clear(); + total.reset(); + } + else if (event.getEntry() == DpsOverlay.UNPAUSE_ENTRY) + { + unpause(); + } + else if (event.getEntry() == DpsOverlay.PAUSE_ENTRY) + { + pause(); + } + } + + @Subscribe + public void onNpcDespawned(NpcDespawned npcDespawned) + { + NPC npc = npcDespawned.getNpc(); + + if (npc.isDead() && BOSSES.contains(npc.getId())) + { + log.debug("Boss has died!"); + + if (dpsConfig.autopause()) + { + pause(); + } + } + } + + private void pause() + { + if (total.isPaused()) + { + return; + } + + log.debug("Pausing"); + + for (DpsMember dpsMember : members.values()) + { + dpsMember.pause(); + } + total.pause(); + + dpsOverlay.setPaused(true); + } + + private void unpause() + { + if (!total.isPaused()) + { + return; + } + + log.debug("Unpausing"); + + for (DpsMember dpsMember : members.values()) + { + dpsMember.unpause(); + } + total.unpause(); + + dpsOverlay.setPaused(false); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsMember.java b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsMember.java new file mode 100644 index 0000000000..e35bbedbd2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsMember.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020 Adam + * 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.dpscounter; + +import java.time.Duration; +import java.time.Instant; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +class DpsMember +{ + private final String name; + private Instant start; + private Instant end; + private int damage; + + void addDamage(int amount) + { + if (start == null) + { + start = Instant.now(); + } + + damage += amount; + } + + float getDps() + { + if (start == null) + { + return 0; + } + + Instant now = end == null ? Instant.now() : end; + int diff = (int) (now.toEpochMilli() - start.toEpochMilli()) / 1000; + if (diff == 0) + { + return 0; + } + + return (float) damage / (float) diff; + } + + void pause() + { + end = Instant.now(); + } + + boolean isPaused() + { + return start == null || end != null; + } + + void unpause() + { + if (end == null) + { + return; + } + + start = start.plus(Duration.between(end, Instant.now())); + end = null; + } + + void reset() + { + damage = 0; + start = end = Instant.now(); + } + + Duration elapsed() + { + return Duration.between(start, end == null ? Instant.now() : end); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsOverlay.java new file mode 100644 index 0000000000..44b31c481c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsOverlay.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2020 Adam + * 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.dpscounter; + +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.text.DecimalFormat; +import java.time.Duration; +import java.util.Map; +import javax.inject.Inject; +import net.runelite.api.Client; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY; +import net.runelite.api.Player; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.components.ComponentConstants; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; +import net.runelite.client.ui.overlay.tooltip.Tooltip; +import net.runelite.client.ui.overlay.tooltip.TooltipManager; +import net.runelite.client.util.QuantityFormatter; +import net.runelite.client.ws.PartyService; + +class DpsOverlay extends Overlay +{ + private static final DecimalFormat DPS_FORMAT = new DecimalFormat("#0.0"); + private static final int PANEL_WIDTH_OFFSET = 10; // assumes 8 for panel component border + 2px between left and right + + static final OverlayMenuEntry RESET_ENTRY = new OverlayMenuEntry(RUNELITE_OVERLAY, "Reset", "DPS counter"); + static final OverlayMenuEntry PAUSE_ENTRY = new OverlayMenuEntry(RUNELITE_OVERLAY, "Pause", "DPS counter"); + static final OverlayMenuEntry UNPAUSE_ENTRY = new OverlayMenuEntry(RUNELITE_OVERLAY, "Unpause", "DPS counter"); + + private final DpsCounterPlugin dpsCounterPlugin; + private final DpsConfig dpsConfig; + private final PartyService partyService; + private final Client client; + private final TooltipManager tooltipManager; + + private final PanelComponent panelComponent = new PanelComponent(); + + @Inject + DpsOverlay(DpsCounterPlugin dpsCounterPlugin, DpsConfig dpsConfig, PartyService partyService, Client client, + TooltipManager tooltipManager) + { + super(dpsCounterPlugin); + this.dpsCounterPlugin = dpsCounterPlugin; + this.dpsConfig = dpsConfig; + this.partyService = partyService; + this.client = client; + this.tooltipManager = tooltipManager; + getMenuEntries().add(RESET_ENTRY); + setPaused(false); + } + + @Override + public void onMouseOver() + { + DpsMember total = dpsCounterPlugin.getTotal(); + Duration elapsed = total.elapsed(); + long s = elapsed.getSeconds(); + String format; + if (s >= 3600) + { + format = String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60)); + } + else + { + format = String.format("%d:%02d", s / 60, (s % 60)); + } + tooltipManager.add(new Tooltip("Elapsed time: " + format)); + } + + @Override + public Dimension render(Graphics2D graphics) + { + Map dpsMembers = dpsCounterPlugin.getMembers(); + if (dpsMembers.isEmpty()) + { + return null; + } + + boolean inParty = !partyService.getMembers().isEmpty(); + boolean showDamage = dpsConfig.showDamage(); + DpsMember total = dpsCounterPlugin.getTotal(); + boolean paused = total.isPaused(); + + panelComponent.getChildren().clear(); + + final String title = (inParty ? "Party " : "") + (showDamage ? "Damage" : "DPS") + (paused ? " (paused)" : ""); + panelComponent.getChildren().add( + TitleComponent.builder() + .text(title) + .build()); + + int maxWidth = ComponentConstants.STANDARD_WIDTH; + FontMetrics fontMetrics = graphics.getFontMetrics(); + + for (DpsMember dpsMember : dpsMembers.values()) + { + String left = dpsMember.getName(); + String right = showDamage ? QuantityFormatter.formatNumber(dpsMember.getDamage()) : DPS_FORMAT.format(dpsMember.getDps()); + maxWidth = Math.max(maxWidth, fontMetrics.stringWidth(left) + fontMetrics.stringWidth(right)); + panelComponent.getChildren().add( + LineComponent.builder() + .left(left) + .right(right) + .build()); + } + + panelComponent.setPreferredSize(new Dimension(maxWidth + PANEL_WIDTH_OFFSET, 0)); + + if (!inParty) + { + Player player = client.getLocalPlayer(); + if (player.getName() != null) + { + DpsMember self = dpsMembers.get(player.getName()); + + if (self != null && total.getDamage() > self.getDamage()) + { + panelComponent.getChildren().add( + LineComponent.builder() + .left(total.getName()) + .right(showDamage ? Integer.toString(total.getDamage()) : DPS_FORMAT.format(total.getDps())) + .build()); + } + } + } + + return panelComponent.render(graphics); + } + + void setPaused(boolean paused) + { + OverlayMenuEntry remove = paused ? PAUSE_ENTRY : UNPAUSE_ENTRY; + OverlayMenuEntry add = paused ? UNPAUSE_ENTRY : PAUSE_ENTRY; + getMenuEntries().remove(remove); + if (!getMenuEntries().contains(add)) + { + getMenuEntries().add(add); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsUpdate.java b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsUpdate.java new file mode 100644 index 0000000000..81e5859b38 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/dpscounter/DpsUpdate.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Adam + * 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.dpscounter; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import net.runelite.http.api.ws.messages.party.PartyMemberMessage; + +@Value +@EqualsAndHashCode(callSuper = true) +public class DpsUpdate extends PartyMemberMessage +{ + private int hit; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/SortOrder.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/SortOrder.java new file mode 100644 index 0000000000..14e3924e6f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/SortOrder.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020, Jake Wilson + * 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.timetracking; + +public enum SortOrder +{ + NONE, + ASC, + DESC +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java index f49f938485..b4ee559e84 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/Overlay.java @@ -69,4 +69,8 @@ public abstract class Overlay implements LayoutableRenderableEntity { return this.getClass().getSimpleName(); } + + public void onMouseOver() + { + } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java index 10870f73b1..09483f41df 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java @@ -235,7 +235,7 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener if (!isResizeable) { // On fixed mode, ABOVE_CHATBOX_RIGHT is in the same location as - // BOTTOM_RIGHT and CANVAST_TOP_RIGHT is same as TOP_RIGHT. + // BOTTOM_RIGHT and CANVAS_TOP_RIGHT is same as TOP_RIGHT. // Just use BOTTOM_RIGHT and TOP_RIGHT to prevent overlays from // drawing over each other. switch (overlayPosition) @@ -316,9 +316,14 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener graphics.setColor(previous); } - if (menuEntries == null && !client.isMenuOpen() && !client.isSpellSelected() && bounds.contains(mouse)) + if (!client.isMenuOpen() && !client.getSpellSelected() && bounds.contains(mouse)) { - menuEntries = createRightClickMenuEntries(overlay); + if (menuEntries == null) + { + menuEntries = createRightClickMenuEntries(overlay); + } + + overlay.onMouseOver(); } } } @@ -634,4 +639,4 @@ public class OverlayRenderer extends MouseAdapter implements KeyListener return entries; } -} +} \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/keyremapping/KeyRemappingListenerTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/keyremapping/KeyRemappingListenerTest.java new file mode 100644 index 0000000000..867413420e --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/keyremapping/KeyRemappingListenerTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020, Adam + * 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.keyremapping; + +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import java.awt.event.KeyEvent; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.client.config.ModifierlessKeybind; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class KeyRemappingListenerTest +{ + @Inject + private KeyRemappingListener keyRemappingListener; + + @Mock + @Bind + private Client client; + + @Mock + @Bind + private KeyRemappingPlugin keyRemappingPlugin; + + @Mock + @Bind + private KeyRemappingConfig keyRemappingConfig; + + @Before + public void setUp() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + + when(client.getGameState()).thenReturn(GameState.LOGGED_IN); + } + + @Test + public void testTypingStateChange() + { + when(keyRemappingConfig.cameraRemap()).thenReturn(true); + when(keyRemappingConfig.up()).thenReturn(new ModifierlessKeybind(KeyEvent.VK_W, 0)); + when(keyRemappingConfig.down()).thenReturn(new ModifierlessKeybind(KeyEvent.VK_S, 0)); + when(keyRemappingConfig.left()).thenReturn(new ModifierlessKeybind(KeyEvent.VK_A, 0)); + when(keyRemappingConfig.right()).thenReturn(new ModifierlessKeybind(KeyEvent.VK_D, 0)); + + when(keyRemappingPlugin.chatboxFocused()).thenReturn(true); + KeyEvent event = mock(KeyEvent.class); + when(event.getKeyCode()).thenReturn(KeyEvent.VK_D); + when(event.getExtendedKeyCode()).thenReturn(KeyEvent.VK_D); // for keybind matches() + keyRemappingListener.keyPressed(event); + verify(event).setKeyCode(KeyEvent.VK_RIGHT); + + // with the plugin now in typing mode, previously pressed and remapped keys should still be mapped + // on key release regardless + when(keyRemappingPlugin.isTyping()).thenReturn(true); + event = mock(KeyEvent.class); + when(event.getKeyCode()).thenReturn(KeyEvent.VK_D); + keyRemappingListener.keyReleased(event); + verify(event).setKeyCode(KeyEvent.VK_RIGHT); + } +}