From 426265c765eaf678f1f67787efe3feace978583f Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 25 Jan 2021 18:09:26 -0500 Subject: [PATCH] ground markers: add option to export and import Co-authored-by: Paul Norton --- .../groundmarkers/GroundMarkerPlugin.java | 17 +- .../GroundMarkerSharingManager.java | 243 ++++++++++++++++++ 2 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerSharingManager.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java index 73e93f86ad..c1e1774931 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java @@ -51,6 +51,7 @@ 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.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.plugins.Plugin; @@ -98,7 +99,13 @@ public class GroundMarkerPlugin extends Plugin @Inject private ChatboxPanelManager chatboxPanelManager; - private void savePoints(int regionId, Collection points) + @Inject + private EventBus eventBus; + + @Inject + private GroundMarkerSharingManager sharingManager; + + void savePoints(int regionId, Collection points) { if (points == null || points.isEmpty()) { @@ -110,7 +117,7 @@ public class GroundMarkerPlugin extends Plugin configManager.setConfiguration(CONFIG_GROUP, REGION_PREFIX + regionId, json); } - private Collection getPoints(int regionId) + Collection getPoints(int regionId) { String json = configManager.getConfiguration(CONFIG_GROUP, REGION_PREFIX + regionId); if (Strings.isNullOrEmpty(json)) @@ -129,7 +136,7 @@ public class GroundMarkerPlugin extends Plugin return configManager.getConfig(GroundMarkerConfig.class); } - private void loadPoints() + void loadPoints() { points.clear(); @@ -181,14 +188,18 @@ public class GroundMarkerPlugin extends Plugin { overlayManager.add(overlay); overlayManager.add(minimapOverlay); + sharingManager.addMenuOptions(); loadPoints(); + eventBus.register(sharingManager); } @Override public void shutDown() { + eventBus.unregister(sharingManager); overlayManager.remove(overlay); overlayManager.remove(minimapOverlay); + sharingManager.removeMenuOptions(); points.clear(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerSharingManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerSharingManager.java new file mode 100644 index 0000000000..8899a81f0d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerSharingManager.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2021, 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.groundmarkers; + +import com.google.common.base.Strings; +import com.google.common.util.concurrent.Runnables; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.events.WidgetMenuOptionClicked; +import static net.runelite.api.widgets.WidgetInfo.WORLD_MAP_OPTION; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.menus.MenuManager; +import net.runelite.client.menus.WidgetMenuOption; +import net.runelite.http.api.RuneLiteAPI; + +@Slf4j +class GroundMarkerSharingManager +{ + private static final WidgetMenuOption EXPORT_MARKERS_OPTION = new WidgetMenuOption("Export", "Ground Markers", WORLD_MAP_OPTION); + private static final WidgetMenuOption IMPORT_MARKERS_OPTION = new WidgetMenuOption("Import", "Ground Markers", WORLD_MAP_OPTION); + + private static final Gson GSON = RuneLiteAPI.GSON; + + private final GroundMarkerPlugin plugin; + private final Client client; + private final MenuManager menuManager; + private final ChatMessageManager chatMessageManager; + private final ChatboxPanelManager chatboxPanelManager; + + @Inject + private GroundMarkerSharingManager(GroundMarkerPlugin plugin, Client client, MenuManager menuManager, ChatMessageManager chatMessageManager, ChatboxPanelManager chatboxPanelManager) + { + this.plugin = plugin; + this.client = client; + this.menuManager = menuManager; + this.chatMessageManager = chatMessageManager; + this.chatboxPanelManager = chatboxPanelManager; + } + + void addMenuOptions() + { + menuManager.addManagedCustomMenu(EXPORT_MARKERS_OPTION); + menuManager.addManagedCustomMenu(IMPORT_MARKERS_OPTION); + } + + void removeMenuOptions() + { + menuManager.removeManagedCustomMenu(EXPORT_MARKERS_OPTION); + menuManager.removeManagedCustomMenu(IMPORT_MARKERS_OPTION); + } + + private boolean widgetMenuClickedEquals(final WidgetMenuOptionClicked event, final WidgetMenuOption target) + { + return event.getMenuTarget().equals(target.getMenuTarget()) && + event.getMenuOption().equals(target.getMenuOption()); + } + + @Subscribe + public void onWidgetMenuOptionClicked(WidgetMenuOptionClicked event) + { + // ensure that the option clicked is the export markers option + if (event.getWidget() != WORLD_MAP_OPTION) + { + return; + } + + if (widgetMenuClickedEquals(event, EXPORT_MARKERS_OPTION)) + { + exportGroundMarkers(); + } + else if (widgetMenuClickedEquals(event, IMPORT_MARKERS_OPTION)) + { + promptForImport(); + } + } + + private void exportGroundMarkers() + { + int[] regions = client.getMapRegions(); + if (regions == null) + { + return; + } + + List activePoints = Arrays.stream(regions) + .mapToObj(regionId -> plugin.getPoints(regionId).stream()) + .flatMap(Function.identity()) + .collect(Collectors.toList()); + + if (activePoints.isEmpty()) + { + sendChatMessage("You have no ground markers to export."); + return; + } + + final String exportDump = GSON.toJson(activePoints); + + log.debug("Exported ground markers: {}", exportDump); + + Toolkit.getDefaultToolkit() + .getSystemClipboard() + .setContents(new StringSelection(exportDump), null); + sendChatMessage(activePoints.size() + " ground markers were copied to your clipboard."); + } + + private void promptForImport() + { + final String clipboardText; + try + { + clipboardText = Toolkit.getDefaultToolkit() + .getSystemClipboard() + .getData(DataFlavor.stringFlavor) + .toString(); + } + catch (IOException | UnsupportedFlavorException ex) + { + sendChatMessage("Unable to read system clipboard."); + log.warn("error reading clipboard", ex); + return; + } + + log.debug("Clipboard contents: {}", clipboardText); + if (Strings.isNullOrEmpty(clipboardText)) + { + sendChatMessage("You do not have any ground markers copied in your clipboard."); + return; + } + + List importPoints; + try + { + // CHECKSTYLE:OFF + importPoints = GSON.fromJson(clipboardText, new TypeToken>(){}.getType()); + // CHECKSTYLE:ON + } + catch (JsonSyntaxException e) + { + log.debug("Malformed JSON for clipboard import", e); + sendChatMessage("You do not have any ground markers copied in your clipboard."); + return; + } + + if (importPoints.isEmpty()) + { + sendChatMessage("You do not have any ground markers copied in your clipboard."); + return; + } + + chatboxPanelManager.openTextMenuInput("Are you sure you want to import " + importPoints.size() + " ground markers?") + .option("Yes", () -> importGroundMarkers(importPoints)) + .option("No", Runnables::doNothing) + .build(); + } + + private void importGroundMarkers(Collection importPoints) + { + // regions being imported may not be loaded on client, + // so need to import each bunch directly into the config + // first, collate the list of unique region ids in the import + Map> regionGroupedPoints = importPoints.stream() + .collect(Collectors.groupingBy(GroundMarkerPoint::getRegionId)); + + // now import each region into the config + regionGroupedPoints.forEach((regionId, groupedPoints) -> + { + // combine imported points with existing region points + log.debug("Importing {} points to region {}", groupedPoints.size(), regionId); + Collection regionPoints = plugin.getPoints(regionId); + + List mergedList = new ArrayList<>(regionPoints.size() + groupedPoints.size()); + // add existing points + mergedList.addAll(regionPoints); + + // add new points + for (GroundMarkerPoint point : groupedPoints) + { + // filter out duplicates + if (!mergedList.contains(point)) + { + mergedList.add(point); + } + } + + plugin.savePoints(regionId, mergedList); + }); + + // reload points from config + log.debug("Reloading points after import"); + plugin.loadPoints(); + sendChatMessage(importPoints.size() + " ground markers were imported from the clipboard."); + } + + private void sendChatMessage(final String message) + { + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage(message) + .build()); + } +}