diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index bce5806616..56608a7bed 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -25,9 +25,9 @@ object ProjectVersions { const val launcherVersion = "3.0.0" - const val rlVersion = "1.8.23" + const val rlVersion = "1.8.24" - const val openosrsVersion = "4.30.0" + const val openosrsVersion = "4.31.0" const val rsversion = 206 const val cacheversion = 165 diff --git a/runelite-api/src/main/java/net/runelite/api/ParamID.java b/runelite-api/src/main/java/net/runelite/api/ParamID.java index d769a715ed..efe4225da7 100644 --- a/runelite-api/src/main/java/net/runelite/api/ParamID.java +++ b/runelite-api/src/main/java/net/runelite/api/ParamID.java @@ -50,4 +50,13 @@ public final class ParamID public static final int SETTING_SLIDER_IS_DRAGGABLE = 1108; public static final int SETTING_SLIDER_DEADZONE = 1109; public static final int SETTING_SLIDER_DEADTIME = 1110; + + public static final int OC_ITEM_OP1 = 451; + public static final int OC_ITEM_OP2 = 452; + public static final int OC_ITEM_OP3 = 453; + public static final int OC_ITEM_OP4 = 454; + public static final int OC_ITEM_OP5 = 455; + public static final int OC_ITEM_OP6 = 456; + public static final int OC_ITEM_OP7 = 457; + public static final int OC_ITEM_OP8 = 458; } diff --git a/runelite-client/src/main/java/net/runelite/client/game/npcoverlay/NpcOverlay.java b/runelite-client/src/main/java/net/runelite/client/game/npcoverlay/NpcOverlay.java index 803c44fe26..3ed64c499b 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/npcoverlay/NpcOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/game/npcoverlay/NpcOverlay.java @@ -105,15 +105,12 @@ private void renderNpcOverlay(Graphics2D graphics, HighlightedNpc highlightedNpc if (highlightedNpc.isSwTile()) { - int size = npcComposition.getSize(); - LocalPoint lp = actor.getLocalLocation(); - - int x = lp.getX() - ((size - 1) * Perspective.LOCAL_TILE_SIZE / 2); - int y = lp.getY() - ((size - 1) * Perspective.LOCAL_TILE_SIZE / 2); - - Polygon southWestTilePoly = Perspective.getCanvasTilePoly(client, new LocalPoint(x, y)); - - renderPoly(graphics, borderColor, borderWidth, fillColor, southWestTilePoly); + LocalPoint lp = LocalPoint.fromWorld(client, actor.getWorldLocation()); + if (lp != null) + { + Polygon tilePoly = Perspective.getCanvasTilePoly(client, lp); + renderPoly(graphics, borderColor, borderWidth, fillColor, tilePoly); + } } if (highlightedNpc.isOutline()) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpConfig.java index f6c699e0e4..ea726525c8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpConfig.java @@ -33,6 +33,17 @@ public interface CorpConfig extends Config { String GROUP = "corp"; + @ConfigItem( + keyName = "leftClickCore", + name = "Left click walk on core", + description = "Prioritizes Walk here over Attack on the Dark energy core", + position = 1 + ) + default boolean leftClickCore() + { + return true; + } + @ConfigItem( keyName = "showDamage", name = "Show damage overlay", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpPlugin.java index dda20fd608..05a5afdf02 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/corp/CorpPlugin.java @@ -37,12 +37,15 @@ import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameState; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; import net.runelite.api.NPC; import net.runelite.api.NpcID; import net.runelite.api.Varbits; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.HitsplatApplied; import net.runelite.api.events.InteractingChanged; +import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.NpcSpawned; import net.runelite.api.events.VarbitChanged; @@ -66,6 +69,9 @@ @Slf4j public class CorpPlugin extends Plugin { + private static final String ATTACK = "Attack"; + private static final String DARK_ENERGY_CORE = "Dark energy core"; + @Getter(AccessLevel.PACKAGE) private NPC corp; @@ -243,4 +249,24 @@ public void onVarbitChanged(VarbitChanged varbitChanged) } } } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded menuEntryAdded) + { + final MenuEntry menuEntry = menuEntryAdded.getMenuEntry(); + final NPC npc = menuEntry.getNpc(); + if (npc == null || !DARK_ENERGY_CORE.equals(npc.getName())) + { + return; + } + + if (menuEntry.getType() != MenuAction.NPC_SECOND_OPTION + || !menuEntry.getOption().equals(ATTACK) + || !config.leftClickCore()) + { + return; + } + + menuEntry.setDeprioritized(true); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java index 139e3ef835..b554fd352d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java @@ -176,4 +176,15 @@ default boolean hideProjectiles() { return false; } + + @ConfigItem( + position = 14, + keyName = "hideDeadNpcs", + name = "Hide Dead NPCs", + description = "Hides NPCs when their health reaches 0" + ) + default boolean hideDeadNpcs() + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java index a8754571e4..ee63182d03 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java @@ -68,6 +68,7 @@ public class EntityHiderPlugin extends Plugin private boolean hideLocalPlayer2D; private boolean hideNPCs; private boolean hideNPCs2D; + private boolean hideDeadNpcs; private boolean hidePets; private boolean hideAttackers; private boolean hideProjectiles; @@ -118,6 +119,7 @@ private void updateConfig() hideNPCs = config.hideNPCs(); hideNPCs2D = config.hideNPCs2D(); + hideDeadNpcs = config.hideDeadNpcs(); hidePets = config.hidePets(); @@ -187,6 +189,12 @@ else if (renderable instanceof NPC) return !hidePets; } + // dead npcs can also be interacting so prioritize it over the interacting check + if (npc.isDead() && hideDeadNpcs) + { + return false; + } + if (npc.getInteracting() == client.getLocalPlayer()) { boolean b = hideAttackers; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index 9b5cb7dd9b..b59afe8358 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -211,7 +211,7 @@ private JPanel buildActionsPanel() SwingUtil.removeButtonDecorations(collapseBtn); collapseBtn.setIcon(EXPAND_ICON); collapseBtn.setSelectedIcon(COLLAPSE_ICON); - SwingUtil.addModalTooltip(collapseBtn, "Collapse All", "Un-Collapse All"); + SwingUtil.addModalTooltip(collapseBtn, "Expand All", "Collapse All"); collapseBtn.setBackground(ColorScheme.DARKER_GRAY_COLOR); collapseBtn.setUI(new BasicButtonUI()); // substance breaks the layout collapseBtn.addActionListener(ev -> changeCollapse()); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java index b88af6f997..00515d0367 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java @@ -878,4 +878,15 @@ default boolean swapTemporossLeave() { return false; } + + @ConfigItem( + keyName = "removeDeadNpcMenus", + name = "Remove dead npc menus", + description = "Remove menu options such as Attack and Talk-to from dead npcs", + section = npcSection + ) + default boolean removeDeadNpcMenus() + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java index 230077f4a7..418b078b1e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java @@ -42,6 +42,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; import javax.inject.Inject; @@ -55,10 +56,13 @@ import net.runelite.api.MenuEntry; import net.runelite.api.NPC; import net.runelite.api.NPCComposition; +import net.runelite.api.NpcID; import net.runelite.api.ObjectComposition; +import net.runelite.api.ParamID; import net.runelite.api.events.ClientTick; import net.runelite.api.events.MenuOpened; import net.runelite.api.events.PostItemComposition; +import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetID; import net.runelite.api.widgets.WidgetInfo; import net.runelite.client.callback.ClientThread; @@ -100,6 +104,9 @@ public class MenuEntrySwapperPlugin extends Plugin private static final String ITEM_KEY_PREFIX = "item_"; private static final String OBJECT_KEY_PREFIX = "object_"; private static final String NPC_KEY_PREFIX = "npc_"; + private static final String NPC_SHIFT_KEY_PREFIX = "npc_shift_"; + private static final String WORN_ITEM_KEY_PREFIX = "wornitem_"; + private static final String WORN_ITEM_SHIFT_KEY_PREFIX = "wornitem_shift_"; // Shift click private static final WidgetMenuOption FIXED_INVENTORY_TAB_CONFIGURE_SC = new WidgetMenuOption(CONFIGURE, @@ -522,6 +529,33 @@ private void unsetItemSwapConfig(boolean shift, int itemId) configManager.unsetConfiguration(shift ? SHIFTCLICK_CONFIG_GROUP : MenuEntrySwapperConfig.GROUP, ITEM_KEY_PREFIX + itemId); } + private Integer getWornItemSwapConfig(boolean shift, int itemId) + { + itemId = ItemVariationMapping.map(itemId); + String config = configManager.getConfiguration(MenuEntrySwapperConfig.GROUP, + (shift ? WORN_ITEM_SHIFT_KEY_PREFIX : WORN_ITEM_KEY_PREFIX) + itemId); + if (config == null || config.isEmpty()) + { + return null; + } + + return Integer.parseInt(config); + } + + private void setWornItemSwapConfig(boolean shift, int itemId, int index) + { + itemId = ItemVariationMapping.map(itemId); + configManager.setConfiguration(MenuEntrySwapperConfig.GROUP, + (shift ? WORN_ITEM_SHIFT_KEY_PREFIX : WORN_ITEM_KEY_PREFIX) + itemId, index); + } + + private void unsetWornItemSwapConfig(boolean shift, int itemId) + { + itemId = ItemVariationMapping.map(itemId); + configManager.unsetConfiguration(MenuEntrySwapperConfig.GROUP, + (shift ? WORN_ITEM_SHIFT_KEY_PREFIX : WORN_ITEM_KEY_PREFIX) + itemId); + } + private void enableCustomization() { rebuildCustomizationMenus(); @@ -544,6 +578,7 @@ public void onMenuOpened(MenuOpened event) { configureObjectClick(event); configureNpcClick(event); + configureWornItems(event); return; } @@ -711,6 +746,26 @@ private void configureObjectClick(MenuOpened event) } } + private Consumer walkHereConsumer(boolean shift, NPCComposition composition) + { + return e -> + { + final String message = new ChatMessageBuilder() + .append("The default ").append(shift ? "shift" : "left").append(" click option for '").append(Text.removeTags(composition.getName())).append("' ") + .append("has been set to Walk here.") + .build(); + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage(message) + .build()); + + log.debug("Set npc {} click swap for {} to Walk here", shift ? "shift" : "left", composition.getId()); + + setNpcSwapConfig(shift, composition.getId(), -1); + }; + } + private void configureNpcClick(MenuOpened event) { if (!shiftModifier() || !config.npcLeftClickCustomization()) @@ -729,18 +784,20 @@ private void configureNpcClick(MenuOpened event) final NPC npc = entry.getNpc(); assert npc != null; final NPCComposition composition = npc.getTransformedComposition(); + assert composition != null; final String[] actions = composition.getActions(); - final Integer swapConfig = getNpcSwapConfig(composition.getId()); + final Integer swapConfig = getNpcSwapConfig(false, composition.getId()); final boolean hasAttack = Arrays.stream(composition.getActions()).anyMatch("Attack"::equalsIgnoreCase); - final MenuAction currentAction = swapConfig != null ? NPC_MENU_TYPES.get(swapConfig) : + final MenuAction currentAction = swapConfig == null ? // Attackable NPCs always have Attack as the first, last (deprioritized), or when hidden, no, option. // Due to this the default action would be either Attack or the first non-Attack option, based on // the game settings. Since it may be valid to swap an option up to override Attack, even when Attack // is left-click, we cannot assume any default currentAction on attackable NPCs. // Non-attackable NPCS have a predictable default action which we can prevent a swap to if no swap // config is set, which just avoids showing a Swap option on a 1-op NPC, which looks odd. - (hasAttack ? null : defaultAction(composition)); + (hasAttack ? null : defaultAction(composition)) : + (swapConfig == -1 ? MenuAction.WALK : NPC_MENU_TYPES.get(swapConfig)); for (int actionIdx = 0; actionIdx < NPC_MENU_TYPES.size(); ++actionIdx) { @@ -759,8 +816,7 @@ private void configureNpcClick(MenuOpened event) continue; } - if ("Pickpocket".equals(actions[actionIdx]) - || "Knock-Out".equals(actions[actionIdx]) + if ("Knock-Out".equals(actions[actionIdx]) || "Lure".equals(actions[actionIdx])) { // https://secure.runescape.com/m=news/another-message-about-unofficial-clients?oldschool=1 @@ -785,11 +841,24 @@ private void configureNpcClick(MenuOpened event) log.debug("Set npc swap for {} to {}", composition.getId(), menuAction); - setNpcSwapConfig(composition.getId(), menuIdx); + setNpcSwapConfig(false, composition.getId(), menuIdx); }); } - if (getNpcSwapConfig(composition.getId()) != null) + // Walk here swap + client.createMenuEntry(idx) + .setOption("Swap left click Walk here") + .setTarget(entry.getTarget()) + .setType(MenuAction.RUNELITE) + .onClick(walkHereConsumer(false, composition)); + + client.createMenuEntry(idx) + .setOption("Swap shift click Walk here") + .setTarget(entry.getTarget()) + .setType(MenuAction.RUNELITE) + .onClick(walkHereConsumer(true, composition)); + + if (getNpcSwapConfig(true, composition.getId()) != null || getNpcSwapConfig(false, composition.getId()) != null) // NOPMD: BrokenNullCheck { // Reset client.createMenuEntry(idx) @@ -799,8 +868,8 @@ private void configureNpcClick(MenuOpened event) .onClick(e -> { final String message = new ChatMessageBuilder() - .append("The default left click option for '").append(Text.removeTags(composition.getName())).append("' ") - .append("has been reset.") + .append("The default left and shift click options for '").append(Text.removeTags(composition.getName())).append("' ") + .append("have been reset.") .build(); chatMessageManager.queue(QueuedMessage.builder() @@ -809,13 +878,129 @@ private void configureNpcClick(MenuOpened event) .build()); log.debug("Unset npc swap for {}", composition.getId()); - unsetNpcSwapConfig(composition.getId()); + unsetNpcSwapConfig(true, composition.getId()); + unsetNpcSwapConfig(false, composition.getId()); }); } } } } + private void configureWornItems(MenuOpened event) + { + if (!shiftModifier()) + { + return; + } + + final MenuEntry[] entries = event.getMenuEntries(); + for (int idx = entries.length - 1; idx >= 0; --idx) + { + final MenuEntry entry = entries[idx]; + Widget w = entry.getWidget(); + + if (w != null && WidgetInfo.TO_GROUP(w.getId()) == WidgetID.EQUIPMENT_GROUP_ID + && "Examine".equals(entry.getOption()) && entry.getIdentifier() == 10) + { + w = w.getChild(1); + if (w != null && w.getItemId() > -1) + { + final ItemComposition itemComposition = itemManager.getItemComposition(w.getItemId()); + final Integer leftClickOp = getWornItemSwapConfig(false, itemComposition.getId()); + final Integer shiftClickOp = getWornItemSwapConfig(true, itemComposition.getId()); + + for (int paramId = ParamID.OC_ITEM_OP1, opId = 2; paramId <= ParamID.OC_ITEM_OP8; ++paramId, ++opId) + { + final String opName = itemComposition.getStringValue(paramId); + if (!Strings.isNullOrEmpty(opName)) + { + if (leftClickOp == null || leftClickOp != opId) + { + client.createMenuEntry(idx) + .setOption("Swap left click " + opName) + .setTarget(entry.getTarget()) + .setType(MenuAction.RUNELITE) + .onClick(wornItemConsumer(itemComposition, opName, opId, false)); + } + if (shiftClickOp == null || shiftClickOp != opId) + { + client.createMenuEntry(idx) + .setOption("Swap shift click " + opName) + .setTarget(entry.getTarget()) + .setType(MenuAction.RUNELITE) + .onClick(wornItemConsumer(itemComposition, opName, opId, true)); + } + } + } + + if (leftClickOp != null) + { + client.createMenuEntry(idx) + .setOption("Reset swap left click") + .setTarget(entry.getTarget()) + .setType(MenuAction.RUNELITE) + .onClick(e -> + { + final String message = new ChatMessageBuilder() + .append("The default worn left click option for '").append(itemComposition.getName()).append("' ") + .append("has been reset.") + .build(); + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage(message) + .build()); + + log.debug("Unset worn item left swap for {}", itemComposition.getName()); + unsetWornItemSwapConfig(false, itemComposition.getId()); + }); + } + if (shiftClickOp != null) + { + client.createMenuEntry(idx) + .setOption("Reset swap shift click") + .setTarget(entry.getTarget()) + .setType(MenuAction.RUNELITE) + .onClick(e -> + { + final String message = new ChatMessageBuilder() + .append("The default worn shift click option for '").append(itemComposition.getName()).append("' ") + .append("has been reset.") + .build(); + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage(message) + .build()); + + log.debug("Unset worn item shift swap for {}", itemComposition.getName()); + unsetWornItemSwapConfig(true, itemComposition.getId()); + }); + } + } + } + } + } + + private Consumer wornItemConsumer(ItemComposition itemComposition, String opName, int opIdx, boolean shift) + { + return e -> + { + final String message = new ChatMessageBuilder() + .append("The default worn ").append(shift ? "shift" : "left").append(" click option for '").append(Text.removeTags(itemComposition.getName())).append("' ") + .append("has been set to '").append(opName).append("'.") + .build(); + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage(message) + .build()); + + log.debug("Set worn item {} swap for {} to {}", shift ? "shift" : "left", itemComposition.getName(), opIdx); + setWornItemSwapConfig(shift, itemComposition.getId(), opIdx); + }; + } + private boolean swapBank(MenuEntry menuEntry, MenuAction type) { if (type != MenuAction.CC_OP && type != MenuAction.CC_OP_LOW_PRIORITY) @@ -952,6 +1137,25 @@ else if (swapAction == menuEntry.getItemOp()) } } + // Worn items swap + Widget w = menuEntry.getWidget(); + if (w != null && WidgetInfo.TO_GROUP(w.getId()) == WidgetID.EQUIPMENT_GROUP_ID) + { + w = w.getChild(1); + if (w != null && w.getItemId() > -1) + { + final Integer wornItemSwapConfig = getWornItemSwapConfig(shiftModifier(), w.getItemId()); + if (wornItemSwapConfig != null) + { + if (wornItemSwapConfig == menuEntry.getIdentifier()) + { + swap(optionIndexes, menuEntries, index, menuEntries.length - 1); + } + return; + } + } + } + if (OBJECT_MENU_TYPES.contains(menuAction)) { // Get multiloc id @@ -981,24 +1185,33 @@ else if (swapAction == menuEntry.getItemOp()) assert npc != null; final NPCComposition composition = npc.getTransformedComposition(); - Integer customOption = getNpcSwapConfig(composition.getId()); + Integer customOption = getNpcSwapConfig(shiftModifier(), composition.getId()); if (customOption != null) { - MenuAction swapAction = NPC_MENU_TYPES.get(customOption); - if (swapAction == menuAction) + // Walk here swap + if (customOption == -1) + { + // we can achieve this by just deprioritizing the normal npc menus + menuEntry.setDeprioritized(true); + } + else { - // Advance to the top-most op for this NPC. Normally menuEntries.length - 1 is examine, and swapping - // with that works due to it being sorted later, but if other plugins like NPC indicators add additional - // menus before examine that are also >1000, like RUNELITE menus, that would result in the >1000 menus being - // reordered relative to each other. - int i = index; - while (i < menuEntries.length - 1 && NPC_MENU_TYPES.contains(menuEntries[i + 1].getType())) + MenuAction swapAction = NPC_MENU_TYPES.get(customOption); + if (swapAction == menuAction) { - ++i; - } + // Advance to the top-most op for this NPC. Normally menuEntries.length - 1 is examine, and swapping + // with that works due to it being sorted later, but if other plugins like NPC indicators add additional + // menus before examine that are also >1000, like RUNELITE menus, that would result in the >1000 menus being + // reordered relative to each other. + int i = index; + while (i < menuEntries.length - 1 && NPC_MENU_TYPES.contains(menuEntries[i + 1].getType())) + { + ++i; + } - swap(optionIndexes, menuEntries, index, i); - return; + swap(optionIndexes, menuEntries, index, i); + return; + } } } } @@ -1049,6 +1262,53 @@ public void onClientTick(ClientTick clientTick) { swapMenuEntry(menuEntries, idx++, entry); } + + if (config.removeDeadNpcMenus()) + { + removeDeadNpcs(); + } + } + + private void removeDeadNpcs() + { + MenuEntry[] oldEntries = client.getMenuEntries(); + MenuEntry[] newEntries = Arrays.stream(oldEntries) + .filter(e -> + { + final NPC npc = e.getNpc(); + if (npc == null) + { + return true; + } + + final int id = npc.getId(); + switch (id) + { + // These NPCs hit 0hp but don't actually die + case NpcID.GARGOYLE: + case NpcID.GARGOYLE_413: + case NpcID.GARGOYLE_1543: + case NpcID.ZYGOMITE: + case NpcID.ZYGOMITE_1024: + case NpcID.ANCIENT_ZYGOMITE: + case NpcID.ROCKSLUG: + case NpcID.ROCKSLUG_422: + case NpcID.DESERT_LIZARD: + case NpcID.DESERT_LIZARD_460: + case NpcID.DESERT_LIZARD_461: + case NpcID.ICE_DEMON: + case NpcID.ICE_DEMON_7585: + return true; + default: + return !npc.isDead(); + + } + }) + .toArray(MenuEntry[]::new); + if (oldEntries.length != newEntries.length) + { + client.setMenuEntries(newEntries); + } } @Subscribe @@ -1278,9 +1538,10 @@ private static MenuAction defaultAction(ObjectComposition objectComposition) return null; } - private Integer getNpcSwapConfig(int npcId) + private Integer getNpcSwapConfig(boolean shift, int npcId) { - String config = configManager.getConfiguration(MenuEntrySwapperConfig.GROUP, NPC_KEY_PREFIX + npcId); + String config = configManager.getConfiguration(MenuEntrySwapperConfig.GROUP, + (shift ? NPC_SHIFT_KEY_PREFIX : NPC_KEY_PREFIX) + npcId); if (config == null || config.isEmpty()) { return null; @@ -1289,14 +1550,14 @@ private Integer getNpcSwapConfig(int npcId) return Integer.parseInt(config); } - private void setNpcSwapConfig(int npcId, int index) + private void setNpcSwapConfig(boolean shift, int npcId, int index) { - configManager.setConfiguration(MenuEntrySwapperConfig.GROUP, NPC_KEY_PREFIX + npcId, index); + configManager.setConfiguration(MenuEntrySwapperConfig.GROUP, (shift ? NPC_SHIFT_KEY_PREFIX : NPC_KEY_PREFIX) + npcId, index); } - private void unsetNpcSwapConfig(int npcId) + private void unsetNpcSwapConfig(boolean shift, int npcId) { - configManager.unsetConfiguration(MenuEntrySwapperConfig.GROUP, NPC_KEY_PREFIX + npcId); + configManager.unsetConfiguration(MenuEntrySwapperConfig.GROUP, (shift ? NPC_SHIFT_KEY_PREFIX : NPC_KEY_PREFIX) + npcId); } private static MenuAction defaultAction(NPCComposition composition) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesConfig.java index 2a956da851..1f93f0483f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesConfig.java @@ -78,11 +78,22 @@ default boolean showXpHour() return true; } + @ConfigItem( + keyName = "showTimeTilGoal", + name = "Show time til goal", + description = "Shows the amount of time until goal level in the globe tooltip box", + position = 4 + ) + default boolean showTimeTilGoal() + { + return true; + } + @ConfigItem( keyName = "hideMaxed", name = "Hide maxed skills", description = "Stop globes from showing up for level 99 skills", - position = 4 + position = 14 ) default boolean hideMaxed() { @@ -93,7 +104,7 @@ default boolean hideMaxed() keyName = "showVirtualLevel", name = "Show virtual level", description = "Shows virtual level if over 99 in a skill and Hide maxed skill is not checked", - position = 5 + position = 15 ) default boolean showVirtualLevel() { @@ -104,7 +115,7 @@ default boolean showVirtualLevel() keyName = "enableCustomArcColor", name = "Enable custom arc color", description = "Enables the custom coloring of the globe's arc instead of using the skill's default color.", - position = 6 + position = 16 ) default boolean enableCustomArcColor() { @@ -116,7 +127,7 @@ default boolean enableCustomArcColor() keyName = "Progress arc color", name = "Progress arc color", description = "Change the color of the progress arc in the xp orb", - position = 7 + position = 17 ) default Color progressArcColor() { @@ -128,7 +139,7 @@ default Color progressArcColor() keyName = "Progress orb outline color", name = "Progress orb outline color", description = "Change the color of the progress orb outline", - position = 8 + position = 18 ) default Color progressOrbOutLineColor() { @@ -140,7 +151,7 @@ default Color progressOrbOutLineColor() keyName = "Progress orb background color", name = "Progress orb background color", description = "Change the color of the progress orb background", - position = 9 + position = 19 ) default Color progressOrbBackgroundColor() { @@ -151,7 +162,7 @@ default Color progressOrbBackgroundColor() keyName = "Progress arc width", name = "Progress arc width", description = "Change the stroke width of the progress arc", - position = 10 + position = 20 ) @Units(Units.PIXELS) default int progressArcStrokeWidth() @@ -163,7 +174,7 @@ default int progressArcStrokeWidth() keyName = "Orb size", name = "Size of orbs", description = "Change the size of the xp orbs", - position = 11 + position = 21 ) @Units(Units.PIXELS) default int xpOrbSize() @@ -175,7 +186,7 @@ default int xpOrbSize() keyName = "Orb duration", name = "Duration of orbs", description = "Change the duration the xp orbs are visible", - position = 12 + position = 22 ) @Units(Units.SECONDS) default int xpOrbDuration() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesOverlay.java index 1243c76712..abdc1998a7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xpglobes/XpGlobesOverlay.java @@ -352,6 +352,16 @@ private void drawTooltip(XpGlobe mouseOverSkill, int goalXp) .build()); } } + + if (config.showTimeTilGoal()) + { + String timeLeft = xpTrackerService.getTimeTilGoal(mouseOverSkill.getSkill()); + xpTooltip.getChildren().add(LineComponent.builder() + .left("Time left:") + .leftColor(Color.ORANGE) + .right(timeLeft) + .build()); + } } tooltipManager.add(this.xpTooltip); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java index cc58c69b55..c5a8226666 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java @@ -204,7 +204,7 @@ else if (durationHoursTotal == 1) // return time remaining in hh:mm:ss or mm:ss format where hh can be > 24 if (durationHoursTotal > 0) { - return String.format("%02d:%02d:%02d", durationHoursTotal, durationMinutes, durationSeconds); + return String.format("%d:%02d:%02d", durationHoursTotal, durationMinutes, durationSeconds); } // Minutes and seconds will always be present diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerService.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerService.java index f423757528..379aadf9d6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerService.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerService.java @@ -62,4 +62,9 @@ public interface XpTrackerService * Get the amount of XP left until goal level */ int getEndGoalXp(Skill skill); + + /** + * Get the amount of time left until goal level + */ + String getTimeTilGoal(Skill skill); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerServiceImpl.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerServiceImpl.java index 0968d14892..d10f410541 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerServiceImpl.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerServiceImpl.java @@ -80,4 +80,10 @@ public int getEndGoalXp(Skill skill) { return plugin.getSkillSnapshot(skill).getEndGoalXp(); } + + @Override + public String getTimeTilGoal(Skill skill) + { + return plugin.getSkillSnapshot(skill).getTimeTillGoalShort(); + } }