Merge branch 'master' of https://github.com/open-osrs/runelite into fix-test-branch

This commit is contained in:
James
2019-11-29 00:56:16 -05:00
405 changed files with 43510 additions and 41872 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

@@ -59,7 +59,7 @@ public class Flexo extends Robot
public static final int fixedWidth = Constants.GAME_FIXED_WIDTH;
public static final int fixedHeight = Constants.GAME_FIXED_HEIGHT;
public static boolean isStretched;
public static final int minDelay = 45;
public static int minDelay = 45;
public static MouseMotionFactory currentMouseMotionFactory;
public boolean pausedIndefinitely = false;
private Robot peer;

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

@@ -175,6 +175,10 @@ class WidgetInfoTableModel extends AbstractTableModel
out.add(new WidgetField<>("RelativeY", Widget::getRelativeY, Widget::setRelativeY, Integer.class));
out.add(new WidgetField<>("Width", Widget::getWidth, Widget::setWidth, Integer.class));
out.add(new WidgetField<>("Height", Widget::getHeight, Widget::setHeight, Integer.class));
out.add(new WidgetField<>("RotationX", Widget::getRotationX, Widget::setRotationX, Integer.class));
out.add(new WidgetField<>("RotationY", Widget::getRotationY, Widget::setRotationY, Integer.class));
out.add(new WidgetField<>("RotationZ", Widget::getRotationZ, Widget::setRotationZ, Integer.class));
out.add(new WidgetField<>("ModelZoom", Widget::getModelZoom, Widget::setModelZoom, Integer.class));
out.add(new WidgetField<>("CanvasLocation", Widget::getCanvasLocation));
out.add(new WidgetField<>("Bounds", Widget::getBounds));
out.add(new WidgetField<>("ScrollX", Widget::getScrollX, Widget::setScrollX, Integer.class));

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

@@ -122,10 +122,9 @@ public class LearnToClickPlugin extends Plugin
MenuEntry[] menuEntries = client.getMenuEntries();
for (MenuEntry entry : menuEntries)
{
if ((entry.getOption().equals("Floating") && this.shouldRightClickMap) ||
(entry.getOption().equals("Hide") && this.shouldRightClickXp) || (entry.getOption().equals("Show")
&& this.shouldRightClickXp) || (entry.getOption().equals("Auto retaliate")
&& this.shouldRightClickRetaliate))
if ((entry.getOption().equals("Floating <col=ff9040>World Map</col>") && this.shouldRightClickMap) ||
(entry.getTarget().equals("<col=ff9040>XP drops</col>") && this.shouldRightClickXp) ||
(entry.getOption().equals("Auto retaliate") && this.shouldRightClickRetaliate))
{
event.setForceRightClick(true);
return;
@@ -136,8 +135,8 @@ public class LearnToClickPlugin extends Plugin
@Subscribe
private void onMenuEntryAdded(MenuEntryAdded event)
{
if ((event.getOption().equals("Floating") && this.shouldRightClickMap) || (event.getOption().equals("Hide")
&& this.shouldRightClickXp) || (event.getOption().equals("Show") && this.shouldRightClickXp) ||
if ((event.getOption().equals("Floating <col=ff9040>World Map</col>") && this.shouldRightClickMap) ||
(event.getTarget().equals("<col=ff9040>XP drops</col>") && this.shouldRightClickXp) ||
(event.getOption().equals("Auto retaliate") && this.shouldRightClickRetaliate))
{
forceRightClickFlag = true;

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

@@ -76,6 +76,7 @@ import net.runelite.api.util.Text;
import net.runelite.api.widgets.Widget;
import static net.runelite.api.widgets.WidgetID.BARROWS_REWARD_GROUP_ID;
import static net.runelite.api.widgets.WidgetID.CHAMBERS_OF_XERIC_REWARD_GROUP_ID;
import static net.runelite.api.widgets.WidgetID.CHATBOX_GROUP_ID;
import static net.runelite.api.widgets.WidgetID.CLUE_SCROLL_REWARD_GROUP_ID;
import static net.runelite.api.widgets.WidgetID.DIALOG_SPRITE_GROUP_ID;
import static net.runelite.api.widgets.WidgetID.KINGDOM_GROUP_ID;
@@ -303,11 +304,11 @@ public class ScreenshotPlugin extends Plugin
String fileName = null;
if (client.getWidget(WidgetInfo.LEVEL_UP_LEVEL) != null)
{
fileName = parseLevelUpWidget(WidgetInfo.LEVEL_UP_LEVEL);
fileName = parseLevelUpWidget(client.getWidget(WidgetInfo.LEVEL_UP_LEVEL));
}
else if (client.getWidget(WidgetInfo.DIALOG_SPRITE_TEXT) != null)
{
fileName = parseLevelUpWidget(WidgetInfo.DIALOG_SPRITE_TEXT);
fileName = parseLevelUpWidget(client.getWidget(WidgetInfo.DIALOG_SPRITE_TEXT));
}
else if (client.getWidget(WidgetInfo.QUEST_COMPLETED_NAME_TEXT) != null)
{
@@ -315,6 +316,10 @@ public class ScreenshotPlugin extends Plugin
String text = client.getWidget(WidgetInfo.QUEST_COMPLETED_NAME_TEXT).getText();
fileName = "Quest(" + text.substring(19, text.length() - 1) + ")";
}
else if (client.getWidget(WidgetInfo.CHATBOX_CONTAINER).getChild(1) != null)
{
fileName = parseLevelUpWidget(client.getWidget(WidgetInfo.CHATBOX_CONTAINER).getChild(1));
}
if (fileName != null)
{
@@ -485,6 +490,7 @@ public class ScreenshotPlugin extends Plugin
break;
case LEVEL_UP_GROUP_ID:
case DIALOG_SPRITE_GROUP_ID:
case CHATBOX_GROUP_ID:
if (!this.screenshotLevels)
{
return;
@@ -549,6 +555,7 @@ public class ScreenshotPlugin extends Plugin
case LEVEL_UP_GROUP_ID:
case DIALOG_SPRITE_GROUP_ID:
case QUEST_COMPLETED_GROUP_ID:
case CHATBOX_GROUP_ID:
{
// level up widget gets loaded prior to the text being set, so wait until the next tick
shouldTakeScreenshot = true;
@@ -574,22 +581,21 @@ public class ScreenshotPlugin extends Plugin
}
/**
* Receives a WidgetInfo pointing to the middle widget of the level-up dialog,
* Receives a Widget containing the level-up dialog,
* and parses it into a shortened string for filename usage.
*
* @param levelUpLevel WidgetInfo pointing to the required text widget,
* with the format "Your Skill (level is/are) now 99."
* @param levelUpWidget Widget containing the level-up text,
* with the format "Your Skill (level is/are) now 99."
* @return Shortened string in the format "Skill(99)"
*/
String parseLevelUpWidget(WidgetInfo levelUpLevel)
String parseLevelUpWidget(Widget levelUpWidget)
{
Widget levelChild = client.getWidget(levelUpLevel);
if (levelChild == null)
if (levelUpWidget == null)
{
return null;
}
Matcher m = LEVEL_UP_PATTERN.matcher(levelChild.getText());
Matcher m = LEVEL_UP_PATTERN.matcher(levelUpWidget.getText());
if (!m.matches())
{
return null;

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

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2018, Magic fTail
* 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.virtuallevels;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import java.util.List;
import lombok.Getter;
import net.runelite.api.ModelID;
import net.runelite.api.Skill;
@Getter
public enum SkillModel
{
CONSTRUCTION1(Skill.CONSTRUCTION, ModelID.HAMMER, 10, 14, 669, 15, 0, 329),
CONSTRUCTION2(Skill.CONSTRUCTION, ModelID.SAW, 11, 14, 615, 111, 0, 451),
COOKING(Skill.COOKING, ModelID.COOKING_SKILL_MODEL, 31, 59, 169, 1593, 0, 963),
CRAFTING1(Skill.CRAFTING, ModelID.HAMMER, 30, 24, 418, 14, 0, 496),
CRAFTING2(Skill.CRAFTING, ModelID.CHISEL, 39, 45, 353, 18, 0, 400),
DEFENCE(Skill.DEFENCE, ModelID.STEEL_KITESHIELD, 34, 37, 337, 1074, 0, 598),
FARMING(Skill.FARMING, ModelID.WATERING_CAN, 31, 52, 118, 1278, 0, 451),
FIREMAKING(Skill.FIREMAKING, ModelID.FIREMAKING_SKILL_MODEL, 29, 55, 115, 1689, 0, 771),
FISHING(Skill.FISHING, ModelID.RAW_TUNA, 33, 30, 351, 1865, 0, 517),
FLETCHING1(Skill.FLETCHING, ModelID.STEEL_ARROW, 43, 19, 254, 1257, 0, 408),
FLETCHING2(Skill.FLETCHING, ModelID.STEEL_ARROW, 46, 44, 223, 177, 0, 444),
HERBLORE(Skill.HERBLORE, ModelID.CLEAN_HERB, 20, 35, 550, 2024, 0, 344),
HITPOINTS(Skill.HITPOINTS, ModelID.HEARTH, 35, 58, 538, 0, 0, 250),
MAGIC(Skill.MAGIC, ModelID.BLUE_WIZARD_HAT, 29, 50, 131, 1913, 0, 344),
MINING(Skill.MINING, ModelID.STEEL_PICKAXE, 38, 33, 292, 1166, 0, 413),
PRAYER(Skill.PRAYER, ModelID.PRAYER_SKILL_MODEL, 29, 27, 582, 504, 0, 505),
RANGED1(Skill.RANGED, ModelID.STEEL_ARROW, 28, 34, 206, 195, 0, 405),
RANGED2(Skill.RANGED, ModelID.SHORTBOW, 42, 17, 422, 1618, 0, 397),
RUNECRAFT(Skill.RUNECRAFT, ModelID.PURE_ESSENCE, 35, 38, 242, 1979, 0, 328),
SLAYER(Skill.SLAYER, ModelID.SLAYER_SKILL_MODEL, 34, 60, 221, 1944, 0, 649),
SMITHING(Skill.SMITHING, ModelID.ANVIL, 34, 53, 97, 1868, 0, 716),
STRENGTH(Skill.STRENGTH, ModelID.STRENGTH_SKILL_MODEL, 35, 23, 512, 14, 0, 631),
AGILITY(Skill.AGILITY, ModelID.AGILITY_SKILL_MODEL, 29, 29, 533, 2040, 0, 685),
THIEVING(Skill.THIEVING, ModelID.HIGHWAYMAN_MASK, 42, 31, 366, 55, 0, 155),
WOODCUTTING(Skill.WOODCUTTING, ModelID.WILLOW_TREE, 20, 69, 116, 1978, 0, 1800),
ATTACK1(Skill.ATTACK, ModelID.STEEL_SWORD, 65, 38, 234, 148, 0, 444),
ATTACK2(Skill.ATTACK, ModelID.STEEL_LONGSWORD, 27, 29, 198, 1419, 0, 330),
HUNTER(Skill.HUNTER, ModelID.FOOTPRINT, 45, 48, 512, 0, 0, 1000);
private static final ListMultimap<Skill, SkillModel> skillModels = ArrayListMultimap.create();
private final Skill skill;
private final int modelID;
private final int originalX;
private final int originalY;
private final int rotationX;
private final int rotationY;
private final int rotationZ;
private final int modelZoom;
SkillModel(Skill skill, int modelID, int originalX, int originalY, int rotationX, int rotationY, int rotationZ, int modelZoom)
{
this.skill = skill;
this.modelID = modelID;
this.originalX = originalX;
this.originalY = originalY;
this.rotationX = rotationX;
this.rotationY = rotationY;
this.rotationZ = rotationZ;
this.modelZoom = modelZoom;
}
static
{
for (SkillModel skillModel : values())
{
skillModels.put(skillModel.skill, skillModel);
}
}
public static List<SkillModel> getSkillModels(Skill skill)
{
return skillModels.get(skill);
}
}

View File

@@ -31,11 +31,22 @@ import net.runelite.client.config.ConfigItem;
@ConfigGroup("virtuallevels")
public interface VirtualLevelsConfig extends Config
{
@ConfigItem(
keyName = "virtualMessage",
name = "Enable level up message for virtual levels",
description = "Configures whether or not to show level up messages for virtual levels",
position = 0
)
default boolean virtualMessage()
{
return true;
}
@ConfigItem(
keyName = "virtualTotalLevel",
name = "Virtual Total Level",
description = "Count virtual levels towards total level",
position = 0
position = 1
)
default boolean virtualTotalLevel()
{

View File

@@ -1,6 +1,7 @@
/*
* Copyright (c) 2018, Joshua Filby <joshua@filby.me>
* Copyright (c) 2018, Jordan Atwood <jordan.atwood423@gmail.com>
* Copyright (c) 2018, Magic fTail
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -26,17 +27,40 @@
package net.runelite.client.plugins.virtuallevels;
import com.google.inject.Provides;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Client;
import net.runelite.api.Experience;
import net.runelite.api.FontID;
import net.runelite.api.GameState;
import net.runelite.api.ScriptID;
import net.runelite.api.Skill;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.api.events.StatChanged;
import net.runelite.api.events.WidgetLoaded;
import net.runelite.api.widgets.JavaScriptCallback;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetPositionMode;
import net.runelite.api.widgets.WidgetSizeMode;
import net.runelite.api.widgets.WidgetTextAlignment;
import net.runelite.api.widgets.WidgetType;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.PluginChanged;
import net.runelite.client.input.KeyListener;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
@@ -47,9 +71,14 @@ import net.runelite.client.plugins.PluginDescriptor;
enabledByDefault = false
)
@Singleton
public class VirtualLevelsPlugin extends Plugin
public class VirtualLevelsPlugin extends Plugin implements KeyListener
{
private static final String TOTAL_LEVEL_TEXT_PREFIX = "Total level:<br>";
private static final int X_OFFSET = 13;
private static final int Y_OFFSET = 16;
private final Map<Skill, Integer> previousXpMap = new EnumMap<>(Skill.class);
private final List<Skill> skillsLeveledUp = new ArrayList<>();
@Inject
private VirtualLevelsConfig config;
@@ -60,6 +89,16 @@ public class VirtualLevelsPlugin extends Plugin
@Inject
private ClientThread clientThread;
@Inject
private EventBus eventBus;
@Inject
private KeyManager keyManager;
private boolean loginTick = false;
private boolean closeMessage;
private boolean messageOpen;
@Provides
VirtualLevelsConfig provideConfig(ConfigManager configManager)
{
@@ -71,6 +110,7 @@ public class VirtualLevelsPlugin extends Plugin
protected void shutDown()
{
clientThread.invoke(this::simulateSkillChange);
keyManager.unregisterKeyListener(this);
}
@Subscribe
@@ -79,6 +119,7 @@ public class VirtualLevelsPlugin extends Plugin
// this is guaranteed to be called after the plugin has been registered by the eventbus. startUp is not.
if (pluginChanged.getPlugin() == this)
{
keyManager.registerKeyListener(this);
clientThread.invoke(this::simulateSkillChange);
}
}
@@ -151,4 +192,226 @@ public class VirtualLevelsPlugin extends Plugin
}
}
}
private void buildVirtualLevelUp(Skill skill)
{
Widget chatboxContainer = client.getWidget(WidgetInfo.CHATBOX_CONTAINER);
if (chatboxContainer == null)
{
return;
}
String skillName = skill.getName();
int skillLevel = Experience.getLevelForXp(client.getSkillExperience(skill));
List<SkillModel> skillModels = SkillModel.getSkillModels(skill);
String prefix = (skill == Skill.AGILITY || skill == Skill.ATTACK) ? "an " : "a ";
Widget levelUpLevel = chatboxContainer.createChild(-1, WidgetType.TEXT);
Widget levelUpText = chatboxContainer.createChild(-1, WidgetType.TEXT);
Widget levelUpContinue = chatboxContainer.createChild(-1, WidgetType.TEXT);
levelUpLevel.setText("Congratulations, you just advanced " + prefix + skillName + " level.");
levelUpLevel.setTextColor(0x000080);
levelUpLevel.setFontId(FontID.QUILL_8);
levelUpLevel.setXPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
levelUpLevel.setOriginalX(73 + X_OFFSET);
levelUpLevel.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
levelUpLevel.setOriginalY(15 + Y_OFFSET);
levelUpLevel.setOriginalWidth(390);
levelUpLevel.setOriginalHeight(30);
levelUpLevel.setXTextAlignment(WidgetTextAlignment.CENTER);
levelUpLevel.setYTextAlignment(WidgetTextAlignment.LEFT);
levelUpLevel.setWidthMode(WidgetSizeMode.ABSOLUTE);
levelUpLevel.revalidate();
levelUpText.setText((skill == Skill.HITPOINTS ? "Your Hitpoints are now " + skillLevel :
"Your " + skillName + " level is now " + skillLevel) + ".");
levelUpText.setFontId(FontID.QUILL_8);
levelUpText.setXPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
levelUpText.setOriginalX(73 + X_OFFSET);
levelUpText.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
levelUpText.setOriginalY(44 + Y_OFFSET);
levelUpText.setOriginalWidth(390);
levelUpText.setOriginalHeight(30);
levelUpText.setXTextAlignment(WidgetTextAlignment.CENTER);
levelUpText.setYTextAlignment(WidgetTextAlignment.LEFT);
levelUpText.setWidthMode(WidgetSizeMode.ABSOLUTE);
levelUpText.revalidate();
levelUpContinue.setText("Click here to continue");
levelUpContinue.setTextColor(0x0000ff);
levelUpContinue.setFontId(FontID.QUILL_8);
levelUpContinue.setXPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
levelUpContinue.setOriginalX(73 + X_OFFSET);
levelUpContinue.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
levelUpContinue.setOriginalY(74 + Y_OFFSET);
levelUpContinue.setOriginalWidth(390);
levelUpContinue.setOriginalHeight(17);
levelUpContinue.setXTextAlignment(WidgetTextAlignment.CENTER);
levelUpContinue.setYTextAlignment(WidgetTextAlignment.LEFT);
levelUpContinue.setWidthMode(WidgetSizeMode.ABSOLUTE);
levelUpContinue.setAction(0, "Continue");
levelUpContinue.setOnOpListener((JavaScriptCallback) ev -> closeNextTick());
levelUpContinue.setOnMouseOverListener((JavaScriptCallback) ev -> levelUpContinue.setTextColor(0xFFFFFF));
levelUpContinue.setOnMouseLeaveListener((JavaScriptCallback) ev -> levelUpContinue.setTextColor(0x0000ff));
levelUpContinue.setHasListener(true);
levelUpContinue.revalidate();
for (SkillModel skillModel : skillModels)
{
buildWidgetModel(chatboxContainer, skillModel);
}
messageOpen = true;
}
private void buildWidgetModel(Widget chatboxContainer, SkillModel model)
{
int iconWidth = 32;
int iconHeight = 32;
if (model.getSkill() == Skill.CONSTRUCTION)
{
iconWidth = 49;
iconHeight = 61;
}
Widget levelUpModel = chatboxContainer.createChild(-1, WidgetType.MODEL);
levelUpModel.setModelId(model.getModelID());
levelUpModel.setXPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
levelUpModel.setOriginalX(model.getOriginalX() + X_OFFSET);
levelUpModel.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
levelUpModel.setOriginalY(model.getOriginalY() + Y_OFFSET);
levelUpModel.setOriginalWidth(iconWidth);
levelUpModel.setOriginalHeight(iconHeight);
levelUpModel.setRotationX(model.getRotationX());
levelUpModel.setRotationY(model.getRotationY());
levelUpModel.setRotationZ(model.getRotationZ());
levelUpModel.setModelZoom(model.getModelZoom());
levelUpModel.revalidate();
}
private void closeNextTick()
{
if (!messageOpen)
{
return;
}
Widget levelUpContinue = client.getWidget(WidgetInfo.CHATBOX_CONTAINER).getChild(2);
levelUpContinue.setText("Please wait...");
messageOpen = false;
closeMessage = true;
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
if (event.getGameState() == GameState.LOGIN_SCREEN)
{
loginTick = true;
return;
}
if (event.getGameState() != GameState.LOGGED_IN)
{
return;
}
for (Skill skill : Skill.values())
{
previousXpMap.put(skill, client.getSkillExperience(skill));
}
}
@Subscribe
public void onStatChanged(StatChanged event)
{
Skill skill = event.getSkill();
int xpAfter = client.getSkillExperience(skill);
int levelAfter = Experience.getLevelForXp(xpAfter);
int xpBefore = previousXpMap.get(skill);
int levelBefore = Experience.getLevelForXp(xpBefore);
previousXpMap.put(skill, xpAfter);
if (!config.virtualMessage() || levelAfter < 100 || levelBefore >= levelAfter)
{
return;
}
if (!loginTick)
{
skillsLeveledUp.add(skill);
}
}
@Subscribe
public void onGameTick(GameTick event)
{
loginTick = false;
if (closeMessage)
{
clientThread.invoke(() -> client.runScript(
ScriptID.MESSAGE_LAYER_CLOSE,
1,
1
));
closeMessage = false;
}
if (skillsLeveledUp.isEmpty())
{
return;
}
Widget chatboxContainer = client.getWidget(WidgetInfo.CHATBOX_CONTAINER);
if (chatboxContainer != null && !chatboxContainer.isHidden())
{
return;
}
Skill skill = skillsLeveledUp.get(0);
skillsLeveledUp.remove(skill);
clientThread.invoke(() -> client.runScript(ScriptID.MESSAGE_LAYER_OPEN));
clientThread.invoke(() -> buildVirtualLevelUp(skill));
WidgetLoaded widgetLoaded = new WidgetLoaded();
widgetLoaded.setGroupId(WidgetID.CHATBOX_GROUP_ID);
eventBus.post(WidgetLoaded.class, widgetLoaded);
}
@Override
public void keyTyped(KeyEvent e)
{
if (e.getKeyChar() != ' ')
{
return;
}
if (messageOpen)
{
closeNextTick();
}
}
@Override
public void keyPressed(KeyEvent e)
{
}
@Override
public void keyReleased(KeyEvent e)
{
}
}

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