diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemStats.java b/http-api/src/main/java/net/runelite/http/api/item/ItemStats.java index 88686eee03..2ae616f703 100644 --- a/http-api/src/main/java/net/runelite/http/api/item/ItemStats.java +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemStats.java @@ -26,6 +26,7 @@ package net.runelite.http.api.item; import lombok.AllArgsConstructor; import lombok.Builder; +import com.google.gson.annotations.SerializedName; import lombok.Value; @Value @@ -36,6 +37,8 @@ public class ItemStats private boolean quest; private boolean equipable; private double weight; + @SerializedName("ge_limit") + private int geLimit; private ItemEquipmentStats equipment; @@ -80,7 +83,7 @@ public class ItemStats newEquipment = equipment; } - return new ItemStats(quest, equipable, newWeight, newEquipment); + return new ItemStats(quest, equipable, newWeight, 0, newEquipment); } } diff --git a/http-api/src/main/java/net/runelite/http/api/util/TypeAdapters.java b/http-api/src/main/java/net/runelite/http/api/util/TypeAdapters.java index 0327e09685..e85f024df5 100644 --- a/http-api/src/main/java/net/runelite/http/api/util/TypeAdapters.java +++ b/http-api/src/main/java/net/runelite/http/api/util/TypeAdapters.java @@ -81,7 +81,7 @@ public class TypeAdapters } in.endObject(); - return new ItemStats(quest, equip, weight, stats); + return new ItemStats(quest, equip, weight, 0, stats); } }; diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index b93c61b55e..77a4d6f15c 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -41,8 +41,10 @@ import java.io.OutputStreamWriter; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; -import java.nio.channels.FileLock; import java.nio.charset.StandardCharsets; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -80,6 +82,7 @@ public class ConfigManager private final ConfigInvocationHandler handler = new ConfigInvocationHandler(this); private final Properties properties = new Properties(); private final Map pendingChanges = new HashMap<>(); + @Inject EventBus eventBus; @@ -459,18 +462,23 @@ public class ConfigManager { ConfigManager.SETTINGS_FILE.getParentFile().mkdirs(); - try (FileOutputStream out = new FileOutputStream(ConfigManager.SETTINGS_FILE)) - { - final FileLock lock = out.getChannel().lock(); + File tempFile = new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME + ".tmp"); - try - { - properties.store(new OutputStreamWriter(out, StandardCharsets.UTF_8), "RuneLite configuration"); - } - finally - { - lock.release(); - } + try (FileOutputStream out = new FileOutputStream(tempFile)) + { + out.getChannel().lock(); + properties.store(new OutputStreamWriter(out, StandardCharsets.UTF_8), "RuneLite configuration"); + // FileOutputStream.close() closes the associated channel, which frees the lock + } + + try + { + Files.move(tempFile.toPath(), ConfigManager.SETTINGS_FILE.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } + catch (AtomicMoveNotSupportedException ex) + { + log.debug("atomic move not supported", ex); + Files.move(tempFile.toPath(), ConfigManager.SETTINGS_FILE.toPath(), StandardCopyOption.REPLACE_EXISTING); } } 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 4c45f032e3..ad3ae36844 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 @@ -24,6 +24,7 @@ */ package net.runelite.client.ui.overlay.worldmap; +import com.google.common.base.Splitter; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics2D; @@ -55,6 +56,8 @@ public class WorldMapOverlay extends Overlay private static final int TOOLTIP_PADDING_HEIGHT = 1; private static final int TOOLTIP_PADDING_WIDTH = 2; + private static final Splitter TOOLTIP_SPLITTER = Splitter.on("
").trimResults().omitEmptyStrings(); + private final WorldMapPointManager worldMapPointManager; private final Client client; @@ -261,6 +264,13 @@ public class WorldMapOverlay extends Overlay return; } + List rows = TOOLTIP_SPLITTER.splitToList(tooltip); + + if (rows.isEmpty()) + { + return; + } + drawPoint = new Point(drawPoint.getX() + TOOLTIP_OFFSET_WIDTH, drawPoint.getY() + TOOLTIP_OFFSET_HEIGHT); final Rectangle bounds = new Rectangle(0, 0, client.getCanvasWidth(), client.getCanvasHeight()); @@ -269,16 +279,19 @@ public class WorldMapOverlay extends Overlay graphics.setColor(JagexColors.TOOLTIP_BACKGROUND); graphics.setFont(FontManager.getRunescapeFont()); FontMetrics fm = graphics.getFontMetrics(); - int width = fm.stringWidth(tooltip); + int width = rows.stream().map(fm::stringWidth).max(Integer::compareTo).get(); int height = fm.getHeight(); - Rectangle tooltipRect = new Rectangle(drawPoint.getX() - TOOLTIP_PADDING_WIDTH, drawPoint.getY() - TOOLTIP_PADDING_HEIGHT, width + TOOLTIP_PADDING_WIDTH * 2, height + TOOLTIP_PADDING_HEIGHT * 2); + Rectangle tooltipRect = new Rectangle(drawPoint.getX() - TOOLTIP_PADDING_WIDTH, drawPoint.getY() - TOOLTIP_PADDING_HEIGHT, width + TOOLTIP_PADDING_WIDTH * 2, height * rows.size() + TOOLTIP_PADDING_HEIGHT * 2); graphics.fillRect((int) tooltipRect.getX(), (int) tooltipRect.getY(), (int) tooltipRect.getWidth(), (int) tooltipRect.getHeight()); graphics.setColor(JagexColors.TOOLTIP_BORDER); graphics.drawRect((int) tooltipRect.getX(), (int) tooltipRect.getY(), (int) tooltipRect.getWidth(), (int) tooltipRect.getHeight()); graphics.setColor(JagexColors.TOOLTIP_TEXT); - graphics.drawString(tooltip, drawPoint.getX(), drawPoint.getY() + height); + for (int i = 0; i < rows.size(); i++) + { + graphics.drawString(rows.get(i), drawPoint.getX(), drawPoint.getY() + (i + 1) * height); + } } private Point clipToRectangle(Point drawPoint, Rectangle mapDisplayRectangle) diff --git a/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java b/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java index ce1ef1aeac..a84a4d57ee 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java +++ b/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java @@ -26,6 +26,8 @@ package net.runelite.client.util; import com.google.common.base.Strings; import java.awt.Desktop; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; import java.io.File; import java.io.IOException; import java.net.URI; @@ -36,7 +38,7 @@ import javax.swing.SwingUtilities; import lombok.extern.slf4j.Slf4j; /** - * Utility class used for browser navigation + * Utility class used for web and file browser navigation */ @Singleton @Slf4j @@ -47,38 +49,71 @@ public class LinkBrowser /** * Tries to navigate to specified URL in browser. In case operation fails, displays message box with message * and copies link to clipboard to navigate to. - * - * @param url url to open - * @return true if operation was successful */ - public static boolean browse(final String url) + public static void browse(final String url) { - if (Strings.isNullOrEmpty(url)) + new Thread(() -> { - return false; - } + if (Strings.isNullOrEmpty(url)) + { + log.warn("LinkBrowser.browse() called with invalid input"); + return; + } - if (attemptDesktopBrowse(url)) - { - log.debug("Opened browser through Desktop#browse to {}", url); - return true; - } + if (attemptDesktopBrowse(url)) + { + log.debug("Opened url through Desktop#browse to {}", url); + return; + } - if (shouldAttemptXdg && attemptXdgOpen(url)) - { - log.debug("Opened browser through xdg-open to {}", url); - return true; - } + if (shouldAttemptXdg && attemptXdgOpen(url)) + { + log.debug("Opened url through xdg-open to {}", url); + return; + } - showMessageBox("Unable to open link. Press 'OK' and link will be copied to your clipboard.", url); - return false; + log.warn("LinkBrowser.browse() could not open {}", url); + showMessageBox("Unable to open link. Press 'OK' and the link will be copied to your clipboard.", url); + }).start(); } - private static boolean attemptXdgOpen(String url) + /** + * Tries to open a directory in the OS native file manager. + * + * @param directory directory to open + */ + public static void open(final String directory) + { + new Thread(() -> + { + if (Strings.isNullOrEmpty(directory)) + { + log.warn("LinkBrowser.open() called with invalid input"); + return; + } + + if (attemptDesktopOpen(directory)) + { + log.debug("Opened directory through Desktop#open to {}", directory); + return; + } + + if (shouldAttemptXdg && attemptXdgOpen(directory)) + { + log.debug("Opened directory through xdg-open to {}", directory); + return; + } + + log.warn("LinkBrowser.open() could not open {}", directory); + showMessageBox("Unable to open folder. Press 'OK' and the folder directory will be copied to your clipboard.", directory); + }).start(); + } + + private static boolean attemptXdgOpen(String resource) { try { - final Process exec = Runtime.getRuntime().exec(new String[]{"xdg-open", url}); + final Process exec = Runtime.getRuntime().exec(new String[]{"xdg-open", resource}); exec.waitFor(); final int ret = exec.exitValue(); @@ -87,7 +122,7 @@ public class LinkBrowser return true; } - log.warn("xdg-open {} returned with error code {}", url, ret); + log.warn("xdg-open {} returned with error code {}", resource, ret); return false; } catch (IOException ex) @@ -98,7 +133,7 @@ public class LinkBrowser } catch (InterruptedException ex) { - log.warn("Interrupted while waiting for xdg-open {} to execute", url); + log.warn("Interrupted while waiting for xdg-open {} to execute", resource); return false; } } @@ -124,7 +159,33 @@ public class LinkBrowser } catch (IOException | URISyntaxException ex) { - log.warn("Failed to open Desktop#browser {}", url, ex); + log.warn("Failed to open Desktop#browse {}", url, ex); + return false; + } + } + + private static boolean attemptDesktopOpen(String directory) + { + if (!Desktop.isDesktopSupported()) + { + return false; + } + + final Desktop desktop = Desktop.getDesktop(); + + if (!desktop.isSupported(Desktop.Action.OPEN)) + { + return false; + } + + try + { + desktop.open(new File(directory)); + return true; + } + catch (IOException ex) + { + log.warn("Failed to open Desktop#open {}", directory, ex); return false; } } @@ -193,8 +254,9 @@ public class LinkBrowser if (result == JOptionPane.OK_OPTION) { - Clipboard.store(data); + final StringSelection stringSelection = new StringSelection(data); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); } }); } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/scripts/LayoutResizableStones.rs2asm b/runelite-client/src/main/scripts/LayoutResizableStones.rs2asm index d6b0955c00..81d887a85f 100644 --- a/runelite-client/src/main/scripts/LayoutResizableStones.rs2asm +++ b/runelite-client/src/main/scripts/LayoutResizableStones.rs2asm @@ -43,7 +43,7 @@ LABEL9: sconst "forceStackStones" ; push event name runelite_callback ; invoke callback iconst 0 ; if 0 is returned, continue normal layout - jump LABEL49 + if_icmpeq LABEL49 LABEL29: iconst 0 iload 3