cannon: use varp for cannonball count

Co-authored-by: Bonfire <5704760+Bonfire@users.noreply.github.com>
This commit is contained in:
Adam
2022-06-10 12:25:10 -04:00
parent f8c27b680b
commit 3ef9095771
3 changed files with 79 additions and 178 deletions

View File

@@ -39,6 +39,7 @@ import lombok.Getter;
@Getter @Getter
public enum VarPlayer public enum VarPlayer
{ {
CANNON_AMMO(3),
ATTACK_STYLE(43), ATTACK_STYLE(43),
QUEST_POINTS(101), QUEST_POINTS(101),
IS_POISONED(102), IS_POISONED(102),

View File

@@ -24,14 +24,10 @@
*/ */
package net.runelite.client.plugins.cannon; package net.runelite.client.plugins.cannon;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Provides; import com.google.inject.Provides;
import java.awt.Color; import java.awt.Color;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -40,25 +36,22 @@ import net.runelite.api.ChatMessageType;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.GameObject; import net.runelite.api.GameObject;
import net.runelite.api.GameState; import net.runelite.api.GameState;
import net.runelite.api.GraphicID;
import net.runelite.api.InventoryID; import net.runelite.api.InventoryID;
import net.runelite.api.Item; import net.runelite.api.Item;
import net.runelite.api.ItemContainer;
import net.runelite.api.ItemID; import net.runelite.api.ItemID;
import net.runelite.api.MenuAction; import net.runelite.api.MenuAction;
import net.runelite.api.ObjectID; import net.runelite.api.ObjectID;
import net.runelite.api.Player; import net.runelite.api.Player;
import net.runelite.api.Projectile; import net.runelite.api.VarPlayer;
import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldArea;
import net.runelite.api.coords.WorldPoint; import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ChatMessage; import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameObjectSpawned; import net.runelite.api.events.GameObjectSpawned;
import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.ItemContainerChanged; import net.runelite.api.events.ItemContainerChanged;
import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.events.ProjectileMoved; import net.runelite.api.events.VarbitChanged;
import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo; import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.Notifier; import net.runelite.client.Notifier;
@@ -80,17 +73,10 @@ import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
@Slf4j @Slf4j
public class CannonPlugin extends Plugin public class CannonPlugin extends Plugin
{ {
private static final Pattern NUMBER_PATTERN = Pattern.compile("([0-9]+)");
static final int MAX_OVERLAY_DISTANCE = 4100; static final int MAX_OVERLAY_DISTANCE = 4100;
static final int MAX_CBALLS = 30; static final int MAX_CBALLS = 30;
private static final Set<Integer> CANNONBALL_PROJECTILE_IDS = ImmutableSet.of(
GraphicID.CANNONBALL, GraphicID.GRANITE_CANNONBALL,
GraphicID.CANNONBALL_OR, GraphicID.GRANITE_CANNONBALL_OR
);
private CannonCounter counter; private CannonCounter counter;
private boolean skipProjectileCheckThisTick;
private boolean cannonBallNotificationSent; private boolean cannonBallNotificationSent;
private WorldPoint clickedCannonLocation; private WorldPoint clickedCannonLocation;
private boolean firstCannonLoad; private boolean firstCannonLoad;
@@ -151,6 +137,7 @@ public class CannonPlugin extends Plugin
{ {
overlayManager.add(cannonOverlay); overlayManager.add(cannonOverlay);
overlayManager.add(cannonSpotOverlay); overlayManager.add(cannonSpotOverlay);
clientThread.invoke(() -> cballsLeft = client.getVar(VarPlayer.CANNON_AMMO));
} }
@Override @Override
@@ -165,7 +152,6 @@ public class CannonPlugin extends Plugin
cannonBallNotificationSent = false; cannonBallNotificationSent = false;
cballsLeft = 0; cballsLeft = 0;
removeCounter(); removeCounter();
skipProjectileCheckThisTick = false;
spotPoints.clear(); spotPoints.clear();
} }
@@ -278,7 +264,7 @@ public class CannonPlugin extends Plugin
} }
} }
} }
@Subscribe @Subscribe
public void onMenuOptionClicked(MenuOptionClicked event) public void onMenuOptionClicked(MenuOptionClicked event)
{ {
@@ -309,32 +295,16 @@ public class CannonPlugin extends Plugin
} }
@Subscribe @Subscribe
public void onProjectileMoved(ProjectileMoved event) public void onVarbitChanged(VarbitChanged varbitChanged)
{ {
Projectile projectile = event.getProjectile(); if (varbitChanged.getIndex() == VarPlayer.CANNON_AMMO.getId())
if (CANNONBALL_PROJECTILE_IDS.contains(projectile.getId()) && cannonPosition != null && cannonWorld == client.getWorld())
{ {
WorldPoint projectileLoc = WorldPoint.fromLocal(client, projectile.getX1(), projectile.getY1(), client.getPlane()); cballsLeft = client.getVar(VarPlayer.CANNON_AMMO);
//Check to see if projectile x,y is 0 else it will continuously decrease while ball is flying. if (config.showCannonNotifications() && !cannonBallNotificationSent && cballsLeft > 0 && config.lowWarningThreshold() >= cballsLeft)
if (cannonPosition.contains(projectileLoc) && projectile.getX() == 0 && projectile.getY() == 0)
{ {
// When there's a chat message about cannon reloaded/unloaded/out of ammo, notifier.notify(String.format("Your cannon has %d cannon balls remaining!", cballsLeft));
// the message event runs before the projectile event. However they run cannonBallNotificationSent = true;
// in the opposite order on the server. So if both fires in the same tick,
// we don't want to update the cannonball counter if it was set to a specific
// amount.
if (!skipProjectileCheckThisTick)
{
cballsLeft--;
if (config.showCannonNotifications() && !cannonBallNotificationSent && cballsLeft > 0 && config.lowWarningThreshold() >= cballsLeft)
{
notifier.notify(String.format("Your cannon has %d cannon balls remaining!", cballsLeft));
cannonBallNotificationSent = true;
}
}
} }
} }
} }
@@ -351,32 +321,13 @@ public class CannonPlugin extends Plugin
{ {
cannonPlaced = true; cannonPlaced = true;
addCounter(); addCounter();
cballsLeft = 0;
firstCannonLoad = true; firstCannonLoad = true;
final ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY);
if (inventory != null)
{
int invCballs = inventory.count(ItemID.GRANITE_CANNONBALL) > 0
? inventory.count(ItemID.GRANITE_CANNONBALL)
: inventory.count(ItemID.CANNONBALL);
// Cannonballs are always forcibly loaded after the furnace is added. If the player has more than
// the max number of cannon balls in their inventory, the cannon will always be fully filled.
// This is preferable to using the proceeding "You load the cannon with x cannon balls" message
// since it will show a lower number of cannon balls if the cannon is already partially-filled
// prior to being placed.
if (invCballs >= MAX_CBALLS)
{
cballsLeft = MAX_CBALLS;
}
}
} }
else if (event.getMessage().contains("You pick up the cannon") else if (event.getMessage().contains("You pick up the cannon")
|| event.getMessage().contains("Your cannon has decayed. Speak to Nulodion to get a new one!") || event.getMessage().contains("Your cannon has decayed. Speak to Nulodion to get a new one!")
|| event.getMessage().contains("Your cannon has been destroyed!")) || event.getMessage().contains("Your cannon has been destroyed!"))
{ {
cannonPlaced = false; cannonPlaced = false;
cballsLeft = 0;
removeCounter(); removeCounter();
cannonPosition = null; cannonPosition = null;
} }
@@ -410,83 +361,21 @@ public class CannonPlugin extends Plugin
clickedCannonLocation = null; clickedCannonLocation = null;
} }
Matcher m = NUMBER_PATTERN.matcher(event.getMessage());
if (m.find())
{
// The cannon will usually refill to MAX_CBALLS, but if the
// player didn't have enough cannonballs in their inventory,
// it could fill up less than that. Filling the cannon to
// cballsLeft + amt is not always accurate though because our
// counter doesn't decrease if the player has been too far away
// from the cannon due to the projectiels not being in memory,
// so our counter can be higher than it is supposed to be.
int amt = Integer.valueOf(m.group());
if (cballsLeft + amt >= MAX_CBALLS)
{
skipProjectileCheckThisTick = true;
cballsLeft = MAX_CBALLS;
}
else
{
cballsLeft += amt;
}
}
else if (event.getMessage().equals("You load the cannon with one cannonball."))
{
if (cballsLeft + 1 >= MAX_CBALLS)
{
skipProjectileCheckThisTick = true;
cballsLeft = MAX_CBALLS;
}
else
{
cballsLeft++;
}
}
cannonBallNotificationSent = false; cannonBallNotificationSent = false;
} }
else if (event.getMessage().contains("Your cannon is out of ammo!")) else if (event.getMessage().contains("Your cannon is out of ammo!"))
{ {
skipProjectileCheckThisTick = true;
// If the player was out of range of the cannon, some cannonballs
// may have been used without the client knowing, so having this
// extra check is a good idea.
cballsLeft = 0;
if (config.showCannonNotifications()) if (config.showCannonNotifications())
{ {
notifier.notify("Your cannon is out of ammo!"); notifier.notify("Your cannon is out of ammo!");
} }
} }
else if (event.getMessage().startsWith("Your cannon contains"))
{
Matcher m = NUMBER_PATTERN.matcher(event.getMessage());
if (m.find())
{
cballsLeft = Integer.parseInt(m.group());
}
}
else if (event.getMessage().startsWith("You unload your cannon and receive Cannonball")
|| event.getMessage().startsWith("You unload your cannon and receive Granite cannonball"))
{
skipProjectileCheckThisTick = true;
cballsLeft = 0;
}
else if (event.getMessage().equals("This isn't your cannon!") || event.getMessage().equals("This is not your cannon.")) else if (event.getMessage().equals("This isn't your cannon!") || event.getMessage().equals("This is not your cannon."))
{ {
clickedCannonLocation = null; clickedCannonLocation = null;
} }
} }
@Subscribe
public void onGameTick(GameTick event)
{
skipProjectileCheckThisTick = false;
}
Color getStateColor() Color getStateColor()
{ {
if (cballsLeft > 15) if (cballsLeft > 15)

View File

@@ -31,10 +31,9 @@ import com.google.inject.testing.fieldbinder.BoundFieldModule;
import javax.inject.Inject; import javax.inject.Inject;
import net.runelite.api.ChatMessageType; import net.runelite.api.ChatMessageType;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.InventoryID; import net.runelite.api.VarPlayer;
import net.runelite.api.ItemContainer;
import net.runelite.api.ItemID;
import net.runelite.api.events.ChatMessage; import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.VarbitChanged;
import net.runelite.client.Notifier; import net.runelite.client.Notifier;
import net.runelite.client.game.ItemManager; import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.OverlayManager;
@@ -45,8 +44,11 @@ import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import static org.mockito.ArgumentMatchers.any;
import org.mockito.Mock; import org.mockito.Mock;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
@@ -89,13 +91,12 @@ public class CannonPluginTest
@Bind @Bind
private OverlayManager overlayManager; private OverlayManager overlayManager;
private static final ChatMessage ADD_FURNACE = new ChatMessage(); private static final VarbitChanged cannonAmmoChanged = new VarbitChanged();
@BeforeClass @BeforeClass
public static void chatMessageSetup() public static void cannonVarpSetup()
{ {
ADD_FURNACE.setType(ChatMessageType.SPAM); cannonAmmoChanged.setIndex(VarPlayer.CANNON_AMMO.getId());
ADD_FURNACE.setMessage("You add the furnace.");
} }
@Before @Before
@@ -105,82 +106,92 @@ public class CannonPluginTest
} }
@Test @Test
public void addWrongTypeOfCannonballs() public void testAmmoCountOnPlace()
{ {
final ChatMessage message = new ChatMessage(); ChatMessage chatMessage = new ChatMessage();
message.setType(ChatMessageType.GAMEMESSAGE); chatMessage.setType(ChatMessageType.SPAM);
message.setMessage("Your cannon contains 20 x Cannonball.<br>You can only add cannonballs of the same kind."); chatMessage.setMessage("You add the furnace.");
plugin.onChatMessage(message); plugin.onChatMessage(chatMessage);
assertEquals(20, plugin.getCballsLeft());
}
@Test
public void addMaxCannonballs()
{
final ItemContainer inventory = mock(ItemContainer.class);
when(client.getItemContainer(InventoryID.INVENTORY)).thenReturn(inventory);
when(inventory.count(ItemID.CANNONBALL)).thenReturn(100);
when(inventory.count(ItemID.GRANITE_CANNONBALL)).thenReturn(0);
plugin.onChatMessage(ADD_FURNACE);
assertTrue(plugin.isCannonPlaced()); assertTrue(plugin.isCannonPlaced());
assertEquals(30, plugin.getCballsLeft()); plugin.onVarbitChanged(cannonAmmoChanged);
assertEquals(0, plugin.getCballsLeft());
plugin.onChatMessage(loadCannonballs(30)); // Some time passes...
when(client.getVar(VarPlayer.CANNON_AMMO)).thenReturn(30);
plugin.onVarbitChanged(cannonAmmoChanged);
assertEquals(30, plugin.getCballsLeft()); assertEquals(30, plugin.getCballsLeft());
} }
@Test @Test
public void addNotMaxCannonballs() public void testCannonInfoBox()
{ {
final ItemContainer inventory = mock(ItemContainer.class); when(config.showInfobox()).thenReturn(true);
when(client.getItemContainer(InventoryID.INVENTORY)).thenReturn(inventory);
when(inventory.count(ItemID.GRANITE_CANNONBALL)).thenReturn(12);
plugin.onChatMessage(ADD_FURNACE); ChatMessage chatMessage = new ChatMessage();
chatMessage.setType(ChatMessageType.SPAM);
chatMessage.setMessage("You add the furnace.");
plugin.onChatMessage(chatMessage);
assertTrue(plugin.isCannonPlaced()); assertTrue(plugin.isCannonPlaced());
assertEquals(0, plugin.getCballsLeft()); assertEquals(0, plugin.getCballsLeft());
verify(infoBoxManager).addInfoBox(any(CannonCounter.class));
plugin.onChatMessage(loadCannonballs(12));
assertEquals(12, plugin.getCballsLeft());
} }
@Test @Test
public void addReclaimedCannonballs() public void testThresholdNotificationShouldNotify()
{ {
final ItemContainer inventory = mock(ItemContainer.class); when(config.showCannonNotifications()).thenReturn(true);
when(client.getItemContainer(InventoryID.INVENTORY)).thenReturn(inventory); when(config.lowWarningThreshold()).thenReturn(10);
when(inventory.count(ItemID.CANNONBALL)).thenReturn(1250);
plugin.onChatMessage(ADD_FURNACE); when(client.getVar(VarPlayer.CANNON_AMMO)).thenReturn(30);
assertTrue(plugin.isCannonPlaced()); plugin.onVarbitChanged(cannonAmmoChanged);
when(client.getVar(VarPlayer.CANNON_AMMO)).thenReturn(10);
plugin.onVarbitChanged(cannonAmmoChanged);
assertEquals(30, plugin.getCballsLeft()); verify(notifier, times(1)).notify("Your cannon has 10 cannon balls remaining!");
plugin.onChatMessage(loadCannonballs(18));
assertEquals(30, plugin.getCballsLeft());
} }
private static ChatMessage loadCannonballs(final int numCannonballs) @Test
public void testThresholdNotificationShouldNotifyOnce()
{ {
final ChatMessage message = new ChatMessage(); when(config.showCannonNotifications()).thenReturn(true);
message.setType(ChatMessageType.GAMEMESSAGE); when(config.lowWarningThreshold()).thenReturn(10);
// Cannons use the same chat message for loading cannonballs regardless of whether they're normal or granite. for (int cballs = 15; cballs >= 8; --cballs)
if (numCannonballs == 1)
{ {
message.setMessage("You load the cannon with one cannonball."); when(client.getVar(VarPlayer.CANNON_AMMO)).thenReturn(cballs);
} plugin.onVarbitChanged(cannonAmmoChanged);
else
{
message.setMessage(String.format("You load the cannon with %s cannonballs.", numCannonballs));
} }
return message; verify(notifier, times(1)).notify("Your cannon has 10 cannon balls remaining!");
} }
@Test
public void testThresholdNotificationsShouldNotNotify()
{
when(config.showCannonNotifications()).thenReturn(true);
when(config.lowWarningThreshold()).thenReturn(0);
when(client.getVar(VarPlayer.CANNON_AMMO)).thenReturn(30);
plugin.onVarbitChanged(cannonAmmoChanged);
when(client.getVar(VarPlayer.CANNON_AMMO)).thenReturn(10);
plugin.onVarbitChanged(cannonAmmoChanged);
verify(notifier, never()).notify("Your cannon has 10 cannon balls remaining!");
}
@Test
public void testCannonOutOfAmmo()
{
when(config.showCannonNotifications()).thenReturn(true);
ChatMessage cannonOutOfAmmo = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "Your cannon is out of ammo!", "", 0);
plugin.onChatMessage(cannonOutOfAmmo);
verify(notifier, times(1)).notify("Your cannon is out of ammo!");
}
} }