Merge remote-tracking branch 'runelite/master'

This commit is contained in:
Owain van Brakel
2019-11-28 19:34:22 +01:00
63 changed files with 1570 additions and 777 deletions

View File

@@ -29,6 +29,7 @@ import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.function.BooleanSupplier;
import javax.annotation.Nullable;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
@@ -41,6 +42,7 @@ public class ClientThread implements Executor
private final ConcurrentLinkedQueue<BooleanSupplier> invokes = new ConcurrentLinkedQueue<>();
@Inject
@Nullable
private Client client;
public void invoke(Runnable r)

View File

@@ -26,12 +26,11 @@ package net.runelite.client.config;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.util.ReflectUtil;
@Slf4j
class ConfigInvocationHandler implements InvocationHandler
@@ -169,12 +168,8 @@ class ConfigInvocationHandler implements InvocationHandler
static Object callDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable
{
// Call the default method implementation - https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
constructor.setAccessible(true);
Class<?> declaringClass = method.getDeclaringClass();
return constructor.newInstance(declaringClass, MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE)
return ReflectUtil.privateLookupIn(declaringClass)
.unreflectSpecial(method, declaringClass)
.bindTo(proxy)
.invokeWithArguments(args);

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019, 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.events;
import lombok.Value;
import net.runelite.api.events.Event;
import net.runelite.http.api.worlds.WorldResult;
/**
* Fired when the @{link net.runelite.client.game.WorldService} refreshes the world list
*/
@Value
public class WorldsFetch implements Event
{
private final WorldResult worldResult;
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (c) 2019, 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.game;
import java.io.IOException;
import java.util.Comparator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.events.WorldsFetch;
import net.runelite.client.util.RunnableExceptionLogger;
import net.runelite.http.api.worlds.World;
import net.runelite.http.api.worlds.WorldClient;
import net.runelite.http.api.worlds.WorldResult;
@Singleton
@Slf4j
public class WorldService
{
private static final int WORLD_FETCH_TIMER = 10; // minutes
private final Client client;
private final ScheduledExecutorService scheduledExecutorService;
private final WorldClient worldClient;
private final EventBus eventBus;
private final CompletableFuture<WorldResult> firstRunFuture = new CompletableFuture<>();
private WorldResult worlds;
@Inject
private WorldService(Client client, ScheduledExecutorService scheduledExecutorService, WorldClient worldClient,
EventBus eventBus)
{
this.client = client;
this.scheduledExecutorService = scheduledExecutorService;
this.worldClient = worldClient;
this.eventBus = eventBus;
scheduledExecutorService.scheduleWithFixedDelay(RunnableExceptionLogger.wrap(this::tick), 0, WORLD_FETCH_TIMER, TimeUnit.MINUTES);
}
private void tick()
{
try
{
if (worlds == null || client.getGameState() == GameState.LOGGED_IN)
{
fetch();
}
}
finally
{
firstRunFuture.complete(worlds);
}
}
private void fetch()
{
log.debug("Fetching worlds");
try
{
WorldResult worldResult = worldClient.lookupWorlds();
worldResult.getWorlds().sort(Comparator.comparingInt(World::getId));
worlds = worldResult;
eventBus.post(WorldsFetch.class, new WorldsFetch(worldResult));
}
catch (IOException ex)
{
log.warn("Error looking up worlds", ex);
}
}
public void refresh()
{
scheduledExecutorService.execute(this::fetch);
}
@Nullable
public WorldResult getWorlds()
{
if (!firstRunFuture.isDone())
{
try
{
return firstRunFuture.get(10, TimeUnit.SECONDS);
}
catch (InterruptedException | ExecutionException | TimeoutException e)
{
log.warn("Failed to retrieve worlds on first run", e);
}
}
return worlds;
}
}

View File

@@ -52,6 +52,7 @@ import net.runelite.client.input.KeyListener;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import org.apache.commons.lang3.StringUtils;
@PluginDescriptor(
name = "Chat History",
@@ -61,7 +62,7 @@ import net.runelite.client.plugins.PluginDescriptor;
@Singleton
public class ChatHistoryPlugin extends Plugin implements KeyListener
{
private static final String WELCOME_MESSAGE = "Welcome to Old School RuneScape.";
private static final String WELCOME_MESSAGE = "Welcome to Old School RuneScape";
private static final String CLEAR_HISTORY = "Clear history";
private static final String CLEAR_PRIVATE = "<col=ffff00>Private:";
private static final int CYCLE_HOTKEY = KeyEvent.VK_TAB;
@@ -137,7 +138,8 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener
{
// Start sending old messages right after the welcome message, as that is most reliable source
// of information that chat history was reset
if (chatMessage.getMessage().equals(WELCOME_MESSAGE))
ChatMessageType chatMessageType = chatMessage.getType();
if (chatMessageType == ChatMessageType.WELCOME && StringUtils.startsWithIgnoreCase(chatMessage.getMessage(), WELCOME_MESSAGE))
{
if (!this.retainChatHistory)
{
@@ -154,7 +156,7 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener
return;
}
switch (chatMessage.getType())
switch (chatMessageType)
{
case PRIVATECHATOUT:
case PRIVATECHAT:
@@ -174,7 +176,7 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener
case FRIENDSCHAT:
case CONSOLE:
final QueuedMessage queuedMessage = QueuedMessage.builder()
.type(chatMessage.getType())
.type(chatMessageType)
.name(chatMessage.getName())
.sender(chatMessage.getSender())
.value(tweakSpaces(chatMessage.getMessage()))

View File

@@ -25,22 +25,21 @@
package net.runelite.client.plugins.defaultworld;
import com.google.inject.Provides;
import io.reactivex.schedulers.Schedulers;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.events.GameStateChanged;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.SessionOpen;
import net.runelite.client.game.WorldService;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.WorldUtil;
import net.runelite.http.api.worlds.World;
import net.runelite.http.api.worlds.WorldClient;
import net.runelite.http.api.worlds.WorldResult;
@PluginDescriptor(
name = "Default World",
@@ -58,10 +57,7 @@ public class DefaultWorldPlugin extends Plugin
private DefaultWorldConfig config;
@Inject
private ClientThread clientThread;
@Inject
private WorldClient worldClient;
private WorldService worldService;
private int worldCache;
private boolean worldChangeRequired;
@@ -122,39 +118,33 @@ public class DefaultWorldPlugin extends Plugin
return;
}
worldClient.lookupWorlds()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.from(clientThread))
.subscribe(
(worldResult) ->
{
if (worldResult == null)
{
return;
}
final WorldResult worldResult = worldService.getWorlds();
final World world = worldResult.findWorld(correctedWorld);
if (worldResult == null)
{
log.warn("Failed to lookup worlds.");
return;
}
if (world != null)
{
final net.runelite.api.World rsWorld = client.createWorld();
rsWorld.setActivity(world.getActivity());
rsWorld.setAddress(world.getAddress());
rsWorld.setId(world.getId());
rsWorld.setPlayerCount(world.getPlayers());
rsWorld.setLocation(world.getLocation());
rsWorld.setTypes(WorldUtil.toWorldTypes(world.getTypes()));
final World world = worldResult.findWorld(correctedWorld);
client.changeWorld(rsWorld);
log.debug("Applied new world {}", correctedWorld);
}
else
{
log.warn("World {} not found.", correctedWorld);
}
},
(e) -> log.warn("Error looking up world {}. Error: {}", correctedWorld, e)
);
if (world != null)
{
final net.runelite.api.World rsWorld = client.createWorld();
rsWorld.setActivity(world.getActivity());
rsWorld.setAddress(world.getAddress());
rsWorld.setId(world.getId());
rsWorld.setPlayerCount(world.getPlayers());
rsWorld.setLocation(world.getLocation());
rsWorld.setTypes(WorldUtil.toWorldTypes(world.getTypes()));
client.changeWorld(rsWorld);
log.debug("Applied new world {}", correctedWorld);
}
else
{
log.warn("World {} not found.", correctedWorld);
}
}
private void applyWorld()

View File

@@ -84,7 +84,9 @@ enum Emoji
ALIEN("(@.@)"),
EGGPLANT("8=D"),
WAVE("(^_^)/"),
HEART_EYES("(*.*)");
HEART_EYES("(*.*)"),
FACEPALM("M-)"),
;
private static final Map<String, Emoji> emojiMap;

View File

@@ -33,14 +33,15 @@ import net.runelite.client.config.Range;
public interface FpsConfig extends Config
{
@ConfigItem(
keyName = "limitMode",
name = "Limit Mode",
description = "Stay at or under the target frames per second even when in this mode",
keyName = "limitFps",
name = "Limit Global FPS",
description = "Global FPS limit in effect regardless of<br>" +
"whether window is in focus or not",
position = 1
)
default FpsLimitMode limitMode()
default boolean limitFps()
{
return FpsLimitMode.NEVER;
return false;
}
@Range(
@@ -49,8 +50,8 @@ public interface FpsConfig extends Config
)
@ConfigItem(
keyName = "maxFps",
name = "FPS target",
description = "Desired max frames per second",
name = "Global FPS target",
description = "Desired max global frames per second",
position = 2
)
default int maxFps()
@@ -58,11 +59,33 @@ public interface FpsConfig extends Config
return 50;
}
@ConfigItem(
keyName = "limitFpsUnfocused",
name = "Limit FPS unfocused",
description = "FPS limit while window is out of focus",
position = 3
)
default boolean limitFpsUnfocused()
{
return false;
}
@ConfigItem(
keyName = "maxFpsUnfocused",
name = "Unfocused FPS target",
description = "Desired max frames per second for unfocused",
position = 4
)
default int maxFpsUnfocused()
{
return 50;
}
@ConfigItem(
keyName = "drawFps",
name = "Draw FPS indicator",
description = "Show a number in the corner for the current FPS",
position = 3
position = 5
)
default boolean drawFps()
{

View File

@@ -69,7 +69,13 @@ public class FpsDrawListener implements Runnable
void reloadConfig()
{
lastMillis = System.currentTimeMillis();
targetDelay = 1000 / Math.max(1, config.maxFps());
int fps = config.limitFpsUnfocused() && !isFocused
? config.maxFpsUnfocused()
: config.maxFps();
targetDelay = 1000 / Math.max(1, fps);
sleepDelay = targetDelay;
for (int i = 0; i < SAMPLE_SIZE; i++)
@@ -81,18 +87,18 @@ public class FpsDrawListener implements Runnable
void onFocusChanged(FocusChanged event)
{
this.isFocused = event.isFocused();
reloadConfig(); // load new delay
}
private boolean isEnforced()
{
return FpsLimitMode.ALWAYS == plugin.getLimitMode()
|| (FpsLimitMode.UNFOCUSED == plugin.getLimitMode() && !isFocused);
return config.limitFps()
|| (config.limitFpsUnfocused() && !isFocused);
}
@Override
public void run()
{
if (!isEnforced())
{
return;

View File

@@ -57,16 +57,16 @@ public class FpsOverlay extends Overlay
// Local dependencies
private final Client client;
private final FpsPlugin plugin;
private final FpsConfig config;
// Often changing values
private boolean isFocused = true;
@Inject
private FpsOverlay(final FpsPlugin plugin, final Client client)
private FpsOverlay(final FpsConfig config, final Client client)
{
this.client = client;
this.plugin = plugin;
this.config = config;
setLayer(OverlayLayer.ABOVE_WIDGETS);
setPriority(OverlayPriority.HIGH);
setPosition(OverlayPosition.DYNAMIC);
@@ -79,8 +79,8 @@ public class FpsOverlay extends Overlay
private boolean isEnforced()
{
return FpsLimitMode.ALWAYS == plugin.getLimitMode()
|| (FpsLimitMode.UNFOCUSED == plugin.getLimitMode() && !isFocused);
return config.limitFps()
|| (config.limitFpsUnfocused() && !isFocused);
}
private Color getFpsValueColor()
@@ -91,7 +91,7 @@ public class FpsOverlay extends Overlay
@Override
public Dimension render(Graphics2D graphics)
{
if (!plugin.isDrawFps())
if (!config.drawFps())
{
return null;
}

View File

@@ -27,8 +27,6 @@ package net.runelite.client.plugins.fps;
import com.google.inject.Inject;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import lombok.AccessLevel;
import lombok.Getter;
import net.runelite.api.events.FocusChanged;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
@@ -71,15 +69,6 @@ public class FpsPlugin extends Plugin
@Inject
private DrawManager drawManager;
@Inject
private FpsConfig fpsConfig;
@Getter(AccessLevel.PACKAGE)
private FpsLimitMode limitMode;
@Getter(AccessLevel.PACKAGE)
private boolean drawFps;
@Provides
FpsConfig provideConfig(ConfigManager configManager)
{
@@ -92,9 +81,6 @@ public class FpsPlugin extends Plugin
if (event.getGroup().equals(CONFIG_GROUP_KEY))
{
drawListener.reloadConfig();
limitMode = fpsConfig.limitMode();
drawFps = fpsConfig.drawFps();
}
}
@@ -108,9 +94,6 @@ public class FpsPlugin extends Plugin
@Override
protected void startUp()
{
limitMode = fpsConfig.limitMode();
drawFps = fpsConfig.drawFps();
overlayManager.add(overlay);
drawManager.registerEveryFrameListener(drawListener);
drawListener.reloadConfig();

View File

@@ -84,7 +84,6 @@ class InventoryGridOverlay extends Overlay
final Point mousePoint = new Point(mouse.getX(), mouse.getY());
final int if1DraggedItemIndex = client.getIf1DraggedItemIndex();
final WidgetItem draggedItem = inventoryWidget.getWidgetItem(if1DraggedItemIndex);
final int itemId = draggedItem.getId();
final Rectangle initialBounds = draggedItem.getCanvasBounds();
if (initialMousePoint == null)
@@ -92,7 +91,7 @@ class InventoryGridOverlay extends Overlay
initialMousePoint = mousePoint;
}
if (itemId == -1 || !hoverActive && initialMousePoint.distance(mousePoint) < DISTANCE_TO_ACTIVATE_HOVER)
if (draggedItem.getId() == -1 || !hoverActive && initialMousePoint.distance(mousePoint) < DISTANCE_TO_ACTIVATE_HOVER)
{
return null;
}
@@ -101,16 +100,15 @@ class InventoryGridOverlay extends Overlay
for (int i = 0; i < INVENTORY_SIZE; ++i)
{
WidgetItem widgetItem = inventoryWidget.getWidgetItem(i);
final int targetItemId = widgetItem.getId();
WidgetItem targetWidgetItem = inventoryWidget.getWidgetItem(i);
final Rectangle bounds = widgetItem.getCanvasBounds();
final Rectangle bounds = targetWidgetItem.getCanvasBounds();
boolean inBounds = bounds.contains(mousePoint);
if (plugin.isShowItem() && inBounds)
{
drawItem(graphics, bounds, itemId);
drawItem(graphics, initialBounds, targetItemId);
drawItem(graphics, bounds, draggedItem);
drawItem(graphics, initialBounds, targetWidgetItem);
}
if (plugin.isShowHighlight() && inBounds)
@@ -128,14 +126,14 @@ class InventoryGridOverlay extends Overlay
return null;
}
private void drawItem(Graphics2D graphics, Rectangle bounds, int itemId)
private void drawItem(Graphics2D graphics, Rectangle bounds, WidgetItem item)
{
if (itemId == -1)
if (item.getId() == -1)
{
return;
}
final BufferedImage draggedItemImage = itemManager.getImage(itemId);
final BufferedImage draggedItemImage = itemManager.getImage(item.getId(), item.getQuantity(), false);
final int x = (int) bounds.getX();
final int y = (int) bounds.getY();

View File

@@ -34,7 +34,6 @@ import lombok.Getter;
import lombok.Setter;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.IconID;
import net.runelite.api.VarClientInt;
import net.runelite.api.VarClientStr;
import net.runelite.api.Varbits;
@@ -212,7 +211,7 @@ public class KeyRemappingPlugin extends Plugin
Widget chatboxInput = client.getWidget(WidgetInfo.CHATBOX_INPUT);
if (chatboxInput != null && chatboxInput.getText().endsWith(PRESS_ENTER_TO_CHAT))
{
chatboxInput.setText(getWaitingText());
setChatboxWidgetInput(chatboxInput, PRESS_ENTER_TO_CHAT);
}
}
);
@@ -227,7 +226,7 @@ public class KeyRemappingPlugin extends Plugin
Widget chatboxInput = client.getWidget(WidgetInfo.CHATBOX_INPUT);
if (chatboxInput != null && chatboxFocused() && !typing)
{
chatboxInput.setText(getWaitingText());
setChatboxWidgetInput(chatboxInput, PRESS_ENTER_TO_CHAT);
}
break;
case SCRIPT_EVENT_BLOCK_CHAT_INPUT:
@@ -246,7 +245,7 @@ public class KeyRemappingPlugin extends Plugin
Widget chatboxInput = client.getWidget(WidgetInfo.CHATBOX_INPUT);
if (chatboxInput != null)
{
chatboxInput.setText(getWaitingText());
setChatboxWidgetInput(chatboxInput, PRESS_ENTER_TO_CHAT);
}
}
@@ -257,39 +256,24 @@ public class KeyRemappingPlugin extends Plugin
{
final boolean isChatboxTransparent = client.isResized() && client.getVar(Varbits.TRANSPARENT_CHATBOX) == 1;
final Color textColor = isChatboxTransparent ? JagexColors.CHAT_TYPED_TEXT_TRANSPARENT_BACKGROUND : JagexColors.CHAT_TYPED_TEXT_OPAQUE_BACKGROUND;
chatboxInput.setText(getPlayerNameWithIcon() + ": " + ColorUtil.wrapWithColorTag(client.getVar(VarClientStr.CHATBOX_TYPED_TEXT) + "*", textColor));
setChatboxWidgetInput(chatboxInput, ColorUtil.wrapWithColorTag(client.getVar(VarClientStr.CHATBOX_TYPED_TEXT) + "*", textColor));
}
}
private String getPlayerNameWithIcon()
{
IconID icon;
switch (client.getAccountType())
{
case IRONMAN:
icon = IconID.IRONMAN;
break;
case ULTIMATE_IRONMAN:
icon = IconID.ULTIMATE_IRONMAN;
break;
case HARDCORE_IRONMAN:
icon = IconID.HARDCORE_IRONMAN;
break;
default:
return client.getLocalPlayer().getName();
}
return icon + client.getLocalPlayer().getName();
}
private String getWaitingText()
private void setChatboxWidgetInput(Widget widget, String input)
{
if (this.hideDisplayName)
{
return PRESS_ENTER_TO_CHAT;
widget.setText(input);
return;
}
else
String text = widget.getText();
int idx = text.indexOf(':');
if (idx != -1)
{
return getPlayerNameWithIcon() + ": " + PRESS_ENTER_TO_CHAT;
String newText = text.substring(0, idx) + ": " + input;
widget.setText(newText);
}
}

View File

@@ -0,0 +1,349 @@
/*
* Copyright (c) 2019, hsamoht <https://github.com/hsamoht>
* Copyright (c) 2019, 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.leaguechaticons;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatPlayer;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.IconID;
import net.runelite.api.IndexedSprite;
import net.runelite.api.MessageNode;
import net.runelite.api.Player;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.api.util.Text;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.WorldService;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.ImageUtil;
import net.runelite.http.api.worlds.World;
import net.runelite.http.api.worlds.WorldResult;
import net.runelite.http.api.worlds.WorldType;
@PluginDescriptor(
name = "Chat League Icons",
description = "Changes the chat icon for players on league worlds",
enabledByDefault = false
)
@Slf4j
public class LeagueChatIconsPlugin extends Plugin
{
private static final String SCRIPT_EVENT_SET_CHATBOX_INPUT = "setChatboxInput";
private static final String IRONMAN_PREFIX = "<img=" + IconID.IRONMAN.getIndex() + ">";
@Inject
private Client client;
@Inject
private ChatMessageManager chatMessageManager;
@Inject
private WorldService worldService;
@Inject
private ClientThread clientThread;
private int leagueIconOffset = -1; // offset for league icon
private boolean onLeagueWorld;
@Override
protected void startUp()
{
onLeagueWorld = false;
clientThread.invoke(() ->
{
if (client.getGameState() == GameState.LOGGED_IN)
{
loadLeagueIcon();
onLeagueWorld = isLeagueWorld(client.getWorld());
if (onLeagueWorld)
{
setChatboxName(getNameChatbox());
}
}
});
}
@Override
protected void shutDown()
{
clientThread.invoke(() ->
{
if (client.getGameState() == GameState.LOGGED_IN && onLeagueWorld)
{
setChatboxName(getNameDefault());
}
});
}
@Subscribe
private void onGameStateChanged(GameStateChanged gameStateChanged)
{
if (gameStateChanged.getGameState() == GameState.LOGGED_IN)
{
loadLeagueIcon();
onLeagueWorld = isLeagueWorld(client.getWorld());
}
}
@Subscribe
private void onScriptCallbackEvent(ScriptCallbackEvent scriptCallbackEvent)
{
if (scriptCallbackEvent.getEventName().equals(SCRIPT_EVENT_SET_CHATBOX_INPUT) && onLeagueWorld)
{
setChatboxName(getNameChatbox());
}
}
@Subscribe
private void onChatMessage(ChatMessage chatMessage)
{
if (client.getGameState() != GameState.LOADING && client.getGameState() != GameState.LOGGED_IN)
{
return;
}
switch (chatMessage.getType())
{
case PRIVATECHAT:
case MODPRIVATECHAT:
// Note this is unable to change icon on PMs if they are not a friend or in clan chat
case FRIENDSCHAT:
String name = Text.removeTags(chatMessage.getName());
if (isChatPlayerOnLeague(name))
{
addLeagueIconToMessage(chatMessage);
}
break;
case PUBLICCHAT:
case MODCHAT:
if (onLeagueWorld)
{
addLeagueIconToMessage(chatMessage);
}
break;
}
}
/**
* Adds the League Icon in front of player names chatting from a league world.
*
* @param chatMessage chat message to edit sender name on
*/
private void addLeagueIconToMessage(ChatMessage chatMessage)
{
String name = chatMessage.getName();
if (!name.startsWith(IRONMAN_PREFIX))
{
// don't replace non-ironman icons, like mods
return;
}
name = Text.removeTags(name);
final MessageNode messageNode = chatMessage.getMessageNode();
messageNode.setName(getNameWithIcon(leagueIconOffset, name));
chatMessageManager.update(messageNode);
client.refreshChat();
}
/**
* Update the player name in the chatbox input
*/
private void setChatboxName(String name)
{
Widget chatboxInput = client.getWidget(WidgetInfo.CHATBOX_INPUT);
if (chatboxInput != null)
{
String text = chatboxInput.getText();
int idx = text.indexOf(':');
if (idx != -1)
{
String newText = name + text.substring(idx);
chatboxInput.setText(newText);
}
}
}
/**
* Gets the league name, including possible icon, of the local player.
*
* @return String of icon + name
*/
private String getNameChatbox()
{
Player player = client.getLocalPlayer();
if (player != null)
{
return getNameWithIcon(leagueIconOffset, player.getName());
}
return null;
}
/**
* Gets the default name, including possible icon, of the local player.
*
* @return String of icon + name
*/
private String getNameDefault()
{
Player player = client.getLocalPlayer();
if (player == null)
{
return null;
}
int iconIndex;
switch (client.getAccountType())
{
case IRONMAN:
iconIndex = IconID.IRONMAN.getIndex();
break;
case HARDCORE_IRONMAN:
iconIndex = IconID.HARDCORE_IRONMAN.getIndex();
break;
case ULTIMATE_IRONMAN:
iconIndex = IconID.ULTIMATE_IRONMAN.getIndex();
break;
default:
return player.getName();
}
return getNameWithIcon(iconIndex, player.getName());
}
/**
* Get a name formatted with icon
*
* @param iconIndex index of the icon
* @param name name of the player
* @return String of icon + name
*/
private static String getNameWithIcon(int iconIndex, String name)
{
String icon = "<img=" + iconIndex + ">";
return icon + name;
}
/**
* Checks if a player name is a friend or clan member on a league world.
*
* @param name name of player to check.
* @return boolean true/false.
*/
private boolean isChatPlayerOnLeague(String name)
{
ChatPlayer player = getChatPlayerFromName(name);
if (player == null)
{
return false;
}
int world = player.getWorld();
return isLeagueWorld(world);
}
/**
* Checks if the world is a League world.
*
* @param worldNumber number of the world to check.
* @return boolean true/false if it is a league world or not.
*/
private boolean isLeagueWorld(int worldNumber)
{
WorldResult worlds = worldService.getWorlds();
if (worlds == null)
{
return false;
}
World world = worlds.findWorld(worldNumber);
return world != null && world.getTypes().contains(WorldType.LEAGUE);
}
/**
* Loads the league icon into the client.
*/
private void loadLeagueIcon()
{
final IndexedSprite[] modIcons = client.getModIcons();
if (leagueIconOffset != -1 || modIcons == null)
{
return;
}
BufferedImage image = ImageUtil.getResourceStreamFromClass(getClass(), "league_icon.png");
IndexedSprite indexedSprite = ImageUtil.getImageIndexedSprite(image, client);
leagueIconOffset = modIcons.length;
final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + 1);
newModIcons[newModIcons.length - 1] = indexedSprite;
client.setModIcons(newModIcons);
}
/**
* Gets a ChatPlayer object from a clean name by searching clan and friends list.
*
* @param name name of player to find.
* @return ChatPlayer if found, else null.
*/
private ChatPlayer getChatPlayerFromName(String name)
{
if (client.isClanMember(name))
{
return Arrays.stream(client.getClanMembers())
.filter(clanMember -> Text.removeTags(clanMember.getUsername()).equals(name))
.findFirst()
.orElse(null);
}
if (client.isFriended(name, true))
{
return Arrays.stream(client.getFriends())
.filter(friend -> Text.removeTags(friend.getName()).equals(name))
.findFirst()
.orElse(null);
}
return null;
}
}

View File

@@ -105,7 +105,7 @@ public interface LootTrackerConfig extends Config
@ConfigItem(
keyName = "syncPanel",
name = "Synchronize panel contents",
description = "Synchronize you local loot tracker with your online (requires being logged in). This means" +
description = "Synchronize your local loot tracker with your online (requires being logged in). This means" +
" that panel is filled with portion of your remote data on startup and deleting data in panel deletes them" +
" also on server."
)

View File

@@ -25,10 +25,12 @@
*/
package net.runelite.client.plugins.music;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Provides;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.ToIntFunction;
import java.util.stream.Collectors;
@@ -76,6 +78,10 @@ import net.runelite.client.plugins.PluginDescriptor;
)
public class MusicPlugin extends Plugin
{
private static final Set<Integer> SOURCELESS_PLAYER_SOUNDS = ImmutableSet.of(
SoundEffectID.TELEPORT_VWOOP
);
@Inject
private Client client;
@@ -559,13 +565,14 @@ public class MusicPlugin extends Plugin
private void onAreaSoundEffectPlayed(AreaSoundEffectPlayed areaSoundEffectPlayed)
{
Actor source = areaSoundEffectPlayed.getSource();
int soundId = areaSoundEffectPlayed.getSoundId();
if (source == client.getLocalPlayer()
&& musicConfig.muteOwnAreaSounds())
{
areaSoundEffectPlayed.consume();
}
else if (source != client.getLocalPlayer()
&& source instanceof Player
&& (source instanceof Player || (source == null && SOURCELESS_PLAYER_SOUNDS.contains(soundId)))
&& musicConfig.muteOtherAreaSounds())
{
areaSoundEffectPlayed.consume();
@@ -576,9 +583,10 @@ public class MusicPlugin extends Plugin
areaSoundEffectPlayed.consume();
}
else if (source == null
&& !SOURCELESS_PLAYER_SOUNDS.contains(soundId)
&& musicConfig.muteEnvironmentAreaSounds())
{
areaSoundEffectPlayed.consume();
}
}
}
}

View File

@@ -45,7 +45,6 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import static net.runelite.api.Constants.REGION_SIZE;
import net.runelite.api.DecorativeObject;
import net.runelite.api.GameObject;
import net.runelite.api.GameState;
@@ -350,8 +349,8 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener
for (ObjectPoint objectPoint : objectPoints)
{
if ((worldPoint.getX() & (REGION_SIZE - 1)) == objectPoint.getRegionX()
&& (worldPoint.getY() & (REGION_SIZE - 1)) == objectPoint.getRegionY())
if (worldPoint.getRegionX() == objectPoint.getRegionX()
&& worldPoint.getRegionY() == objectPoint.getRegionY())
{
// Transform object to get the name which matches against what we've stored
if (objectPoint.getName().equals(getObjectDefinition(object.getId()).getName()))
@@ -446,18 +445,27 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener
}
final int regionId = worldPoint.getRegionID();
final ObjectPoint point = new ObjectPoint(
object.getId(),
name,
regionId,
worldPoint.getX() & (REGION_SIZE - 1),
worldPoint.getY() & (REGION_SIZE - 1),
worldPoint.getRegionX(),
worldPoint.getRegionY(),
client.getPlane());
Set<ObjectPoint> objectPoints = points.computeIfAbsent(regionId, k -> new HashSet<>());
if (objectPoints.contains(point))
if (objects.remove(object))
{
objectPoints.remove(point);
objects.remove(object);
// Use object id instead of name to match the object point with this object due to the object name being
// able to change because of multilocs.
if (!objectPoints.removeIf(op -> (op.getId() == -1 || op.getId() == object.getId())
&& op.getRegionX() == worldPoint.getRegionX()
&& op.getRegionY() == worldPoint.getRegionY()
&& op.getZ() == worldPoint.getPlane()))
{
log.warn("unable to find object point for unmarked object {}", object.getId());
}
log.debug("Unmarking object: {}", point);
}
else

View File

@@ -25,11 +25,16 @@
package net.runelite.client.plugins.objectindicators;
import lombok.Value;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Value
@Data
@NoArgsConstructor
@AllArgsConstructor
class ObjectPoint
{
private int id = -1;
private String name;
private int regionId;
private int regionX;

View File

@@ -33,7 +33,7 @@ import lombok.Getter;
import net.runelite.client.plugins.raids.solver.Layout;
import net.runelite.client.plugins.raids.solver.Room;
public class Raid
class Raid
{
@Getter(AccessLevel.PACKAGE)
private final RaidRoom[] rooms = new RaidRoom[16];

View File

@@ -38,7 +38,8 @@ enum TeleportWidget
WidgetInfo.SPELL_LUMBRIDGE_HOME_TELEPORT.getId(),
WidgetInfo.SPELL_EDGEVILLE_HOME_TELEPORT.getId(),
WidgetInfo.SPELL_LUNAR_HOME_TELEPORT.getId(),
WidgetInfo.SPELL_ARCEUUS_HOME_TELEPORT.getId()
WidgetInfo.SPELL_ARCEUUS_HOME_TELEPORT.getId(),
WidgetInfo.SPELL_KOUREND_HOME_TELEPORT.getId()
);
private static final Collection MINIGAME_TELEPORT_IDS = ImmutableList.of(
WidgetInfo.MINIGAME_TELEPORT_BUTTON.getId()

View File

@@ -25,23 +25,27 @@
package net.runelite.client.plugins.woodcutting;
import com.google.common.collect.ImmutableMap;
import java.time.Duration;
import java.util.Map;
import lombok.AccessLevel;
import javax.annotation.Nullable;
import lombok.Getter;
import static net.runelite.api.ObjectID.REDWOOD;
import static net.runelite.api.ObjectID.REDWOOD_29670;
import static net.runelite.api.NullObjectID.NULL_10823;
import static net.runelite.api.NullObjectID.NULL_10835;
import net.runelite.api.ObjectID;
import static net.runelite.api.ObjectID.*;
@Getter(AccessLevel.PACKAGE)
@Getter
enum Tree
{
REDWOOD_TREE_SPAWN(REDWOOD, REDWOOD_29670);
private final int[] treeIds;
Tree(final int... treeIds)
{
this.treeIds = treeIds;
}
REGULAR_TREE(null, TREE, TREE_1277, TREE_1278, TREE_1279, TREE_1280),
OAK_TREE(Duration.ofMillis(8500), ObjectID.OAK_TREE, OAK_TREE_4540, OAK_10820),
WILLOW_TREE(Duration.ofMillis(8500), WILLOW, WILLOW_10833, WILLOW_10831),
MAPLE_TREE(Duration.ofSeconds(35), ObjectID.MAPLE_TREE, MAPLE_TREE_10832, MAPLE_TREE_36681),
TEAK_TREE(Duration.ofMillis(8500), TEAK, TEAK_36686),
MAHOGANY_TREE(Duration.ofMillis(8500), MAHOGANY, MAHOGANY_36688),
YEW_TREE(Duration.ofMinutes(1), YEW, NULL_10823, YEW_36683),
MAGIC_TREE(Duration.ofMinutes(2), MAGIC_TREE_10834, NULL_10835),
REDWOOD(Duration.ofMinutes(2), ObjectID.REDWOOD, REDWOOD_29670);
private static final Map<Integer, Tree> TREES;
@@ -60,6 +64,16 @@ enum Tree
TREES = builder.build();
}
@Nullable
private final Duration respawnTime;
private final int[] treeIds;
Tree(@org.jetbrains.annotations.Nullable Duration respawnTime, int... treeIds)
{
this.respawnTime = respawnTime;
this.treeIds = treeIds;
}
static Tree findTree(int objectId)
{
return TREES.get(objectId);

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* Copyright (c) 2019, David <Dava96@github.com>
* 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.woodcutting;
import java.time.Instant;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.api.coords.LocalPoint;
@AllArgsConstructor
@Getter
class TreeRespawn
{
private final Tree tree;
private final LocalPoint location;
private final Instant startTime;
private final int respawnTime;
boolean isExpired()
{
return Instant.now().isAfter(startTime.plusMillis(respawnTime));
}
}

View File

@@ -85,4 +85,15 @@ public interface WoodcuttingConfig extends Config
{
return false;
}
@ConfigItem(
position = 6,
keyName = "showRespawnTimers",
name = "Show respawn timers",
description = "Configures whether to display the respawn timer overlay"
)
default boolean showRespawnTimers()
{
return true;
}
}

View File

@@ -43,7 +43,6 @@ import net.runelite.client.ui.overlay.components.TitleComponent;
import net.runelite.client.ui.overlay.components.table.TableAlignment;
import net.runelite.client.ui.overlay.components.table.TableComponent;
@Singleton
class WoodcuttingOverlay extends Overlay
{
@@ -51,17 +50,18 @@ class WoodcuttingOverlay extends Overlay
private final Client client;
private final WoodcuttingPlugin plugin;
private final WoodcuttingConfig config;
private final XpTrackerService xpTrackerService;
private final PanelComponent panelComponent = new PanelComponent();
@Inject
private WoodcuttingOverlay(final Client client, final WoodcuttingPlugin plugin, final XpTrackerService xpTrackerService)
private WoodcuttingOverlay(Client client, WoodcuttingPlugin plugin, WoodcuttingConfig config, XpTrackerService xpTrackerService)
{
super(plugin);
setPosition(OverlayPosition.TOP_LEFT);
this.client = client;
this.plugin = plugin;
this.config = config;
this.xpTrackerService = xpTrackerService;
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Woodcutting overlay"));
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY, WOODCUTTING_RESET, "Woodcutting overlay"));
@@ -70,7 +70,7 @@ class WoodcuttingOverlay extends Overlay
@Override
public Dimension render(Graphics2D graphics)
{
if (!plugin.isShowWoodcuttingStats())
if (!config.showWoodcuttingStats())
{
return null;
}
@@ -107,7 +107,7 @@ class WoodcuttingOverlay extends Overlay
{
tableComponent.addRow("Logs cut:", Integer.toString(actions));
if (plugin.isShowGPEarned())
if (config.showGPEarned())
{
tableComponent.addRow("GP earned:", Integer.toString((plugin.getGpEarned())));
}
@@ -123,5 +123,4 @@ class WoodcuttingOverlay extends Overlay
return panelComponent.render(graphics);
}
}

View File

@@ -27,17 +27,18 @@ package net.runelite.client.plugins.woodcutting;
import com.google.inject.Provides;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.GameState;
import net.runelite.api.ItemID;
import net.runelite.api.MenuOpcode;
import net.runelite.api.Player;
@@ -51,7 +52,6 @@ import net.runelite.api.events.GameTick;
import net.runelite.client.Notifier;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.OverlayMenuClicked;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin;
@@ -64,14 +64,21 @@ import net.runelite.client.ui.overlay.OverlayMenuEntry;
@PluginDescriptor(
name = "Woodcutting",
description = "Show woodcutting statistics and/or bird nest notifications",
tags = {"birds", "nest", "notifications", "overlay", "skilling", "wc"}
tags = {"birds", "nest", "notifications", "overlay", "skilling", "wc"},
enabledByDefault = false
)
@Singleton
@PluginDependency(XpTrackerPlugin.class)
@Slf4j
public class WoodcuttingPlugin extends Plugin
{
private static final Pattern WOOD_CUT_PATTERN = Pattern.compile("You get (?:some|an)[\\w ]+(?:logs?|mushrooms)\\.");
@Getter
private final Set<GameObject> treeObjects = new HashSet<>();
@Getter(AccessLevel.PACKAGE)
private final List<TreeRespawn> respawns = new ArrayList<>();
@Inject
private Notifier notifier;
@@ -93,24 +100,13 @@ public class WoodcuttingPlugin extends Plugin
@Inject
private ItemManager itemManager;
@Getter(AccessLevel.PACKAGE)
@Getter
private WoodcuttingSession session;
@Getter(AccessLevel.PACKAGE)
@Getter
private Axe axe;
@Getter(AccessLevel.PACKAGE)
private final Set<GameObject> treeObjects = new HashSet<>();
private int statTimeout;
boolean showNestNotification;
@Getter(AccessLevel.PACKAGE)
private boolean showWoodcuttingStats;
@Getter(AccessLevel.PACKAGE)
private boolean showRedwoodTrees;
@Getter(AccessLevel.PACKAGE)
private boolean showGPEarned;
private boolean recentlyLoggedIn;
private int treeTypeID;
@Getter(AccessLevel.PACKAGE)
private int gpEarned;
@@ -122,19 +118,18 @@ public class WoodcuttingPlugin extends Plugin
}
@Override
protected void startUp()
protected void startUp() throws Exception
{
updateConfig();
overlayManager.add(overlay);
overlayManager.add(treesOverlay);
}
@Override
protected void shutDown()
protected void shutDown() throws Exception
{
overlayManager.remove(overlay);
overlayManager.remove(treesOverlay);
respawns.clear();
treeObjects.clear();
session = null;
axe = null;
@@ -155,12 +150,16 @@ public class WoodcuttingPlugin extends Plugin
@Subscribe
private void onGameTick(GameTick gameTick)
{
recentlyLoggedIn = false;
respawns.removeIf(TreeRespawn::isExpired);
if (session == null || session.getLastLogCut() == null)
{
return;
}
Duration statTimeout = Duration.ofMinutes(this.statTimeout);
Duration statTimeout = Duration.ofMinutes(config.statTimeout());
Duration sinceCut = Duration.between(session.getLastLogCut(), Instant.now());
if (sinceCut.compareTo(statTimeout) >= 0)
@@ -189,13 +188,90 @@ public class WoodcuttingPlugin extends Plugin
gpEarned += itemManager.getItemPrice(treeTypeID);
}
if (event.getMessage().contains("A bird's nest falls out of the tree") && this.showNestNotification)
if (event.getMessage().contains("A bird's nest falls out of the tree") && config.showNestNotification())
{
notifier.notify("A bird nest has spawned!");
}
}
}
@Subscribe
private void onGameObjectSpawned(final GameObjectSpawned event)
{
GameObject gameObject = event.getGameObject();
Tree tree = Tree.findTree(gameObject.getId());
if (tree == Tree.REDWOOD)
{
treeObjects.add(gameObject);
}
}
@Subscribe
private void onGameObjectDespawned(final GameObjectDespawned event)
{
final GameObject object = event.getGameObject();
Tree tree = Tree.findTree(object.getId());
if (tree != null)
{
if (tree.getRespawnTime() != null && !recentlyLoggedIn)
{
log.debug("Adding respawn timer for {} tree at {}", tree, object.getLocalLocation());
TreeRespawn treeRespawn = new TreeRespawn(tree, object.getLocalLocation(), Instant.now(), (int) tree.getRespawnTime().toMillis());
respawns.add(treeRespawn);
}
if (tree == Tree.REDWOOD)
{
treeObjects.remove(event.getGameObject());
}
}
}
@Subscribe
private void onGameObjectChanged(final GameObjectChanged event)
{
treeObjects.remove(event.getGameObject());
}
@Subscribe
private void onGameStateChanged(final GameStateChanged event)
{
switch (event.getGameState())
{
case LOADING:
case HOPPING:
respawns.clear();
treeObjects.clear();
break;
case LOGGED_IN:
// After login trees that are depleted will be changed,
// wait for the next game tick before watching for
// trees to despawn
recentlyLoggedIn = true;
break;
}
}
@Subscribe
private void onAnimationChanged(final AnimationChanged event)
{
Player local = client.getLocalPlayer();
if (event.getActor() != local)
{
return;
}
int animId = local.getAnimation();
Axe axe = Axe.findAxeByAnimId(animId);
if (axe != null)
{
this.axe = axe;
}
}
private void typeOfLogCut(String message)
{
if (message.contains("mushrooms."))
@@ -239,75 +315,4 @@ public class WoodcuttingPlugin extends Plugin
treeTypeID = ItemID.LOGS;
}
}
@Subscribe
private void onGameObjectSpawned(final GameObjectSpawned event)
{
GameObject gameObject = event.getGameObject();
Tree tree = Tree.findTree(gameObject.getId());
if (tree != null)
{
treeObjects.add(gameObject);
}
}
@Subscribe
private void onGameObjectDespawned(final GameObjectDespawned event)
{
treeObjects.remove(event.getGameObject());
}
@Subscribe
private void onGameObjectChanged(final GameObjectChanged event)
{
treeObjects.remove(event.getGameObject());
}
@Subscribe
private void onGameStateChanged(final GameStateChanged event)
{
if (event.getGameState() != GameState.LOGGED_IN)
{
treeObjects.clear();
}
}
@Subscribe
private void onAnimationChanged(final AnimationChanged event)
{
Player local = client.getLocalPlayer();
if (event.getActor() != local)
{
return;
}
int animId = local.getAnimation();
Axe axe = Axe.findAxeByAnimId(animId);
if (axe != null)
{
this.axe = axe;
}
}
@Subscribe
private void onConfigChanged(ConfigChanged event)
{
if (!event.getGroup().equals("woodcutting"))
{
return;
}
updateConfig();
}
private void updateConfig()
{
this.statTimeout = config.statTimeout();
this.showNestNotification = config.showNestNotification();
this.showWoodcuttingStats = config.showWoodcuttingStats();
this.showRedwoodTrees = config.showRedwoodTrees();
this.showGPEarned = config.showGPEarned();
}
}

View File

@@ -1,6 +1,7 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* Copyright (c) 2019, David <Dava96@github.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -25,29 +26,36 @@
*/
package net.runelite.client.plugins.woodcutting;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.time.Instant;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.Perspective;
import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayUtil;
import net.runelite.client.ui.overlay.components.ProgressPieComponent;
@Singleton
class WoodcuttingTreesOverlay extends Overlay
{
private final Client client;
private final WoodcuttingConfig config;
private final ItemManager itemManager;
private final WoodcuttingPlugin plugin;
@Inject
private WoodcuttingTreesOverlay(final Client client, final ItemManager itemManager, final WoodcuttingPlugin plugin)
private WoodcuttingTreesOverlay(final Client client, final WoodcuttingConfig config, final ItemManager itemManager, final WoodcuttingPlugin plugin)
{
this.client = client;
this.config = config;
this.itemManager = itemManager;
this.plugin = plugin;
setLayer(OverlayLayer.ABOVE_SCENE);
@@ -57,15 +65,22 @@ class WoodcuttingTreesOverlay extends Overlay
@Override
public Dimension render(Graphics2D graphics)
{
if (plugin.getSession() == null || !plugin.isShowRedwoodTrees())
renderAxes(graphics);
renderTimers(graphics);
return null;
}
private void renderAxes(Graphics2D graphics)
{
if (plugin.getSession() == null || !config.showRedwoodTrees())
{
return null;
return;
}
Axe axe = plugin.getAxe();
if (axe == null)
{
return null;
return;
}
for (GameObject treeObject : plugin.getTreeObjects())
@@ -75,7 +90,34 @@ class WoodcuttingTreesOverlay extends Overlay
OverlayUtil.renderImageLocation(client, graphics, treeObject.getLocalLocation(), itemManager.getImage(axe.getItemId()), 120);
}
}
return null;
}
}
private void renderTimers(Graphics2D graphics)
{
List<TreeRespawn> respawns = plugin.getRespawns();
if (respawns.isEmpty() || !config.showRespawnTimers())
{
return;
}
Instant now = Instant.now();
for (TreeRespawn treeRespawn : respawns)
{
LocalPoint loc = treeRespawn.getLocation();
float percent = (now.toEpochMilli() - treeRespawn.getStartTime().toEpochMilli()) / (float) treeRespawn.getRespawnTime();
Point point = Perspective.localToCanvas(client, loc, client.getPlane());
if (point == null || percent > 1.0f)
{
continue;
}
ProgressPieComponent ppc = new ProgressPieComponent();
ppc.setBorderColor(Color.ORANGE);
ppc.setFill(Color.YELLOW);
ppc.setPosition(point);
ppc.setProgress(percent);
ppc.render(graphics);
}
}
}

View File

@@ -30,11 +30,9 @@ import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ObjectArrays;
import com.google.inject.Provides;
import io.reactivex.schedulers.Schedulers;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.Instant;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
@@ -75,6 +73,8 @@ import net.runelite.client.config.ConfigManager;
import net.runelite.client.config.Keybind;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.WorldsFetch;
import net.runelite.client.game.WorldService;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
@@ -87,7 +87,6 @@ import net.runelite.client.util.ImageUtil;
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;
import net.runelite.http.api.worlds.WorldType;
import org.apache.commons.lang3.ArrayUtils;
@@ -126,9 +125,6 @@ public class WorldHopperPlugin extends Plugin
@Inject
private ChatMessageManager chatMessageManager;
@Inject
private ScheduledExecutorService executorService;
@Inject
private WorldHopperConfig config;
@@ -139,7 +135,7 @@ public class WorldHopperPlugin extends Plugin
private WorldHopperPingOverlay worldHopperOverlay;
@Inject
private WorldClient worldClient;
private WorldService worldService;
private ScheduledExecutorService hopperExecutorService;
@@ -154,11 +150,9 @@ public class WorldHopperPlugin extends Plugin
private int favoriteWorld1, favoriteWorld2;
private ScheduledFuture<?> worldResultFuture, pingFuture, currPingFuture;
private WorldResult worldResult;
private ScheduledFuture<?> pingFuture, currPingFuture;
private int currentWorld;
private Instant lastFetch;
private boolean firstRun;
private Keybind previousKey;
private Keybind nextKey;
@@ -203,7 +197,6 @@ public class WorldHopperPlugin extends Plugin
{
updateConfig();
firstRun = true;
currentPing = -1;
keyManager.registerKeyListener(previousKeyListener);
@@ -231,12 +224,15 @@ public class WorldHopperPlugin extends Plugin
// The plugin has its own executor for pings, as it blocks for a long time
hopperExecutorService = new ExecutorServiceExceptionLogger(Executors.newSingleThreadScheduledExecutor());
// On first run this schedules an initial ping on hopperExecutorService
worldResultFuture = executorService.scheduleAtFixedRate(this::tick, 0, WORLD_FETCH_TIMER, TimeUnit.MINUTES);
// Run the first-run ping
hopperExecutorService.execute(this::pingInitialWorlds);
// Give some initial delay - this won't run until after pingInitialWorlds finishes from tick() anyway
pingFuture = hopperExecutorService.scheduleWithFixedDelay(this::pingNextWorld, 15, 3, TimeUnit.SECONDS);
currPingFuture = hopperExecutorService.scheduleWithFixedDelay(this::pingCurrentWorld, 15, 1, TimeUnit.SECONDS);
// populate initial world list
updateList();
}
@Override
@@ -253,11 +249,6 @@ public class WorldHopperPlugin extends Plugin
keyManager.unregisterKeyListener(previousKeyListener);
keyManager.unregisterKeyListener(nextKeyListener);
worldResultFuture.cancel(true);
worldResultFuture = null;
worldResult = null;
lastFetch = null;
clientToolbar.removeNavigation(navButton);
hopperExecutorService.shutdown();
@@ -393,6 +384,7 @@ public class WorldHopperPlugin extends Plugin
// Don't add entry if user is offline
ChatPlayer player = getChatPlayerFromName(event.getTarget());
WorldResult worldResult = worldService.getWorlds();
if (player == null || player.getWorld() == 0 || player.getWorld() == client.getWorld()
|| worldResult == null)
@@ -484,26 +476,6 @@ public class WorldHopperPlugin extends Plugin
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();
// Ping worlds once at startup
if (firstRun)
{
firstRun = false;
// On first run we ping all of the worlds at once to initialize the ping values
hopperExecutorService.execute(this::pingInitialWorlds);
}
}
void refresh()
{
Instant now = Instant.now();
@@ -513,29 +485,14 @@ public class WorldHopperPlugin extends Plugin
return;
}
fetchWorlds();
lastFetch = now;
worldService.refresh();
}
private void fetchWorlds()
@Subscribe
public void onWorldsFetch(WorldsFetch worldsFetch)
{
log.debug("Fetching worlds");
worldClient.lookupWorlds()
.subscribeOn(Schedulers.io())
.take(1)
.subscribe(
(worldResult) ->
{
if (worldResult != null)
{
worldResult.getWorlds().sort(Comparator.comparingInt(World::getId));
this.worldResult = worldResult;
this.lastFetch = Instant.now();
updateList();
}
},
(ex) -> log.warn("Error looking up worlds", ex)
);
updateList();
}
/**
@@ -543,11 +500,16 @@ public class WorldHopperPlugin extends Plugin
*/
private void updateList()
{
SwingUtilities.invokeLater(() -> panel.populate(worldResult.getWorlds()));
WorldResult worldResult = worldService.getWorlds();
if (worldResult != null)
{
SwingUtilities.invokeLater(() -> panel.populate(worldResult.getWorlds()));
}
}
private void hop(boolean previous)
{
WorldResult worldResult = worldService.getWorlds();
if (worldResult == null || client.getGameState() != GameState.LOGGED_IN)
{
return;
@@ -658,6 +620,7 @@ public class WorldHopperPlugin extends Plugin
private void hop(int worldId)
{
WorldResult worldResult = worldService.getWorlds();
// Don't try to hop if the world doesn't exist
World world = worldResult.findWorld(worldId);
if (world == null)
@@ -801,6 +764,7 @@ public class WorldHopperPlugin extends Plugin
*/
private void pingInitialWorlds()
{
WorldResult worldResult = worldService.getWorlds();
if (worldResult == null || !this.showSidebar || !this.ping)
{
return;
@@ -838,6 +802,7 @@ public class WorldHopperPlugin extends Plugin
*/
private void pingNextWorld()
{
WorldResult worldResult = worldService.getWorlds();
if (worldResult == null || !this.showSidebar || !this.ping)
{
return;
@@ -874,6 +839,7 @@ public class WorldHopperPlugin extends Plugin
*/
private void pingCurrentWorld()
{
WorldResult worldResult = worldService.getWorlds();
// There is no reason to ping the current world if not logged in, as the overlay doesn't draw
if (worldResult == null || !this.displayPing || client.getGameState() != GameState.LOGGED_IN)
{

View File

@@ -70,7 +70,8 @@ class ClientConfigLoader
supplier = new HostSupplier();
}
url = supplier.get();
String host = supplier.get();
url = url.newBuilder().host(host).build();
continue;
}

View File

@@ -1,6 +1,5 @@
/*
* Copyright (c) 2019 Abex
* Copyright (c) 2019, Lucas <https://github.com/lucwousin>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -25,8 +24,10 @@
*/
package net.runelite.client.rs;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Queue;
import java.util.Random;
@@ -36,41 +37,46 @@ import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.api.worlds.World;
import net.runelite.http.api.worlds.WorldClient;
import net.runelite.http.api.worlds.WorldResult;
import okhttp3.HttpUrl;
import net.runelite.http.api.worlds.WorldType;
@Slf4j
class HostSupplier implements Supplier<HttpUrl>
class HostSupplier implements Supplier<String>
{
private final Random random = new Random();
private final Queue<HttpUrl> hosts = new ArrayDeque<>();
private final Random random = new Random(System.nanoTime());
private Queue<String> hosts = new ArrayDeque<>();
@Override
public HttpUrl get()
public String get()
{
if (!hosts.isEmpty())
{
return hosts.poll();
}
List<HttpUrl> newHosts = new WorldClient(RuneLiteAPI.CLIENT)
.lookupWorlds()
.map(WorldResult::getWorlds)
.blockingSingle()
.stream()
.map(World::getAddress)
.map(HttpUrl::parse)
.collect(Collectors.toList());
try
{
List<String> newHosts = new WorldClient(RuneLiteAPI.CLIENT)
.lookupWorlds()
.getWorlds()
.stream()
.filter(w -> w.getTypes().isEmpty() || EnumSet.of(WorldType.MEMBERS).equals(w.getTypes()))
.map(World::getAddress)
.collect(Collectors.toList());
Collections.shuffle(newHosts, random);
Collections.shuffle(newHosts, random);
hosts.addAll(newHosts.subList(0, 16));
hosts.addAll(newHosts.subList(0, 16));
}
catch (IOException e)
{
log.warn("Unable to retrieve world list", e);
}
while (hosts.size() < 2)
{
hosts.add(HttpUrl.parse("oldschool" + (random.nextInt(50) + 1) + ".runescape.COM"));
hosts.add("oldschool" + (random.nextInt(50) + 1) + ".runescape.COM");
}
return hosts.poll();
}
}
}

View File

@@ -1024,9 +1024,9 @@ public class ClientUI
setOpacityMethod.invoke(peerField.get(frame), opacity);
}
catch (NoSuchFieldException | NoSuchMethodException | ClassNotFoundException | IllegalAccessException | InvocationTargetException e)
catch (NoSuchFieldException | NoSuchMethodException | ClassNotFoundException | IllegalAccessException | InvocationTargetException | NullPointerException e)
{
e.printStackTrace();
// e.printStackTrace();
}
});
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* Copyright (c) 2018, Abex
* 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.util;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectUtil
{
private ReflectUtil()
{
}
public static MethodHandles.Lookup privateLookupIn(Class clazz)
{
try
{
// Java 9+ has privateLookupIn method on MethodHandles, but since we are shipping and using Java 8
// we need to access it via reflection. This is preferred way because it's Java 9+ public api and is
// likely to not change
final Method privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
return (MethodHandles.Lookup) privateLookupIn.invoke(null, clazz, MethodHandles.lookup());
}
catch (InvocationTargetException | IllegalAccessException e)
{
throw new RuntimeException(e);
}
catch (NoSuchMethodException e)
{
try
{
// In Java 8 we first do standard lookupIn class
final MethodHandles.Lookup lookupIn = MethodHandles.lookup().in(clazz);
// and then we mark it as trusted for private lookup via reflection on private field
final Field modes = MethodHandles.Lookup.class.getDeclaredField("allowedModes");
modes.setAccessible(true);
modes.setInt(lookupIn, -1); // -1 == TRUSTED
return lookupIn;
}
catch (ReflectiveOperationException ex)
{
throw new RuntimeException(ex);
}
}
}
}