@@ -168,6 +168,17 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- net.runelite:discord also has this -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.java.dev.jna</groupId>
|
||||||
|
<artifactId>jna</artifactId>
|
||||||
|
<version>4.5.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.java.dev.jna</groupId>
|
||||||
|
<artifactId>jna-platform</artifactId>
|
||||||
|
<version>4.5.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.runelite</groupId>
|
<groupId>net.runelite</groupId>
|
||||||
|
|||||||
@@ -79,4 +79,15 @@ public interface WorldHopperConfig extends Config
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "ping",
|
||||||
|
name = "Show world ping",
|
||||||
|
description = "Shows ping to each game world",
|
||||||
|
position = 4
|
||||||
|
)
|
||||||
|
default boolean ping()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.worldhopper;
|
package net.runelite.client.plugins.worldhopper;
|
||||||
|
|
||||||
|
import com.google.common.base.Stopwatch;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ObjectArrays;
|
import com.google.common.collect.ObjectArrays;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
@@ -37,6 +38,7 @@ import java.util.EnumSet;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -73,8 +75,10 @@ import net.runelite.client.eventbus.Subscribe;
|
|||||||
import net.runelite.client.input.KeyManager;
|
import net.runelite.client.input.KeyManager;
|
||||||
import net.runelite.client.plugins.Plugin;
|
import net.runelite.client.plugins.Plugin;
|
||||||
import net.runelite.client.plugins.PluginDescriptor;
|
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.ClientToolbar;
|
||||||
import net.runelite.client.ui.NavigationButton;
|
import net.runelite.client.ui.NavigationButton;
|
||||||
|
import net.runelite.client.util.ExecutorServiceExceptionLogger;
|
||||||
import net.runelite.client.util.HotkeyListener;
|
import net.runelite.client.util.HotkeyListener;
|
||||||
import net.runelite.client.util.Text;
|
import net.runelite.client.util.Text;
|
||||||
import net.runelite.client.util.WorldUtil;
|
import net.runelite.client.util.WorldUtil;
|
||||||
@@ -92,6 +96,7 @@ import org.apache.commons.lang3.ArrayUtils;
|
|||||||
public class WorldHopperPlugin extends Plugin
|
public class WorldHopperPlugin extends Plugin
|
||||||
{
|
{
|
||||||
private static final int WORLD_FETCH_TIMER = 10;
|
private static final int WORLD_FETCH_TIMER = 10;
|
||||||
|
private static final int WORLD_PING_TIMER = 10;
|
||||||
private static final int REFRESH_THROTTLE = 60_000; // ms
|
private static final int REFRESH_THROTTLE = 60_000; // ms
|
||||||
private static final int TICK_THROTTLE = (int) Duration.ofMinutes(10).toMillis();
|
private static final int TICK_THROTTLE = (int) Duration.ofMinutes(10).toMillis();
|
||||||
|
|
||||||
@@ -126,6 +131,8 @@ public class WorldHopperPlugin extends Plugin
|
|||||||
@Inject
|
@Inject
|
||||||
private WorldHopperConfig config;
|
private WorldHopperConfig config;
|
||||||
|
|
||||||
|
private final ScheduledExecutorService hopperExecutorService = new ExecutorServiceExceptionLogger(Executors.newSingleThreadScheduledExecutor());
|
||||||
|
|
||||||
private NavigationButton navButton;
|
private NavigationButton navButton;
|
||||||
private WorldSwitcherPanel panel;
|
private WorldSwitcherPanel panel;
|
||||||
|
|
||||||
@@ -137,9 +144,10 @@ public class WorldHopperPlugin extends Plugin
|
|||||||
|
|
||||||
private int favoriteWorld1, favoriteWorld2;
|
private int favoriteWorld1, favoriteWorld2;
|
||||||
|
|
||||||
private ScheduledFuture<?> worldResultFuture;
|
private ScheduledFuture<?> worldResultFuture, pingFuture;
|
||||||
private WorldResult worldResult;
|
private WorldResult worldResult;
|
||||||
private Instant lastFetch;
|
private Instant lastFetch;
|
||||||
|
private boolean firstRun;
|
||||||
|
|
||||||
private final HotkeyListener previousKeyListener = new HotkeyListener(() -> config.previousKey())
|
private final HotkeyListener previousKeyListener = new HotkeyListener(() -> config.previousKey())
|
||||||
{
|
{
|
||||||
@@ -167,11 +175,11 @@ public class WorldHopperPlugin extends Plugin
|
|||||||
@Override
|
@Override
|
||||||
protected void startUp() throws Exception
|
protected void startUp() throws Exception
|
||||||
{
|
{
|
||||||
|
firstRun = true;
|
||||||
|
|
||||||
keyManager.registerKeyListener(previousKeyListener);
|
keyManager.registerKeyListener(previousKeyListener);
|
||||||
keyManager.registerKeyListener(nextKeyListener);
|
keyManager.registerKeyListener(nextKeyListener);
|
||||||
|
|
||||||
worldResultFuture = executorService.scheduleAtFixedRate(this::tick, 0, WORLD_FETCH_TIMER, TimeUnit.MINUTES);
|
|
||||||
|
|
||||||
panel = new WorldSwitcherPanel(this);
|
panel = new WorldSwitcherPanel(this);
|
||||||
|
|
||||||
final BufferedImage icon;
|
final BufferedImage icon;
|
||||||
@@ -191,11 +199,17 @@ public class WorldHopperPlugin extends Plugin
|
|||||||
{
|
{
|
||||||
clientToolbar.addNavigation(navButton);
|
clientToolbar.addNavigation(navButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
worldResultFuture = executorService.scheduleAtFixedRate(this::tick, 0, WORLD_FETCH_TIMER, TimeUnit.MINUTES);
|
||||||
|
pingFuture = hopperExecutorService.scheduleAtFixedRate(this::pingWorlds, WORLD_PING_TIMER, WORLD_PING_TIMER, TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void shutDown() throws Exception
|
protected void shutDown() throws Exception
|
||||||
{
|
{
|
||||||
|
pingFuture.cancel(true);
|
||||||
|
pingFuture = null;
|
||||||
|
|
||||||
keyManager.unregisterKeyListener(previousKeyListener);
|
keyManager.unregisterKeyListener(previousKeyListener);
|
||||||
keyManager.unregisterKeyListener(nextKeyListener);
|
keyManager.unregisterKeyListener(nextKeyListener);
|
||||||
|
|
||||||
@@ -205,20 +219,37 @@ public class WorldHopperPlugin extends Plugin
|
|||||||
lastFetch = null;
|
lastFetch = null;
|
||||||
|
|
||||||
clientToolbar.removeNavigation(navButton);
|
clientToolbar.removeNavigation(navButton);
|
||||||
|
|
||||||
|
hopperExecutorService.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onConfigChanged(final ConfigChanged event)
|
public void onConfigChanged(final ConfigChanged event)
|
||||||
{
|
{
|
||||||
if (event.getGroup().equals(WorldHopperConfig.GROUP) && event.getKey().equals("showSidebar"))
|
if (event.getGroup().equals(WorldHopperConfig.GROUP))
|
||||||
{
|
{
|
||||||
if (config.showSidebar())
|
switch (event.getKey())
|
||||||
{
|
{
|
||||||
clientToolbar.addNavigation(navButton);
|
case "showSidebar":
|
||||||
}
|
if (config.showSidebar())
|
||||||
else
|
{
|
||||||
{
|
clientToolbar.addNavigation(navButton);
|
||||||
clientToolbar.removeNavigation(navButton);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clientToolbar.removeNavigation(navButton);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ping":
|
||||||
|
if (config.ping())
|
||||||
|
{
|
||||||
|
SwingUtilities.invokeLater(() -> panel.showPing());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SwingUtilities.invokeLater(() -> panel.hidePing());
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,6 +431,13 @@ public class WorldHopperPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchWorlds();
|
fetchWorlds();
|
||||||
|
|
||||||
|
// Ping worlds once at startup
|
||||||
|
if (firstRun)
|
||||||
|
{
|
||||||
|
firstRun = false;
|
||||||
|
hopperExecutorService.execute(this::pingWorlds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh()
|
void refresh()
|
||||||
@@ -690,4 +728,24 @@ public class WorldHopperPlugin extends Plugin
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void pingWorlds()
|
||||||
|
{
|
||||||
|
if (worldResult == null || !config.showSidebar() || !config.ping())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||||
|
|
||||||
|
for (World world : worldResult.getWorlds())
|
||||||
|
{
|
||||||
|
int ping = Ping.ping(world);
|
||||||
|
SwingUtilities.invokeLater(() -> panel.updatePing(world, ping));
|
||||||
|
}
|
||||||
|
|
||||||
|
stopwatch.stop();
|
||||||
|
|
||||||
|
log.debug("Done pinging worlds in {}", stopwatch.elapsed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.border.EmptyBorder;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.runelite.client.ui.ColorScheme;
|
import net.runelite.client.ui.ColorScheme;
|
||||||
import net.runelite.client.ui.DynamicGridLayout;
|
import net.runelite.client.ui.DynamicGridLayout;
|
||||||
@@ -49,12 +48,14 @@ class WorldSwitcherPanel extends PluginPanel
|
|||||||
|
|
||||||
private static final int WORLD_COLUMN_WIDTH = 60;
|
private static final int WORLD_COLUMN_WIDTH = 60;
|
||||||
private static final int PLAYERS_COLUMN_WIDTH = 40;
|
private static final int PLAYERS_COLUMN_WIDTH = 40;
|
||||||
|
private static final int PING_COLUMN_WIDTH = 47;
|
||||||
|
|
||||||
private final JPanel listContainer = new JPanel();
|
private final JPanel listContainer = new JPanel();
|
||||||
|
|
||||||
private WorldTableHeader worldHeader;
|
private WorldTableHeader worldHeader;
|
||||||
private WorldTableHeader playersHeader;
|
private WorldTableHeader playersHeader;
|
||||||
private WorldTableHeader activityHeader;
|
private WorldTableHeader activityHeader;
|
||||||
|
private WorldTableHeader pingHeader;
|
||||||
|
|
||||||
private WorldOrder orderIndex = WorldOrder.WORLD;
|
private WorldOrder orderIndex = WorldOrder.WORLD;
|
||||||
private boolean ascendingOrder = true;
|
private boolean ascendingOrder = true;
|
||||||
@@ -112,12 +113,48 @@ class WorldSwitcherPanel extends PluginPanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updatePing(World world, int ping)
|
||||||
|
{
|
||||||
|
for (WorldTableRow worldTableRow : rows)
|
||||||
|
{
|
||||||
|
if (worldTableRow.getWorld() == 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()
|
void updateList()
|
||||||
{
|
{
|
||||||
rows.sort((r1, r2) ->
|
rows.sort((r1, r2) ->
|
||||||
{
|
{
|
||||||
switch (orderIndex)
|
switch (orderIndex)
|
||||||
{
|
{
|
||||||
|
case PING:
|
||||||
|
return Integer.compare(r1.getPing(), r2.getPing()) * (ascendingOrder ? 1 : -1);
|
||||||
case WORLD:
|
case WORLD:
|
||||||
return Integer.compare(r1.getWorld().getId(), r2.getWorld().getId()) * (ascendingOrder ? 1 : -1);
|
return Integer.compare(r1.getWorld().getId(), r2.getWorld().getId()) * (ascendingOrder ? 1 : -1);
|
||||||
case PLAYERS:
|
case PLAYERS:
|
||||||
@@ -126,7 +163,6 @@ class WorldSwitcherPanel extends PluginPanel
|
|||||||
return r1.getWorld().getActivity().compareTo(r2.getWorld().getActivity()) * -1 * (ascendingOrder ? 1 : -1);
|
return r1.getWorld().getActivity().compareTo(r2.getWorld().getActivity()) * -1 * (ascendingOrder ? 1 : -1);
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -191,12 +227,16 @@ class WorldSwitcherPanel extends PluginPanel
|
|||||||
|
|
||||||
private void orderBy(WorldOrder order)
|
private void orderBy(WorldOrder order)
|
||||||
{
|
{
|
||||||
|
pingHeader.highlight(false, ascendingOrder);
|
||||||
worldHeader.highlight(false, ascendingOrder);
|
worldHeader.highlight(false, ascendingOrder);
|
||||||
playersHeader.highlight(false, ascendingOrder);
|
playersHeader.highlight(false, ascendingOrder);
|
||||||
activityHeader.highlight(false, ascendingOrder);
|
activityHeader.highlight(false, ascendingOrder);
|
||||||
|
|
||||||
switch (order)
|
switch (order)
|
||||||
{
|
{
|
||||||
|
case PING:
|
||||||
|
pingHeader.highlight(true, ascendingOrder);
|
||||||
|
break;
|
||||||
case WORLD:
|
case WORLD:
|
||||||
worldHeader.highlight(true, ascendingOrder);
|
worldHeader.highlight(true, ascendingOrder);
|
||||||
break;
|
break;
|
||||||
@@ -219,6 +259,23 @@ class WorldSwitcherPanel extends PluginPanel
|
|||||||
{
|
{
|
||||||
JPanel header = new JPanel(new BorderLayout());
|
JPanel header = new JPanel(new BorderLayout());
|
||||||
JPanel leftSide = 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 = new WorldTableHeader("World", orderIndex == WorldOrder.WORLD, ascendingOrder, plugin::refresh);
|
||||||
worldHeader.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0));
|
worldHeader.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0));
|
||||||
@@ -253,7 +310,6 @@ class WorldSwitcherPanel extends PluginPanel
|
|||||||
});
|
});
|
||||||
|
|
||||||
activityHeader = new WorldTableHeader("Activity", orderIndex == WorldOrder.ACTIVITY, ascendingOrder, plugin::refresh);
|
activityHeader = new WorldTableHeader("Activity", orderIndex == WorldOrder.ACTIVITY, ascendingOrder, plugin::refresh);
|
||||||
activityHeader.setBorder(new EmptyBorder(3, 5, 3, 5));
|
|
||||||
activityHeader.addMouseListener(new MouseAdapter()
|
activityHeader.addMouseListener(new MouseAdapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
@@ -269,10 +325,13 @@ class WorldSwitcherPanel extends PluginPanel
|
|||||||
});
|
});
|
||||||
|
|
||||||
leftSide.add(worldHeader, BorderLayout.WEST);
|
leftSide.add(worldHeader, BorderLayout.WEST);
|
||||||
leftSide.add(playersHeader, BorderLayout.EAST);
|
leftSide.add(playersHeader, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
rightSide.add(activityHeader, BorderLayout.CENTER);
|
||||||
|
rightSide.add(pingHeader, BorderLayout.EAST);
|
||||||
|
|
||||||
header.add(leftSide, BorderLayout.WEST);
|
header.add(leftSide, BorderLayout.WEST);
|
||||||
header.add(activityHeader, BorderLayout.CENTER);
|
header.add(rightSide, BorderLayout.CENTER);
|
||||||
|
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class WorldTableHeader extends JPanel
|
|||||||
setLayout(new BorderLayout(5, 0));
|
setLayout(new BorderLayout(5, 0));
|
||||||
setBorder(new CompoundBorder(
|
setBorder(new CompoundBorder(
|
||||||
BorderFactory.createMatteBorder(0, 0, 0, 1, ColorScheme.MEDIUM_GRAY_COLOR),
|
BorderFactory.createMatteBorder(0, 0, 0, 1, ColorScheme.MEDIUM_GRAY_COLOR),
|
||||||
new EmptyBorder(0, 5, 0, 5)));
|
new EmptyBorder(0, 5, 0, 2)));
|
||||||
setBackground(ColorScheme.SCROLL_TRACK_COLOR);
|
setBackground(ColorScheme.SCROLL_TRACK_COLOR);
|
||||||
|
|
||||||
addMouseListener(new MouseAdapter()
|
addMouseListener(new MouseAdapter()
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class WorldTableRow extends JPanel
|
|||||||
|
|
||||||
private static final int WORLD_COLUMN_WIDTH = 60;
|
private static final int WORLD_COLUMN_WIDTH = 60;
|
||||||
private static final int PLAYERS_COLUMN_WIDTH = 40;
|
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 CURRENT_WORLD = new Color(66, 227, 17);
|
||||||
private static final Color UNAVAILABLE_WORLD = Color.GRAY.darker().darker();
|
private static final Color UNAVAILABLE_WORLD = Color.GRAY.darker().darker();
|
||||||
@@ -75,6 +76,7 @@ class WorldTableRow extends JPanel
|
|||||||
private JLabel worldField;
|
private JLabel worldField;
|
||||||
private JLabel playerCountField;
|
private JLabel playerCountField;
|
||||||
private JLabel activityField;
|
private JLabel activityField;
|
||||||
|
private JLabel pingField;
|
||||||
private BiConsumer<World, Boolean> onFavorite;
|
private BiConsumer<World, Boolean> onFavorite;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -83,6 +85,8 @@ class WorldTableRow extends JPanel
|
|||||||
@Getter(AccessLevel.PACKAGE)
|
@Getter(AccessLevel.PACKAGE)
|
||||||
private int updatedPlayerCount;
|
private int updatedPlayerCount;
|
||||||
|
|
||||||
|
private int ping;
|
||||||
|
|
||||||
private Color lastBackground;
|
private Color lastBackground;
|
||||||
private boolean current;
|
private boolean current;
|
||||||
|
|
||||||
@@ -151,12 +155,18 @@ class WorldTableRow extends JPanel
|
|||||||
setComponentPopupMenu(popupMenu);
|
setComponentPopupMenu(popupMenu);
|
||||||
|
|
||||||
JPanel leftSide = new JPanel(new BorderLayout());
|
JPanel leftSide = new JPanel(new BorderLayout());
|
||||||
|
JPanel rightSide = new JPanel(new BorderLayout());
|
||||||
leftSide.setOpaque(false);
|
leftSide.setOpaque(false);
|
||||||
|
rightSide.setOpaque(false);
|
||||||
|
|
||||||
JPanel worldField = buildWorldField();
|
JPanel worldField = buildWorldField();
|
||||||
worldField.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0));
|
worldField.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0));
|
||||||
worldField.setOpaque(false);
|
worldField.setOpaque(false);
|
||||||
|
|
||||||
|
JPanel pingField = buildPingField();
|
||||||
|
pingField.setPreferredSize(new Dimension(PING_COLUMN_WIDTH, 0));
|
||||||
|
pingField.setOpaque(false);
|
||||||
|
|
||||||
JPanel playersField = buildPlayersField();
|
JPanel playersField = buildPlayersField();
|
||||||
playersField.setPreferredSize(new Dimension(PLAYERS_COLUMN_WIDTH, 0));
|
playersField.setPreferredSize(new Dimension(PLAYERS_COLUMN_WIDTH, 0));
|
||||||
playersField.setOpaque(false);
|
playersField.setOpaque(false);
|
||||||
@@ -168,10 +178,12 @@ class WorldTableRow extends JPanel
|
|||||||
recolour(current);
|
recolour(current);
|
||||||
|
|
||||||
leftSide.add(worldField, BorderLayout.WEST);
|
leftSide.add(worldField, BorderLayout.WEST);
|
||||||
leftSide.add(playersField, BorderLayout.EAST);
|
leftSide.add(playersField, BorderLayout.CENTER);
|
||||||
|
rightSide.add(activityField, BorderLayout.CENTER);
|
||||||
|
rightSide.add(pingField, BorderLayout.EAST);
|
||||||
|
|
||||||
add(leftSide, BorderLayout.WEST);
|
add(leftSide, BorderLayout.WEST);
|
||||||
add(activityField, BorderLayout.CENTER);
|
add(rightSide, BorderLayout.CENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setFavoriteMenu(boolean favorite)
|
void setFavoriteMenu(boolean favorite)
|
||||||
@@ -199,9 +211,31 @@ class WorldTableRow extends JPanel
|
|||||||
playerCountField.setText(String.valueOf(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)
|
public void recolour(boolean current)
|
||||||
{
|
{
|
||||||
playerCountField.setForeground(current ? CURRENT_WORLD : Color.WHITE);
|
playerCountField.setForeground(current ? CURRENT_WORLD : Color.WHITE);
|
||||||
|
pingField.setForeground(current ? CURRENT_WORLD : Color.WHITE);
|
||||||
|
|
||||||
if (current)
|
if (current)
|
||||||
{
|
{
|
||||||
@@ -244,6 +278,19 @@ class WorldTableRow extends JPanel
|
|||||||
return column;
|
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).
|
* Builds the activity list field (containing that world's activity/theme).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||||
|
* 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<String> getFieldOrder()
|
||||||
|
{
|
||||||
|
return Arrays.asList("address", "status", "roundTripTime", "dataSize", "reserved", "data", "ttl", "tos", "flags", "optionsSize", "optionsData");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user