From fb5b4bf014f7f34e65cf6d15dfaf8849ee97135c Mon Sep 17 00:00:00 2001 From: superiorser9 <58041466+superiorser9@users.noreply.github.com> Date: Thu, 4 Mar 2021 00:38:53 +0000 Subject: [PATCH 01/22] object indicators: don't clear markers on connection lost --- .../client/plugins/objectindicators/ObjectIndicatorsPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index 6e24212005..86a27c8915 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -208,7 +208,7 @@ public class ObjectIndicatorsPlugin extends Plugin } } - if (gameStateChanged.getGameState() != GameState.LOGGED_IN) + if (gameStateChanged.getGameState() != GameState.LOGGED_IN && gameStateChanged.getGameState() != GameState.CONNECTION_LOST) { objects.clear(); } From 60b8d3370f4b50709ca931395c7531310fd2bc4f Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 3 Mar 2021 23:35:14 -0500 Subject: [PATCH 02/22] Fix slayer plugin setting config values to null null is not permitted in properties and throws an exception. Additionally mark value in setConfiguration as NonNull. --- .../java/net/runelite/client/config/ConfigManager.java | 3 ++- .../net/runelite/client/plugins/slayer/SlayerPlugin.java | 9 ++++++++- .../runelite/client/plugins/slayer/SlayerPluginTest.java | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index 42a6d4e965..93510dc94a 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -78,6 +78,7 @@ import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; +import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.Player; @@ -482,7 +483,7 @@ public class ConfigManager setConfiguration(groupName, null, key, value); } - public void setConfiguration(String groupName, String profile, String key, String value) + public void setConfiguration(String groupName, String profile, String key, @NonNull String value) { if (Strings.isNullOrEmpty(groupName) || Strings.isNullOrEmpty(key)) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java index 24816d559e..bc9281980e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java @@ -316,7 +316,14 @@ public class SlayerPlugin extends Plugin private void setProfileConfig(String key, Object value) { - configManager.setRSProfileConfiguration(SlayerConfig.GROUP_NAME, key, value); + if (value != null) + { + configManager.setRSProfileConfiguration(SlayerConfig.GROUP_NAME, key, value); + } + else + { + configManager.unsetRSProfileConfiguration(SlayerConfig.GROUP_NAME, key); + } } private void save() diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerPluginTest.java index 21af0f0538..f10e525ce8 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerPluginTest.java @@ -553,6 +553,8 @@ public class SlayerPluginTest assertEquals("", slayerPlugin.getTaskName()); assertEquals(0, slayerPlugin.getAmount()); + + verify(configManager).unsetRSProfileConfiguration(SlayerConfig.GROUP_NAME, SlayerConfig.TASK_LOC_KEY); } @Test From 7793ca049a1bd6a764239ef2c1fa9b8fa4c74df3 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 3 Mar 2021 23:50:28 -0500 Subject: [PATCH 03/22] config manager: include profile in unset configchanged event --- .../src/main/java/net/runelite/client/config/ConfigManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index 93510dc94a..b1a9092368 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -597,6 +597,7 @@ public class ConfigManager ConfigChanged configChanged = new ConfigChanged(); configChanged.setGroup(groupName); + configChanged.setProfile(profile); configChanged.setKey(key); configChanged.setOldValue(oldValue); From d26e3ec7b2f6a0fc1ec471a290c5a7ed12c78eed Mon Sep 17 00:00:00 2001 From: Jordan Atwood Date: Tue, 9 Feb 2021 22:58:27 -0800 Subject: [PATCH 04/22] opponent info: Add simple interaction tests --- .../opponentinfo/OpponentInfoPlugin.java | 3 + .../opponentinfo/OpponentInfoPluginTest.java | 180 ++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java index 801148c447..65fcf0af05 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java @@ -25,6 +25,7 @@ */ package net.runelite.client.plugins.opponentinfo; +import com.google.common.annotations.VisibleForTesting; import com.google.inject.Provides; import java.time.Duration; import java.time.Instant; @@ -80,6 +81,8 @@ public class OpponentInfoPlugin extends Plugin @Getter(AccessLevel.PACKAGE) private Actor lastOpponent; + @Getter(AccessLevel.PACKAGE) + @VisibleForTesting private Instant lastTime; @Provides diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java new file mode 100644 index 0000000000..4b213bde95 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2021, Jordan Atwood + * 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.opponentinfo; + +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 net.runelite.api.Actor; +import net.runelite.api.Client; +import net.runelite.api.events.InteractingChanged; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.client.ui.overlay.OverlayManager; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.junit.Test; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class OpponentInfoPluginTest +{ + @Mock + @Bind + Client client; + + @Mock + @Bind + OverlayManager overlayManager; + + @Mock + @Bind + OpponentInfoConfig config; + + @Mock + @Bind + OpponentInfoOverlay opponentInfoOverlay; + + @Mock + @Bind + PlayerComparisonOverlay playerComparisonOverlay; + + @Inject + OpponentInfoPlugin plugin; + + private final Player localPlayer = mock(Player.class); + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + + when(client.getLocalPlayer()).thenReturn(localPlayer); + } + + @Test + public void testNpcInteractions() + { + final NPC npc = mock(NPC.class); + + interactingChanged(npc, localPlayer); + + assertNull(plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(npc, null); + + assertNull(plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(localPlayer, npc); + + assertSame(npc, plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(npc, localPlayer); + + assertSame(npc, plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(localPlayer, null); + + // last opponent is remembered for 5 seconds + assertSame(npc, plugin.getLastOpponent()); + assertNotNull(plugin.getLastTime()); + } + + @Test + public void testOtherPlayerInteractions() + { + final Player otherPlayer = mock(Player.class); + + interactingChanged(otherPlayer, localPlayer); + + assertNull(plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(otherPlayer, null); + + assertNull(plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(localPlayer, otherPlayer); + + assertSame(otherPlayer, plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(otherPlayer, localPlayer); + + assertSame(otherPlayer, plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(localPlayer, null); + + // last opponent is remembered for 5 seconds + assertSame(otherPlayer, plugin.getLastOpponent()); + assertNotNull(plugin.getLastTime()); + } + + @Test + public void testNonLocalPlayerInteractions() + { + final Player otherPlayer = mock(Player.class); + final NPC npc = mock(NPC.class); + + interactingChanged(otherPlayer, npc); + + assertNull(plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(npc, otherPlayer); + + assertNull(plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(otherPlayer, null); + + assertNull(plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + + interactingChanged(npc, null); + + assertNull(plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); + } + + private void interactingChanged(final Actor source, final Actor target) + { + when(source.getInteracting()).thenReturn(target); + plugin.onInteractingChanged(new InteractingChanged(source, target)); + } +} From 48ea01dd28fbd6e132faacc0f9d5587fb0d7db2d Mon Sep 17 00:00:00 2001 From: chaticon Date: Tue, 9 Feb 2021 22:33:33 -0800 Subject: [PATCH 05/22] opponentinfo: Show health bar of actors attacking the player --- .../opponentinfo/OpponentInfoPlugin.java | 46 ++-- .../opponentinfo/OpponentInfoPluginTest.java | 197 +++++++++++++++++- 2 files changed, 219 insertions(+), 24 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java index 65fcf0af05..b3f0b145ed 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java @@ -1,6 +1,7 @@ /* * Copyright (c) 2016-2018, Adam * Copyright (c) 2018, Jordan Atwood + * Copyright (c) 2021, Andre Araya * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -137,33 +138,50 @@ public class OpponentInfoPlugin extends Plugin @Subscribe public void onInteractingChanged(InteractingChanged event) { - if (event.getSource() != client.getLocalPlayer()) + final Actor player = client.getLocalPlayer(); + if (player == null) { return; } + final Actor opponent = player.getInteracting(); + final Actor source = event.getSource(); + final Actor target = event.getTarget(); - Actor opponent = event.getTarget(); - - if (opponent == null) + if (source == player) + { + // You have attacked an enemy + if (target != null) + { + lastOpponent = target; + lastTime = null; + } + // You have stopped attacking an enemy which is not attacking you + else if (lastOpponent != null && lastOpponent.getInteracting() != player) + { + lastTime = Instant.now(); + } + } + // You are attacked while not attacking anything + else if (target == player && opponent == null) + { + lastOpponent = source; + lastTime = null; + } + // An enemy which was previously attacking you (which you were not attacking back) has changed its target + else if (source == lastOpponent && opponent != lastOpponent) { lastTime = Instant.now(); - return; } - - lastOpponent = opponent; } @Subscribe public void onGameTick(GameTick gameTick) { - if (lastOpponent != null - && lastTime != null - && client.getLocalPlayer().getInteracting() == null) + if (lastTime != null + && Duration.between(lastTime, Instant.now()).compareTo(WAIT) > 0) { - if (Duration.between(lastTime, Instant.now()).compareTo(WAIT) > 0) - { - lastOpponent = null; - } + lastOpponent = null; + lastTime = null; } } diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java index 4b213bde95..e288170d83 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Jordan Atwood + * Copyright (c) 2021, Andre Araya * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -88,13 +89,14 @@ public class OpponentInfoPluginTest interactingChanged(npc, localPlayer); - assertNull(plugin.getLastOpponent()); + assertSame(npc, plugin.getLastOpponent()); assertNull(plugin.getLastTime()); interactingChanged(npc, null); - assertNull(plugin.getLastOpponent()); - assertNull(plugin.getLastTime()); + // last opponent is remembered for 5 seconds + assertSame(npc, plugin.getLastOpponent()); + assertNotNull(plugin.getLastTime()); interactingChanged(localPlayer, npc); @@ -108,9 +110,9 @@ public class OpponentInfoPluginTest interactingChanged(localPlayer, null); - // last opponent is remembered for 5 seconds + // last opponent is remembered as it is still attacking the player assertSame(npc, plugin.getLastOpponent()); - assertNotNull(plugin.getLastTime()); + assertNull(plugin.getLastTime()); } @Test @@ -120,13 +122,14 @@ public class OpponentInfoPluginTest interactingChanged(otherPlayer, localPlayer); - assertNull(plugin.getLastOpponent()); + assertSame(otherPlayer, plugin.getLastOpponent()); assertNull(plugin.getLastTime()); interactingChanged(otherPlayer, null); - assertNull(plugin.getLastOpponent()); - assertNull(plugin.getLastTime()); + // last opponent is remembered for 5 seconds + assertSame(otherPlayer, plugin.getLastOpponent()); + assertNotNull(plugin.getLastTime()); interactingChanged(localPlayer, otherPlayer); @@ -140,9 +143,9 @@ public class OpponentInfoPluginTest interactingChanged(localPlayer, null); - // last opponent is remembered for 5 seconds + // last opponent is remembered as it is still attacking the player assertSame(otherPlayer, plugin.getLastOpponent()); - assertNotNull(plugin.getLastTime()); + assertNull(plugin.getLastTime()); } @Test @@ -172,6 +175,180 @@ public class OpponentInfoPluginTest assertNull(plugin.getLastTime()); } + /* + * Verify that the current opponent in multi is always the npc that the player is attacking + */ + @Test + public void testAttackingEnemyInMulti() + { + final Player otherPlayer = mock(Player.class); + final NPC aggro = mock(NPC.class), attacked = mock(NPC.class); + + interactingChanged(localPlayer, null); + + // verify that there is currently no opponent + assertNull(plugin.getLastOpponent()); + + // some npc attacks the player + interactingChanged(aggro, localPlayer); + + // verify that the current opponent is the aggressive npc + assertSame(aggro, plugin.getLastOpponent()); + + // the player attacks a different npc + interactingChanged(localPlayer, attacked); + + // verify the attacked npc is now considered the current opponent + assertSame(attacked, plugin.getLastOpponent()); + + // the npc attacks them back + interactingChanged(attacked, localPlayer); + + // verify that the current opponent is still the attacked npc + assertSame(attacked, plugin.getLastOpponent()); + + // the player is attacked by an aggressive npc while attacking a different npc + interactingChanged(aggro, localPlayer); + + // verify that the current opponent is still the npc the player is attacking + assertSame(attacked, plugin.getLastOpponent()); + + // the npc attacking the player that is not the current opponent attacks a different player + interactingChanged(aggro, otherPlayer); + + // verify that the current opponent is still the npc the player is attacking + assertSame(attacked, plugin.getLastOpponent()); + + // the npc the player is attacking, the current opponent, attacks a different player + interactingChanged(attacked, otherPlayer); + + // verify that the current opponent is still the npc the player is attacking + assertSame(attacked, plugin.getLastOpponent()); + } + + /* + * Verify that the current opponent is the expected npc while the player is not attacking anything + */ + @Test + public void testIdleInMulti() + { + final Player otherPlayer = mock(Player.class); + final NPC aggroFirst = mock(NPC.class), aggroRecent = mock(NPC.class); + + interactingChanged(localPlayer, null); + + // verify that there is currently no opponent + assertNull(plugin.getLastOpponent()); + + // some npc attacks the player + interactingChanged(aggroFirst, localPlayer); + + // verify that the current opponent is the aggressive npc + assertSame(aggroFirst, plugin.getLastOpponent()); + + // the player is attacked by a different npc + interactingChanged(aggroRecent, localPlayer); + + // verify that the current opponent is the most recent aggressor + assertSame(aggroRecent, plugin.getLastOpponent()); + + // an npc that is not the current opponent targets another player + interactingChanged(aggroFirst, otherPlayer); + + // verify that the current opponent is still the most recent aggressor + assertSame(aggroRecent, plugin.getLastOpponent()); + + // the current opponent switches targets to another player + interactingChanged(aggroRecent, otherPlayer); + + // verify that there is no longer an opponent + // (if lastTime is not null, then the lastOpponent will become null after OpponentInfoPlugin.WAIT time) + assertNotNull(plugin.getLastTime()); + } + + /* + * Verify that the current opponent is the expected npc in singles + */ + @Test + public void testSingles() + { + final Player otherPlayer = mock(Player.class); + final NPC npc = mock(NPC.class); + + interactingChanged(localPlayer, null); + + // verify that there is currently no opponent + assertNull(plugin.getLastOpponent()); + + // some npc attacks the player + interactingChanged(npc, localPlayer); + + // verify that the attacking npc is the current opponent + assertSame(npc, plugin.getLastOpponent()); + + // the npc stops attacking the player + interactingChanged(npc, null); + + // verify that there is no longer an opponent + // (if lastTime is not null, then the lastOpponent will become null after OpponentInfoPlugin.WAIT time) + assertNotNull(plugin.getLastTime()); + + // the player attacks the npc + interactingChanged(localPlayer, npc); + + // verify that the attacked npc is the current opponent + assertSame(npc, plugin.getLastOpponent()); + // verify that the hp bar will no longer be hidden + assertNull(plugin.getLastTime()); + + // the npc attacks them back + interactingChanged(npc, localPlayer); + + // verify that the attacked npc is still the current opponent + assertSame(npc, plugin.getLastOpponent()); + + // the player stops attacking the npc + interactingChanged(localPlayer, null); + + // verify that the npc attacking the player is still the current opponent + assertSame(npc, plugin.getLastOpponent()); + // verify that the hp bar will not be hidden (because the npc is still attacking the player) + assertNull(plugin.getLastTime()); + + // the player attacks the npc again + interactingChanged(localPlayer, npc); + + // verify the npc is still the opponent + assertSame(npc, plugin.getLastOpponent()); + + // the npc stops attacking the player + interactingChanged(npc, null); + + // verify that the attacked npc is still the current opponent + assertSame(npc, plugin.getLastOpponent()); + + // the player stops attacking the npc while the npc is not attacking the player + interactingChanged(localPlayer, null); + + // verify that there is no longer an opponent + // (if lastTime is not null, then the lastOpponent will become null after OpponentInfoPlugin.WAIT time) + assertNotNull(plugin.getLastTime()); + + // the npc attacks a different player + interactingChanged(npc, otherPlayer); + + // verify there is still no opponent + // (if lastTime is not null, then the lastOpponent will become null after OpponentInfoPlugin.WAIT time) + assertNotNull(plugin.getLastTime()); + + // the npc stops attacking the other player + interactingChanged(npc, null); + + // verify there is still no opponent + // (if lastTime is not null, then the lastOpponent will become null after OpponentInfoPlugin.WAIT time) + assertNotNull(plugin.getLastTime()); + } + private void interactingChanged(final Actor source, final Actor target) { when(source.getInteracting()).thenReturn(target); From 463b0d73baaff507ce8203b091a6a2a4e5ba3ef5 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 24 Feb 2021 10:20:52 -0700 Subject: [PATCH 06/22] http-api: encode json Colors as ARGB hex codes --- .../http/api/gson/ColorTypeAdapter.java | 17 +++++++++++++---- .../http/api/gson/ColorTypeAdapterTest.java | 17 ++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java b/http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java index 6f4b003df0..feec0f6d66 100644 --- a/http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java +++ b/http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java @@ -43,10 +43,7 @@ public class ColorTypeAdapter extends TypeAdapter } int rgba = value.getRGB(); - out.beginObject() - .name("value") - .value(rgba) - .endObject(); + out.value(String.format("#%08X", rgba)); } @Override @@ -57,7 +54,18 @@ public class ColorTypeAdapter extends TypeAdapter case NULL: in.nextNull(); return null; + case STRING: + { + String value = in.nextString(); + if (value.charAt(0) == '#') + { + value = value.substring(1); + } + int intValue = Integer.parseUnsignedInt(value, 16); + return new Color(intValue, true); + } case BEGIN_OBJECT: + { in.beginObject(); double value = 0; while (in.peek() != JsonToken.END_OBJECT) @@ -74,6 +82,7 @@ public class ColorTypeAdapter extends TypeAdapter } in.endObject(); return new Color((int) value, true); + } } return null; // throws } diff --git a/http-api/src/test/java/net/runelite/http/api/gson/ColorTypeAdapterTest.java b/http-api/src/test/java/net/runelite/http/api/gson/ColorTypeAdapterTest.java index d7f5e6fd4c..0fe0b2226a 100644 --- a/http-api/src/test/java/net/runelite/http/api/gson/ColorTypeAdapterTest.java +++ b/http-api/src/test/java/net/runelite/http/api/gson/ColorTypeAdapterTest.java @@ -34,17 +34,24 @@ public class ColorTypeAdapterTest @Test public void test() { - test("null", null); - test("{\"value\":-13347208,\"falpha\":0.0}", new Color(0x12345678, false)); - test("{\"value\":305419896,\"falpha\":0.0}", new Color(0x12345678, true)); - test("{\"value\":-1.4221317E7,\"falpha\":0.0}", new Color(0xFF26FFFB, true)); + test("null", null, true); + test("{\"value\":-13347208,\"falpha\":0.0}", new Color(0x12345678, false), false); + test("{\"value\":305419896,\"falpha\":0.0}", new Color(0x12345678, true), false); + test("{\"value\":-1.4221317E7,\"falpha\":0.0}", new Color(0xFF26FFFB, true), false); + test("\"#FF345678\"", new Color(0x12345678, false), true); + test("\"#12345678\"", new Color(0x12345678, true), true); + test("\"#FF26FFFB\"", new Color(0xFF26FFFB, true), true); } - private void test(String json, Color object) + private void test(String json, Color object, boolean exactEncoding) { Color parsed = RuneLiteAPI.GSON.fromJson(json, Color.class); Assert.assertEquals(object, parsed); String serialized = RuneLiteAPI.GSON.toJson(object); + if (exactEncoding) + { + Assert.assertEquals(json, serialized); + } Color roundTripped = RuneLiteAPI.GSON.fromJson(serialized, Color.class); Assert.assertEquals(object, roundTripped); } From 06b8e1b79881d6d6e171905d64c5f2891e2335cc Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 24 Feb 2021 10:21:31 -0700 Subject: [PATCH 07/22] http-api: encode json Instants as millis since epoch --- .../runelite/http/api/gson/InstantTypeAdapter.java | 13 +++++++------ .../http/api/gson/InstantTypeAdapterTest.java | 11 ++++++++--- .../loottracker/LootTrackerControllerTest.java | 3 ++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java b/http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java index bf92af94b8..d5162c0e01 100644 --- a/http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java +++ b/http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java @@ -43,12 +43,7 @@ public class InstantTypeAdapter extends TypeAdapter return; } - out.beginObject() - .name("seconds") - .value(value.getEpochSecond()) - .name("nanos") - .value(value.getNano()) - .endObject(); + out.value(value.toEpochMilli()); } @Override @@ -60,6 +55,12 @@ public class InstantTypeAdapter extends TypeAdapter return null; } + if (in.peek() == JsonToken.NUMBER) + { + long jsTime = in.nextLong(); + return Instant.ofEpochMilli(jsTime); + } + long seconds = 0; int nanos = 0; in.beginObject(); diff --git a/http-api/src/test/java/net/runelite/http/api/gson/InstantTypeAdapterTest.java b/http-api/src/test/java/net/runelite/http/api/gson/InstantTypeAdapterTest.java index dab70a4f96..f9ed850843 100644 --- a/http-api/src/test/java/net/runelite/http/api/gson/InstantTypeAdapterTest.java +++ b/http-api/src/test/java/net/runelite/http/api/gson/InstantTypeAdapterTest.java @@ -34,15 +34,20 @@ public class InstantTypeAdapterTest @Test public void test() { - test("null", null); - test("{\"seconds\":1609538310,\"nanos\":291698903}", Instant.ofEpochSecond(1609538310, 291698903)); + test("null", null, true); + test("{\"seconds\":1609538310,\"nanos\":291000000}", Instant.ofEpochSecond(1609538310, 291_000_000), false); + test("1609538310291", Instant.ofEpochSecond(1609538310, 291_000_000), true); } - private void test(String json, Instant object) + private void test(String json, Instant object, boolean exactEncoding) { Instant parsed = RuneLiteAPI.GSON.fromJson(json, Instant.class); Assert.assertEquals(object, parsed); String serialized = RuneLiteAPI.GSON.toJson(object); + if (exactEncoding) + { + Assert.assertEquals(json, serialized); + } Instant roundTripped = RuneLiteAPI.GSON.fromJson(serialized, Instant.class); Assert.assertEquals(object, roundTripped); } diff --git a/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java b/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java index 685479a638..68e64270d6 100644 --- a/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java +++ b/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java @@ -26,6 +26,7 @@ package net.runelite.http.service.loottracker; import java.io.IOException; import java.time.Instant; +import java.time.temporal.ChronoField; import java.util.Collections; import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -88,7 +89,7 @@ public class LootTrackerControllerTest { LootRecord lootRecord = new LootRecord(); lootRecord.setType(LootRecordType.NPC); - lootRecord.setTime(Instant.now()); + lootRecord.setTime(Instant.now().with(ChronoField.NANO_OF_SECOND, 0)); lootRecord.setDrops(Collections.singletonList(new GameItem(4151, 1))); String data = RuneLiteAPI.GSON.toJson(Collections.singletonList(lootRecord)); From c96c8b979e327ae1d9d79d9c4198141717223204 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 7 Mar 2021 11:49:15 -0500 Subject: [PATCH 08/22] barrows: simplify timer creation logic There is a rare bug which can cause multiple timers to appear at once, and although I cannot figure out what is wrong with the existing code, this simplifies the logic sufficiently that it shouldn't happen anymore. Co-authored-by: Alexsuperfly --- .../client/plugins/barrows/BarrowsPlugin.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barrows/BarrowsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/barrows/BarrowsPlugin.java index cde5c6bb73..0cb6174d6e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/barrows/BarrowsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/barrows/BarrowsPlugin.java @@ -77,7 +77,6 @@ public class BarrowsPlugin extends Plugin private static final int CRYPT_REGION_ID = 14231; private LoopTimer barrowsPrayerDrainTimer; - private boolean wasInCrypt = false; @Getter private Widget puzzleAnswer; @@ -128,7 +127,6 @@ public class BarrowsPlugin extends Plugin overlayManager.remove(barrowsOverlay); overlayManager.remove(brotherOverlay); puzzleAnswer = null; - wasInCrypt = false; stopPrayerDrainTimer(); // Restore widgets @@ -157,18 +155,14 @@ public class BarrowsPlugin extends Plugin @Subscribe public void onGameStateChanged(GameStateChanged event) { - if (event.getGameState() == GameState.LOADING) - { - wasInCrypt = isInCrypt(); - } - else if (event.getGameState() == GameState.LOGGED_IN) + if (event.getGameState() == GameState.LOGGED_IN) { boolean isInCrypt = isInCrypt(); - if (wasInCrypt && !isInCrypt) + if (!isInCrypt && barrowsPrayerDrainTimer != null) { stopPrayerDrainTimer(); } - else if (!wasInCrypt && isInCrypt) + else if (isInCrypt && barrowsPrayerDrainTimer == null) { startPrayerDrainTimer(); } @@ -233,6 +227,7 @@ public class BarrowsPlugin extends Plugin { if (config.showPrayerDrainTimer()) { + assert barrowsPrayerDrainTimer == null; final LoopTimer loopTimer = new LoopTimer( PRAYER_DRAIN_INTERVAL_MS, ChronoUnit.MILLIS, From 15a8c6f3ab3221a054f0b943a4466bf345e0b048 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 7 Mar 2021 12:07:30 -0500 Subject: [PATCH 09/22] http service: remove unused sprite endpoint --- .../http/service/sprite/SpriteController.java | 73 ----------- .../http/service/sprite/SpriteService.java | 117 ------------------ 2 files changed, 190 deletions(-) delete mode 100644 http-service/src/main/java/net/runelite/http/service/sprite/SpriteController.java delete mode 100644 http-service/src/main/java/net/runelite/http/service/sprite/SpriteService.java diff --git a/http-service/src/main/java/net/runelite/http/service/sprite/SpriteController.java b/http-service/src/main/java/net/runelite/http/service/sprite/SpriteController.java deleted file mode 100644 index 80d8f738ba..0000000000 --- a/http-service/src/main/java/net/runelite/http/service/sprite/SpriteController.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2018, Adam - * 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.http.service.sprite; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import java.io.IOException; -import java.util.concurrent.TimeUnit; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/sprite") -public class SpriteController -{ - @Autowired - private SpriteService spriteService; - - private final LoadingCache spriteCache = CacheBuilder.newBuilder() - .maximumSize(1024L) - .expireAfterWrite(10, TimeUnit.MINUTES) - .build(new CacheLoader() - { - @Override - public byte[] load(Integer key) throws Exception - { - byte[] data = spriteService.getImagePng(key >>> 16, key & 0xffff); - return data != null ? data : new byte[0]; - } - }); - - @GetMapping(produces = "image/png") - public ResponseEntity getSprite( - @RequestParam int spriteId, - @RequestParam(defaultValue = "0") int frameId - ) throws IOException - { - byte[] data = spriteCache.getUnchecked(spriteId << 16 | frameId); - if (data == null || data.length == 0) - { - return ResponseEntity.notFound().build(); - } - - return ResponseEntity.ok(data); - } -} diff --git a/http-service/src/main/java/net/runelite/http/service/sprite/SpriteService.java b/http-service/src/main/java/net/runelite/http/service/sprite/SpriteService.java deleted file mode 100644 index 3a3fa015aa..0000000000 --- a/http-service/src/main/java/net/runelite/http/service/sprite/SpriteService.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2018, Adam - * 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.http.service.sprite; - -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import javax.imageio.ImageIO; -import net.runelite.cache.IndexType; -import net.runelite.cache.definitions.SpriteDefinition; -import net.runelite.cache.definitions.loaders.SpriteLoader; -import net.runelite.cache.fs.ArchiveFiles; -import net.runelite.cache.fs.FSFile; -import net.runelite.http.service.cache.CacheService; -import net.runelite.http.service.cache.beans.ArchiveEntry; -import net.runelite.http.service.cache.beans.CacheEntry; -import net.runelite.http.service.cache.beans.IndexEntry; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class SpriteService -{ - @Autowired - private CacheService cacheService; - - public SpriteDefinition getSprite(int spriteId, int frameId) throws IOException - { - CacheEntry cache = cacheService.findMostRecent(); - if (cache == null) - { - return null; - } - - IndexEntry index = cacheService.findIndexForCache(cache, IndexType.SPRITES.getNumber()); - if (index == null) - { - return null; - } - - ArchiveEntry archive = cacheService.findArchiveForIndex(index, spriteId); - if (archive == null) - { - return null; - } - - ArchiveFiles files = cacheService.getArchiveFiles(archive); - if (files == null) - { - return null; - } - - FSFile file = files.getFiles().get(0); - byte[] contents = file.getContents(); - SpriteDefinition[] sprite = new SpriteLoader().load(archive.getArchiveId(), contents); - if (frameId < 0 || frameId >= sprite.length) - { - return null; - } - - return sprite[frameId]; - } - - public BufferedImage getImage(int spriteId, int frameId) throws IOException - { - SpriteDefinition sprite = getSprite(spriteId, frameId); - if (sprite == null) - { - return null; - } - - BufferedImage bufferedImage = getSpriteImage(sprite); - return bufferedImage; - } - - public byte[] getImagePng(int spriteId, int frameId) throws IOException - { - BufferedImage image = getImage(spriteId, frameId); - if (image == null) - { - return null; - } - - ByteArrayOutputStream bao = new ByteArrayOutputStream(); - ImageIO.write(image, "png", bao); - return bao.toByteArray(); - } - - private BufferedImage getSpriteImage(SpriteDefinition sprite) - { - BufferedImage image = new BufferedImage(sprite.getWidth(), sprite.getHeight(), BufferedImage.TYPE_INT_ARGB); - image.setRGB(0, 0, sprite.getWidth(), sprite.getHeight(), sprite.getPixels(), 0, sprite.getWidth()); - return image; - } -} From 12a84f4c616f2c268e1b70cbef454ac527408671 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 7 Mar 2021 12:12:53 -0500 Subject: [PATCH 10/22] http service: remove unused cache item image endpoint --- .../http/service/cache/CacheController.java | 109 ------------------ 1 file changed, 109 deletions(-) diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheController.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheController.java index f86f453412..6ba8290bcf 100644 --- a/http-service/src/main/java/net/runelite/http/service/cache/CacheController.java +++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheController.java @@ -24,35 +24,20 @@ */ package net.runelite.http.service.cache; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; import java.util.stream.Collectors; -import javax.imageio.ImageIO; import lombok.extern.slf4j.Slf4j; import net.runelite.cache.ConfigType; import net.runelite.cache.IndexType; import net.runelite.cache.definitions.ItemDefinition; -import net.runelite.cache.definitions.ModelDefinition; import net.runelite.cache.definitions.NpcDefinition; import net.runelite.cache.definitions.ObjectDefinition; -import net.runelite.cache.definitions.SpriteDefinition; -import net.runelite.cache.definitions.TextureDefinition; import net.runelite.cache.definitions.loaders.ItemLoader; -import net.runelite.cache.definitions.loaders.ModelLoader; import net.runelite.cache.definitions.loaders.NpcLoader; import net.runelite.cache.definitions.loaders.ObjectLoader; -import net.runelite.cache.definitions.loaders.SpriteLoader; -import net.runelite.cache.definitions.loaders.TextureLoader; -import net.runelite.cache.definitions.providers.ItemProvider; -import net.runelite.cache.definitions.providers.ModelProvider; -import net.runelite.cache.definitions.providers.SpriteProvider; -import net.runelite.cache.definitions.providers.TextureProvider; import net.runelite.cache.fs.ArchiveFiles; -import net.runelite.cache.fs.Container; import net.runelite.cache.fs.FSFile; -import net.runelite.cache.item.ItemSpriteFactory; import net.runelite.http.api.cache.Cache; import net.runelite.http.api.cache.CacheArchive; import net.runelite.http.api.cache.CacheIndex; @@ -61,11 +46,9 @@ import net.runelite.http.service.cache.beans.CacheEntry; import net.runelite.http.service.cache.beans.IndexEntry; import net.runelite.http.service.util.exception.NotFoundException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -222,98 +205,6 @@ public class CacheController return itemdef; } - @GetMapping(path = "item/{itemId}/image", produces = "image/png") - public ResponseEntity getItemImage( - @PathVariable int itemId, - @RequestParam(defaultValue = "1") int quantity, - @RequestParam(defaultValue = "1") int border, - @RequestParam(defaultValue = "3153952") int shadowColor - ) throws IOException - { - final CacheEntry cache = cacheService.findMostRecent(); - ItemProvider itemProvider = new ItemProvider() - { - @Override - public ItemDefinition provide(int itemId) - { - try - { - return getItem(itemId); - } - catch (IOException ex) - { - log.warn(null, ex); - return null; - } - } - }; - ModelProvider modelProvider = new ModelProvider() - { - @Override - public ModelDefinition provide(int modelId) throws IOException - { - IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.MODELS.getNumber()); - ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, modelId); - byte[] archiveData = Container.decompress(cacheService.getArchive(archiveEntry), null).data; - return new ModelLoader().load(modelId, archiveData); - } - }; - SpriteProvider spriteProvider = new SpriteProvider() - { - @Override - public SpriteDefinition provide(int spriteId, int frameId) - { - try - { - IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.SPRITES.getNumber()); - ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, spriteId); - byte[] archiveData = Container.decompress(cacheService.getArchive(archiveEntry), null).data; - SpriteDefinition[] defs = new SpriteLoader().load(spriteId, archiveData); - return defs[frameId]; - } - catch (Exception ex) - { - log.warn(null, ex); - return null; - } - } - }; - - TextureProvider textureProvider2 = new TextureProvider() - { - @Override - public TextureDefinition[] provide() - { - try - { - IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.TEXTURES.getNumber()); - ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, 0); - ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry); - TextureLoader loader = new TextureLoader(); - TextureDefinition[] defs = new TextureDefinition[archiveFiles.getFiles().size()]; - int i = 0; - for (FSFile file : archiveFiles.getFiles()) - { - TextureDefinition def = loader.load(file.getFileId(), file.getContents()); - defs[i++] = def; - } - return defs; - } - catch (Exception ex) - { - log.warn(null, ex); - return null; - } - } - }; - - BufferedImage itemImage = ItemSpriteFactory.createSprite(itemProvider, modelProvider, spriteProvider, textureProvider2, - itemId, quantity, border, shadowColor, false); - ByteArrayOutputStream bao = new ByteArrayOutputStream(); - ImageIO.write(itemImage, "png", bao); - return ResponseEntity.ok(bao.toByteArray()); - } - @GetMapping("object/{objectId}") public ObjectDefinition getObject( @PathVariable int objectId From b3b7491680277c4b2a5e5e82c5082b2f776b4e47 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 7 Mar 2021 23:20:09 -0500 Subject: [PATCH 11/22] http service: remove unused price endpoints Also remove time from ItemPrice, which is no longer needed now that the endpoint for fetching historic price data is gone --- .../net/runelite/http/api/item/ItemPrice.java | 2 - .../http/service/item/ItemController.java | 75 ------------------- .../http/service/item/ItemService.java | 56 -------------- 3 files changed, 133 deletions(-) diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java b/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java index 001c63deb8..26896c5351 100644 --- a/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java @@ -24,7 +24,6 @@ */ package net.runelite.http.api.item; -import java.time.Instant; import lombok.Data; @Data @@ -33,5 +32,4 @@ public class ItemPrice private int id; private String name; private int price; - private Instant time; } diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java index 331f01db69..bb1faa3d70 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java @@ -29,26 +29,19 @@ import com.google.common.base.Suppliers; import com.google.common.hash.HashCode; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; -import java.time.Instant; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.TimeUnit; import net.runelite.http.api.item.ItemPrice; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.CacheControl; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/item") public class ItemController { - private static final int MAX_BATCH_LOOKUP = 1024; - private static class MemoizedPrices { final ItemPrice[] prices; @@ -84,79 +77,11 @@ public class ItemController itemPrice.setId(priceEntry.getItem()); itemPrice.setName(priceEntry.getName()); itemPrice.setPrice(priceEntry.getPrice()); - itemPrice.setTime(priceEntry.getTime()); return itemPrice; }) .toArray(ItemPrice[]::new)), 30, TimeUnit.MINUTES); } - @GetMapping("/{itemId}/price") - public ResponseEntity itemPrice( - @PathVariable int itemId, - @RequestParam(required = false) Instant time - ) - { - Instant now = Instant.now(); - - if (time != null && time.isAfter(now)) - { - time = now; - } - - PriceEntry priceEntry = itemService.getPrice(itemId, time); - - if (time != null) - { - if (priceEntry == null) - { - // we maybe can't backfill this - return ResponseEntity.notFound() - .cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic()) - .build(); - } - } - else if (priceEntry == null) - { - // Price is unknown - return ResponseEntity.notFound() - .cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic()) - .build(); - } - - ItemPrice itemPrice = new ItemPrice(); - itemPrice.setId(itemId); - itemPrice.setName(priceEntry.getName()); - itemPrice.setPrice(priceEntry.getPrice()); - itemPrice.setTime(priceEntry.getTime()); - - return ResponseEntity.ok() - .cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic()) - .body(itemPrice); - } - - @GetMapping("/price") - public ItemPrice[] prices(@RequestParam("id") int[] itemIds) - { - if (itemIds.length > MAX_BATCH_LOOKUP) - { - itemIds = Arrays.copyOf(itemIds, MAX_BATCH_LOOKUP); - } - - List prices = itemService.getPrices(itemIds); - - return prices.stream() - .map(priceEntry -> - { - ItemPrice itemPrice = new ItemPrice(); - itemPrice.setId(priceEntry.getItem()); - itemPrice.setName(priceEntry.getName()); - itemPrice.setPrice(priceEntry.getPrice()); - itemPrice.setTime(priceEntry.getTime()); - return itemPrice; - }) - .toArray(ItemPrice[]::new); - } - @GetMapping("/prices") public ResponseEntity prices() { diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java index bf40d8ec96..886a08d265 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java @@ -31,11 +31,9 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; -import java.util.Set; import lombok.extern.slf4j.Slf4j; import net.runelite.cache.definitions.ItemDefinition; import net.runelite.http.api.RuneLiteAPI; @@ -111,60 +109,6 @@ public class ItemService } } - private PriceEntry getPrice(Connection con, int itemId, Instant time) - { - if (time != null) - { - return con.createQuery("select item, name, price, time, fetched_time from prices t1 join items t2 on t1.item=t2.id where item = :item and time <= :time order by time desc limit 1") - .addParameter("item", itemId) - .addParameter("time", time.toString()) - .executeAndFetchFirst(PriceEntry.class); - } - else - { - return con.createQuery("select item, name, price, time, fetched_time from prices t1 join items t2 on t1.item=t2.id where item = :item order by time desc limit 1") - .addParameter("item", itemId) - .executeAndFetchFirst(PriceEntry.class); - } - } - - public PriceEntry getPrice(int itemId, Instant time) - { - try (Connection con = sql2o.open()) - { - return getPrice(con, itemId, time); - } - } - - public List getPrices(int... itemIds) - { - try (Connection con = sql2o.open()) - { - Set seen = new HashSet<>(); - List priceEntries = new ArrayList<>(itemIds.length); - - for (int itemId : itemIds) - { - if (seen.contains(itemId)) - { - continue; - } - seen.add(itemId); - - PriceEntry priceEntry = getPrice(con, itemId, null); - - if (priceEntry == null) - { - continue; - } - - priceEntries.add(priceEntry); - } - - return priceEntries; - } - } - public ItemEntry fetchItem(int itemId) { try From 5ba3e510dcecdf7b220f904807db72606c385b37 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 7 Mar 2021 23:24:16 -0500 Subject: [PATCH 12/22] item service: correct scheduled delay for reloading tradable items --- .../main/java/net/runelite/http/service/item/ItemService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java index 886a08d265..50520401ee 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java @@ -268,7 +268,7 @@ public class ItemService fetchPrice(id); } - @Scheduled(fixedDelay = 1_8000_000) // 30 minutes + @Scheduled(fixedDelay = 1_800_000) // 30 minutes public void reloadItems() throws IOException { List items = cacheService.getItems(); From aa46b117311f3625c455ca51756bba01d7d7d288 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 7 Mar 2021 23:26:23 -0500 Subject: [PATCH 13/22] cache updater: fix race with saving archives and computing archive hashes The tasks submitted to the executor compute and set the archive hashes, but the hashes are required by CacheStorage to save them. Ensure the tasks are complete first before saving. --- .../main/java/net/runelite/cache/updater/CacheUpdater.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cache-updater/src/main/java/net/runelite/cache/updater/CacheUpdater.java b/cache-updater/src/main/java/net/runelite/cache/updater/CacheUpdater.java index 1a8f81c5ec..817ba7fbe2 100644 --- a/cache-updater/src/main/java/net/runelite/cache/updater/CacheUpdater.java +++ b/cache-updater/src/main/java/net/runelite/cache/updater/CacheUpdater.java @@ -120,7 +120,6 @@ public class CacheUpdater implements CommandLineRunner CacheEntry newCache = created ? cache : cacheDao.createCache(con, rsVersion, Instant.now()); storage.setCacheEntry(newCache); - store.save(); // ensure objects are added to the store before they become // visible in the database @@ -130,6 +129,10 @@ public class CacheUpdater implements CommandLineRunner logger.debug("Waiting for termination of executor..."); } + // CacheStorage requires archive hashes to be set, which is set in the executor tasks, so it must be + // run after shutdown + store.save(); + // commit database con.commit(); } From 1bdfe4b0fd129d3dfc99e5e6b54f619d9100fcca Mon Sep 17 00:00:00 2001 From: James N <29741471+jn011@users.noreply.github.com> Date: Tue, 9 Mar 2021 18:40:37 +1000 Subject: [PATCH 14/22] FaloTheBardClue: Accept trimmed Dragon defender and Tzhaar-ket-om (#13317) --- .../client/plugins/cluescrolls/clues/FaloTheBardClue.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java index c208d5238f..2135bbbe52 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java @@ -58,13 +58,12 @@ public class FaloTheBardClue extends ClueScroll implements TextClueScroll, NpcCl new FaloTheBardClue("A mark used to increase one's grace, found atop a seer's place.", item(MARK_OF_GRACE)), new FaloTheBardClue("A molten beast with fiery breath, you acquire these with its death.", item(LAVA_DRAGON_BONES)), new FaloTheBardClue("A shiny helmet of flight, to obtain this with melee, struggle you might.", item(ARMADYL_HELMET)), - // The wiki doesn't specify whether the trimmed dragon defender will work so I've assumed that it doesn't - new FaloTheBardClue("A sword held in the other hand, red its colour, Cyclops strength you must withstand.", any("Dragon or Avernic Defender", item(DRAGON_DEFENDER), item(DRAGON_DEFENDER_L), item(AVERNIC_DEFENDER), item(AVERNIC_DEFENDER_L))), + new FaloTheBardClue("A sword held in the other hand, red its colour, Cyclops strength you must withstand.", any("Dragon or Avernic Defender", item(DRAGON_DEFENDER), item(DRAGON_DEFENDER_T), item(DRAGON_DEFENDER_L), item(AVERNIC_DEFENDER), item(AVERNIC_DEFENDER_L))), new FaloTheBardClue("A token used to kill mythical beasts, in hopes of a blade or just for an xp feast.", item(WARRIOR_GUILD_TOKEN)), new FaloTheBardClue("Green is my favourite, mature ale I do love, this takes your herblore above.", item(GREENMANS_ALEM)), new FaloTheBardClue("It can hold down a boat or crush a goat, this object, you see, is quite heavy.", item(BARRELCHEST_ANCHOR)), new FaloTheBardClue("It comes from the ground, underneath the snowy plain. Trolls aplenty, with what looks like a mane.", item(BASALT)), - new FaloTheBardClue("No attack to wield, only strength is required, made of obsidian, but with no room for a shield.", item(TZHAARKETOM)), + new FaloTheBardClue("No attack to wield, only strength is required, made of obsidian, but with no room for a shield.", any("Tzhaar-ket-om", item(TZHAARKETOM), item(TZHAARKETOM_T))), new FaloTheBardClue("Penance healers runners and more, obtaining this body often gives much deplore.", any("Fighter Torso", item(FIGHTER_TORSO), item(FIGHTER_TORSO_L))), new FaloTheBardClue("Strangely found in a chest, many believe these gloves are the best.", item(BARROWS_GLOVES)), new FaloTheBardClue("These gloves of white won't help you fight, but aid in cooking, they just might.", item(COOKING_GAUNTLETS)), From ded38ec99e6e73ed49483fbfa8a4817d6e1e8638 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 9 Mar 2021 14:09:54 -0500 Subject: [PATCH 15/22] http service: add wiki price service --- .../runelite/http/service/wiki/ItemPrice.java | 37 +++++ .../http/service/wiki/WikiPriceService.java | 141 ++++++++++++++++++ .../src/main/resources/application.yaml | 5 +- 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 http-service/src/main/java/net/runelite/http/service/wiki/ItemPrice.java create mode 100644 http-service/src/main/java/net/runelite/http/service/wiki/WikiPriceService.java diff --git a/http-service/src/main/java/net/runelite/http/service/wiki/ItemPrice.java b/http-service/src/main/java/net/runelite/http/service/wiki/ItemPrice.java new file mode 100644 index 0000000000..0b0d941d9c --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/wiki/ItemPrice.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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 HOLDER 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.http.service.wiki; + +import lombok.Data; + +@Data +class ItemPrice +{ + private int high; + private int highTime; + private int low; + private int lowTime; +} diff --git a/http-service/src/main/java/net/runelite/http/service/wiki/WikiPriceService.java b/http-service/src/main/java/net/runelite/http/service/wiki/WikiPriceService.java new file mode 100644 index 0000000000..d822f79491 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/wiki/WikiPriceService.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2021, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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 HOLDER 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.http.service.wiki; + +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Query; +import org.sql2o.Sql2o; + +@Service +@Slf4j +public class WikiPriceService +{ + private static final String CREATE = "CREATE TABLE IF NOT EXISTS `wiki_prices` (\n" + + " `item_id` int(11) NOT NULL,\n" + + " `high` int(11) NOT NULL,\n" + + " `highTime` int(11) NOT NULL,\n" + + " `low` int(11) NOT NULL,\n" + + " `lowTime` int(11) NOT NULL,\n" + + " `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n" + + " PRIMARY KEY (`item_id`)\n" + + ") ENGINE=InnoDB;"; + + private final Sql2o sql2o; + + @Value("${runelite.wiki.url}") + private String url; + + @Autowired + public WikiPriceService(@Qualifier("Runelite SQL2O") Sql2o sql2o) + { + this.sql2o = sql2o; + + try (Connection con = sql2o.open()) + { + con.createQuery(CREATE).executeUpdate(); + } + } + + @Scheduled(initialDelay = 1000 * 5, fixedDelayString = "${runelite.wiki.poll.ms}") + private void updateDatabase() + { + try + { + Map summary = getPrices(); + + try (Connection con = sql2o.beginTransaction()) + { + Query query = con.createQuery("INSERT INTO wiki_prices (item_id, high, highTime, low, lowTime)" + + " VALUES (:itemId, :high, :highTime, :low, :lowTime)" + + " ON DUPLICATE KEY UPDATE high = VALUES(high), highTime = VALUES(highTime)," + + " low = VALUES(low), lowTime = VALUES(lowTime)"); + + for (Map.Entry entry : summary.entrySet()) + { + Integer itemId = entry.getKey(); + ItemPrice item = entry.getValue(); + + query + .addParameter("itemId", itemId) + .addParameter("high", item.getHigh()) + .addParameter("highTime", item.getHighTime()) + .addParameter("low", item.getLow()) + .addParameter("lowTime", item.getLowTime()) + .addToBatch(); + } + + query.executeBatch(); + con.commit(false); + } + } + catch (IOException e) + { + log.warn("Error while updating wiki prices", e); + } + } + + private Map getPrices() throws IOException + { + HttpUrl httpUrl = HttpUrl.parse(url); + Request request = new Request.Builder() + .url(httpUrl) + .header("User-Agent", "RuneLite") + .build(); + + try (Response responseOk = RuneLiteAPI.CLIENT.newCall(request).execute()) + { + if (!responseOk.isSuccessful()) + { + throw new IOException("Error retrieving prices: " + responseOk.message()); + } + + Type type = new TypeToken>() + { + }.getType(); + + return RuneLiteAPI.GSON.fromJson(responseOk.body().string(), type); + } + catch (JsonSyntaxException ex) + { + throw new IOException(ex); + } + } +} diff --git a/http-service/src/main/resources/application.yaml b/http-service/src/main/resources/application.yaml index 3e56fb066d..22dc39273a 100644 --- a/http-service/src/main/resources/application.yaml +++ b/http-service/src/main/resources/application.yaml @@ -43,4 +43,7 @@ runelite: ge: history: 90 # days loottracker: - history: 90 # days \ No newline at end of file + history: 90 # days + wiki: + poll.ms: 300000 # 5 minutes + url: https://prices.runescape.wiki/osrs/latest From 12844291302ce865d6b08f2cb46dcdb464eb7ae5 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 9 Mar 2021 14:15:00 -0500 Subject: [PATCH 16/22] item controller: add config for price cache time --- .../runelite/http/service/item/ItemController.java | 12 +++++++++--- http-service/src/main/resources/application.yaml | 2 ++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java index bb1faa3d70..a4906e3ffd 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java @@ -32,6 +32,7 @@ import com.google.common.hash.Hashing; import java.util.concurrent.TimeUnit; import net.runelite.http.api.item.ItemPrice; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.CacheControl; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -62,13 +63,18 @@ public class ItemController } private final ItemService itemService; + private final int priceCache; private final Supplier memoizedPrices; @Autowired - public ItemController(ItemService itemService) + public ItemController( + ItemService itemService, + @Value("${runelite.price.cache}") int priceCache + ) { this.itemService = itemService; + this.priceCache = priceCache; memoizedPrices = Suppliers.memoizeWithExpiration(() -> new MemoizedPrices(itemService.fetchPrices().stream() .map(priceEntry -> @@ -79,7 +85,7 @@ public class ItemController itemPrice.setPrice(priceEntry.getPrice()); return itemPrice; }) - .toArray(ItemPrice[]::new)), 30, TimeUnit.MINUTES); + .toArray(ItemPrice[]::new)), priceCache, TimeUnit.MINUTES); } @GetMapping("/prices") @@ -88,7 +94,7 @@ public class ItemController MemoizedPrices memorizedPrices = this.memoizedPrices.get(); return ResponseEntity.ok() .eTag(memorizedPrices.hash) - .cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic()) + .cacheControl(CacheControl.maxAge(priceCache, TimeUnit.MINUTES).cachePublic()) .body(memorizedPrices.prices); } } diff --git a/http-service/src/main/resources/application.yaml b/http-service/src/main/resources/application.yaml index 22dc39273a..93aba72ffc 100644 --- a/http-service/src/main/resources/application.yaml +++ b/http-service/src/main/resources/application.yaml @@ -47,3 +47,5 @@ runelite: wiki: poll.ms: 300000 # 5 minutes url: https://prices.runescape.wiki/osrs/latest + price: + cache: 30 # minutes From 48352f11c9b81ab2f941533a16f2274819bb0448 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 9 Mar 2021 14:17:21 -0500 Subject: [PATCH 17/22] item service: include wiki prices in price data --- .../net/runelite/http/api/item/ItemPrice.java | 1 + .../http/service/item/ItemController.java | 15 +++++++++++- .../http/service/item/ItemService.java | 23 ++++++------------- .../http/service/item/PriceEntry.java | 2 ++ 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java b/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java index 26896c5351..faf5bfc3fb 100644 --- a/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemPrice.java @@ -32,4 +32,5 @@ public class ItemPrice private int id; private String name; private int price; + private int wikiPrice; } diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java index a4906e3ffd..e546344d4c 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java @@ -24,12 +24,12 @@ */ package net.runelite.http.service.item; -import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.hash.HashCode; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import net.runelite.http.api.item.ItemPrice; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -83,11 +83,24 @@ public class ItemController itemPrice.setId(priceEntry.getItem()); itemPrice.setName(priceEntry.getName()); itemPrice.setPrice(priceEntry.getPrice()); + itemPrice.setWikiPrice(computeWikiPrice(priceEntry)); return itemPrice; }) .toArray(ItemPrice[]::new)), priceCache, TimeUnit.MINUTES); } + private static int computeWikiPrice(PriceEntry priceEntry) + { + if (priceEntry.getLow() > 0 && priceEntry.getHigh() > 0) + { + return (priceEntry.getLow() + priceEntry.getHigh()) / 2; + } + else + { + return Math.max(priceEntry.getLow(), priceEntry.getHigh()); + } + } + @GetMapping("/prices") public ResponseEntity prices() { diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java index 50520401ee..d25621260e 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java @@ -30,7 +30,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Random; @@ -141,7 +140,7 @@ public class ItemService } } - private List fetchPrice(int itemId) + private void fetchPrice(int itemId) { RSPrices rsprice; try @@ -151,12 +150,11 @@ public class ItemService catch (IOException ex) { log.warn("unable to fetch price for item {}", itemId, ex); - return null; + return; } try (Connection con = sql2o.beginTransaction()) { - List entries = new ArrayList<>(); Instant now = Instant.now(); Query query = con.createQuery("insert into prices (item, price, time, fetched_time) values (:item, :price, :time, :fetched_time) " @@ -169,13 +167,6 @@ public class ItemService Instant time = Instant.ofEpochMilli(ts); - PriceEntry priceEntry = new PriceEntry(); - priceEntry.setItem(itemId); - priceEntry.setPrice(price); - priceEntry.setTime(time); - priceEntry.setFetched_time(now); - entries.add(priceEntry); - query .addParameter("item", itemId) .addParameter("price", price) @@ -186,8 +177,6 @@ public class ItemService query.executeBatch(); con.commit(false); - - return entries; } } @@ -195,9 +184,11 @@ public class ItemService { try (Connection con = sql2o.beginTransaction()) { - Query query = con.createQuery("select t2.item, t3.name, t2.time, prices.price, prices.fetched_time from (select t1.item as item, max(t1.time) as time from prices t1 group by item) t2 " + - " join prices on t2.item=prices.item and t2.time=prices.time" + - " join items t3 on t2.item=t3.id"); + Query query = con.createQuery("select t2.item, t3.name, t2.time, prices.price, prices.fetched_time, t4.high, t4.low" + + " from (select t1.item as item, max(t1.time) as time from prices t1 group by item) t2" + + " join prices on t2.item=prices.item and t2.time=prices.time" + + " join items t3 on t2.item=t3.id" + + " join wiki_prices t4 on t2.item=t4.item_id"); return query.executeAndFetch(PriceEntry.class); } } diff --git a/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java b/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java index 00f0e6815c..4d29d7e98d 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java +++ b/http-service/src/main/java/net/runelite/http/service/item/PriceEntry.java @@ -35,4 +35,6 @@ class PriceEntry private int price; private Instant time; private Instant fetched_time; + private int high; + private int low; } From 9e20540214e365139a509aae736ad11c66975295 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 9 Mar 2021 14:19:49 -0500 Subject: [PATCH 18/22] client: add option to use wiki traded prices --- .../client/config/RuneLiteConfig.java | 33 ++++++++++++------- .../net/runelite/client/game/ItemManager.java | 20 +++++------ 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java b/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java index 36b3084946..a3dac72172 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java @@ -381,17 +381,6 @@ public interface RuneLiteConfig extends Config return ComponentConstants.STANDARD_BACKGROUND_COLOR; } - @ConfigItem( - keyName = "blockExtraMouseButtons", - name = "Block Extra Mouse Buttons", - description = "Blocks extra mouse buttons (4 and above)", - position = 44 - ) - default boolean blockExtraMouseButtons() - { - return true; - } - @ConfigItem( keyName = "sidebarToggleKey", name = "Sidebar Toggle Key", @@ -415,4 +404,26 @@ public interface RuneLiteConfig extends Config { return new Keybind(KeyEvent.VK_F12, InputEvent.CTRL_DOWN_MASK); } + + @ConfigItem( + keyName = "blockExtraMouseButtons", + name = "Block extra mouse buttons", + description = "Blocks extra mouse buttons (4 and above)", + position = 50 + ) + default boolean blockExtraMouseButtons() + { + return true; + } + + @ConfigItem( + keyName = "useWikiItemPrices", + name = "Use actively traded price", + description = "Use actively traded prices, sourced from the RuneScape wiki, for item prices", + position = 51 + ) + default boolean useWikiItemPrices() + { + return true; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java index d524a9a5e3..fd1bb4e268 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java @@ -55,6 +55,7 @@ import net.runelite.api.SpritePixels; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.PostItemComposition; import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.util.AsyncBufferedImage; @@ -86,6 +87,7 @@ public class ItemManager private final Client client; private final ClientThread clientThread; private final ItemClient itemClient; + private final RuneLiteConfig runeLiteConfig; private Map itemPrices = Collections.emptyMap(); private Map itemStats = Collections.emptyMap(); @@ -170,11 +172,12 @@ public class ItemManager @Inject public ItemManager(Client client, ScheduledExecutorService scheduledExecutorService, ClientThread clientThread, - OkHttpClient okHttpClient, EventBus eventBus) + OkHttpClient okHttpClient, EventBus eventBus, RuneLiteConfig runeLiteConfig) { this.client = client; this.clientThread = clientThread; this.itemClient = new ItemClient(okHttpClient); + this.runeLiteConfig = runeLiteConfig; scheduledExecutorService.scheduleWithFixedDelay(this::loadPrices, 0, 30, TimeUnit.MINUTES); scheduledExecutorService.submit(this::loadStats); @@ -292,17 +295,17 @@ public class ItemManager */ public int getItemPrice(int itemID) { - return getItemPrice(itemID, false); + return getItemPriceWithSource(itemID, runeLiteConfig.useWikiItemPrices()); } /** * Look up an item's price * * @param itemID item id - * @param ignoreUntradeableMap should the price returned ignore items that are not tradeable for coins in regular way + * @param useWikiPrice use the actively traded/wiki price * @return item price */ - public int getItemPrice(int itemID, boolean ignoreUntradeableMap) + public int getItemPriceWithSource(int itemID, boolean useWikiPrice) { if (itemID == COINS_995) { @@ -330,19 +333,14 @@ public class ItemManager if (ip != null) { - price += ip.getPrice(); + price = useWikiPrice && ip.getWikiPrice() > 0 ? ip.getWikiPrice() : ip.getPrice(); } } else { for (final ItemMapping mappedItem : mappedItems) { - if (ignoreUntradeableMap && mappedItem.isUntradeable()) - { - continue; - } - - price += getItemPrice(mappedItem.getTradeableItem(), ignoreUntradeableMap) * mappedItem.getQuantity(); + price += getItemPriceWithSource(mappedItem.getTradeableItem(), useWikiPrice) * mappedItem.getQuantity(); } } From 71595d0b6e150ef00721ee7c3cfee4c711f012c9 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 9 Mar 2021 14:22:12 -0500 Subject: [PATCH 19/22] chat commands: support wiki prices in price command --- .../client/plugins/chatcommands/ChatCommandsPlugin.java | 6 +++++- .../client/plugins/chatcommands/ChatCommandsPluginTest.java | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java index 4d7bac6c1e..e9c2f0c174 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java @@ -65,6 +65,7 @@ import net.runelite.client.chat.ChatCommandManager; import net.runelite.client.chat.ChatMessageBuilder; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.config.ConfigManager; +import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ChatInput; import net.runelite.client.game.ItemManager; @@ -177,6 +178,9 @@ public class ChatCommandsPlugin extends Plugin @Inject private ChatClient chatClient; + @Inject + private RuneLiteConfig runeLiteConfig; + @Override public void startUp() { @@ -996,7 +1000,7 @@ public class ChatCommandsPlugin extends Plugin ItemPrice item = retrieveFromList(results, search); int itemId = item.getId(); - int itemPrice = item.getPrice(); + int itemPrice = runeLiteConfig.useWikiItemPrices() && item.getWikiPrice() > 0 ? item.getWikiPrice() : item.getPrice(); final ChatMessageBuilder builder = new ChatMessageBuilder() .append(ChatColorType.NORMAL) diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/chatcommands/ChatCommandsPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/chatcommands/ChatCommandsPluginTest.java index 969bc8edf6..9b5eaea2a0 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/chatcommands/ChatCommandsPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/chatcommands/ChatCommandsPluginTest.java @@ -49,6 +49,7 @@ import net.runelite.client.chat.ChatCommandManager; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.config.ChatColorConfig; import net.runelite.client.config.ConfigManager; +import net.runelite.client.config.RuneLiteConfig; import net.runelite.http.api.chat.ChatClient; import net.runelite.http.api.hiscore.HiscoreClient; import net.runelite.http.api.hiscore.HiscoreSkill; @@ -106,6 +107,10 @@ public class ChatCommandsPluginTest @Bind ChatClient chatClient; + @Mock + @Bind + RuneLiteConfig runeLiteConfig; + @Mock @Bind ChatCommandsConfig chatCommandsConfig; From d5dda05d556284d7a8c4d5fd106c681b4b6bfe28 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 9 Mar 2021 14:24:04 -0500 Subject: [PATCH 20/22] ge plugin: support wiki prices in search panel --- .../grandexchange/GrandExchangeOffersPanel.java | 2 +- .../plugins/grandexchange/GrandExchangePanel.java | 12 ++++-------- .../grandexchange/GrandExchangeSearchPanel.java | 11 +++++++++-- .../grandexchange/GrandExchangePluginTest.java | 5 ----- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java index a64f4b1474..a9100c52a0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeOffersPanel.java @@ -38,7 +38,7 @@ import net.runelite.api.ItemComposition; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.components.PluginErrorPanel; -public class GrandExchangeOffersPanel extends JPanel +class GrandExchangeOffersPanel extends JPanel { private static final String ERROR_PANEL = "ERROR_PANEL"; private static final String OFFERS_PANEL = "OFFERS_PANEL"; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java index 0b59270de5..a3fc4c70dc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePanel.java @@ -27,13 +27,10 @@ package net.runelite.client.plugins.grandexchange; import java.awt.BorderLayout; -import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; import javax.swing.JPanel; import javax.swing.border.EmptyBorder; import lombok.Getter; -import net.runelite.client.callback.ClientThread; -import net.runelite.client.game.ItemManager; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.PluginPanel; import net.runelite.client.ui.components.materialtabs.MaterialTab; @@ -49,21 +46,20 @@ class GrandExchangePanel extends PluginPanel private final MaterialTab searchTab; @Getter - private GrandExchangeSearchPanel searchPanel; + private final GrandExchangeSearchPanel searchPanel; @Getter private GrandExchangeOffersPanel offersPanel; @Inject - private GrandExchangePanel(ClientThread clientThread, ItemManager itemManager, ScheduledExecutorService executor) + private GrandExchangePanel(GrandExchangeSearchPanel searchPanel) { super(false); + this.searchPanel = searchPanel; + setLayout(new BorderLayout()); setBackground(ColorScheme.DARK_GRAY_COLOR); - // Search Panel - searchPanel = new GrandExchangeSearchPanel(clientThread, itemManager, executor); - //Offers Panel offersPanel = new GrandExchangeOffersPanel(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java index 723d9f697a..942ee98cde 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java @@ -34,12 +34,14 @@ import java.awt.GridBagLayout; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; +import javax.inject.Inject; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import net.runelite.api.ItemComposition; import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.game.ItemManager; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.components.IconTextField; @@ -64,6 +66,7 @@ class GrandExchangeSearchPanel extends JPanel private final ClientThread clientThread; private final ItemManager itemManager; private final ScheduledExecutorService executor; + private final RuneLiteConfig runeLiteConfig; private final IconTextField searchBar = new IconTextField(); @@ -78,11 +81,14 @@ class GrandExchangeSearchPanel extends JPanel private final List itemsList = new ArrayList<>(); - GrandExchangeSearchPanel(ClientThread clientThread, ItemManager itemManager, ScheduledExecutorService executor) + @Inject + private GrandExchangeSearchPanel(ClientThread clientThread, ItemManager itemManager, + ScheduledExecutorService executor, RuneLiteConfig runeLiteConfig) { this.clientThread = clientThread; this.itemManager = itemManager; this.executor = executor; + this.runeLiteConfig = runeLiteConfig; setLayout(new BorderLayout()); setBackground(ColorScheme.DARK_GRAY_COLOR); @@ -192,6 +198,7 @@ class GrandExchangeSearchPanel extends JPanel cardLayout.show(centerPanel, RESULTS_PANEL); int count = 0; + boolean useActivelyTradedPrice = runeLiteConfig.useWikiItemPrices(); for (ItemPrice item : result) { @@ -206,7 +213,7 @@ class GrandExchangeSearchPanel extends JPanel ItemComposition itemComp = itemManager.getItemComposition(itemId); ItemStats itemStats = itemManager.getItemStats(itemId, false); - int itemPrice = item.getPrice(); + int itemPrice = useActivelyTradedPrice && item.getWikiPrice() > 0 ? item.getWikiPrice() : item.getPrice(); int itemLimit = itemStats != null ? itemStats.getGeLimit() : 0; AsyncBufferedImage itemImage = itemManager.getImage(itemId); diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/grandexchange/GrandExchangePluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/grandexchange/GrandExchangePluginTest.java index e627dac19b..8624a9017a 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/grandexchange/GrandExchangePluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/grandexchange/GrandExchangePluginTest.java @@ -32,7 +32,6 @@ import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.GameState; @@ -109,10 +108,6 @@ public class GrandExchangePluginTest @Bind private MouseManager mouseManager; - @Mock - @Bind - private ScheduledExecutorService scheduledExecutorService; - @Mock @Bind private GrandExchangeClient grandExchangeClient; From 5fc1f0260d90246cc590ee0888849e59a91a3a27 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 9 Mar 2021 14:26:38 -0500 Subject: [PATCH 21/22] ge plugin: use wiki prices for actively traded price --- .../grandexchange/GrandExchangeConfig.java | 10 +-- .../grandexchange/GrandExchangePlugin.java | 67 +++---------------- 2 files changed, 14 insertions(+), 63 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java index c7db3ab0ab..ab4e78c9a0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeConfig.java @@ -57,13 +57,13 @@ public interface GrandExchangeConfig extends Config @ConfigItem( position = 3, - keyName = "enableOsbPrices", - name = "Enable OSB actively traded prices", - description = "Shows the OSBuddy actively traded price on the GE buy interface" + keyName = "showActivelyTradedPrice", + name = "Enable actively traded prices", + description = "Shows the actively traded price on the GE buy interface, sourced from the RuneScape wiki" ) - default boolean enableOsbPrices() + default boolean showActivelyTradedPrice() { - return false; + return true; } @ConfigItem( diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java index f3d9b513b3..0517769726 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -36,7 +36,6 @@ import com.google.gson.Gson; import com.google.inject.Provides; import java.awt.Color; import java.awt.image.BufferedImage; -import java.io.IOException; import java.net.NetworkInterface; import java.net.SocketException; import java.time.Duration; @@ -48,7 +47,6 @@ import java.util.EnumSet; import java.util.Enumeration; import java.util.List; import java.util.Locale; -import java.util.concurrent.ScheduledExecutorService; import java.util.function.ToIntFunction; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -105,7 +103,6 @@ import net.runelite.http.api.ge.GrandExchangeClient; import net.runelite.http.api.ge.GrandExchangeTrade; import net.runelite.http.api.item.ItemStats; import net.runelite.http.api.osbuddy.OSBGrandExchangeClient; -import net.runelite.http.api.osbuddy.OSBGrandExchangeResult; import net.runelite.http.api.worlds.WorldType; import okhttp3.OkHttpClient; import org.apache.commons.lang3.time.DurationFormatUtils; @@ -124,7 +121,6 @@ public class GrandExchangePlugin extends Plugin private static final int GE_LOGIN_BURST_WINDOW = 2; // ticks private static final int OFFER_CONTAINER_ITEM = 21; private static final int OFFER_DEFAULT_ITEM_ID = 6512; - private static final String OSB_GE_TEXT = "
OSBuddy Actively traded price: "; private static final String BUY_LIMIT_GE_TEXT = "
Buy limit: "; private static final String BUY_LIMIT_KEY = "buylimit"; @@ -173,9 +169,6 @@ public class GrandExchangePlugin extends Plugin @Inject private Notifier notifier; - @Inject - private ScheduledExecutorService executorService; - @Inject private SessionManager sessionManager; @@ -189,16 +182,10 @@ public class GrandExchangePlugin extends Plugin private Widget grandExchangeItem; private String grandExchangeExamine; - private int osbItem; - private OSBGrandExchangeResult osbGrandExchangeResult; - @Inject private GrandExchangeClient grandExchangeClient; private int lastLoginTick; - @Inject - private OSBGrandExchangeClient osbGrandExchangeClient; - private boolean wasFuzzySearch; private String machineUuid; @@ -318,9 +305,6 @@ public class GrandExchangePlugin extends Plugin grandExchangeClient.setUuid(null); } - osbItem = -1; - osbGrandExchangeResult = null; - lastLoginTick = -1; } @@ -880,50 +864,17 @@ public class GrandExchangePlugin extends Plugin } } + if (config.showActivelyTradedPrice()) + { + final int price = itemManager.getItemPriceWithSource(itemId, true); + if (price > 0) + { + text += "
Actively traded price: " + QuantityFormatter.formatNumber(price); + } + } + grandExchangeExamine = text; geText.setText(text); - - if (!config.enableOsbPrices()) - { - return; - } - - // If we already have the result, use it - if (osbGrandExchangeResult != null && osbGrandExchangeResult.getItem_id() == itemId && osbGrandExchangeResult.getOverall_average() > 0) - { - grandExchangeExamine = text + OSB_GE_TEXT + QuantityFormatter.formatNumber(osbGrandExchangeResult.getOverall_average()); - geText.setText(grandExchangeExamine); - } - - if (osbItem == itemId) - { - // avoid starting duplicate lookups - return; - } - - osbItem = itemId; - - log.debug("Looking up OSB item price {}", itemId); - - final String start = text; - executorService.submit(() -> - { - try - { - final OSBGrandExchangeResult result = osbGrandExchangeClient.lookupItem(itemId); - if (result != null && result.getOverall_average() > 0) - { - osbGrandExchangeResult = result; - // Update the text on the widget too - grandExchangeExamine = start + OSB_GE_TEXT + QuantityFormatter.formatNumber(result.getOverall_average()); - geText.setText(grandExchangeExamine); - } - } - catch (IOException e) - { - log.debug("Error getting price of item {}", itemId, e); - } - }); } static void openGeLink(String name, int itemId) From 36ec314ab7d592c5b0f59183d7a5ee72bbf1fe35 Mon Sep 17 00:00:00 2001 From: Jordan Atwood Date: Tue, 9 Mar 2021 21:30:58 -0800 Subject: [PATCH 22/22] Revert "opponentinfo: Show health bar of actors attacking the player" This reverts commit 48ea01dd28fbd6e132faacc0f9d5587fb0d7db2d. --- .../opponentinfo/OpponentInfoPlugin.java | 46 ++-- .../opponentinfo/OpponentInfoPluginTest.java | 197 +----------------- 2 files changed, 24 insertions(+), 219 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java index b3f0b145ed..65fcf0af05 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPlugin.java @@ -1,7 +1,6 @@ /* * Copyright (c) 2016-2018, Adam * Copyright (c) 2018, Jordan Atwood - * Copyright (c) 2021, Andre Araya * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -138,50 +137,33 @@ public class OpponentInfoPlugin extends Plugin @Subscribe public void onInteractingChanged(InteractingChanged event) { - final Actor player = client.getLocalPlayer(); - if (player == null) + if (event.getSource() != client.getLocalPlayer()) { return; } - final Actor opponent = player.getInteracting(); - final Actor source = event.getSource(); - final Actor target = event.getTarget(); - if (source == player) - { - // You have attacked an enemy - if (target != null) - { - lastOpponent = target; - lastTime = null; - } - // You have stopped attacking an enemy which is not attacking you - else if (lastOpponent != null && lastOpponent.getInteracting() != player) - { - lastTime = Instant.now(); - } - } - // You are attacked while not attacking anything - else if (target == player && opponent == null) - { - lastOpponent = source; - lastTime = null; - } - // An enemy which was previously attacking you (which you were not attacking back) has changed its target - else if (source == lastOpponent && opponent != lastOpponent) + Actor opponent = event.getTarget(); + + if (opponent == null) { lastTime = Instant.now(); + return; } + + lastOpponent = opponent; } @Subscribe public void onGameTick(GameTick gameTick) { - if (lastTime != null - && Duration.between(lastTime, Instant.now()).compareTo(WAIT) > 0) + if (lastOpponent != null + && lastTime != null + && client.getLocalPlayer().getInteracting() == null) { - lastOpponent = null; - lastTime = null; + if (Duration.between(lastTime, Instant.now()).compareTo(WAIT) > 0) + { + lastOpponent = null; + } } } diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java index e288170d83..4b213bde95 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/opponentinfo/OpponentInfoPluginTest.java @@ -1,6 +1,5 @@ /* * Copyright (c) 2021, Jordan Atwood - * Copyright (c) 2021, Andre Araya * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -89,14 +88,13 @@ public class OpponentInfoPluginTest interactingChanged(npc, localPlayer); - assertSame(npc, plugin.getLastOpponent()); + assertNull(plugin.getLastOpponent()); assertNull(plugin.getLastTime()); interactingChanged(npc, null); - // last opponent is remembered for 5 seconds - assertSame(npc, plugin.getLastOpponent()); - assertNotNull(plugin.getLastTime()); + assertNull(plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); interactingChanged(localPlayer, npc); @@ -110,9 +108,9 @@ public class OpponentInfoPluginTest interactingChanged(localPlayer, null); - // last opponent is remembered as it is still attacking the player + // last opponent is remembered for 5 seconds assertSame(npc, plugin.getLastOpponent()); - assertNull(plugin.getLastTime()); + assertNotNull(plugin.getLastTime()); } @Test @@ -122,14 +120,13 @@ public class OpponentInfoPluginTest interactingChanged(otherPlayer, localPlayer); - assertSame(otherPlayer, plugin.getLastOpponent()); + assertNull(plugin.getLastOpponent()); assertNull(plugin.getLastTime()); interactingChanged(otherPlayer, null); - // last opponent is remembered for 5 seconds - assertSame(otherPlayer, plugin.getLastOpponent()); - assertNotNull(plugin.getLastTime()); + assertNull(plugin.getLastOpponent()); + assertNull(plugin.getLastTime()); interactingChanged(localPlayer, otherPlayer); @@ -143,9 +140,9 @@ public class OpponentInfoPluginTest interactingChanged(localPlayer, null); - // last opponent is remembered as it is still attacking the player + // last opponent is remembered for 5 seconds assertSame(otherPlayer, plugin.getLastOpponent()); - assertNull(plugin.getLastTime()); + assertNotNull(plugin.getLastTime()); } @Test @@ -175,180 +172,6 @@ public class OpponentInfoPluginTest assertNull(plugin.getLastTime()); } - /* - * Verify that the current opponent in multi is always the npc that the player is attacking - */ - @Test - public void testAttackingEnemyInMulti() - { - final Player otherPlayer = mock(Player.class); - final NPC aggro = mock(NPC.class), attacked = mock(NPC.class); - - interactingChanged(localPlayer, null); - - // verify that there is currently no opponent - assertNull(plugin.getLastOpponent()); - - // some npc attacks the player - interactingChanged(aggro, localPlayer); - - // verify that the current opponent is the aggressive npc - assertSame(aggro, plugin.getLastOpponent()); - - // the player attacks a different npc - interactingChanged(localPlayer, attacked); - - // verify the attacked npc is now considered the current opponent - assertSame(attacked, plugin.getLastOpponent()); - - // the npc attacks them back - interactingChanged(attacked, localPlayer); - - // verify that the current opponent is still the attacked npc - assertSame(attacked, plugin.getLastOpponent()); - - // the player is attacked by an aggressive npc while attacking a different npc - interactingChanged(aggro, localPlayer); - - // verify that the current opponent is still the npc the player is attacking - assertSame(attacked, plugin.getLastOpponent()); - - // the npc attacking the player that is not the current opponent attacks a different player - interactingChanged(aggro, otherPlayer); - - // verify that the current opponent is still the npc the player is attacking - assertSame(attacked, plugin.getLastOpponent()); - - // the npc the player is attacking, the current opponent, attacks a different player - interactingChanged(attacked, otherPlayer); - - // verify that the current opponent is still the npc the player is attacking - assertSame(attacked, plugin.getLastOpponent()); - } - - /* - * Verify that the current opponent is the expected npc while the player is not attacking anything - */ - @Test - public void testIdleInMulti() - { - final Player otherPlayer = mock(Player.class); - final NPC aggroFirst = mock(NPC.class), aggroRecent = mock(NPC.class); - - interactingChanged(localPlayer, null); - - // verify that there is currently no opponent - assertNull(plugin.getLastOpponent()); - - // some npc attacks the player - interactingChanged(aggroFirst, localPlayer); - - // verify that the current opponent is the aggressive npc - assertSame(aggroFirst, plugin.getLastOpponent()); - - // the player is attacked by a different npc - interactingChanged(aggroRecent, localPlayer); - - // verify that the current opponent is the most recent aggressor - assertSame(aggroRecent, plugin.getLastOpponent()); - - // an npc that is not the current opponent targets another player - interactingChanged(aggroFirst, otherPlayer); - - // verify that the current opponent is still the most recent aggressor - assertSame(aggroRecent, plugin.getLastOpponent()); - - // the current opponent switches targets to another player - interactingChanged(aggroRecent, otherPlayer); - - // verify that there is no longer an opponent - // (if lastTime is not null, then the lastOpponent will become null after OpponentInfoPlugin.WAIT time) - assertNotNull(plugin.getLastTime()); - } - - /* - * Verify that the current opponent is the expected npc in singles - */ - @Test - public void testSingles() - { - final Player otherPlayer = mock(Player.class); - final NPC npc = mock(NPC.class); - - interactingChanged(localPlayer, null); - - // verify that there is currently no opponent - assertNull(plugin.getLastOpponent()); - - // some npc attacks the player - interactingChanged(npc, localPlayer); - - // verify that the attacking npc is the current opponent - assertSame(npc, plugin.getLastOpponent()); - - // the npc stops attacking the player - interactingChanged(npc, null); - - // verify that there is no longer an opponent - // (if lastTime is not null, then the lastOpponent will become null after OpponentInfoPlugin.WAIT time) - assertNotNull(plugin.getLastTime()); - - // the player attacks the npc - interactingChanged(localPlayer, npc); - - // verify that the attacked npc is the current opponent - assertSame(npc, plugin.getLastOpponent()); - // verify that the hp bar will no longer be hidden - assertNull(plugin.getLastTime()); - - // the npc attacks them back - interactingChanged(npc, localPlayer); - - // verify that the attacked npc is still the current opponent - assertSame(npc, plugin.getLastOpponent()); - - // the player stops attacking the npc - interactingChanged(localPlayer, null); - - // verify that the npc attacking the player is still the current opponent - assertSame(npc, plugin.getLastOpponent()); - // verify that the hp bar will not be hidden (because the npc is still attacking the player) - assertNull(plugin.getLastTime()); - - // the player attacks the npc again - interactingChanged(localPlayer, npc); - - // verify the npc is still the opponent - assertSame(npc, plugin.getLastOpponent()); - - // the npc stops attacking the player - interactingChanged(npc, null); - - // verify that the attacked npc is still the current opponent - assertSame(npc, plugin.getLastOpponent()); - - // the player stops attacking the npc while the npc is not attacking the player - interactingChanged(localPlayer, null); - - // verify that there is no longer an opponent - // (if lastTime is not null, then the lastOpponent will become null after OpponentInfoPlugin.WAIT time) - assertNotNull(plugin.getLastTime()); - - // the npc attacks a different player - interactingChanged(npc, otherPlayer); - - // verify there is still no opponent - // (if lastTime is not null, then the lastOpponent will become null after OpponentInfoPlugin.WAIT time) - assertNotNull(plugin.getLastTime()); - - // the npc stops attacking the other player - interactingChanged(npc, null); - - // verify there is still no opponent - // (if lastTime is not null, then the lastOpponent will become null after OpponentInfoPlugin.WAIT time) - assertNotNull(plugin.getLastTime()); - } - private void interactingChanged(final Actor source, final Actor target) { when(source.getInteracting()).thenReturn(target);