World hopper data refreshing (#4918)

Update world list on world hop. Schedule a refresh every 10 minutes, and add a refresh option.
This commit is contained in:
Ruben Amendoeira
2018-08-16 00:59:30 +01:00
committed by Adam
parent 41634c96ab
commit b0a9d24928
4 changed files with 260 additions and 80 deletions

View File

@@ -31,11 +31,16 @@ import com.google.common.eventbus.Subscribe;
import com.google.inject.Provides;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Future;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.swing.SwingUtilities;
@@ -50,9 +55,11 @@ import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.Varbits;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.PlayerMenuOptionClicked;
import net.runelite.api.events.VarbitChanged;
import net.runelite.api.events.WorldListLoad;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.chat.ChatColorType;
@@ -81,6 +88,10 @@ import org.apache.commons.lang3.ArrayUtils;
@Slf4j
public class WorldHopperPlugin extends Plugin
{
private static final int WORLD_FETCH_TIMER = 10;
private static final int REFRESH_THROTTLE = 60_000; // ms
private static final int TICK_THROTTLE = (int) Duration.ofMinutes(10).toMillis();
private static final String HOP_TO = "Hop-to";
private static final String KICK_OPTION = "Kick";
private static final ImmutableList<String> BEFORE_OPTIONS = ImmutableList.of("Add friend", "Remove friend", KICK_OPTION);
@@ -113,9 +124,14 @@ public class WorldHopperPlugin extends Plugin
private NavigationButton navButton;
private WorldSwitcherPanel panel;
private int lastWorld;
private int favoriteWorld1, favoriteWorld2;
private Future<?> worldResultFuture;
private ScheduledFuture<?> worldResultFuture;
private WorldResult worldResult;
private Instant lastFetch;
private final HotkeyListener previousKeyListener = new HotkeyListener(() -> config.previousKey())
{
@Override
@@ -145,25 +161,7 @@ public class WorldHopperPlugin extends Plugin
keyManager.registerKeyListener(previousKeyListener);
keyManager.registerKeyListener(nextKeyListener);
worldResultFuture = executorService.submit(() ->
{
try
{
WorldResult worldResult = new WorldClient().lookupWorlds();
if (worldResult != null)
{
worldResult.getWorlds().sort(Comparator.comparingInt(World::getId));
this.worldResult = worldResult;
SwingUtilities.invokeLater(() -> panel.populate(worldResult.getWorlds()));
}
}
catch (IOException ex)
{
log.warn("Error looking up worlds", ex);
}
});
worldResultFuture = executorService.scheduleAtFixedRate(this::tick, 0, WORLD_FETCH_TIMER, TimeUnit.MINUTES);
panel = new WorldSwitcherPanel(this);
@@ -195,6 +193,7 @@ public class WorldHopperPlugin extends Plugin
worldResultFuture.cancel(true);
worldResultFuture = null;
worldResult = null;
lastFetch = null;
clientToolbar.removeNavigation(navButton);
}
@@ -347,6 +346,94 @@ public class WorldHopperPlugin extends Plugin
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
// If the player has disabled the side bar plugin panel, do not update the UI
if (config.showSidebar() && gameStateChanged.getGameState() == GameState.LOGGED_IN)
{
if (lastWorld != client.getWorld())
{
int newWorld = client.getWorld();
panel.switchCurrentHighlight(newWorld, lastWorld);
lastWorld = newWorld;
}
}
}
@Subscribe
public void onWorldListLoad(WorldListLoad worldListLoad)
{
if (!config.showSidebar())
{
return;
}
Map<Integer, Integer> worldData = new HashMap<>();
for (net.runelite.api.World w : worldListLoad.getWorlds())
{
worldData.put(w.getId(), w.getPlayerCount());
}
panel.updateListData(worldData);
this.lastFetch = Instant.now(); // This counts as a fetch as it updates populations
}
private void tick()
{
Instant now = Instant.now();
if (lastFetch != null && now.toEpochMilli() - lastFetch.toEpochMilli() < TICK_THROTTLE)
{
log.debug("Throttling world refresh tick");
return;
}
fetchWorlds();
}
void refresh()
{
Instant now = Instant.now();
if (lastFetch != null && now.toEpochMilli() - lastFetch.toEpochMilli() < REFRESH_THROTTLE)
{
log.debug("Throttling world refresh");
return;
}
fetchWorlds();
}
private void fetchWorlds()
{
log.debug("Fetching worlds");
try
{
WorldResult worldResult = new WorldClient().lookupWorlds();
if (worldResult != null)
{
worldResult.getWorlds().sort(Comparator.comparingInt(World::getId));
this.worldResult = worldResult;
this.lastFetch = Instant.now();
updateList();
}
}
catch (IOException ex)
{
log.warn("Error looking up worlds", ex);
}
}
/**
* This method ONLY updates the list's UI, not the actual world list and data it displays.
*/
private void updateList()
{
SwingUtilities.invokeLater(() -> panel.populate(worldResult.getWorlds()));
}
private void hop(boolean previous)
{
if (worldResult == null || client.getGameState() != GameState.LOGGED_IN)

View File

@@ -32,7 +32,9 @@ 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 javax.swing.border.EmptyBorder;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.ui.ColorScheme;
@@ -57,7 +59,7 @@ class WorldSwitcherPanel extends PluginPanel
private WorldOrder orderIndex = WorldOrder.WORLD;
private boolean ascendingOrder = true;
private List<World> worlds;
private ArrayList<WorldTableRow> rows = new ArrayList<>();
private WorldHopperPlugin plugin;
WorldSwitcherPanel(WorldHopperPlugin plugin)
@@ -75,37 +77,73 @@ class WorldSwitcherPanel extends PluginPanel
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<Integer, Integer> 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 updateList()
{
worlds.sort((w1, w2) ->
rows.sort((r1, r2) ->
{
switch (orderIndex)
{
case WORLD:
return Integer.compare(w1.getId(), w2.getId()) * (ascendingOrder ? 1 : -1);
return Integer.compare(r1.getWorld().getId(), r2.getWorld().getId()) * (ascendingOrder ? 1 : -1);
case PLAYERS:
return Integer.compare(w1.getPlayers(), w2.getPlayers()) * (ascendingOrder ? 1 : -1);
return Integer.compare(r1.getUpdatedPlayerCount(), r2.getUpdatedPlayerCount()) * (ascendingOrder ? 1 : -1);
case ACTIVITY:
return w1.getActivity().compareTo(w2.getActivity()) * (ascendingOrder ? 1 : -1);
return r1.getWorld().getActivity().compareTo(r2.getWorld().getActivity()) * (ascendingOrder ? 1 : -1);
default:
return 0;
}
});
worlds.sort((w1, w2) ->
rows.sort((r1, r2) ->
{
boolean b1 = plugin.isFavorite(w1);
boolean b2 = plugin.isFavorite(w2);
boolean b1 = plugin.isFavorite(r1.getWorld());
boolean b2 = plugin.isFavorite(r2.getWorld());
return Boolean.compare(b2, b1);
});
listContainer.removeAll();
for (int i = 0; i < worlds.size(); i++)
for (int i = 0; i < rows.size(); i++)
{
World world = worlds.get(i);
listContainer.add(buildRow(world, i % 2 == 0, world.getId() == plugin.getCurrentWorld(), plugin.isFavorite(world)));
WorldTableRow row = rows.get(i);
row.setBackground(i % 2 == 0 ? ODD_ROW : ColorScheme.DARK_GRAY_COLOR);
listContainer.add(row);
}
listContainer.revalidate();
@@ -114,7 +152,14 @@ class WorldSwitcherPanel extends PluginPanel
void populate(List<World> worlds)
{
this.worlds = new ArrayList<>(worlds);
rows.clear();
for (int i = 0; i < worlds.size(); i++)
{
World world = worlds.get(i);
rows.add(buildRow(world, i % 2 == 0, world.getId() == plugin.getCurrentWorld(), plugin.isFavorite(world)));
}
updateList();
}
@@ -149,38 +194,49 @@ class WorldSwitcherPanel extends PluginPanel
JPanel header = new JPanel(new BorderLayout());
JPanel leftSide = new JPanel(new BorderLayout());
worldHeader = new WorldTableHeader("World", orderIndex == WorldOrder.WORLD, ascendingOrder);
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);
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);
activityHeader = new WorldTableHeader("Activity", orderIndex == WorldOrder.ACTIVITY, ascendingOrder, plugin::refresh);
activityHeader.setBorder(new EmptyBorder(3, 5, 3, 5));
activityHeader.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent mouseEvent)
{
if (SwingUtilities.isRightMouseButton(mouseEvent))
{
return;
}
ascendingOrder = orderIndex != WorldOrder.ACTIVITY || !ascendingOrder;
orderBy(WorldOrder.ACTIVITY);
}
@@ -198,9 +254,9 @@ class WorldSwitcherPanel extends PluginPanel
/**
* Builds a table row, that displays the world's information.
*/
private JPanel buildRow(World world, boolean stripe, boolean current, boolean favorite)
private WorldTableRow buildRow(World world, boolean stripe, boolean current, boolean favorite)
{
JPanel row = new WorldTableRow(world, current, favorite,
WorldTableRow row = new WorldTableRow(world, current, favorite,
world1 ->
{
plugin.hopTo(world1);

View File

@@ -30,10 +30,13 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import javax.annotation.Nonnull;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import net.runelite.client.ui.ColorScheme;
@@ -61,7 +64,7 @@ class WorldTableHeader extends JPanel
// Determines if this header column is being used to order the list
private boolean ordering = false;
WorldTableHeader(String title, boolean ordered, boolean ascending)
WorldTableHeader(String title, boolean ordered, boolean ascending, @Nonnull Runnable onRefresh)
{
setLayout(new BorderLayout(5, 0));
setBorder(new CompoundBorder(
@@ -93,6 +96,19 @@ class WorldTableHeader extends JPanel
textLabel.setText(title);
textLabel.setFont(FontManager.getRunescapeSmallFont());
final JMenuItem refresh = new JMenuItem("Refresh worlds");
refresh.addActionListener(e ->
{
onRefresh.run();
});
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
popupMenu.add(refresh);
textLabel.setComponentPopupMenu(popupMenu);
setComponentPopupMenu(popupMenu);
highlight(ordered, ascending);
add(textLabel, BorderLayout.WEST);

View File

@@ -37,6 +37,8 @@ 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;
@@ -67,12 +69,24 @@ class WorldTableRow extends JPanel
FLAG_GER = new ImageIcon(ImageUtil.getResourceStreamFromClass(WorldHopperPlugin.class, "flag_ger.png"));
}
private JLabel worldField;
private JLabel playerCountField;
private JLabel activityField;
@Getter
private final World world;
@Getter(AccessLevel.PACKAGE)
private int updatedPlayerCount;
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.updatedPlayerCount = world.getPlayers();
setLayout(new BorderLayout());
setBorder(new EmptyBorder(2, 0, 2, 0));
@@ -142,18 +156,20 @@ class WorldTableRow extends JPanel
JPanel leftSide = new JPanel(new BorderLayout());
leftSide.setOpaque(false);
JPanel worldField = buildWorldField(world);
JPanel worldField = buildWorldField();
worldField.setPreferredSize(new Dimension(WORLD_COLUMN_WIDTH, 0));
worldField.setOpaque(false);
JPanel playersField = buildPlayersField(world);
JPanel playersField = buildPlayersField();
playersField.setPreferredSize(new Dimension(PLAYERS_COLUMN_WIDTH, 0));
playersField.setOpaque(false);
JPanel activityField = buildActivityField(world);
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.EAST);
@@ -161,19 +177,49 @@ class WorldTableRow extends JPanel
add(activityField, BorderLayout.CENTER);
}
void updatePlayerCount(int playerCount)
{
this.updatedPlayerCount = playerCount;
playerCountField.setText(String.valueOf(playerCount));
}
public void recolour(boolean current)
{
playerCountField.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.PVP_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);
}
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(World world)
private JPanel buildPlayersField()
{
JPanel column = new JPanel(new BorderLayout());
column.setBorder(new EmptyBorder(0, 5, 0, 5));
JLabel label = new JLabel(world.getPlayers() + "");
label.setFont(FontManager.getRunescapeSmallFont());
label.setForeground(current ? CURRENT_WORLD : Color.WHITE);
playerCountField = new JLabel(world.getPlayers() + "");
playerCountField.setFont(FontManager.getRunescapeSmallFont());
column.add(label, BorderLayout.WEST);
column.add(playerCountField, BorderLayout.WEST);
return column;
}
@@ -181,31 +227,15 @@ class WorldTableRow extends JPanel
/**
* Builds the activity list field (containing that world's activity/theme).
*/
private JPanel buildActivityField(World world)
private JPanel buildActivityField()
{
JPanel column = new JPanel(new BorderLayout());
column.setBorder(new EmptyBorder(0, 5, 0, 5));
JLabel label = new JLabel(world.getActivity());
label.setFont(FontManager.getRunescapeSmallFont());
activityField = new JLabel(world.getActivity());
activityField.setFont(FontManager.getRunescapeSmallFont());
if (current)
{
label.setForeground(CURRENT_WORLD);
}
else if (world.getTypes().contains(WorldType.PVP)
|| world.getTypes().contains(WorldType.PVP_HIGH_RISK)
|| world.getTypes().contains(WorldType.DEADMAN)
|| world.getTypes().contains(WorldType.SEASONAL_DEADMAN))
{
label.setForeground(DANGEROUS_WORLD);
}
else if (world.getTypes().contains(WorldType.TOURNAMENT))
{
label.setForeground(TOURNAMENT_WORLD);
}
column.add(label, BorderLayout.WEST);
column.add(activityField, BorderLayout.WEST);
return column;
}
@@ -213,26 +243,17 @@ class WorldTableRow extends JPanel
/**
* Builds the world list field (containing the country's flag and the world index).
*/
private JPanel buildWorldField(World world)
private JPanel buildWorldField()
{
JPanel column = new JPanel(new BorderLayout(7, 0));
column.setBorder(new EmptyBorder(0, 5, 0, 5));
JLabel label = new JLabel(world.getId() + "");
if (current)
{
label.setForeground(CURRENT_WORLD);
}
else
{
label.setForeground(world.getTypes().contains(WorldType.MEMBERS) ? MEMBERS_WORLD : FREE_WORLD);
}
worldField = new JLabel(world.getId() + "");
JLabel flag = new JLabel(getFlag(world.getLocation()));
column.add(flag, BorderLayout.WEST);
column.add(label, BorderLayout.CENTER);
column.add(worldField, BorderLayout.CENTER);
return column;
}