diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperConfig.java index ea770dadda..f7a75781fd 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperConfig.java @@ -1,126 +1,115 @@ -/* - * Copyright (c) 2018, Lotto - * 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.worldhopper; - -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; -import net.runelite.client.config.Keybind; - -@ConfigGroup(WorldHopperConfig.GROUP) -public interface WorldHopperConfig extends Config -{ - String GROUP = "worldhopper"; - - @ConfigItem( - keyName = "previousKey", - name = "Quick-hop previous", - description = "When you press this key you'll hop to the previous world", - position = 0 - ) - default Keybind previousKey() - { - return new Keybind(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); - } - - @ConfigItem( - keyName = "nextKey", - name = "Quick-hop next", - description = "When you press this key you'll hop to the next world", - position = 1 - ) - default Keybind nextKey() - { - return new Keybind(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); - } - - @ConfigItem( - keyName = "quickhopOutOfDanger", - name = "Quick-hop out of dangerous worlds", - description = "Don't hop to a PVP/high risk world when quick-hopping", - position = 2 - ) - default boolean quickhopOutOfDanger() - { - return true; - } - - @ConfigItem( - keyName = "showSidebar", - name = "Show world hopper sidebar", - description = "Show sidebar containing all worlds that mimics in-game interface", - position = 3 - ) - default boolean showSidebar() - { - return true; - } - - @ConfigItem( - keyName = "ping", - name = "Show world ping", - description = "Shows ping to each game world", - position = 4 - ) - default boolean ping() - { - return true; - } - - @ConfigItem( - keyName = "showMessage", - name = "Show world hop message in chat", - description = "Shows what world is being hopped to in the chat", - position = 5 - ) - default boolean showWorldHopMessage() - { - return true; - } - - @ConfigItem( - keyName = "subscriptionFilter", - name = "Show subscription types", - description = "Only show free worlds, member worlds, or both types of worlds in sidebar", - position = 6 - ) - default SubscriptionFilterMode subscriptionFilter() - { - return SubscriptionFilterMode.BOTH; - } - - @ConfigItem( - keyName = "showHistory", - name = "Show history tab", - description = "Shows the history tab", - position = 7 - ) - default boolean showHistory() - { - return true; - } -} +/* + * Copyright (c) 2018, Lotto + * 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.worldhopper; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Keybind; + +@ConfigGroup(WorldHopperConfig.GROUP) +public interface WorldHopperConfig extends Config +{ + String GROUP = "worldhopper"; + + @ConfigItem( + keyName = "previousKey", + name = "Quick-hop previous", + description = "When you press this key you'll hop to the previous world", + position = 0 + ) + default Keybind previousKey() + { + return new Keybind(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); + } + + @ConfigItem( + keyName = "nextKey", + name = "Quick-hop next", + description = "When you press this key you'll hop to the next world", + position = 1 + ) + default Keybind nextKey() + { + return new Keybind(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); + } + + @ConfigItem( + keyName = "quickhopOutOfDanger", + name = "Quick-hop out of dangerous worlds", + description = "Don't hop to a PVP/high risk world when quick-hopping", + position = 2 + ) + default boolean quickhopOutOfDanger() + { + return true; + } + + @ConfigItem( + keyName = "showSidebar", + name = "Show world hopper sidebar", + description = "Show sidebar containing all worlds that mimics in-game interface", + position = 3 + ) + default boolean showSidebar() + { + return true; + } + + @ConfigItem( + keyName = "ping", + name = "Show world ping", + description = "Shows ping to each game world", + position = 4 + ) + default boolean ping() + { + return true; + } + + @ConfigItem( + keyName = "showMessage", + name = "Show world hop message in chat", + description = "Shows what world is being hopped to in the chat", + position = 5 + ) + default boolean showWorldHopMessage() + { + return true; + } + + @ConfigItem( + keyName = "subscriptionFilter", + name = "Show subscription types", + description = "Only show free worlds, member worlds, or both types of worlds in sidebar", + position = 6 + ) + default SubscriptionFilterMode subscriptionFilter() + { + return SubscriptionFilterMode.BOTH; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java index 33852bc40f..84abfcf9f5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java @@ -75,13 +75,13 @@ import net.runelite.client.eventbus.Subscribe; import net.runelite.client.input.KeyManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.worldhopper.ping.Ping; import net.runelite.client.ui.ClientToolbar; import net.runelite.client.ui.NavigationButton; import net.runelite.client.util.ExecutorServiceExceptionLogger; import net.runelite.client.util.HotkeyListener; import net.runelite.client.util.Text; import net.runelite.client.util.WorldUtil; -import net.runelite.client.util.ping.Ping; import net.runelite.http.api.worlds.World; import net.runelite.http.api.worlds.WorldClient; import net.runelite.http.api.worlds.WorldResult; @@ -258,49 +258,10 @@ public class WorldHopperPlugin extends Plugin panel.setFilterMode(config.subscriptionFilter()); updateList(); break; - case "showHistory": - panel.updateLayout(); - break; } } } - boolean showHistory() - { - return config.showHistory(); - } - - Map getHistory() - { - Map history = configManager.getConfiguration(WorldHopperConfig.GROUP, "history", Map.class); - if (history == null) - { - history = new HashMap(); - } - - return history; - } - - void clearHistory() - { - Map history = getHistory(); - history.clear(); - configManager.setConfiguration(WorldHopperConfig.GROUP, "history", history); - } - - void addToHistory() - { - addToHistory(client.getWorld()); - } - - void addToHistory(int world) - { - long unixTime = System.currentTimeMillis() / 1000L; - Map history = getHistory(); - history.put(String.valueOf(world), String.valueOf(unixTime)); - configManager.setConfiguration(WorldHopperConfig.GROUP, "history", history); - } - private void setFavoriteConfig(int world) { configManager.setConfiguration(WorldHopperConfig.GROUP, "favorite_" + world, true); @@ -457,12 +418,6 @@ public class WorldHopperPlugin extends Plugin lastWorld = newWorld; } } - - if (gameStateChanged.getGameState() == GameState.LOGGED_IN) - { - addToHistory(client.getWorld()); - panel.updateList(); - } } @Subscribe @@ -699,8 +654,6 @@ public class WorldHopperPlugin extends Plugin quickHopTargetWorld = rsWorld; displaySwitcherAttempts = 0; - - addToHistory(worldId); } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldSwitcherPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldSwitcherPanel.java index acfd4b25ea..27eb29b321 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldSwitcherPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldSwitcherPanel.java @@ -1,644 +1,399 @@ -/* - * Copyright (c) 2018, Psikoi - * 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.worldhopper; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.GridLayout; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JPanel; -import javax.swing.JTabbedPane; -import javax.swing.SwingUtilities; -import javax.swing.border.Border; -import lombok.AccessLevel; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import net.runelite.client.ui.ColorScheme; -import net.runelite.client.ui.DynamicGridLayout; -import net.runelite.client.ui.PluginPanel; -import net.runelite.http.api.worlds.World; -import net.runelite.http.api.worlds.WorldType; - -@Slf4j -class WorldSwitcherPanel extends PluginPanel -{ - private static final Color ODD_ROW = new Color(44, 44, 44); - - private static final int WORLD_COLUMN_WIDTH = 60; - private static final int PLAYERS_COLUMN_WIDTH = 40; - private static final int PING_COLUMN_WIDTH = 47; - - private final JPanel headerContainer; - private final JPanel headerHistContainer; - private final JPanel listContainer = new JPanel(); - private final JPanel histContainer = new JPanel(); - - private WorldTableHeader worldHeader; - private WorldTableHeader playersHeader; - private WorldTableHeader activityHeader; - private WorldTableHeader pingHeader; - - private WorldOrder orderIndex = WorldOrder.WORLD; - private boolean ascendingOrder = true; - - private ArrayList rows = new ArrayList<>(); - private ArrayList histRows = new ArrayList<>(); - private WorldHopperPlugin plugin; - @Setter(AccessLevel.PACKAGE) - private SubscriptionFilterMode filterMode; - - WorldSwitcherPanel(WorldHopperPlugin plugin) - { - this.plugin = plugin; - - setBorder(null); - setLayout(new DynamicGridLayout(0, 1)); - - headerContainer = buildHeader(); - headerHistContainer = buildHistoryHeader(); - - listContainer.setLayout(new GridLayout(0, 1)); - histContainer.setLayout(new GridLayout(0, 1)); - - updateLayout(); - } - - void updateLayout() - { - if (this.getComponentCount() > 0) - { - for (Component c : this.getComponents()) - { - this.remove(c); - } - } - - if (plugin.showHistory()) - { - Component tabs = createTabs(); - add(tabs); - } - else - { - add(headerContainer); - add(listContainer); - } - } - - void switchCurrentHighlight(int newWorld, int lastWorld) - { - for (WorldTableRow row : rows) - { - if (row.getWorld().getId() == newWorld) - { - row.recolour(true); - } - else if (row.getWorld().getId() == lastWorld) - { - row.recolour(false); - } - } - - for (WorldTableRow row : histRows) - { - if (row.getWorld().getId() == newWorld) - { - row.recolour(true); - } - else if (row.getWorld().getId() == lastWorld) - { - row.recolour(false); - } - } - } - - void updateListData(Map worldData) - { - for (WorldTableRow worldTableRow : rows) - { - World world = worldTableRow.getWorld(); - Integer playerCount = worldData.get(world.getId()); - if (playerCount != null) - { - worldTableRow.updatePlayerCount(playerCount); - } - } - - for (WorldTableRow worldTableRow : histRows) - { - World world = worldTableRow.getWorld(); - Integer playerCount = worldData.get(world.getId()); - if (playerCount != null) - { - worldTableRow.updatePlayerCount(playerCount); - } - } - - // If the list is being ordered by player count, then it has to be re-painted - // to properly display the new data - if (orderIndex == WorldOrder.PLAYERS) - { - updateList(); - } - } - - void updatePing(int world, int ping) - { - for (WorldTableRow worldTableRow : rows) - { - if (worldTableRow.getWorld().getId() == world) - { - worldTableRow.setPing(ping); - - // If the panel is sorted by ping, re-sort it - if (orderIndex == WorldOrder.PING) - { - updateList(); - } - break; - } - } - - for (WorldTableRow worldTableRow : histRows) - { - if (worldTableRow.getWorld().getId() == world) - { - worldTableRow.setPing(ping); - - // If the panel is sorted by ping, re-sort it - if (orderIndex == WorldOrder.PING) - { - updateList(); - } - break; - } - } - } - - void hidePing() - { - for (WorldTableRow worldTableRow : rows) - { - worldTableRow.hidePing(); - } - - for (WorldTableRow worldTableRow : histRows) - { - worldTableRow.hidePing(); - } - } - - void showPing() - { - for (WorldTableRow worldTableRow : rows) - { - worldTableRow.showPing(); - } - - for (WorldTableRow worldTableRow : histRows) - { - worldTableRow.showPing(); - } - } - - void updateList() - { - rows.sort((r1, r2) -> - { - switch (orderIndex) - { - case PING: - return Integer.compare(r1.getPing(), r2.getPing()) * (ascendingOrder ? 1 : -1); - case WORLD: - return Integer.compare(r1.getWorld().getId(), r2.getWorld().getId()) * (ascendingOrder ? 1 : -1); - case PLAYERS: - return Integer.compare(r1.getUpdatedPlayerCount(), r2.getUpdatedPlayerCount()) * (ascendingOrder ? 1 : -1); - case ACTIVITY: - return r1.getWorld().getActivity().compareTo(r2.getWorld().getActivity()) * -1 * (ascendingOrder ? 1 : -1); - default: - return 0; - } - }); - - // Leave empty activity worlds on the bottom of the list - if (orderIndex == WorldOrder.ACTIVITY) - { - rows.sort((r1, r2) -> r1.getWorld().getActivity().equals("-") ? 1 : -1); - } - - rows.sort((r1, r2) -> - { - boolean b1 = plugin.isFavorite(r1.getWorld()); - boolean b2 = plugin.isFavorite(r2.getWorld()); - return Boolean.compare(b2, b1); - }); - - listContainer.removeAll(); - histContainer.removeAll(); - - Map history = plugin.getHistory(); - Map matchedHist = new HashMap<>(); - for (int i = 0; i < rows.size(); i++) - { - WorldTableRow row = rows.get(i); - row.setBackground(i % 2 == 0 ? ODD_ROW : ColorScheme.DARK_GRAY_COLOR); - listContainer.add(row); - - String worldNum = String.valueOf(row.getWorld().getId()); - if (history.containsKey(worldNum)) - { - // Add toa list that we can sort later - matchedHist.put(worldNum, history.get(worldNum)); - } - } - - // Sort by ascending - matchedHist = matchedHist.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, - (e1, e2) -> e1, LinkedHashMap::new)); - - // Add matched rows to history list - Iterator it = matchedHist.entrySet().iterator(); - int histRowCount = 0; - while (it.hasNext()) - { - Map.Entry pair = (Map.Entry) it.next(); - for (WorldTableRow r : rows) - { - WorldTableRow histRow = r; - histRow.setBackground(histRowCount % 2 == 0 ? ODD_ROW : ColorScheme.DARK_GRAY_COLOR); - if (String.valueOf(r.getWorld().getId()).equals(pair.getKey())) - { - histContainer.add(r); - histRowCount++; - break; - } - } - it.remove(); - } - - listContainer.revalidate(); - listContainer.repaint(); - histContainer.revalidate(); - histContainer.repaint(); - } - - Component createTabs() - { - // Constraints for GB Layout - GridBagConstraints listConst = new GridBagConstraints(); - listConst.gridx = 0; - listConst.gridy = 1; - listConst.fill = GridBagConstraints.HORIZONTAL; - GridBagConstraints headConst = new GridBagConstraints(); - headConst.gridx = 0; - headConst.gridy = 0; - headConst.fill = GridBagConstraints.HORIZONTAL; - GridBagConstraints resetConst = new GridBagConstraints(); - resetConst.gridx = 0; - resetConst.gridy = 2; - resetConst.fill = GridBagConstraints.HORIZONTAL; - - // Border so that the scrollbar doesn't go over ping - Border paddingBorder = BorderFactory.createEmptyBorder(0, 0, 0, 5); - - // Clear history button - JButton resetBtn = new JButton("Clear History"); - resetBtn.addActionListener(e -> - { - plugin.clearHistory(); - plugin.addToHistory(); - updateList(); - }); - - // World Selector page - JPanel worldPanel = new JPanel(); - worldPanel.setBorder(paddingBorder); - worldPanel.setLayout(new GridBagLayout()); - worldPanel.add(headerContainer, headConst); - worldPanel.add(listContainer, listConst); - - // History page - JPanel histPanel = new JPanel(); - histPanel.setBorder(paddingBorder); - histPanel.setLayout(new GridBagLayout()); - histPanel.add(headerHistContainer, headConst); - histPanel.add(histContainer, listConst); - histPanel.add(resetBtn, resetConst); - - JTabbedPane worldTabs = new JTabbedPane(); - worldTabs.setName("tabs"); - worldTabs.addTab("Worlds", worldPanel); - worldTabs.addTab("History", histPanel); - - // This is a fix for preventing stretching of WorldTableRows - worldTabs.addChangeListener(e -> - { - switch (worldTabs.getSelectedIndex()) - { - case 0: - histPanel.remove(histContainer); - if (worldPanel.getComponentCount() < 2) - { - worldPanel.add(listContainer, listConst); - } - break; - case 1: - worldPanel.remove(listContainer); - if (histPanel.getComponentCount() < 3) - { - histPanel.add(histContainer, listConst); - } - break; - } - }); - - return worldTabs; - } - - void updateFavoriteMenu(int world, boolean favorite) - { - for (WorldTableRow row : rows) - { - if (row.getWorld().getId() == world) - { - row.setFavoriteMenu(favorite); - } - } - - for (WorldTableRow row : histRows) - { - if (row.getWorld().getId() == world) - { - row.setFavoriteMenu(favorite); - } - } - } - - void resetAllFavoriteMenus() - { - for (WorldTableRow row : rows) - { - row.setFavoriteMenu(false); - } - - for (WorldTableRow row : histRows) - { - row.setFavoriteMenu(false); - } - } - - void populate(List worlds) - { - Map pingHistory = new HashMap<>(); - - for (WorldTableRow row : rows) - { - pingHistory.put(row.getWorld().getId(), row.getPing()); - } - - rows.clear(); - - for (int i = 0; i < worlds.size(); i++) - { - World world = worlds.get(i); - - switch (filterMode) - { - case FREE: - if (world.getTypes().contains(WorldType.MEMBERS)) - { - continue; - } - break; - case MEMBERS: - if (!world.getTypes().contains(WorldType.MEMBERS)) - { - continue; - } - break; - } - - Integer ping = pingHistory.getOrDefault(world.getId(), 0); - rows.add(buildRow(world, i % 2 == 0, world.getId() == plugin.getCurrentWorld() && plugin.getLastWorld() != 0, plugin.isFavorite(world), ping)); - } - - updateList(); - } - - private void orderBy(WorldOrder order) - { - pingHeader.highlight(false, ascendingOrder); - worldHeader.highlight(false, ascendingOrder); - playersHeader.highlight(false, ascendingOrder); - activityHeader.highlight(false, ascendingOrder); - - switch (order) - { - case PING: - pingHeader.highlight(true, ascendingOrder); - break; - case WORLD: - worldHeader.highlight(true, ascendingOrder); - break; - case PLAYERS: - playersHeader.highlight(true, ascendingOrder); - break; - case ACTIVITY: - activityHeader.highlight(true, ascendingOrder); - break; - } - - orderIndex = order; - updateList(); - } - - /** - * Builds the entire table header. - */ - private JPanel buildHistoryHeader() - { - JPanel header = new JPanel(new BorderLayout()); - JPanel leftSide = new JPanel(new BorderLayout()); - JPanel rightSide = new JPanel(new BorderLayout()); - - WorldTableHeader pingHeader = new WorldTableHeader("Ping", false, ascendingOrder, plugin::refresh); - pingHeader.setPreferredSize(new Dimension(PING_COLUMN_WIDTH, 0)); - - WorldTableHeader worldHeader = new WorldTableHeader("World", false, ascendingOrder, plugin::refresh); - worldHeader.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0)); - - WorldTableHeader playersHeader = new WorldTableHeader("#", false, ascendingOrder, plugin::refresh); - playersHeader.setPreferredSize(new Dimension(PLAYERS_COLUMN_WIDTH, 0)); - - WorldTableHeader activityHeader = new WorldTableHeader("Activity", false, ascendingOrder, plugin::refresh); - - leftSide.add(worldHeader, BorderLayout.WEST); - leftSide.add(playersHeader, BorderLayout.CENTER); - - rightSide.add(activityHeader, BorderLayout.CENTER); - rightSide.add(pingHeader, BorderLayout.EAST); - - header.add(leftSide, BorderLayout.WEST); - header.add(rightSide, BorderLayout.CENTER); - - return header; - } - - private JPanel buildHeader() - { - JPanel header = new JPanel(new BorderLayout()); - JPanel leftSide = new JPanel(new BorderLayout()); - JPanel rightSide = new JPanel(new BorderLayout()); - - pingHeader = new WorldTableHeader("Ping", orderIndex == WorldOrder.PING, ascendingOrder, plugin::refresh); - pingHeader.setPreferredSize(new Dimension(PING_COLUMN_WIDTH, 0)); - pingHeader.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent mouseEvent) - { - if (SwingUtilities.isRightMouseButton(mouseEvent)) - { - return; - } - ascendingOrder = orderIndex != WorldOrder.PING || !ascendingOrder; - orderBy(WorldOrder.PING); - } - }); - - worldHeader = new WorldTableHeader("World", orderIndex == WorldOrder.WORLD, ascendingOrder, plugin::refresh); - worldHeader.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0)); - worldHeader.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent mouseEvent) - { - if (SwingUtilities.isRightMouseButton(mouseEvent)) - { - return; - } - ascendingOrder = orderIndex != WorldOrder.WORLD || !ascendingOrder; - orderBy(WorldOrder.WORLD); - } - }); - - playersHeader = new WorldTableHeader("#", orderIndex == WorldOrder.PLAYERS, ascendingOrder, plugin::refresh); - playersHeader.setPreferredSize(new Dimension(PLAYERS_COLUMN_WIDTH, 0)); - playersHeader.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent mouseEvent) - { - if (SwingUtilities.isRightMouseButton(mouseEvent)) - { - return; - } - ascendingOrder = orderIndex != WorldOrder.PLAYERS || !ascendingOrder; - orderBy(WorldOrder.PLAYERS); - } - }); - - activityHeader = new WorldTableHeader("Activity", orderIndex == WorldOrder.ACTIVITY, ascendingOrder, plugin::refresh); - activityHeader.addMouseListener(new MouseAdapter() - { - @Override - public void mousePressed(MouseEvent mouseEvent) - { - if (SwingUtilities.isRightMouseButton(mouseEvent)) - { - return; - } - ascendingOrder = orderIndex != WorldOrder.ACTIVITY || !ascendingOrder; - orderBy(WorldOrder.ACTIVITY); - } - }); - - leftSide.add(worldHeader, BorderLayout.WEST); - leftSide.add(playersHeader, BorderLayout.CENTER); - - rightSide.add(activityHeader, BorderLayout.CENTER); - rightSide.add(pingHeader, BorderLayout.EAST); - - header.add(leftSide, BorderLayout.WEST); - header.add(rightSide, BorderLayout.CENTER); - - return header; - } - - /** - * Builds a table row, that displays the world's information. - */ - private WorldTableRow buildRow(World world, boolean stripe, boolean current, boolean favorite, Integer ping) - { - WorldTableRow row = new WorldTableRow(world, current, favorite, - world1 -> - { - plugin.hopTo(world1); - }, - (world12, add) -> - { - if (add) - { - plugin.addToFavorites(world12); - } - else - { - plugin.removeFromFavorites(world12); - } - - updateList(); - } - ); - row.setBackground(stripe ? ODD_ROW : ColorScheme.DARK_GRAY_COLOR); - return row; - } - - /** - * Enumerates the multiple ordering options for the world list. - */ - private enum WorldOrder - { - WORLD, - PLAYERS, - ACTIVITY, - PING - } -} +/* + * Copyright (c) 2018, Psikoi + * 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.worldhopper; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import lombok.AccessLevel; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.DynamicGridLayout; +import net.runelite.client.ui.PluginPanel; +import net.runelite.http.api.worlds.World; +import net.runelite.http.api.worlds.WorldType; + +@Slf4j +class WorldSwitcherPanel extends PluginPanel +{ + private static final Color ODD_ROW = new Color(44, 44, 44); + + private static final int WORLD_COLUMN_WIDTH = 60; + private static final int PLAYERS_COLUMN_WIDTH = 40; + private static final int PING_COLUMN_WIDTH = 47; + + private final JPanel listContainer = new JPanel(); + + private WorldTableHeader worldHeader; + private WorldTableHeader playersHeader; + private WorldTableHeader activityHeader; + private WorldTableHeader pingHeader; + + private WorldOrder orderIndex = WorldOrder.WORLD; + private boolean ascendingOrder = true; + + private ArrayList rows = new ArrayList<>(); + private WorldHopperPlugin plugin; + @Setter(AccessLevel.PACKAGE) + private SubscriptionFilterMode filterMode; + + WorldSwitcherPanel(WorldHopperPlugin plugin) + { + this.plugin = plugin; + + setBorder(null); + setLayout(new DynamicGridLayout(0, 1)); + + JPanel headerContainer = buildHeader(); + + listContainer.setLayout(new GridLayout(0, 1)); + + add(headerContainer); + add(listContainer); + } + + void switchCurrentHighlight(int newWorld, int lastWorld) + { + for (WorldTableRow row : rows) + { + if (row.getWorld().getId() == newWorld) + { + row.recolour(true); + } + else if (row.getWorld().getId() == lastWorld) + { + row.recolour(false); + } + } + } + + void updateListData(Map worldData) + { + for (WorldTableRow worldTableRow : rows) + { + World world = worldTableRow.getWorld(); + Integer playerCount = worldData.get(world.getId()); + if (playerCount != null) + { + worldTableRow.updatePlayerCount(playerCount); + } + } + + // If the list is being ordered by player count, then it has to be re-painted + // to properly display the new data + if (orderIndex == WorldOrder.PLAYERS) + { + updateList(); + } + } + + void updatePing(int world, int ping) + { + for (WorldTableRow worldTableRow : rows) + { + if (worldTableRow.getWorld().getId() == world) + { + worldTableRow.setPing(ping); + + // If the panel is sorted by ping, re-sort it + if (orderIndex == WorldOrder.PING) + { + updateList(); + } + break; + } + } + } + + void hidePing() + { + for (WorldTableRow worldTableRow : rows) + { + worldTableRow.hidePing(); + } + } + + void showPing() + { + for (WorldTableRow worldTableRow : rows) + { + worldTableRow.showPing(); + } + } + + void updateList() + { + rows.sort((r1, r2) -> + { + switch (orderIndex) + { + case PING: + return Integer.compare(r1.getPing(), r2.getPing()) * (ascendingOrder ? 1 : -1); + case WORLD: + return Integer.compare(r1.getWorld().getId(), r2.getWorld().getId()) * (ascendingOrder ? 1 : -1); + case PLAYERS: + return Integer.compare(r1.getUpdatedPlayerCount(), r2.getUpdatedPlayerCount()) * (ascendingOrder ? 1 : -1); + case ACTIVITY: + return r1.getWorld().getActivity().compareTo(r2.getWorld().getActivity()) * -1 * (ascendingOrder ? 1 : -1); + default: + return 0; + } + }); + + // Leave empty activity worlds on the bottom of the list + if (orderIndex == WorldOrder.ACTIVITY) + { + rows.sort((r1, r2) -> r1.getWorld().getActivity().equals("-") ? 1 : -1); + } + + rows.sort((r1, r2) -> + { + boolean b1 = plugin.isFavorite(r1.getWorld()); + boolean b2 = plugin.isFavorite(r2.getWorld()); + return Boolean.compare(b2, b1); + }); + + listContainer.removeAll(); + + for (int i = 0; i < rows.size(); i++) + { + WorldTableRow row = rows.get(i); + row.setBackground(i % 2 == 0 ? ODD_ROW : ColorScheme.DARK_GRAY_COLOR); + listContainer.add(row); + } + + listContainer.revalidate(); + listContainer.repaint(); + } + + void updateFavoriteMenu(int world, boolean favorite) + { + for (WorldTableRow row : rows) + { + if (row.getWorld().getId() == world) + { + row.setFavoriteMenu(favorite); + } + } + } + + void resetAllFavoriteMenus() + { + for (WorldTableRow row : rows) + { + row.setFavoriteMenu(false); + } + + } + + void populate(List worlds) + { + rows.clear(); + + for (int i = 0; i < worlds.size(); i++) + { + World world = worlds.get(i); + + switch (filterMode) + { + case FREE: + if (world.getTypes().contains(WorldType.MEMBERS)) + { + continue; + } + break; + case MEMBERS: + if (!world.getTypes().contains(WorldType.MEMBERS)) + { + continue; + } + break; + } + + rows.add(buildRow(world, i % 2 == 0, world.getId() == plugin.getCurrentWorld() && plugin.getLastWorld() != 0, plugin.isFavorite(world))); + } + + updateList(); + } + + private void orderBy(WorldOrder order) + { + pingHeader.highlight(false, ascendingOrder); + worldHeader.highlight(false, ascendingOrder); + playersHeader.highlight(false, ascendingOrder); + activityHeader.highlight(false, ascendingOrder); + + switch (order) + { + case PING: + pingHeader.highlight(true, ascendingOrder); + break; + case WORLD: + worldHeader.highlight(true, ascendingOrder); + break; + case PLAYERS: + playersHeader.highlight(true, ascendingOrder); + break; + case ACTIVITY: + activityHeader.highlight(true, ascendingOrder); + break; + } + + orderIndex = order; + updateList(); + } + + /** + * Builds the entire table header. + */ + private JPanel buildHeader() + { + JPanel header = new JPanel(new BorderLayout()); + JPanel leftSide = new JPanel(new BorderLayout()); + JPanel rightSide = new JPanel(new BorderLayout()); + + pingHeader = new WorldTableHeader("Ping", orderIndex == WorldOrder.PING, ascendingOrder, plugin::refresh); + pingHeader.setPreferredSize(new Dimension(PING_COLUMN_WIDTH, 0)); + pingHeader.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + if (SwingUtilities.isRightMouseButton(mouseEvent)) + { + return; + } + ascendingOrder = orderIndex != WorldOrder.PING || !ascendingOrder; + orderBy(WorldOrder.PING); + } + }); + + worldHeader = new WorldTableHeader("World", orderIndex == WorldOrder.WORLD, ascendingOrder, plugin::refresh); + worldHeader.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0)); + worldHeader.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + if (SwingUtilities.isRightMouseButton(mouseEvent)) + { + return; + } + ascendingOrder = orderIndex != WorldOrder.WORLD || !ascendingOrder; + orderBy(WorldOrder.WORLD); + } + }); + + playersHeader = new WorldTableHeader("#", orderIndex == WorldOrder.PLAYERS, ascendingOrder, plugin::refresh); + playersHeader.setPreferredSize(new Dimension(PLAYERS_COLUMN_WIDTH, 0)); + playersHeader.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + if (SwingUtilities.isRightMouseButton(mouseEvent)) + { + return; + } + ascendingOrder = orderIndex != WorldOrder.PLAYERS || !ascendingOrder; + orderBy(WorldOrder.PLAYERS); + } + }); + + activityHeader = new WorldTableHeader("Activity", orderIndex == WorldOrder.ACTIVITY, ascendingOrder, plugin::refresh); + activityHeader.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + if (SwingUtilities.isRightMouseButton(mouseEvent)) + { + return; + } + ascendingOrder = orderIndex != WorldOrder.ACTIVITY || !ascendingOrder; + orderBy(WorldOrder.ACTIVITY); + } + }); + + leftSide.add(worldHeader, BorderLayout.WEST); + leftSide.add(playersHeader, BorderLayout.CENTER); + + rightSide.add(activityHeader, BorderLayout.CENTER); + rightSide.add(pingHeader, BorderLayout.EAST); + + header.add(leftSide, BorderLayout.WEST); + header.add(rightSide, BorderLayout.CENTER); + + return header; + } + + /** + * Builds a table row, that displays the world's information. + */ + private WorldTableRow buildRow(World world, boolean stripe, boolean current, boolean favorite) + { + WorldTableRow row = new WorldTableRow(world, current, favorite, + world1 -> + { + plugin.hopTo(world1); + }, + (world12, add) -> + { + if (add) + { + plugin.addToFavorites(world12); + } + else + { + plugin.removeFromFavorites(world12); + } + + updateList(); + } + ); + row.setBackground(stripe ? ODD_ROW : ColorScheme.DARK_GRAY_COLOR); + return row; + } + + /** + * Enumerates the multiple ordering options for the world list. + */ + private enum WorldOrder + { + WORLD, + PLAYERS, + ACTIVITY, + PING + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java index 6dd39ccfeb..a664d6536c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java @@ -1,343 +1,342 @@ -/* - * Copyright (c) 2018, Psikoi - * 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.worldhopper; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import javax.swing.ImageIcon; -import javax.swing.JLabel; -import javax.swing.JMenuItem; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.border.EmptyBorder; -import lombok.AccessLevel; -import lombok.Getter; -import net.runelite.client.ui.FontManager; -import net.runelite.client.util.ImageUtil; -import net.runelite.http.api.worlds.World; -import net.runelite.http.api.worlds.WorldType; - -class WorldTableRow extends JPanel -{ - private static final ImageIcon FLAG_AUS; - private static final ImageIcon FLAG_UK; - private static final ImageIcon FLAG_US; - private static final ImageIcon FLAG_GER; - - private static final int WORLD_COLUMN_WIDTH = 60; - private static final int PLAYERS_COLUMN_WIDTH = 40; - private static final int PING_COLUMN_WIDTH = 35; - - private static final Color CURRENT_WORLD = new Color(66, 227, 17); - private static final Color UNAVAILABLE_WORLD = Color.GRAY.darker().darker(); - private static final Color DANGEROUS_WORLD = new Color(251, 62, 62); - private static final Color TOURNAMENT_WORLD = new Color(79, 145, 255); - private static final Color MEMBERS_WORLD = new Color(210, 193, 53); - private static final Color FREE_WORLD = new Color(200, 200, 200); - - static - { - FLAG_AUS = new ImageIcon(ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "flag_aus.png")); - FLAG_UK = new ImageIcon(ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "flag_uk.png")); - FLAG_US = new ImageIcon(ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "flag_us.png")); - FLAG_GER = new ImageIcon(ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "flag_ger.png")); - } - - private final JMenuItem favoriteMenuOption = new JMenuItem(); - - private JLabel worldField; - private JLabel playerCountField; - private JLabel activityField; - private JLabel pingField; - private BiConsumer onFavorite; - - @Getter - private final World world; - - @Getter(AccessLevel.PACKAGE) - private int updatedPlayerCount; - - private int ping; - - private Color lastBackground; - private boolean current; - - WorldTableRow(World world, boolean current, boolean favorite, Consumer onSelect, BiConsumer onFavorite) - { - this.current = current; - this.world = world; - this.onFavorite = onFavorite; - this.updatedPlayerCount = world.getPlayers(); - this. - - setLayout(new BorderLayout()); - setBorder(new EmptyBorder(2, 0, 2, 0)); - - addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent mouseEvent) - { - if (mouseEvent.getClickCount() == 2) - { - if (onSelect != null) - { - onSelect.accept(world); - } - } - } - - @Override - public void mousePressed(MouseEvent mouseEvent) - { - if (mouseEvent.getClickCount() == 2) - { - setBackground(getBackground().brighter()); - } - } - - @Override - public void mouseReleased(MouseEvent mouseEvent) - { - if (mouseEvent.getClickCount() == 2) - { - setBackground(getBackground().darker()); - } - } - - @Override - public void mouseEntered(MouseEvent mouseEvent) - { - WorldTableRow.this.lastBackground = getBackground(); - setBackground(getBackground().brighter()); - } - - @Override - public void mouseExited(MouseEvent mouseEvent) - { - setBackground(lastBackground); - } - }); - - setFavoriteMenu(favorite); - - final JPopupMenu popupMenu = new JPopupMenu(); - popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5)); - popupMenu.add(favoriteMenuOption); - - setComponentPopupMenu(popupMenu); - - JPanel leftSide = new JPanel(new BorderLayout()); - JPanel rightSide = new JPanel(new BorderLayout()); - leftSide.setOpaque(false); - rightSide.setOpaque(false); - - JPanel worldField = buildWorldField(); - worldField.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0)); - worldField.setOpaque(false); - - JPanel pingField = buildPingField(); - pingField.setPreferredSize(new Dimension(PING_COLUMN_WIDTH, 0)); - pingField.setOpaque(false); - - JPanel playersField = buildPlayersField(); - playersField.setPreferredSize(new Dimension(PLAYERS_COLUMN_WIDTH, 0)); - playersField.setOpaque(false); - - JPanel activityField = buildActivityField(); - activityField.setBorder(new EmptyBorder(5, 5, 5, 5)); - activityField.setOpaque(false); - - recolour(current); - - leftSide.add(worldField, BorderLayout.WEST); - leftSide.add(playersField, BorderLayout.CENTER); - rightSide.add(activityField, BorderLayout.CENTER); - rightSide.add(pingField, BorderLayout.EAST); - - add(leftSide, BorderLayout.WEST); - add(rightSide, BorderLayout.CENTER); - } - - void setFavoriteMenu(boolean favorite) - { - String favoriteAction = favorite ? - "Remove " + world.getId() + " from favorites" : - "Add " + world.getId() + " to favorites"; - - favoriteMenuOption.setText(favoriteAction); - - for (ActionListener listener : favoriteMenuOption.getActionListeners()) - { - favoriteMenuOption.removeActionListener(listener); - } - - favoriteMenuOption.addActionListener(e -> - { - onFavorite.accept(world, !favorite); - }); - } - - void updatePlayerCount(int playerCount) - { - this.updatedPlayerCount = playerCount; - playerCountField.setText(String.valueOf(playerCount)); - } - - void setPing(int ping) - { - this.ping = ping; - pingField.setText(ping <= 0 ? "-" : Integer.toString(ping)); - } - - void hidePing() - { - pingField.setText("-"); - } - - void showPing() - { - setPing(ping); // to update pingField - } - - int getPing() - { - return ping; - } - - public void recolour(boolean current) - { - playerCountField.setForeground(current ? CURRENT_WORLD : Color.WHITE); - pingField.setForeground(current ? CURRENT_WORLD : Color.WHITE); - - if (current) - { - activityField.setForeground(CURRENT_WORLD); - worldField.setForeground(CURRENT_WORLD); - return; - } - else if (world.getTypes().contains(WorldType.PVP) - || world.getTypes().contains(WorldType.HIGH_RISK) - || world.getTypes().contains(WorldType.DEADMAN) - || world.getTypes().contains(WorldType.SEASONAL_DEADMAN)) - { - activityField.setForeground(DANGEROUS_WORLD); - } - else if (world.getTypes().contains(WorldType.TOURNAMENT)) - { - activityField.setForeground(TOURNAMENT_WORLD); - } - else - { - activityField.setForeground(Color.WHITE); - } - - worldField.setForeground(world.getTypes().contains(WorldType.MEMBERS) ? MEMBERS_WORLD : FREE_WORLD); - } - - /** - * Builds the players list field (containing the amount of players logged in that world). - */ - private JPanel buildPlayersField() - { - JPanel column = new JPanel(new BorderLayout()); - column.setBorder(new EmptyBorder(0, 5, 0, 5)); - - playerCountField = new JLabel(world.getPlayers() + ""); - playerCountField.setFont(FontManager.getRunescapeSmallFont()); - - column.add(playerCountField, BorderLayout.WEST); - - return column; - } - - private JPanel buildPingField() - { - JPanel column = new JPanel(new BorderLayout()); - column.setBorder(new EmptyBorder(0, 5, 0, 5)); - - pingField = new JLabel("-"); - pingField.setFont(FontManager.getRunescapeSmallFont()); - - column.add(pingField, BorderLayout.EAST); - - return column; - } - - /** - * Builds the activity list field (containing that world's activity/theme). - */ - private JPanel buildActivityField() - { - JPanel column = new JPanel(new BorderLayout()); - column.setBorder(new EmptyBorder(0, 5, 0, 5)); - - activityField = new JLabel(world.getActivity()); - activityField.setFont(FontManager.getRunescapeSmallFont()); - - column.add(activityField, BorderLayout.WEST); - - return column; - } - - /** - * Builds the world list field (containing the country's flag and the world index). - */ - private JPanel buildWorldField() - { - JPanel column = new JPanel(new BorderLayout(7, 0)); - column.setBorder(new EmptyBorder(0, 5, 0, 5)); - - worldField = new JLabel(world.getId() + ""); - - JLabel flag = new JLabel(getFlag(world.getLocation())); - - column.add(flag, BorderLayout.WEST); - column.add(worldField, BorderLayout.CENTER); - - return column; - } - - private ImageIcon getFlag(int locationId) - { - switch (locationId) - { - case 0: - return FLAG_US; - case 1: - return FLAG_UK; - case 3: - return FLAG_AUS; - default: - return FLAG_GER; - } - } -} +/* + * Copyright (c) 2018, Psikoi + * 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.worldhopper; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.border.EmptyBorder; +import lombok.AccessLevel; +import lombok.Getter; +import net.runelite.client.ui.FontManager; +import net.runelite.client.util.ImageUtil; +import net.runelite.http.api.worlds.World; +import net.runelite.http.api.worlds.WorldType; + +class WorldTableRow extends JPanel +{ + private static final ImageIcon FLAG_AUS; + private static final ImageIcon FLAG_UK; + private static final ImageIcon FLAG_US; + private static final ImageIcon FLAG_GER; + + private static final int WORLD_COLUMN_WIDTH = 60; + private static final int PLAYERS_COLUMN_WIDTH = 40; + private static final int PING_COLUMN_WIDTH = 35; + + private static final Color CURRENT_WORLD = new Color(66, 227, 17); + private static final Color UNAVAILABLE_WORLD = Color.GRAY.darker().darker(); + private static final Color DANGEROUS_WORLD = new Color(251, 62, 62); + private static final Color TOURNAMENT_WORLD = new Color(79, 145, 255); + private static final Color MEMBERS_WORLD = new Color(210, 193, 53); + private static final Color FREE_WORLD = new Color(200, 200, 200); + + static + { + FLAG_AUS = new ImageIcon(ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "flag_aus.png")); + FLAG_UK = new ImageIcon(ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "flag_uk.png")); + FLAG_US = new ImageIcon(ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "flag_us.png")); + FLAG_GER = new ImageIcon(ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "flag_ger.png")); + } + + private final JMenuItem favoriteMenuOption = new JMenuItem(); + + private JLabel worldField; + private JLabel playerCountField; + private JLabel activityField; + private JLabel pingField; + private BiConsumer onFavorite; + + @Getter + private final World world; + + @Getter(AccessLevel.PACKAGE) + private int updatedPlayerCount; + + private int ping; + + private Color lastBackground; + private boolean current; + + WorldTableRow(World world, boolean current, boolean favorite, Consumer onSelect, BiConsumer onFavorite) + { + this.current = current; + this.world = world; + this.onFavorite = onFavorite; + this.updatedPlayerCount = world.getPlayers(); + + setLayout(new BorderLayout()); + setBorder(new EmptyBorder(2, 0, 2, 0)); + + addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent mouseEvent) + { + if (mouseEvent.getClickCount() == 2) + { + if (onSelect != null) + { + onSelect.accept(world); + } + } + } + + @Override + public void mousePressed(MouseEvent mouseEvent) + { + if (mouseEvent.getClickCount() == 2) + { + setBackground(getBackground().brighter()); + } + } + + @Override + public void mouseReleased(MouseEvent mouseEvent) + { + if (mouseEvent.getClickCount() == 2) + { + setBackground(getBackground().darker()); + } + } + + @Override + public void mouseEntered(MouseEvent mouseEvent) + { + WorldTableRow.this.lastBackground = getBackground(); + setBackground(getBackground().brighter()); + } + + @Override + public void mouseExited(MouseEvent mouseEvent) + { + setBackground(lastBackground); + } + }); + + setFavoriteMenu(favorite); + + final JPopupMenu popupMenu = new JPopupMenu(); + popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5)); + popupMenu.add(favoriteMenuOption); + + setComponentPopupMenu(popupMenu); + + JPanel leftSide = new JPanel(new BorderLayout()); + JPanel rightSide = new JPanel(new BorderLayout()); + leftSide.setOpaque(false); + rightSide.setOpaque(false); + + JPanel worldField = buildWorldField(); + worldField.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0)); + worldField.setOpaque(false); + + JPanel pingField = buildPingField(); + pingField.setPreferredSize(new Dimension(PING_COLUMN_WIDTH, 0)); + pingField.setOpaque(false); + + JPanel playersField = buildPlayersField(); + playersField.setPreferredSize(new Dimension(PLAYERS_COLUMN_WIDTH, 0)); + playersField.setOpaque(false); + + JPanel activityField = buildActivityField(); + activityField.setBorder(new EmptyBorder(5, 5, 5, 5)); + activityField.setOpaque(false); + + recolour(current); + + leftSide.add(worldField, BorderLayout.WEST); + leftSide.add(playersField, BorderLayout.CENTER); + rightSide.add(activityField, BorderLayout.CENTER); + rightSide.add(pingField, BorderLayout.EAST); + + add(leftSide, BorderLayout.WEST); + add(rightSide, BorderLayout.CENTER); + } + + void setFavoriteMenu(boolean favorite) + { + String favoriteAction = favorite ? + "Remove " + world.getId() + " from favorites" : + "Add " + world.getId() + " to favorites"; + + favoriteMenuOption.setText(favoriteAction); + + for (ActionListener listener : favoriteMenuOption.getActionListeners()) + { + favoriteMenuOption.removeActionListener(listener); + } + + favoriteMenuOption.addActionListener(e -> + { + onFavorite.accept(world, !favorite); + }); + } + + void updatePlayerCount(int playerCount) + { + this.updatedPlayerCount = playerCount; + playerCountField.setText(String.valueOf(playerCount)); + } + + void setPing(int ping) + { + this.ping = ping; + pingField.setText(ping <= 0 ? "-" : Integer.toString(ping)); + } + + void hidePing() + { + pingField.setText("-"); + } + + void showPing() + { + setPing(ping); // to update pingField + } + + int getPing() + { + return ping; + } + + public void recolour(boolean current) + { + playerCountField.setForeground(current ? CURRENT_WORLD : Color.WHITE); + pingField.setForeground(current ? CURRENT_WORLD : Color.WHITE); + + if (current) + { + activityField.setForeground(CURRENT_WORLD); + worldField.setForeground(CURRENT_WORLD); + return; + } + else if (world.getTypes().contains(WorldType.PVP) + || world.getTypes().contains(WorldType.HIGH_RISK) + || world.getTypes().contains(WorldType.DEADMAN) + || world.getTypes().contains(WorldType.SEASONAL_DEADMAN)) + { + activityField.setForeground(DANGEROUS_WORLD); + } + else if (world.getTypes().contains(WorldType.TOURNAMENT)) + { + activityField.setForeground(TOURNAMENT_WORLD); + } + else + { + activityField.setForeground(Color.WHITE); + } + + worldField.setForeground(world.getTypes().contains(WorldType.MEMBERS) ? MEMBERS_WORLD : FREE_WORLD); + } + + /** + * Builds the players list field (containing the amount of players logged in that world). + */ + private JPanel buildPlayersField() + { + JPanel column = new JPanel(new BorderLayout()); + column.setBorder(new EmptyBorder(0, 5, 0, 5)); + + playerCountField = new JLabel(world.getPlayers() + ""); + playerCountField.setFont(FontManager.getRunescapeSmallFont()); + + column.add(playerCountField, BorderLayout.WEST); + + return column; + } + + private JPanel buildPingField() + { + JPanel column = new JPanel(new BorderLayout()); + column.setBorder(new EmptyBorder(0, 5, 0, 5)); + + pingField = new JLabel("-"); + pingField.setFont(FontManager.getRunescapeSmallFont()); + + column.add(pingField, BorderLayout.EAST); + + return column; + } + + /** + * Builds the activity list field (containing that world's activity/theme). + */ + private JPanel buildActivityField() + { + JPanel column = new JPanel(new BorderLayout()); + column.setBorder(new EmptyBorder(0, 5, 0, 5)); + + activityField = new JLabel(world.getActivity()); + activityField.setFont(FontManager.getRunescapeSmallFont()); + + column.add(activityField, BorderLayout.WEST); + + return column; + } + + /** + * Builds the world list field (containing the country's flag and the world index). + */ + private JPanel buildWorldField() + { + JPanel column = new JPanel(new BorderLayout(7, 0)); + column.setBorder(new EmptyBorder(0, 5, 0, 5)); + + worldField = new JLabel(world.getId() + ""); + + JLabel flag = new JLabel(getFlag(world.getLocation())); + + column.add(flag, BorderLayout.WEST); + column.add(worldField, BorderLayout.CENTER); + + return column; + } + + private ImageIcon getFlag(int locationId) + { + switch (locationId) + { + case 0: + return FLAG_US; + case 1: + return FLAG_UK; + case 3: + return FLAG_AUS; + default: + return FLAG_GER; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/IPHlpAPI.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/IPHlpAPI.java new file mode 100644 index 0000000000..109ea36f77 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/IPHlpAPI.java @@ -0,0 +1,40 @@ +/* + * 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.worldhopper.ping; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; + +interface IPHlpAPI extends Library +{ + IPHlpAPI INSTANCE = Native.loadLibrary("IPHlpAPI", IPHlpAPI.class); + + Pointer IcmpCreateFile(); + + boolean IcmpCloseHandle(Pointer handle); + + int IcmpSendEcho(Pointer IcmpHandle, int DestinationAddress, Pointer RequestData, short RequestSize, Pointer RequestOptions, IcmpEchoReply ReplyBuffer, int ReplySize, int Timeout); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/IcmpEchoReply.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/IcmpEchoReply.java new file mode 100644 index 0000000000..0e1cc198a3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/IcmpEchoReply.java @@ -0,0 +1,60 @@ +/* + * 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.worldhopper.ping; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.WinDef; +import java.util.Arrays; +import java.util.List; + +public class IcmpEchoReply extends Structure +{ + private static final int IP_OPTION_INFO_SIZE = 1 + 1 + 1 + 1 + (Pointer.SIZE == 8 ? 12 : 4); // on 64bit vms add 4 byte padding + public static final int SIZE = 4 + 4 + 4 + 2 + 2 + Pointer.SIZE + IP_OPTION_INFO_SIZE; + + public WinDef.ULONG address; + public WinDef.ULONG status; + public WinDef.ULONG roundTripTime; + public WinDef.USHORT dataSize; + public WinDef.USHORT reserved; + public WinDef.PVOID data; + public WinDef.UCHAR ttl; + public WinDef.UCHAR tos; + public WinDef.UCHAR flags; + public WinDef.UCHAR optionsSize; + public WinDef.PVOID optionsData; + + IcmpEchoReply(Pointer p) + { + super(p); + } + + @Override + protected List getFieldOrder() + { + return Arrays.asList("address", "status", "roundTripTime", "dataSize", "reserved", "data", "ttl", "tos", "flags", "optionsSize", "optionsData"); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java new file mode 100644 index 0000000000..fd9a84fa57 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java @@ -0,0 +1,103 @@ +/* + * 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.worldhopper.ping; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.util.OSType; +import net.runelite.http.api.worlds.World; + +@Slf4j +public class Ping +{ + private static final String RUNELITE_PING = "RuneLitePing"; + + private static final int TIMEOUT = 2000; + private static final int PORT = 43594; + + public static int ping(World world) + { + try + { + switch (OSType.getOSType()) + { + case Windows: + return windowsPing(world); + default: + return tcpPing(world); + } + } + catch (IOException ex) + { + log.warn("error pinging", ex); + return -1; + } + } + + private static int windowsPing(World world) throws UnknownHostException + { + IPHlpAPI ipHlpAPI = IPHlpAPI.INSTANCE; + Pointer ptr = ipHlpAPI.IcmpCreateFile(); + InetAddress inetAddress = InetAddress.getByName(world.getAddress()); + byte[] address = inetAddress.getAddress(); + String dataStr = RUNELITE_PING; + int dataLength = dataStr.length() + 1; + Pointer data = new Memory(dataLength); + data.setString(0L, dataStr); + IcmpEchoReply icmpEchoReply = new IcmpEchoReply(new Memory(IcmpEchoReply.SIZE + dataLength)); + assert icmpEchoReply.size() == IcmpEchoReply.SIZE; + int packed = (address[0] & 0xff) | ((address[1] & 0xff) << 8) | ((address[2] & 0xff) << 16) | ((address[3] & 0xff) << 24); + int ret = ipHlpAPI.IcmpSendEcho(ptr, packed, data, (short) (dataLength), Pointer.NULL, icmpEchoReply, IcmpEchoReply.SIZE + dataLength, TIMEOUT); + if (ret != 1) + { + ipHlpAPI.IcmpCloseHandle(ptr); + return -1; + } + + int rtt = Math.toIntExact(icmpEchoReply.roundTripTime.longValue()); + ipHlpAPI.IcmpCloseHandle(ptr); + + return rtt; + } + + private static int tcpPing(World world) throws IOException + { + try (Socket socket = new Socket()) + { + socket.setSoTimeout(TIMEOUT); + InetAddress inetAddress = InetAddress.getByName(world.getAddress()); + long start = System.nanoTime(); + socket.connect(new InetSocketAddress(inetAddress, PORT)); + long end = System.nanoTime(); + return (int) ((end - start) / 1000000L); + } + } +}