diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FairyRingLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FairyRingLocation.java new file mode 100644 index 0000000000..664d4db0e9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FairyRingLocation.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018, Morgan Lewis + * 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 HOLDER 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.worldmap; + +import java.awt.image.BufferedImage; +import lombok.Getter; +import net.runelite.api.coords.WorldPoint; + +@Getter +enum FairyRingLocation +{ + AIR("AIR", new WorldPoint(2699, 3249, 0)), + AIQ("AIQ", new WorldPoint(2995, 3112, 0)), + AJR("AJR", new WorldPoint(2779, 3615, 0)), + AJS("AJS", new WorldPoint(2499, 3898, 0)), + AKQ("AKQ", new WorldPoint(2318, 3617, 0)), + AKS("AKS", new WorldPoint(2570, 2958, 0)), + ALP("ALP", new WorldPoint(2502, 3638, 0)), + ALQ("ALQ", new WorldPoint(3598, 3496, 0)), + ALS("ALS", new WorldPoint(2643, 3497, 0)), + BIP("BIP", new WorldPoint(3409, 3326, 0)), + BIQ("BIQ", new WorldPoint(3248, 3095, 0)), + BIS("BIS", new WorldPoint(2635, 3268, 0)), + BJQ("BJQ", new WorldPoint(1737, 5342, 0)), + BJS("BJS", new WorldPoint(2147, 3069, 0)), + BKP("BKP", new WorldPoint(2384, 3037, 0)), + BKR("BKR", new WorldPoint(3468, 3433, 0)), + BLP("BLP", new WorldPoint(2432, 5127, 0)), + BLR("BLR", new WorldPoint(2739, 3353, 0)), + CIP("CIP", new WorldPoint(2512, 3886, 0)), + CIQ("CIQ", new WorldPoint(2527, 3129, 0)), + CJR("CJR", new WorldPoint(2704, 3578, 0)), + CKR("CKR", new WorldPoint(2800, 3005, 0)), + CKS("CKS", new WorldPoint(3446, 3472, 0)), + CLP("CLP", new WorldPoint(3081, 3208, 0)), + CLS("CLS", new WorldPoint(2681, 3083, 0)), + DIP("DIP", new WorldPoint(3039, 4757, 0)), + DIS("DIS", new WorldPoint(3109, 3149, 0)), + DJP("DJP", new WorldPoint(2657, 3232, 0)), + DJR("DJR", new WorldPoint(1452, 3659, 0)), + DKP("DKP", new WorldPoint(2899, 3113, 0)), + DKR("DKR", new WorldPoint(3126, 3496, 0)), + DKS("DKS", new WorldPoint(2743, 3721, 0)), + DLQ("DLQ", new WorldPoint(3422, 3018, 0)), + DLR("DLR", new WorldPoint(2212, 3101, 0)), + CIS("CIS", new WorldPoint(1638, 3868, 0)), + CLR("CLR", new WorldPoint(2737, 2739, 0)), + ZANARIS("Zanaris", new WorldPoint(2411, 4436, 0)); + + private final String code; + private final WorldPoint location; + private final FairyRingPoint fairyRingPoint; + + FairyRingLocation(String code, WorldPoint location) + { + this.code = code; + this.location = location; + this.fairyRingPoint = new FairyRingPoint(code, location); + } + + static void setIcon(BufferedImage image) + { + for (FairyRingLocation fairyRingLocation : values()) + { + fairyRingLocation.fairyRingPoint.setImage(image); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FairyRingPoint.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FairyRingPoint.java new file mode 100644 index 0000000000..3422d9cf0e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FairyRingPoint.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018, Morgan Lewis + * 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 HOLDER 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.worldmap; + +import net.runelite.api.coords.WorldPoint; +import static net.runelite.client.plugins.worldmap.WorldMapPlugin.BLANK_ICON; +import net.runelite.client.ui.overlay.worldmap.WorldMapPoint; + +class FairyRingPoint extends WorldMapPoint +{ + FairyRingPoint(String tooltip, WorldPoint worldPoint) + { + super(worldPoint, BLANK_ICON); + setTooltip("Fairy Ring - " + tooltip); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapConfig.java new file mode 100644 index 0000000000..7b915b1429 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapConfig.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018, Morgan Lewis + * 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 HOLDER 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.worldmap; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup( + keyName = WorldMapPlugin.CONFIG_KEY, + name = "World Map", + description = "Various World Map enhancements" +) +public interface WorldMapConfig extends Config +{ + @ConfigItem( + keyName = WorldMapPlugin.CONFIG_KEY_FAIRY_RING_TOOLTIPS, + name = "Show fairy ring codes in tooltip", + description = "Display the code for fairy rings in the icon tooltip", + position = 1 + ) + default boolean fairyRingTooltips() + { + return true; + } + + @ConfigItem( + keyName = WorldMapPlugin.CONFIG_KEY_FAIRY_RING_ICON, + name = "Show fairy ring travel icon", + description = "Override the travel icon for fairy rings", + position = 2 + ) + default boolean fairyRingIcon() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java new file mode 100644 index 0000000000..f6b9817a6b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2018, Morgan Lewis + * 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 HOLDER 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.worldmap; + +import com.google.common.eventbus.Subscribe; +import com.google.inject.Inject; +import com.google.inject.Provides; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.Arrays; +import javax.imageio.ImageIO; +import net.runelite.api.events.ConfigChanged; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.worldmap.WorldMapPointManager; + +@PluginDescriptor( + name = "World Map" +) +public class WorldMapPlugin extends Plugin +{ + static final BufferedImage BLANK_ICON; + static final BufferedImage FAIRY_TRAVEL_ICON; + + static final String CONFIG_KEY = "worldmap"; + static final String CONFIG_KEY_FAIRY_RING_TOOLTIPS = "fairyRingTooltips"; + static final String CONFIG_KEY_FAIRY_RING_ICON = "fairyRingIcon"; + + static + { + //A size of 17 gives us a buffer when triggering tooltips + final int iconBufferSize = 17; + + BLANK_ICON = new BufferedImage(iconBufferSize, iconBufferSize, BufferedImage.TYPE_INT_ARGB); + + try + { + synchronized (ImageIO.class) + { + FAIRY_TRAVEL_ICON = new BufferedImage(iconBufferSize, iconBufferSize, BufferedImage.TYPE_INT_ARGB); + final BufferedImage icon = ImageIO.read(WorldMapPlugin.class.getResourceAsStream("fairy_ring_travel.png")); + FAIRY_TRAVEL_ICON.getGraphics().drawImage(icon, 1, 1, null); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Inject + private WorldMapConfig config; + + @Inject + private WorldMapPointManager worldMapPointManager; + + @Provides + WorldMapConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(WorldMapConfig.class); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals(CONFIG_KEY)) + { + switch (event.getKey()) + { + case CONFIG_KEY_FAIRY_RING_TOOLTIPS: + if (config.fairyRingTooltips()) + { + Arrays.stream(FairyRingLocation.values()) + .map(FairyRingLocation::getFairyRingPoint) + .forEach(worldMapPointManager::add); + } + else + { + worldMapPointManager.removeIf(FairyRingPoint.class::isInstance); + } + break; + case CONFIG_KEY_FAIRY_RING_ICON: + FairyRingLocation.setIcon(config.fairyRingIcon() ? FAIRY_TRAVEL_ICON : BLANK_ICON); + break; + } + } + } + + @Override + protected void startUp() throws Exception + { + FairyRingLocation.setIcon(config.fairyRingIcon() ? FAIRY_TRAVEL_ICON : BLANK_ICON); + + if (config.fairyRingTooltips()) + { + Arrays.stream(FairyRingLocation.values()) + .map(FairyRingLocation::getFairyRingPoint) + .forEach(worldMapPointManager::add); + } + } + + @Override + protected void shutDown() throws Exception + { + worldMapPointManager.removeIf(FairyRingPoint.class::isInstance); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/worldmap/WorldMapOverlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/worldmap/WorldMapOverlay.java index 9923b02b8b..d16d8c798d 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/worldmap/WorldMapOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/worldmap/WorldMapOverlay.java @@ -182,17 +182,31 @@ public class WorldMapOverlay extends Overlay Float pixelsPerTile = ro.getWorldMapZoom(); - Point worldMapPosition = ro.getWorldMapPosition(); - int xWorldDiff = worldPoint.getX() - worldMapPosition.getX(); - int yWorldDiff = worldPoint.getY() - worldMapPosition.getY(); - yWorldDiff = -yWorldDiff; - Widget map = clientProvider.get().getWidget(WidgetInfo.WORLD_MAP_VIEW); if (map != null) { Rectangle worldMapRect = map.getBounds(); - int xGraphDiff = (int) (xWorldDiff * pixelsPerTile + worldMapRect.getWidth() / 2 + worldMapRect.getX()); - int yGraphDiff = (int) (yWorldDiff * pixelsPerTile + worldMapRect.getHeight() / 2 + worldMapRect.getY()); + + int widthInTiles = (int) Math.ceil(worldMapRect.getWidth() / pixelsPerTile); + int heightInTiles = (int) Math.ceil(worldMapRect.getHeight() / pixelsPerTile); + + Point worldMapPosition = ro.getWorldMapPosition(); + + //Offset in tiles from anchor sides + int yTileMax = worldMapPosition.getY() - heightInTiles / 2; + int yTileOffset = (yTileMax - worldPoint.getY() - 1) * -1; + int xTileOffset = worldPoint.getX() + widthInTiles / 2 - worldMapPosition.getX(); + + int xGraphDiff = ((int) (xTileOffset * pixelsPerTile)); + int yGraphDiff = (int) (yTileOffset * pixelsPerTile); + + //Center on tile. + yGraphDiff -= pixelsPerTile - Math.ceil(pixelsPerTile / 2); + xGraphDiff += pixelsPerTile - Math.ceil(pixelsPerTile / 2); + + yGraphDiff = worldMapRect.height - yGraphDiff; + yGraphDiff += (int) worldMapRect.getY(); + xGraphDiff += (int) worldMapRect.getX(); return new Point(xGraphDiff, yGraphDiff); } diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/worldmap/fairy_ring_travel.png b/runelite-client/src/main/resources/net/runelite/client/plugins/worldmap/fairy_ring_travel.png new file mode 100644 index 0000000000..2bce38a741 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/worldmap/fairy_ring_travel.png differ diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/WorldMapManagerMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/WorldMapManagerMixin.java new file mode 100644 index 0000000000..3f51e31e48 --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/WorldMapManagerMixin.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018, Morgan Lewis + * 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.mixins; + +import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Replace; +import net.runelite.api.mixins.Shadow; +import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSWorldMapManager; + +@Mixin(RSWorldMapManager.class) +public abstract class WorldMapManagerMixin implements RSWorldMapManager +{ + @Shadow("clientInstance") + static RSClient client; + + /* + The worldMapZoom is essentially pixels per tile. In most instances + getPixelsPerTile returns the same as worldMapZoom. + + At some map widths when 100% zoomed in the Jagex version of this function + returns 7.89 instead of 8.0 (the worldMapZoom at this level). + This would cause both the x and y positions of the map to shift + slightly when the map was certain widths. + + This mixin function replaces Jagex calculation with getWorldMapZoom. + This small change makes the world map tile sizing predictable. + */ + @Replace("getPixelsPerTile") + @Override + public float getPixelsPerTile(int graphicsDiff, int worldDiff) + { + return client.getRenderOverview().getWorldMapZoom(); + } + +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSWorldMapManager.java b/runescape-api/src/main/java/net/runelite/rs/api/RSWorldMapManager.java index 7c34747cca..60377bc659 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSWorldMapManager.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSWorldMapManager.java @@ -38,4 +38,7 @@ public interface RSWorldMapManager extends WorldMapManager @Import("mapSurfaceBaseOffsetY") int getSurfaceOffsetY(); + + @Import("getPixelsPerTile") + float getPixelsPerTile(int graphicsDiff, int worldDiff); }