diff --git a/runelite-api/src/main/java/net/runelite/api/AnimationID.java b/runelite-api/src/main/java/net/runelite/api/AnimationID.java index 122a66c047..fc8303ac04 100644 --- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java +++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java @@ -204,6 +204,7 @@ public final class AnimationID public static final int LEAGUE_HOME_TELEPORT_4 = 8803; public static final int LEAGUE_HOME_TELEPORT_5 = 8805; public static final int LEAGUE_HOME_TELEPORT_6 = 8807; + public static final int RAID_LIGHT_ANIMATION = 3101; public static final int CONSTRUCTION = 3676; public static final int CONSTRUCTION_IMCANDO = 8912; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java index 0b926d30e0..f07a5a6bbc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java @@ -29,7 +29,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.Builder; import lombok.Data; -import lombok.Value; import net.runelite.api.coords.WorldPoint; @Data @@ -66,11 +65,4 @@ class GroundItem { return lootType != LootType.UNKNOWN; } - - @Value - static class GroundItemKey - { - private int itemId; - private WorldPoint location; - } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java index 96f454cd1c..01693334e1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java @@ -403,4 +403,26 @@ public interface GroundItemsConfig extends Config { return false; } + + @ConfigItem( + keyName = "showLootbeamForHighlighted", + name = "Highlighted item lootbeams", + description = "Configures lootbeams to show for all highlighted items.", + position = 30 + ) + default boolean showLootbeamForHighlighted() + { + return false; + } + + @ConfigItem( + keyName = "showLootbeamTier", + name = "Lootbeam tier", + description = "Configures which price tiers will trigger a lootbeam", + position = 31 + ) + default HighlightTier showLootbeamTier() + { + return HighlightTier.HIGH; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java index e939470613..9fb512c688 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java @@ -49,6 +49,7 @@ import net.runelite.api.coords.WorldPoint; import static net.runelite.client.plugins.grounditems.GroundItemsPlugin.MAX_QUANTITY; import net.runelite.client.plugins.grounditems.config.DespawnTimerMode; import static net.runelite.client.plugins.grounditems.config.ItemHighlightMode.MENU; +import static net.runelite.client.plugins.grounditems.config.ItemHighlightMode.NONE; import net.runelite.client.plugins.grounditems.config.PriceDisplayMode; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; @@ -107,7 +108,8 @@ public class GroundItemsOverlay extends Overlay @Override public Dimension render(Graphics2D graphics) { - final boolean dontShowOverlay = (config.itemHighlightMode() == MENU || plugin.isHideAll()) && !plugin.isHotKeyPressed(); + final boolean dontShowOverlay = (config.itemHighlightMode() == MENU || config.itemHighlightMode() == NONE + || plugin.isHideAll()) && !plugin.isHotKeyPressed(); if (dontShowOverlay && !config.highlightTiles()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java index b7a8fc6500..a9e479885e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -28,7 +28,9 @@ package net.runelite.client.plugins.grounditems; import com.google.common.cache.CacheBuilder; import com.google.common.cache.LoadingCache; import com.google.common.collect.EvictingQueue; +import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Table; import com.google.inject.Provides; import java.awt.Color; import java.awt.Rectangle; @@ -38,7 +40,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; @@ -71,6 +73,7 @@ import net.runelite.api.events.ItemSpawned; import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuOptionClicked; import net.runelite.client.Notifier; +import net.runelite.client.callback.ClientThread; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; @@ -83,7 +86,7 @@ import net.runelite.client.input.MouseManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.grounditems.config.HighlightTier; -import static net.runelite.client.plugins.grounditems.config.ItemHighlightMode.OVERLAY; +import net.runelite.client.plugins.grounditems.config.ItemHighlightMode; import net.runelite.client.plugins.grounditems.config.MenuHighlightMode; import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.BOTH; import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.NAME; @@ -158,6 +161,9 @@ public class GroundItemsPlugin extends Plugin @Inject private Client client; + @Inject + private ClientThread clientThread; + @Inject private ItemManager itemManager; @@ -177,12 +183,13 @@ public class GroundItemsPlugin extends Plugin private ScheduledExecutorService executor; @Getter - private final Map collectedGroundItems = new LinkedHashMap<>(); + private final Table collectedGroundItems = HashBasedTable.create(); private List priceChecks = ImmutableList.of(); private LoadingCache highlightedItems; private LoadingCache hiddenItems; private final Queue droppedItemQueue = EvictingQueue.create(16); // recently dropped items private int lastUsedItem; + private final Map lootbeams = new HashMap<>(); @Provides GroundItemsConfig provideConfig(ConfigManager configManager) @@ -213,6 +220,7 @@ public class GroundItemsPlugin extends Plugin hiddenItemList = null; highlightedItemsList = null; collectedGroundItems.clear(); + clientThread.invokeLater(this::removeAllLootbeams); } @Subscribe @@ -230,6 +238,7 @@ public class GroundItemsPlugin extends Plugin if (event.getGameState() == GameState.LOADING) { collectedGroundItems.clear(); + lootbeams.clear(); } } @@ -240,19 +249,23 @@ public class GroundItemsPlugin extends Plugin Tile tile = itemSpawned.getTile(); GroundItem groundItem = buildGroundItem(tile, item); - - GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation()); - GroundItem existing = collectedGroundItems.putIfAbsent(groundItemKey, groundItem); + GroundItem existing = collectedGroundItems.get(tile.getWorldLocation(), item.getId()); if (existing != null) { existing.setQuantity(existing.getQuantity() + groundItem.getQuantity()); // The spawn time remains set at the oldest spawn } + else + { + collectedGroundItems.put(tile.getWorldLocation(), item.getId(), groundItem); + } if (!config.onlyShowLoot()) { notifyHighlightedItem(groundItem); } + + handleLootbeam(tile.getWorldLocation()); } @Subscribe @@ -261,8 +274,7 @@ public class GroundItemsPlugin extends Plugin TileItem item = itemDespawned.getItem(); Tile tile = itemDespawned.getTile(); - GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation()); - GroundItem groundItem = collectedGroundItems.get(groundItemKey); + GroundItem groundItem = collectedGroundItems.get(tile.getWorldLocation(), item.getId()); if (groundItem == null) { return; @@ -270,7 +282,7 @@ public class GroundItemsPlugin extends Plugin if (groundItem.getQuantity() <= item.getQuantity()) { - collectedGroundItems.remove(groundItemKey); + collectedGroundItems.remove(tile.getWorldLocation(), item.getId()); } else { @@ -280,6 +292,8 @@ public class GroundItemsPlugin extends Plugin // time groundItem.setSpawnTime(null); } + + handleLootbeam(tile.getWorldLocation()); } @Subscribe @@ -291,12 +305,13 @@ public class GroundItemsPlugin extends Plugin int newQuantity = itemQuantityChanged.getNewQuantity(); int diff = newQuantity - oldQuantity; - GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation()); - GroundItem groundItem = collectedGroundItems.get(groundItemKey); + GroundItem groundItem = collectedGroundItems.get(tile.getWorldLocation(), item.getId()); if (groundItem != null) { groundItem.setQuantity(groundItem.getQuantity() + diff); } + + handleLootbeam(tile.getWorldLocation()); } @Subscribe @@ -366,8 +381,7 @@ public class GroundItemsPlugin extends Plugin for (ItemStack itemStack : items) { WorldPoint location = WorldPoint.fromLocal(client, itemStack.getLocation()); - GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(itemStack.getId(), location); - GroundItem groundItem = collectedGroundItems.get(groundItemKey); + GroundItem groundItem = collectedGroundItems.get(location, itemStack.getId()); if (groundItem != null) { groundItem.setLootType(lootType); @@ -460,12 +474,14 @@ public class GroundItemsPlugin extends Plugin } priceChecks = priceCheckBuilder.build(); + + clientThread.invokeLater(this::handleLootbeams); } @Subscribe public void onMenuEntryAdded(MenuEntryAdded event) { - if (config.itemHighlightMode() != OVERLAY) + if (config.itemHighlightMode() == ItemHighlightMode.MENU || config.itemHighlightMode() == ItemHighlightMode.BOTH) { final boolean telegrabEntry = event.getOption().equals("Cast") && event.getTarget().startsWith(TELEGRAB_TEXT) && event.getType() == CAST_ON_ITEM; if (!(event.getOption().equals("Take") && event.getType() == THIRD_OPTION) && !telegrabEntry) @@ -481,8 +497,7 @@ public class GroundItemsPlugin extends Plugin MenuEntry lastEntry = menuEntries[menuEntries.length - 1]; final WorldPoint worldPoint = WorldPoint.fromScene(client, sceneX, sceneY, client.getPlane()); - GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(itemId, worldPoint); - GroundItem groundItem = collectedGroundItems.get(groundItemKey); + GroundItem groundItem = collectedGroundItems.get(worldPoint, itemId); int quantity = groundItem.getQuantity(); final int gePrice = groundItem.getGePrice(); @@ -705,4 +720,103 @@ public class GroundItemsPlugin extends Plugin lastUsedItem = clickedItem.getId(); } } + + private void handleLootbeam(WorldPoint worldPoint) + { + /* + * Return and remove the lootbeam from this location if lootbeam are disabled + * Lootbeam can be at this location if the config was just changed + */ + if (!(config.showLootbeamForHighlighted() || config.showLootbeamTier() != HighlightTier.OFF)) + { + removeLootbeam(worldPoint); + return; + } + + int price = -1; + Collection groundItems = collectedGroundItems.row(worldPoint).values(); + for (GroundItem groundItem : groundItems) + { + if ((config.onlyShowLoot() && !groundItem.isMine())) + { + continue; + } + + /* + * highlighted items have the highest priority so if an item is highlighted at this location + * we can early return + */ + NamedQuantity item = new NamedQuantity(groundItem); + if (config.showLootbeamForHighlighted() + && TRUE.equals(highlightedItems.getUnchecked(item))) + { + addLootbeam(worldPoint, config.highlightedColor()); + return; + } + + // Explicit hide takes priority over implicit highlight + if (TRUE.equals(hiddenItems.getUnchecked(item))) + { + continue; + } + + int itemPrice = getValueByMode(groundItem.getGePrice(), groundItem.getHaPrice()); + price = Math.max(itemPrice, price); + } + + if (config.showLootbeamTier() != HighlightTier.OFF) + { + for (PriceHighlight highlight : priceChecks) + { + if (price > highlight.getPrice() && price > config.showLootbeamTier().getValueFromTier(config)) + { + addLootbeam(worldPoint, highlight.color); + return; + } + } + } + + removeLootbeam(worldPoint); + } + + private void handleLootbeams() + { + for (WorldPoint worldPoint : collectedGroundItems.rowKeySet()) + { + handleLootbeam(worldPoint); + } + } + + private void removeAllLootbeams() + { + for (Lootbeam lootbeam : lootbeams.values()) + { + lootbeam.remove(); + } + + lootbeams.clear(); + } + + private void addLootbeam(WorldPoint worldPoint, Color color) + { + Lootbeam lootbeam = lootbeams.get(worldPoint); + if (lootbeam == null) + { + lootbeam = new Lootbeam(client, worldPoint, color); + lootbeams.put(worldPoint, lootbeam); + } + else + { + lootbeam.setColor(color); + } + } + + private void removeLootbeam(WorldPoint worldPoint) + { + Lootbeam lootbeam = lootbeams.remove(worldPoint); + if (lootbeam != null) + { + lootbeam.remove(); + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/Lootbeam.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/Lootbeam.java new file mode 100644 index 0000000000..1d95a39dc1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/Lootbeam.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021, Trevor + * 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.grounditems; + +import net.runelite.api.AnimationID; +import net.runelite.api.Client; +import net.runelite.api.JagexColor; +import net.runelite.api.RuneLiteObject; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import java.awt.Color; + +class Lootbeam +{ + private static final int RAID_LIGHT_MODEL = 5809; + private static final short RAID_LIGHT_FIND_COLOR = 6371; + + private final RuneLiteObject runeLiteObject; + private final Client client; + private Color color; + + public Lootbeam(Client client, WorldPoint worldPoint, Color color) + { + this.client = client; + runeLiteObject = client.createRuneLiteObject(); + + setColor(color); + runeLiteObject.setAnimation(client.loadAnimation(AnimationID.RAID_LIGHT_ANIMATION)); + runeLiteObject.setShouldLoop(true); + + LocalPoint lp = LocalPoint.fromWorld(client, worldPoint); + runeLiteObject.setLocation(lp, client.getPlane()); + + runeLiteObject.setActive(true); + } + + public void setColor(Color color) + { + if (this.color != null && this.color.equals(color)) + { + return; + } + + this.color = color; + runeLiteObject.setModel(client.loadModel( + RAID_LIGHT_MODEL, + new short[]{RAID_LIGHT_FIND_COLOR}, + new short[]{JagexColor.rgbToHSL(color.getRGB(), 1.0d)} + )); + } + + public void remove() + { + runeLiteObject.setActive(false); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ItemHighlightMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ItemHighlightMode.java index 9b873a639e..23d7060ef9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ItemHighlightMode.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/ItemHighlightMode.java @@ -31,6 +31,7 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public enum ItemHighlightMode { + NONE("None"), OVERLAY("Overlay"), MENU("Right-click menu"), BOTH("Both"); diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java index 48d6296ca5..92cd6b28bc 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java @@ -111,6 +111,8 @@ public class GroundItemsPluginTest when(client.getLocalPlayer()).thenReturn(mock(Player.class)); when(config.getHiddenItems()).thenReturn(""); + when(config.showLootbeamForHighlighted()).thenReturn(false); + when(config.showLootbeamTier()).thenReturn(HighlightTier.OFF); } @Test