spec counter: add spec drops

Co-authored-by: Adam <Adam@sigterm.info>
This commit is contained in:
Hexagon
2022-05-26 21:02:21 -03:00
committed by Adam
parent eb0cbd5f49
commit 9fe186cad2
6 changed files with 315 additions and 5 deletions

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2022, Hexagon <hexagon@fking.work>
* 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.specialcounter;
import java.awt.Color;
import java.awt.Font;
import java.awt.image.BufferedImage;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import net.runelite.client.ui.FontManager;
@Data
@AllArgsConstructor(access = AccessLevel.PRIVATE)
class PlayerInfoDrop
{
private final int startCycle;
private final int endCycle;
private final int playerIdx;
private final String text;
private final int startHeightOffset;
private final int endHeightOffset;
private final Font font;
private final Color color;
private final BufferedImage image;
public static Builder builder(int startCycle, int endCycle, int playerIdx, String text)
{
return new Builder(startCycle, endCycle, playerIdx, text);
}
@RequiredArgsConstructor
@Accessors(fluent = true)
@Setter
static class Builder
{
private final int startCycle;
private final int endCycle;
private final int playerIdx;
private final String text;
private int startHeightOffset = 0;
private int endHeightOffset = 200;
private Font font = FontManager.getRunescapeBoldFont();
private Color color = Color.WHITE;
private BufferedImage image;
public PlayerInfoDrop build()
{
if (startCycle > endCycle)
{
throw new IllegalArgumentException("endCycle must be after startCycle");
}
if (playerIdx < 0 || playerIdx > 2047)
{
throw new IllegalArgumentException("playerIdx must be between 0-2047");
}
return new PlayerInfoDrop(startCycle, endCycle, playerIdx, text, startHeightOffset, endHeightOffset, font, color, image);
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (c) 2022, Hexagon <hexagon@fking.work>
* 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.specialcounter;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Iterator;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Client;
import net.runelite.api.Player;
import net.runelite.api.Point;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
import net.runelite.client.ui.overlay.OverlayUtil;
import net.runelite.client.util.ColorUtil;
import net.runelite.client.util.ImageUtil;
@Singleton
class PlayerInfoDropOverlay extends Overlay
{
private final SpecialCounterPlugin plugin;
private final SpecialCounterConfig config;
private final Client client;
@Inject
private PlayerInfoDropOverlay(SpecialCounterPlugin plugin, SpecialCounterConfig config, Client client)
{
this.plugin = plugin;
this.config = config;
this.client = client;
setPosition(OverlayPosition.DYNAMIC);
setPriority(OverlayPriority.MED);
}
@Override
public Dimension render(Graphics2D graphics)
{
final List<PlayerInfoDrop> infoDrops = plugin.getPlayerInfoDrops();
if (infoDrops.isEmpty())
{
return null;
}
final int cycle = client.getGameCycle();
for (Iterator<PlayerInfoDrop> iterator = infoDrops.iterator(); iterator.hasNext();)
{
PlayerInfoDrop infoDrop = iterator.next();
if (cycle < infoDrop.getStartCycle())
{
continue;
}
if (cycle > infoDrop.getEndCycle())
{
iterator.remove();
continue;
}
if (!config.specDrops())
{
continue;
}
Player player = client.getCachedPlayers()[infoDrop.getPlayerIdx()];
if (player == null)
{
continue;
}
int elapsed = cycle - infoDrop.getStartCycle();
int percent = elapsed * 100 / (infoDrop.getEndCycle() - infoDrop.getStartCycle());
int currentHeight = infoDrop.getEndHeightOffset() * percent / 100;
String text = infoDrop.getText();
graphics.setFont(infoDrop.getFont());
Point textLocation = player.getCanvasTextLocation(graphics, text, player.getLogicalHeight() + infoDrop.getStartHeightOffset() + currentHeight);
if (textLocation == null)
{
continue;
}
int alpha = 255 - (255 * percent / 100);
BufferedImage image = infoDrop.getImage();
if (image != null)
{
int textHeight = graphics.getFontMetrics().getHeight() - graphics.getFontMetrics().getMaxDescent();
int textMargin = image.getWidth() / 2;
int x = textLocation.getX() - textMargin - 1;
int y = textLocation.getY() - textHeight / 2 - image.getHeight() / 2;
Point imageLocation = new Point(x, y);
textLocation = new Point(textLocation.getX() + textMargin, textLocation.getY());
OverlayUtil.renderImageLocation(graphics, imageLocation, ImageUtil.alphaOffset(image, alpha - 255));
}
drawText(graphics, textLocation, text, infoDrop.getColor(), alpha);
}
return null;
}
private static void drawText(Graphics2D g, Point point, String text, Color color, int colorAlpha)
{
g.setColor(ColorUtil.colorWithAlpha(Color.BLACK, colorAlpha));
g.drawString(text, point.getX() + 1, point.getY() + 1);
g.setColor(ColorUtil.colorWithAlpha(color, colorAlpha));
g.drawString(text, point.getX(), point.getY());
}
}

View File

@@ -25,6 +25,7 @@
*/
package net.runelite.client.plugins.specialcounter;
import java.awt.Color;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@@ -45,6 +46,28 @@ public interface SpecialCounterConfig extends Config
@ConfigItem(
position = 1,
keyName = "specDrops",
name = "Spec Drops",
description = "Draws an overlay over the player when a special attack hits"
)
default boolean specDrops()
{
return true;
}
@ConfigItem(
position = 2,
keyName = "specDropColor",
name = "Spec Drop Color",
description = "Text color for spec drops"
)
default Color specDropColor()
{
return Color.WHITE;
}
@ConfigItem(
position = 10,
keyName = "dragonWarhammerThreshold",
name = "Dragon Warhammer",
description = "Threshold for Dragon Warhammer (0 to disable)"
@@ -55,7 +78,7 @@ public interface SpecialCounterConfig extends Config
}
@ConfigItem(
position = 2,
position = 20,
keyName = "arclightThreshold",
name = "Arclight",
description = "Threshold for Arclight (0 to disable)"
@@ -66,7 +89,7 @@ public interface SpecialCounterConfig extends Config
}
@ConfigItem(
position = 3,
position = 30,
keyName = "darklightThreshold",
name = "Darklight",
description = "Threshold for Darklight (0 to disable)"
@@ -77,7 +100,7 @@ public interface SpecialCounterConfig extends Config
}
@ConfigItem(
position = 4,
position = 40,
keyName = "bandosGodswordThreshold",
name = "Bandos Godsword",
description = "Threshold for Bandos Godsword (0 to disable)"
@@ -88,7 +111,7 @@ public interface SpecialCounterConfig extends Config
}
@ConfigItem(
position = 5,
position = 50,
keyName = "bulwarkThreshold",
name = "Dinh's Bulwark",
description = "Threshold for Dinh's Bulwark (0 to disable)"

View File

@@ -26,11 +26,16 @@ package net.runelite.client.plugins.specialcounter;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Provides;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Actor;
import net.runelite.api.Client;
@@ -44,6 +49,7 @@ import net.runelite.api.NPC;
import net.runelite.api.NpcID;
import net.runelite.api.VarPlayer;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.CommandExecuted;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.HitsplatApplied;
@@ -57,7 +63,9 @@ import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.ws.PartyService;
import net.runelite.client.ws.WSClient;
@@ -95,6 +103,9 @@ public class SpecialCounterPlugin extends Plugin
private final Set<Integer> interactedNpcIds = new HashSet<>();
private final SpecialCounter[] specialCounter = new SpecialCounter[SpecialWeapon.values().length];
@Getter(AccessLevel.PACKAGE)
private final List<PlayerInfoDrop> playerInfoDrops = new ArrayList<>();
@Inject
private Client client;
@@ -119,6 +130,12 @@ public class SpecialCounterPlugin extends Plugin
@Inject
private SpecialCounterConfig config;
@Inject
private OverlayManager overlayManager;
@Inject
private PlayerInfoDropOverlay playerInfoDropOverlay;
@Provides
SpecialCounterConfig getConfig(ConfigManager configManager)
{
@@ -128,6 +145,7 @@ public class SpecialCounterPlugin extends Plugin
@Override
protected void startUp()
{
overlayManager.add(playerInfoDropOverlay);
wsClient.registerMessage(SpecialCounterUpdate.class);
currentWorld = -1;
specialPercentage = -1;
@@ -140,6 +158,7 @@ public class SpecialCounterPlugin extends Plugin
protected void shutDown()
{
removeCounters();
overlayManager.remove(playerInfoDropOverlay);
wsClient.unregisterMessage(SpecialCounterUpdate.class);
}
@@ -267,15 +286,18 @@ public class SpecialCounterPlugin extends Plugin
if (wasSpec && specialWeapon != null && hitsplat.getAmount() > 0)
{
int hit = getHit(specialWeapon, hitsplat);
int localPlayerId = client.getLocalPlayer().getId();
updateCounter(specialWeapon, null, hit);
if (!party.getMembers().isEmpty())
{
final SpecialCounterUpdate specialCounterUpdate = new SpecialCounterUpdate(interactingId, specialWeapon, hit);
final SpecialCounterUpdate specialCounterUpdate = new SpecialCounterUpdate(interactingId, specialWeapon, hit, client.getWorld(), localPlayerId);
specialCounterUpdate.setMemberId(party.getLocalMember().getMemberId());
wsClient.send(specialCounterUpdate);
}
playerInfoDrops.add(createSpecInfoDrop(specialWeapon, hit, localPlayerId));
}
}
@@ -334,9 +356,23 @@ public class SpecialCounterPlugin extends Plugin
{
updateCounter(event.getWeapon(), name, event.getHit());
}
if (event.getWorld() == client.getWorld())
{
playerInfoDrops.add(createSpecInfoDrop(event.getWeapon(), event.getHit(), event.getPlayerId()));
}
});
}
@Subscribe
public void onCommandExecuted(CommandExecuted commandExecuted)
{
if (commandExecuted.getCommand().equals("spec"))
{
playerInfoDrops.add(createSpecInfoDrop(SpecialWeapon.BANDOS_GODSWORD, 42, client.getLocalPlayer().getId()));
}
}
private SpecialWeapon usedSpecialWeapon()
{
ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT);
@@ -424,4 +460,17 @@ public class SpecialCounterPlugin extends Plugin
{
return specialWeapon.isDamage() ? hitsplat.getAmount() : 1;
}
private PlayerInfoDrop createSpecInfoDrop(SpecialWeapon weapon, int hit, int playerId)
{
int cycle = client.getGameCycle();
BufferedImage image = ImageUtil.resizeImage(itemManager.getImage(weapon.getItemID()[0]), 24, 24);
return PlayerInfoDrop.builder(cycle, cycle + 100, playerId, Integer.toString(hit))
.color(config.specDropColor())
.startHeightOffset(100)
.endHeightOffset(400)
.image(image)
.build();
}
}

View File

@@ -35,4 +35,6 @@ public class SpecialCounterUpdate extends PartyMemberMessage
private final int npcId;
private final SpecialWeapon weapon;
private final int hit;
private final int world;
private final int playerId;
}

View File

@@ -28,6 +28,7 @@ import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.testing.fieldbinder.Bind;
import com.google.inject.testing.fieldbinder.BoundFieldModule;
import java.awt.image.BufferedImage;
import net.runelite.api.Actor;
import net.runelite.api.Client;
import net.runelite.api.EquipmentInventorySlot;
@@ -44,13 +45,16 @@ import net.runelite.api.events.InteractingChanged;
import net.runelite.api.events.VarbitChanged;
import net.runelite.client.Notifier;
import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.AsyncBufferedImage;
import net.runelite.client.ws.PartyService;
import net.runelite.client.ws.WSClient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import org.mockito.Mock;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
@@ -90,6 +94,14 @@ public class SpecialCounterPluginTest
@Bind
private SpecialCounterConfig specialCounterConfig;
@Mock
@Bind
private OverlayManager overlayManager;
@Mock
@Bind
private PlayerInfoDropOverlay playerInfoDropOverlay;
@Inject
private SpecialCounterPlugin specialCounterPlugin;
@@ -107,6 +119,8 @@ public class SpecialCounterPluginTest
when(client.getVar(VarPlayer.SPECIAL_ATTACK_PERCENT)).thenReturn(100);
specialCounterPlugin.onVarbitChanged(new VarbitChanged());
// Set up item image for spec info drop
when(itemManager.getImage(anyInt())).thenReturn(new AsyncBufferedImage(24, 24, BufferedImage.TYPE_INT_ARGB));
}
private static HitsplatApplied hitsplat(Actor target, Hitsplat.HitsplatType type)