diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsConfig.java new file mode 100644 index 0000000000..6e54e41df1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsConfig.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.objectindicators; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("objectindicators") +public interface ObjectIndicatorsConfig extends Config +{ + @ConfigItem( + keyName = "markerColor", + name = "Marker color", + description = "Configures the color of object marker" + ) + default Color markerColor() + { + return Color.YELLOW; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java new file mode 100644 index 0000000000..af9431038e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.objectindicators; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Polygon; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.GameObject; +import net.runelite.api.TileObject; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.OverlayUtil; + +class ObjectIndicatorsOverlay extends Overlay +{ + private final Client client; + private final ObjectIndicatorsConfig config; + private final ObjectIndicatorsPlugin plugin; + + @Inject + private ObjectIndicatorsOverlay(Client client, ObjectIndicatorsConfig config, ObjectIndicatorsPlugin plugin) + { + this.client = client; + this.config = config; + this.plugin = plugin; + setPosition(OverlayPosition.DYNAMIC); + setPriority(OverlayPriority.LOW); + setLayer(OverlayLayer.ABOVE_SCENE); + } + + @Override + public Dimension render(Graphics2D graphics) + { + for (TileObject object : plugin.getObjects()) + { + if (object.getPlane() != client.getPlane()) + { + continue; + } + + final Polygon polygon; + + if (object instanceof GameObject) + { + polygon = ((GameObject) object).getConvexHull(); + } + else + { + polygon = object.getCanvasTilePoly(); + } + + if (polygon == null) + { + continue; + } + + OverlayUtil.renderPolygon(graphics, polygon, config.markerColor()); + } + + return null; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java new file mode 100644 index 0000000000..c2abf64bc0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, 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.objectindicators; + +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.inject.Provides; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import static net.runelite.api.Constants.REGION_SIZE; +import net.runelite.api.GameObject; +import net.runelite.api.GameState; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.ObjectComposition; +import net.runelite.api.Tile; +import net.runelite.api.TileObject; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.FocusChanged; +import net.runelite.api.events.GameObjectDespawned; +import net.runelite.api.events.GameObjectSpawned; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.KeyManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +@Slf4j +@PluginDescriptor( + name = "Object Markers", + description = "Enable marking of objects using the Shift key", + tags = {"overlay", "objects", "mark", "marker"}, + enabledByDefault = false +) +public class ObjectIndicatorsPlugin extends Plugin implements KeyListener +{ + private static final String CONFIG_GROUP = "objectindicators"; + private static final String MARK = "Mark object"; + + private final Gson GSON = new Gson(); + @Getter(AccessLevel.PACKAGE) + private final List objects = new ArrayList<>(); + private final Map> points = new HashMap<>(); + private boolean hotKeyPressed; + + @Inject + private Client client; + + @Inject + private ConfigManager configManager; + + @Inject + private OverlayManager overlayManager; + + @Inject + private ObjectIndicatorsOverlay overlay; + + @Inject + private KeyManager keyManager; + + @Provides + ObjectIndicatorsConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(ObjectIndicatorsConfig.class); + } + + @Override + protected void startUp() + { + overlayManager.add(overlay); + keyManager.registerKeyListener(this); + } + + @Override + protected void shutDown() + { + overlayManager.remove(overlay); + keyManager.unregisterKeyListener(this); + points.clear(); + objects.clear(); + hotKeyPressed = false; + } + + @Override + public void keyTyped(KeyEvent e) + { + + } + + @Override + public void keyPressed(KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_SHIFT) + { + hotKeyPressed = true; + } + } + + @Override + public void keyReleased(KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_SHIFT) + { + hotKeyPressed = false; + } + } + + @Subscribe + public void onFocusChanged(final FocusChanged event) + { + if (!event.isFocused()) + { + hotKeyPressed = false; + } + } + + @Subscribe + public void onGameObjectSpawned(GameObjectSpawned event) + { + final WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, event.getGameObject().getLocalLocation()); + final Set objectPoints = points.get(worldPoint.getRegionID()); + + if (objectPoints == null) + { + return; + } + + for (ObjectPoint objectPoint : objectPoints) + { + if ((worldPoint.getX() & (REGION_SIZE - 1)) == objectPoint.getRegionX() + && (worldPoint.getY() & (REGION_SIZE - 1)) == objectPoint.getRegionY()) + { + if (objectPoint.getName().equals(client.getObjectDefinition(event.getGameObject().getId()).getName())) + { + objects.add(event.getGameObject()); + break; + } + } + } + } + + @Subscribe + public void onGameObjectDespawned(GameObjectDespawned event) + { + objects.remove(event.getGameObject()); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + GameState gameState = gameStateChanged.getGameState(); + if (gameState == GameState.LOADING) + { + // Reload points with new map regions + + points.clear(); + for (int regionId : client.getMapRegions()) + { + // load points for region + final Set regionPoints = loadPoints(regionId); + if (regionPoints != null) + { + points.put(regionId, regionPoints); + } + } + } + + if (gameStateChanged.getGameState() != GameState.LOGGED_IN) + { + objects.clear(); + } + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded event) + { + if (!hotKeyPressed || event.getType() != MenuAction.EXAMINE_OBJECT.getId()) + { + return; + } + + MenuEntry[] menuEntries = client.getMenuEntries(); + menuEntries = Arrays.copyOf(menuEntries, menuEntries.length + 1); + MenuEntry menuEntry = menuEntries[menuEntries.length - 1] = new MenuEntry(); + menuEntry.setOption(MARK); + menuEntry.setTarget(event.getTarget()); + menuEntry.setParam0(event.getActionParam0()); + menuEntry.setParam1(event.getActionParam1()); + menuEntry.setIdentifier(event.getIdentifier()); + menuEntry.setType(MenuAction.CANCEL.getId()); + client.setMenuEntries(menuEntries); + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + if (!event.getMenuOption().equals(MARK)) + { + return; + } + + TileObject object = findTileObject(client.getSelectedSceneTile(), event.getId()); + if (object == null) + { + return; + } + + ObjectComposition objectDefinition = client.getObjectDefinition(object.getId()); + String name = objectDefinition.getName(); + if (Strings.isNullOrEmpty(name)) + { + return; + } + + markObject(name, object); + } + + private TileObject findTileObject(Tile tile, int id) + { + final GameObject[] tileGameObjects = tile.getGameObjects(); + + for (GameObject object : tileGameObjects) + { + if (object == null) + { + continue; + } + + if (object.getId() == id) + { + return object; + } + + // Check impostors + final ObjectComposition comp = client.getObjectDefinition(object.getId()); + + if (comp.getImpostorIds() != null) + { + for (int impostorId : comp.getImpostorIds()) + { + if (impostorId == id) + { + return object; + } + } + } + } + + return null; + } + + private void markObject(String name, final TileObject object) + { + if (object == null) + { + return; + } + + final WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, object.getLocalLocation()); + final int regionId = worldPoint.getRegionID(); + final ObjectPoint point = new ObjectPoint( + name, + regionId, + worldPoint.getX() & (REGION_SIZE - 1), + worldPoint.getY() & (REGION_SIZE - 1), + client.getPlane()); + + Set objectPoints = points.computeIfAbsent(regionId, k -> new HashSet<>()); + + if (objectPoints.contains(point)) + { + objectPoints.remove(point); + objects.remove(object); + } + else + { + objectPoints.add(point); + objects.add(object); + } + + savePoints(regionId, objectPoints); + } + + private void savePoints(final int id, final Set points) + { + if (points.isEmpty()) + { + configManager.unsetConfiguration(CONFIG_GROUP, "region_" + id); + } + else + { + final String json = GSON.toJson(points); + configManager.setConfiguration(CONFIG_GROUP, "region_" + id, json); + } + } + + private Set loadPoints(final int id) + { + final String json = configManager.getConfiguration(CONFIG_GROUP, "region_" + id); + + if (Strings.isNullOrEmpty(json)) + { + return null; + } + + return GSON.fromJson(json, new TypeToken>() + { + }.getType()); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectPoint.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectPoint.java new file mode 100644 index 0000000000..576f0e7c72 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectPoint.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.objectindicators; + +import lombok.Value; + +@Value +class ObjectPoint +{ + private String name; + private int regionId; + private int regionX; + private int regionY; + private int z; +} \ No newline at end of file