Merge pull request #318 from runelite-extended/normalworldhopper

Restore Worldhopper
This commit is contained in:
Tyler Bochard
2019-05-20 14:21:00 -04:00
committed by GitHub
7 changed files with 1060 additions and 1161 deletions

View File

@@ -1,126 +1,115 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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 <https://github.com/devLotto>
* 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;
}
}

View File

@@ -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<String, String> getHistory()
{
Map<String, String> history = configManager.getConfiguration(WorldHopperConfig.GROUP, "history", Map.class);
if (history == null)
{
history = new HashMap<String, String>();
}
return history;
}
void clearHistory()
{
Map<String, String> 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<String, String> 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

View File

@@ -1,343 +1,342 @@
/*
* Copyright (c) 2018, Psikoi <https://github.com/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<World, Boolean> 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<World> onSelect, BiConsumer<World, Boolean> 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 <https://github.com/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<World, Boolean> 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<World> onSelect, BiConsumer<World, Boolean> 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;
}
}
}

View File

@@ -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);
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}
}