diff --git a/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java b/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java index 9d7ea5fcd7..502f7049c8 100644 --- a/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java +++ b/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java @@ -111,7 +111,7 @@ public class ChatMessageManager } } - private void onChatMessage(ChatMessage chatMessage) + void onChatMessage(ChatMessage chatMessage) { MessageNode messageNode = chatMessage.getMessageNode(); ChatMessageType chatMessageType = chatMessage.getType(); @@ -167,7 +167,11 @@ public class ChatMessageManager continue; } - messageNode.setValue(ColorUtil.wrapWithColorTag(messageNode.getValue(), chatColor.getColor())); + // Replace tags in the message with the new color so embedded won't reset the color + final Color color = chatColor.getColor(); + messageNode.setValue(ColorUtil.wrapWithColorTag( + messageNode.getValue().replace(ColorUtil.CLOSING_COLOR_TAG, ColorUtil.colorTag(color)), + color)); break; } } diff --git a/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java b/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java index 72da2a84a0..e24676bef3 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java +++ b/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java @@ -99,6 +99,8 @@ public enum AgilityShortcut EDGEVILLE_DUNGEON_MONKEYBARS(15, "Monkey Bars", null, MONKEYBARS_23566), TROLLHEIM_ROCKS(15, "Rocks", null, new WorldPoint(2838, 3614, 0), ROCKS_3748), // No fixed world map location, but rocks near death plateau have a requirement of 15 YANILLE_UNDERWALL_TUNNEL(16, "Underwall Tunnel", new WorldPoint(2574, 3109, 0), HOLE_16520, CASTLE_WALL), + KOUREND_CATACOMBS_SOUTH_WEST_CREVICE_NORTH(17, "Crevice", new WorldPoint(1647, 10008, 0), CRACK_28892), + KOUREND_CATACOMBS_SOUTH_WEST_CREVICE_SOUTH(17, "Crevice", new WorldPoint(1645, 10001, 0), CRACK_28892), YANILLE_WATCHTOWER_TRELLIS(18, "Trellis", null, TRELLIS_20056), COAL_TRUCKS_LOG_BALANCE(20, "Log Balance", new WorldPoint(2598, 3475, 0), LOG_BALANCE_23274), GRAND_EXCHANGE_UNDERWALL_TUNNEL(21, "Underwall Tunnel", new WorldPoint(3139, 3515, 0), UNDERWALL_TUNNEL_16529, UNDERWALL_TUNNEL_16530), @@ -106,6 +108,8 @@ public enum AgilityShortcut OBSERVATORY_SCALE_CLIFF(23, "Grapple Rocks", new WorldPoint(2447, 3155, 0), NULL_31849), EAGLES_PEAK_ROCK_CLIMB(25, "Rock Climb", new WorldPoint(2320, 3499, 0), ROCKS_19849), FALADOR_UNDERWALL_TUNNEL(26, "Underwall Tunnel", new WorldPoint(2947, 3313, 0), UNDERWALL_TUNNEL, UNDERWALL_TUNNEL_16528), + KOUREND_CATACOMBS_PILLAR_JUMP_NORTH(28, "Pillar Jump", new WorldPoint(1613, 10071, 0)), + KOUREND_CATACOMBS_PILLAR_JUMP_SOUTH(28, "Pillar Jump", new WorldPoint(1609, 10060, 0)), MOUNT_KARUULM_LOWER(29, "Rocks", new WorldPoint(1324, 3782, 0), ROCKS_34397), CORSAIR_COVE_RESOURCE_ROCKS(30, "Rocks", new WorldPoint(2486, 2898, 0), ROCKS_31758, ROCKS_31759), SOUTHEAST_KARAJMA_STEPPING_STONES(30, "Stepping Stones", new WorldPoint(2924, 2946, 0), STEPPING_STONES, STEPPING_STONES_23646, STEPPING_STONES_23647), @@ -116,6 +120,8 @@ public enum AgilityShortcut CAIRN_ISLE_ROCKS(32, "Rocks", null, ROCKS_2231), ARDOUGNE_LOG_BALANCE(33, "Log Balance", new WorldPoint(2602, 3336, 0), LOG_BALANCE_16546, LOG_BALANCE_16547, LOG_BALANCE_16548), BRIMHAVEN_DUNGEON_MEDIUM_PIPE(34, "Pipe Squeeze", null, new WorldPoint(2698, 9501, 0), PIPE_21727), + KOUREND_CATACOMBS_NORTH_EAST_CREVICE_NORTH(34, "Crevice", new WorldPoint(1715, 10057, 0), CRACK_28892), + KOUREND_CATACOMBS_NORTH_EAST_CREVICE_SOUTH(34, "Crevice", new WorldPoint(1705, 10077, 0), CRACK_28892), CATHERBY_OBELISK_GRAPPLE(36, "Grapple Rock", new WorldPoint(2841, 3434, 0), CROSSBOW_TREE_17062), GNOME_STRONGHOLD_ROCKS(37, "Rocks", new WorldPoint(2485, 3515, 0), ROCKS_16534, ROCKS_16535), AL_KHARID_MINING_PITCLIFF_SCRAMBLE(38, "Rocks", new WorldPoint(3305, 3315, 0), ROCKS_16549, ROCKS_16550), @@ -195,6 +201,7 @@ public enum AgilityShortcut TROLL_STRONGHOLD_WALL_CLIMB(73, "Rocks", new WorldPoint(2841, 3694, 0), ROCKS_16464), ARCEUUS_ESSENSE_MINE_WEST(73, "Rock Climb", new WorldPoint(1742, 3853, 0), ROCKS_27984, ROCKS_27985), LAVA_DRAGON_ISLE_JUMP(74, "Stepping Stone", new WorldPoint(3200, 3807, 0), STEPPING_STONE_14918), + FORTHOS_DUNGEON_SPIKED_BLADES(75, "Spiked Blades", new WorldPoint(1819, 9946, 0), STRANGE_FLOOR_34834), REVENANT_CAVES_DEMONS_JUMP(75, "Jump", new WorldPoint(3199, 10135, 0), PILLAR_31561), REVENANT_CAVES_ANKOU_EAST(75, "Jump", new WorldPoint(3201, 10195, 0), PILLAR_31561), REVENANT_CAVES_ANKOU_NORTH(75, "Jump", new WorldPoint(3180, 10209, 0), PILLAR_31561), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java index 963c8efea4..1197f43379 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java @@ -369,6 +369,11 @@ public class DevToolsPlugin extends Plugin client.playSoundEffect(id); break; } + case "msg": + { + client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", String.join(" ", args), ""); + break; + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierConfig.java index 73878b954b..df04a38727 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierConfig.java @@ -85,12 +85,23 @@ public interface IdleNotifierConfig extends Config { return false; } + + @ConfigItem( + keyName = "movementidle", + name = "Idle Movement Notifications", + description = "Configures if idle movement notifications are enabled e.g. running, walking", + position = 6 + ) + default boolean movementIdle() + { + return false; + } @ConfigItem( keyName = "logoutidle", name = "Idle Logout Notifications", description = "Configures if the idle logout notifications are enabled", - position = 6 + position = 7 ) default boolean logoutIdle() { @@ -101,7 +112,7 @@ public interface IdleNotifierConfig extends Config keyName = "outofcombatsound", name = "Out of Combat Sound", description = "Plays a custom sound whenever you leave combat", - position = 7 + position = 8 ) default boolean outOfCombatSound() { @@ -109,10 +120,10 @@ public interface IdleNotifierConfig extends Config } @ConfigItem( - position = 8, keyName = "skullNotification", name = "Skull Notification", - description = "Receive a notification when you skull." + description = "Receive a notification when you skull.", + position = 9 ) default boolean showSkullNotification() { @@ -120,10 +131,10 @@ public interface IdleNotifierConfig extends Config } @ConfigItem( - position = 9, keyName = "unskullNotification", name = "Unskull Notification", - description = "Receive a notification when you unskull." + description = "Receive a notification when you unskull.", + position = 10 ) default boolean showUnskullNotification() { @@ -134,7 +145,7 @@ public interface IdleNotifierConfig extends Config keyName = "timeout", name = "Idle Notification Delay (ms)", description = "The notification delay after the player is idle", - position = 10 + position = 11 ) default int getIdleNotificationDelay() { @@ -145,7 +156,7 @@ public interface IdleNotifierConfig extends Config keyName = "hitpoints", name = "Hitpoints Notification Threshold", description = "The amount of hitpoints to send a notification at. A value of 0 will disable notification.", - position = 11 + position = 12 ) default int getHitpointsThreshold() { @@ -156,7 +167,7 @@ public interface IdleNotifierConfig extends Config keyName = "playHealthSound", name = "Play sound for Low Health", description = "Will play a sound for every Low Health notification sent", - position = 12 + position = 13 ) default boolean getPlayHealthSound() { @@ -167,7 +178,7 @@ public interface IdleNotifierConfig extends Config keyName = "prayer", name = "Prayer Notification Threshold", description = "The amount of prayer points to send a notification at. A value of 0 will disable notification.", - position = 13 + position = 14 ) default int getPrayerThreshold() { @@ -178,7 +189,7 @@ public interface IdleNotifierConfig extends Config keyName = "playPrayerSound", name = "Play sound for Low Prayer", description = "Will play a sound for every Low Prayer notification sent", - position = 14 + position = 15 ) default boolean getPlayPrayerSound() { @@ -188,8 +199,8 @@ public interface IdleNotifierConfig extends Config @ConfigItem( keyName = "oxygen", name = "Oxygen Notification Threshold", - position = 15, - description = "The amount of remaining oxygen to send a notification at. A value of 0 will disable notification." + description = "The amount of remaining oxygen to send a notification at. A value of 0 will disable notification.", + position = 16 ) default int getOxygenThreshold() { @@ -199,8 +210,8 @@ public interface IdleNotifierConfig extends Config @ConfigItem( keyName = "spec", name = "Special Attack Energy Notification Threshold", - position = 16, - description = "The amount of spec energy reached to send a notification at. A value of 0 will disable notification." + description = "The amount of spec energy reached to send a notification at. A value of 0 will disable notification.", + position = 17 ) default int getSpecEnergyThreshold() { @@ -211,7 +222,7 @@ public interface IdleNotifierConfig extends Config keyName = "specSound", name = "Special Attack Energy Sound", description = "Plays a custom sound accompanying Special Attack energy notifications", - position = 17 + position = 18 ) default boolean getSpecSound() { @@ -222,7 +233,7 @@ public interface IdleNotifierConfig extends Config keyName = "overspec", name = "Over Special Energy Notification", description = "Will repeat notifications for any value over the special energy threshold", - position = 18 + position = 19 ) default boolean getOverSpecEnergy() { @@ -232,8 +243,8 @@ public interface IdleNotifierConfig extends Config @ConfigItem( keyName = "pkers", name = "PKer Notifier", - position = 19, description = "Notifies if an attackable player based on your level range appears on screen.", + position = 20, group = "PvP", warning = "This will not notify you if the player is in your cc or is online on your friends list." ) @@ -245,14 +256,12 @@ public interface IdleNotifierConfig extends Config @ConfigItem( keyName = "resourceDoor", name = "Resource Door Notifier", - position = 20, description = "Notifies if the wilderness resource area door is opened", + position = 21, group = "PvP" ) - default boolean notifyResourceDoor() { return false; } - } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java index 84413b9085..3775e9ba91 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java @@ -59,6 +59,7 @@ import net.runelite.api.VarPlayer; import net.runelite.api.Varbits; import net.runelite.api.WorldType; import net.runelite.api.WallObject; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.AnimationChanged; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.GameStateChanged; @@ -246,6 +247,9 @@ public class IdleNotifierPlugin extends Plugin private int lastAnimation = AnimationID.IDLE; private Instant lastInteracting; private Actor lastInteract; + private Instant lastMoving; + private WorldPoint lastPosition; + private boolean notifyPosition = false; private boolean notifyHitpoints = true; private boolean notifyPrayer = true; private boolean notifyOxygen = true; @@ -292,6 +296,8 @@ public class IdleNotifierPlugin extends Plugin private boolean notifyPkers; private boolean notifyResourceDoor; private boolean outOfItemsIdle; + @Setter(AccessLevel.PACKAGE) + private boolean movementIdle; @Provides IdleNotifierConfig provideConfig(ConfigManager configManager) @@ -591,6 +597,11 @@ public class IdleNotifierPlugin extends Plugin lastAnimation = IDLE; } + if (this.movementIdle && checkMovementIdle(waitDuration, local)) + { + notifier.notify("[" + local.getName() + "] has stopped moving!"); + } + if (this.interactionIdle && checkInteractionIdle(waitDuration, local)) { if (lastInteractWasCombat) @@ -874,7 +885,36 @@ public class IdleNotifierPlugin extends Plugin resetOutOfItemsIdleChecks(); return true; } + return false; + } + private boolean checkMovementIdle(Duration waitDuration, Player local) + { + if (lastPosition == null) + { + lastPosition = local.getWorldLocation(); + return false; + } + + WorldPoint position = local.getWorldLocation(); + + if (lastPosition.equals(position)) + { + if (notifyPosition + && local.getAnimation() == IDLE + && Instant.now().compareTo(lastMoving.plus(waitDuration)) >= 0) + { + notifyPosition = false; + // Return true only if we weren't just breaking out of an animation + return lastAnimation == IDLE; + } + } + else + { + notifyPosition = true; + lastPosition = position; + lastMoving = Instant.now(); + } return false; } @@ -1001,5 +1041,6 @@ public class IdleNotifierPlugin extends Plugin this.notifyPkers = config.notifyPkers(); this.notifyResourceDoor = config.notifyResourceDoor(); this.outOfItemsIdle = config.outOfItemsIdle(); + this.movementIdle = config.movementIdle(); } } diff --git a/runelite-client/src/main/java/net/runelite/client/util/ColorUtil.java b/runelite-client/src/main/java/net/runelite/client/util/ColorUtil.java index 7b881f8f2f..3f23c38f53 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/ColorUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/ColorUtil.java @@ -34,7 +34,7 @@ public class ColorUtil public static final int MIN_RGB_VALUE = 0; private static final String OPENING_COLOR_TAG_START = " + * 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.chat; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import java.awt.Color; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.MessageNode; +import net.runelite.api.events.ChatMessage; +import net.runelite.client.config.ChatColorConfig; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Matchers.eq; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ChatMessageManagerTest +{ + @Mock + @Bind + private Client client; + + @Mock + @Bind + private ChatColorConfig chatColorConfig; + + @Inject + private ChatMessageManager chatMessageManager; + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + + chatMessageManager.loadColors(); + } + + @Test + public void onChatMessage() + { + when(chatColorConfig.opaquePublicChat()).thenReturn(Color.decode("#b20000")); + + chatMessageManager.loadColors(); + + ChatMessage chatMessage = new ChatMessage(); + chatMessage.setType(ChatMessageType.PUBLICCHAT); + + MessageNode messageNode = mock(MessageNode.class); + chatMessage.setMessageNode(messageNode); + + when(messageNode.getValue()).thenReturn("Your dodgy necklace protects you. It has 1 charge left."); + chatMessageManager.onChatMessage(chatMessage); + + verify(messageNode).setValue(eq("Your dodgy necklace protects you. It has 1 charge left.")); + } +} \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPluginTest.java index 27a7543ca4..0ea5aad57e 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPluginTest.java @@ -38,6 +38,7 @@ import net.runelite.api.NPCDefinition; import net.runelite.api.Player; import net.runelite.api.VarPlayer; import net.runelite.api.WorldType; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.AnimationChanged; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; @@ -277,4 +278,19 @@ public class IdleNotifierPluginTest plugin.onGameTick(GameTick.INSTANCE); verify(notifier).notify(eq("[" + PLAYER_NAME + "] has restored spec energy!")); } + + @Test + public void testMovementIdle() + { + plugin.setMovementIdle(true); + + when(player.getWorldLocation()).thenReturn(new WorldPoint(0, 0, 0)); + plugin.onGameTick(GameTick.INSTANCE); + when(player.getWorldLocation()).thenReturn(new WorldPoint(1, 0, 0)); + plugin.onGameTick(GameTick.INSTANCE); + // No movement here + plugin.onGameTick(GameTick.INSTANCE); + + verify(notifier).notify(eq("[" + PLAYER_NAME + "] has stopped moving!")); + } } \ No newline at end of file