@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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)"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user