This commit is contained in:
therealunull
2020-12-14 05:58:02 -05:00
parent b86aa9c5cc
commit 3248f22650
52 changed files with 8764 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
/*
* Copyright (c) 2018, Seth <http://github.com/sethtroll>
* 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.barrows;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import javax.inject.Inject;
import net.runelite.api.Client;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
import net.runelite.api.Varbits;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
import net.runelite.client.ui.overlay.components.LineComponent;
public class BarrowsBrotherSlainOverlay extends OverlayPanel
{
private final Client client;
@Inject
private BarrowsBrotherSlainOverlay(BarrowsPlugin plugin, Client client)
{
super(plugin);
setPosition(OverlayPosition.TOP_LEFT);
setPriority(OverlayPriority.LOW);
this.client = client;
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Barrows overlay"));
}
@Override
public Dimension render(Graphics2D graphics)
{
// Do not display overlay if potential is null/hidden
final Widget potential = client.getWidget(WidgetInfo.BARROWS_POTENTIAL);
if (potential == null || potential.isHidden())
{
return null;
}
// Hide original overlay
final Widget barrowsBrothers = client.getWidget(WidgetInfo.BARROWS_BROTHERS);
if (barrowsBrothers != null)
{
barrowsBrothers.setHidden(true);
potential.setHidden(true);
}
for (BarrowsBrothers brother : BarrowsBrothers.values())
{
final boolean brotherSlain = client.getVar(brother.getKilledVarbit()) > 0;
String slain = brotherSlain ? "\u2713" : "\u2717";
panelComponent.getChildren().add(LineComponent.builder()
.left(brother.getName())
.right(slain)
.rightColor(brotherSlain ? Color.GREEN : Color.RED)
.build());
}
final int rewardPotential = rewardPotential();
float rewardPercent = rewardPotential / 10.12f;
panelComponent.getChildren().add(LineComponent.builder()
.left("Potential")
.right(rewardPercent != 0 ? rewardPercent + "%" : "0%")
.rightColor(rewardPotential >= 756 && rewardPotential < 881 ? Color.GREEN : rewardPotential < 631 ? Color.WHITE : Color.YELLOW)
.build());
return super.render(graphics);
}
/**
* Compute the barrows reward potential. Potential rewards are based off of the amount of
* potential.
* <p>
* The reward potential thresholds are as follows:
* Mind rune - 381
* Chaos rune - 506
* Death rune - 631
* Blood rune - 756
* Bolt rack - 881
* Half key - 1006
* Dragon med - 1012
*
* @return potential, 0-1012 inclusive
* @see <a href="https://twitter.com/jagexkieren/status/705428283509366785?lang=en">source</a>
*/
private int rewardPotential()
{
// this is from [proc,barrows_overlay_reward]
int brothers = client.getVar(Varbits.BARROWS_KILLED_AHRIM)
+ client.getVar(Varbits.BARROWS_KILLED_DHAROK)
+ client.getVar(Varbits.BARROWS_KILLED_GUTHAN)
+ client.getVar(Varbits.BARROWS_KILLED_KARIL)
+ client.getVar(Varbits.BARROWS_KILLED_TORAG)
+ client.getVar(Varbits.BARROWS_KILLED_VERAC);
return client.getVar(Varbits.BARROWS_REWARD_POTENTIAL) + brothers * 2;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* 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.barrows;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.Varbits;
import net.runelite.api.coords.WorldPoint;
@RequiredArgsConstructor
public enum BarrowsBrothers
{
AHRIM("Ahrim", new WorldPoint(3566, 3289, 0), Varbits.BARROWS_KILLED_AHRIM),
DHAROK("Dharok", new WorldPoint(3575, 3298, 0), Varbits.BARROWS_KILLED_DHAROK),
GUTHAN("Guthan", new WorldPoint(3577, 3283, 0), Varbits.BARROWS_KILLED_GUTHAN),
KARIL("Karil", new WorldPoint(3566, 3275, 0), Varbits.BARROWS_KILLED_KARIL),
TORAG("Torag", new WorldPoint(3553, 3283, 0), Varbits.BARROWS_KILLED_TORAG),
VERAC("Verac", new WorldPoint(3557, 3298, 0), Varbits.BARROWS_KILLED_VERAC);
@Getter
private final String name;
@Getter
private final WorldPoint location;
@Getter
private final Varbits killedVarbit;
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* 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.barrows;
import java.awt.Color;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("barrows")
public interface BarrowsConfig extends Config
{
@ConfigItem(
keyName = "showBrotherLoc",
name = "Show Brothers location",
description = "Configures whether or not the brothers location is displayed",
position = 1
)
default boolean showBrotherLoc()
{
return true;
}
@ConfigItem(
keyName = "showChestValue",
name = "Show Value of Chests",
description = "Configure whether to show total exchange value of chest when opened",
position = 2
)
default boolean showChestValue()
{
return true;
}
@ConfigItem(
keyName = "brotherLocColor",
name = "Brother location color",
description = "Change the color of the name displayed on the minimap",
position = 3
)
default Color brotherLocColor()
{
return Color.CYAN;
}
@ConfigItem(
keyName = "deadBrotherLocColor",
name = "Dead Brother loc. color",
description = "Change the color of the name displayed on the minimap for a dead brother",
position = 4
)
default Color deadBrotherLocColor()
{
return Color.RED;
}
@ConfigItem(
keyName = "showPuzzleAnswer",
name = "Show Puzzle Answer",
description = "Configures if the puzzle answer should be shown.",
position = 5
)
default boolean showPuzzleAnswer()
{
return true;
}
@ConfigItem(
keyName = "showPrayerDrainTimer",
name = "Show Prayer Drain Timer",
description = "Configure whether or not a countdown until the next prayer drain is displayed",
position = 6
)
default boolean showPrayerDrainTimer()
{
return true;
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* 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.barrows;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Perspective;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.widgets.Widget;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
class BarrowsOverlay extends Overlay
{
private final Client client;
private final BarrowsPlugin plugin;
private final BarrowsConfig config;
@Inject
private BarrowsOverlay(Client client, BarrowsPlugin plugin, BarrowsConfig config)
{
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_WIDGETS);
this.client = client;
this.plugin = plugin;
this.config = config;
}
@Override
public Dimension render(Graphics2D graphics)
{
Widget puzzleAnswer = plugin.getPuzzleAnswer();
if (config.showBrotherLoc())
{
renderBarrowsBrothers(graphics);
}
if (puzzleAnswer != null && config.showPuzzleAnswer() && !puzzleAnswer.isHidden())
{
Rectangle answerRect = puzzleAnswer.getBounds();
graphics.setColor(Color.GREEN);
graphics.draw(answerRect);
}
return null;
}
private void renderBarrowsBrothers(Graphics2D graphics)
{
for (BarrowsBrothers brother : BarrowsBrothers.values())
{
LocalPoint localLocation = LocalPoint.fromWorld(client, brother.getLocation());
if (localLocation == null)
{
continue;
}
String brotherLetter = Character.toString(brother.getName().charAt(0));
net.runelite.api.Point minimapText = Perspective.getCanvasTextMiniMapLocation(client, graphics,
localLocation, brotherLetter);
if (minimapText != null)
{
graphics.setColor(Color.black);
graphics.drawString(brotherLetter, minimapText.getX() + 1, minimapText.getY() + 1);
if (client.getVar(brother.getKilledVarbit()) > 0)
{
graphics.setColor(config.deadBrotherLocColor());
}
else
{
graphics.setColor(config.brotherLocColor());
}
graphics.drawString(brotherLetter, minimapText.getX(), minimapText.getY());
}
}
}
}

View File

@@ -0,0 +1,256 @@
/*
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* 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.barrows;
import com.google.common.collect.ImmutableList;
import com.google.inject.Provides;
import java.time.temporal.ChronoUnit;
import javax.inject.Inject;
import lombok.Getter;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemContainer;
import net.runelite.api.Player;
import net.runelite.api.SpriteID;
import net.runelite.client.events.ConfigChanged;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.WidgetLoaded;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.ItemManager;
import net.runelite.client.game.SpriteManager;
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.ui.overlay.infobox.InfoBoxPriority;
import net.runelite.client.ui.overlay.infobox.LoopTimer;
import net.runelite.client.util.QuantityFormatter;
@PluginDescriptor(
name = "Barrows Brothers",
description = "Show helpful information for the Barrows minigame",
tags = {"combat", "minigame", "bosses", "pve", "pvm"}
)
public class BarrowsPlugin extends Plugin
{
private static final ImmutableList<WidgetInfo> POSSIBLE_SOLUTIONS = ImmutableList.of(
WidgetInfo.BARROWS_PUZZLE_ANSWER1,
WidgetInfo.BARROWS_PUZZLE_ANSWER2,
WidgetInfo.BARROWS_PUZZLE_ANSWER3
);
private static final long PRAYER_DRAIN_INTERVAL_MS = 18200;
private static final int CRYPT_REGION_ID = 14231;
private LoopTimer barrowsPrayerDrainTimer;
private boolean wasInCrypt = false;
@Getter
private Widget puzzleAnswer;
@Inject
private OverlayManager overlayManager;
@Inject
private BarrowsOverlay barrowsOverlay;
@Inject
private BarrowsBrotherSlainOverlay brotherOverlay;
@Inject
private Client client;
@Inject
private ItemManager itemManager;
@Inject
private SpriteManager spriteManager;
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private ChatMessageManager chatMessageManager;
@Inject
private BarrowsConfig config;
@Provides
BarrowsConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(BarrowsConfig.class);
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(barrowsOverlay);
overlayManager.add(brotherOverlay);
}
@Override
protected void shutDown()
{
overlayManager.remove(barrowsOverlay);
overlayManager.remove(brotherOverlay);
puzzleAnswer = null;
wasInCrypt = false;
stopPrayerDrainTimer();
// Restore widgets
final Widget potential = client.getWidget(WidgetInfo.BARROWS_POTENTIAL);
if (potential != null)
{
potential.setHidden(false);
}
final Widget barrowsBrothers = client.getWidget(WidgetInfo.BARROWS_BROTHERS);
if (barrowsBrothers != null)
{
barrowsBrothers.setHidden(false);
}
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (event.getGroup().equals("barrows") && !config.showPrayerDrainTimer())
{
stopPrayerDrainTimer();
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
if (event.getGameState() == GameState.LOADING)
{
wasInCrypt = isInCrypt();
// on region changes the tiles get set to null
puzzleAnswer = null;
}
else if (event.getGameState() == GameState.LOGGED_IN)
{
boolean isInCrypt = isInCrypt();
if (wasInCrypt && !isInCrypt)
{
stopPrayerDrainTimer();
}
else if (!wasInCrypt && isInCrypt)
{
startPrayerDrainTimer();
}
}
}
@Subscribe
public void onWidgetLoaded(WidgetLoaded event)
{
if (event.getGroupId() == WidgetID.BARROWS_REWARD_GROUP_ID && config.showChestValue())
{
ItemContainer barrowsRewardContainer = client.getItemContainer(InventoryID.BARROWS_REWARD);
Item[] items = barrowsRewardContainer.getItems();
long chestPrice = 0;
for (Item item : items)
{
long itemStack = (long) itemManager.getItemPrice(item.getId()) * (long) item.getQuantity();
chestPrice += itemStack;
}
final ChatMessageBuilder message = new ChatMessageBuilder()
.append(ChatColorType.HIGHLIGHT)
.append("Your chest is worth around ")
.append(QuantityFormatter.formatNumber(chestPrice))
.append(" coins.")
.append(ChatColorType.NORMAL);
chatMessageManager.queue(QueuedMessage.builder()
.type(ChatMessageType.ITEM_EXAMINE)
.runeLiteFormattedMessage(message.build())
.build());
}
else if (event.getGroupId() == WidgetID.BARROWS_PUZZLE_GROUP_ID)
{
final int answer = client.getWidget(WidgetInfo.BARROWS_FIRST_PUZZLE).getModelId() - 3;
puzzleAnswer = null;
for (WidgetInfo puzzleNode : POSSIBLE_SOLUTIONS)
{
final Widget widgetToCheck = client.getWidget(puzzleNode);
if (widgetToCheck != null && widgetToCheck.getModelId() == answer)
{
puzzleAnswer = client.getWidget(puzzleNode);
break;
}
}
}
}
private void startPrayerDrainTimer()
{
if (config.showPrayerDrainTimer())
{
final LoopTimer loopTimer = new LoopTimer(
PRAYER_DRAIN_INTERVAL_MS,
ChronoUnit.MILLIS,
null,
this,
true);
spriteManager.getSpriteAsync(SpriteID.TAB_PRAYER, 0, loopTimer);
loopTimer.setPriority(InfoBoxPriority.MED);
loopTimer.setTooltip("Prayer Drain");
infoBoxManager.addInfoBox(loopTimer);
barrowsPrayerDrainTimer = loopTimer;
}
}
private void stopPrayerDrainTimer()
{
infoBoxManager.removeInfoBox(barrowsPrayerDrainTimer);
barrowsPrayerDrainTimer = null;
}
private boolean isInCrypt()
{
Player localPlayer = client.getLocalPlayer();
return localPlayer != null && localPlayer.getWorldLocation().getRegionID() == CRYPT_REGION_ID;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* 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.blastfurnace;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import lombok.Getter;
import net.runelite.api.ItemID;
import net.runelite.api.Varbits;
public enum BarsOres
{
COPPER_ORE(Varbits.BLAST_FURNACE_COPPER_ORE, ItemID.COPPER_ORE),
TIN_ORE(Varbits.BLAST_FURNACE_TIN_ORE, ItemID.TIN_ORE),
IRON_ORE(Varbits.BLAST_FURNACE_IRON_ORE, ItemID.IRON_ORE),
COAL(Varbits.BLAST_FURNACE_COAL, ItemID.COAL),
MITHRIL_ORE(Varbits.BLAST_FURNACE_MITHRIL_ORE, ItemID.MITHRIL_ORE),
ADAMANTITE_ORE(Varbits.BLAST_FURNACE_ADAMANTITE_ORE, ItemID.ADAMANTITE_ORE),
RUNITE_ORE(Varbits.BLAST_FURNACE_RUNITE_ORE, ItemID.RUNITE_ORE),
SILVER_ORE(Varbits.BLAST_FURNACE_SILVER_ORE, ItemID.SILVER_ORE),
GOLD_ORE(Varbits.BLAST_FURNACE_GOLD_ORE, ItemID.GOLD_ORE),
BRONZE_BAR(Varbits.BLAST_FURNACE_BRONZE_BAR, ItemID.BRONZE_BAR),
IRON_BAR(Varbits.BLAST_FURNACE_IRON_BAR, ItemID.IRON_BAR),
STEEL_BAR(Varbits.BLAST_FURNACE_STEEL_BAR, ItemID.STEEL_BAR),
MITHRIL_BAR(Varbits.BLAST_FURNACE_MITHRIL_BAR, ItemID.MITHRIL_BAR),
ADAMANTITE_BAR(Varbits.BLAST_FURNACE_ADAMANTITE_BAR, ItemID.ADAMANTITE_BAR),
RUNITE_BAR(Varbits.BLAST_FURNACE_RUNITE_BAR, ItemID.RUNITE_BAR),
SILVER_BAR(Varbits.BLAST_FURNACE_SILVER_BAR, ItemID.SILVER_BAR),
GOLD_BAR(Varbits.BLAST_FURNACE_GOLD_BAR, ItemID.GOLD_BAR);
private static final Map<Varbits, BarsOres> VARBIT;
static
{
ImmutableMap.Builder<Varbits, BarsOres> builder = new ImmutableMap.Builder<>();
for (BarsOres s : values())
{
builder.put(s.getVarbit(), s);
}
VARBIT = builder.build();
}
@Getter
private final Varbits varbit;
@Getter
private final int itemID;
BarsOres(Varbits varbit, int itemID)
{
this.varbit = varbit;
this.itemID = itemID;
}
public static BarsOres getVarbit(Varbits varbit)
{
return VARBIT.get(varbit);
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* 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.blastfurnace;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Shape;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.InventoryID;
import net.runelite.api.ItemContainer;
import net.runelite.api.ItemID;
import net.runelite.api.Point;
import net.runelite.api.Varbits;
import net.runelite.api.coords.LocalPoint;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayPosition;
class BlastFurnaceClickBoxOverlay extends Overlay
{
private static final int MAX_DISTANCE = 2350;
private final Client client;
private final BlastFurnacePlugin plugin;
private final BlastFurnaceConfig config;
@Inject
private BlastFurnaceClickBoxOverlay(Client client, BlastFurnacePlugin plugin, BlastFurnaceConfig config)
{
setPosition(OverlayPosition.DYNAMIC);
this.client = client;
this.plugin = plugin;
this.config = config;
}
@Override
public Dimension render(Graphics2D graphics)
{
int dispenserState = client.getVar(Varbits.BAR_DISPENSER);
if (config.showConveyorBelt() && plugin.getConveyorBelt() != null)
{
Color color = dispenserState == 1 ? Color.RED : Color.GREEN;
renderObject(plugin.getConveyorBelt(), graphics, color);
}
if (config.showBarDispenser() && plugin.getBarDispenser() != null)
{
boolean hasIceGloves = hasIceGloves();
Color color = dispenserState == 2 && hasIceGloves ? Color.GREEN : (dispenserState == 3 ? Color.GREEN : Color.RED);
renderObject(plugin.getBarDispenser(), graphics, color);
}
return null;
}
private boolean hasIceGloves()
{
ItemContainer equipmentContainer = client.getItemContainer(InventoryID.EQUIPMENT);
if (equipmentContainer == null)
{
return false;
}
return equipmentContainer.contains(ItemID.ICE_GLOVES);
}
private void renderObject(GameObject object, Graphics2D graphics, Color color)
{
LocalPoint localLocation = client.getLocalPlayer().getLocalLocation();
Point mousePosition = client.getMouseCanvasPosition();
LocalPoint location = object.getLocalLocation();
if (localLocation.distanceTo(location) <= MAX_DISTANCE)
{
Shape objectClickbox = object.getClickbox();
if (objectClickbox != null)
{
if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY()))
{
graphics.setColor(color.darker());
}
else
{
graphics.setColor(color);
}
graphics.draw(objectClickbox);
graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20));
graphics.fill(objectClickbox);
}
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* 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.blastfurnace;
import java.awt.Dimension;
import java.awt.Graphics2D;
import javax.inject.Inject;
import net.runelite.api.Client;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
import static net.runelite.api.Varbits.BLAST_FURNACE_COFFER;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.components.LineComponent;
import net.runelite.client.util.QuantityFormatter;
import static org.apache.commons.lang3.time.DurationFormatUtils.formatDuration;
class BlastFurnaceCofferOverlay extends OverlayPanel
{
private static final float COST_PER_HOUR = 72000.0f;
private final Client client;
private final BlastFurnacePlugin plugin;
private final BlastFurnaceConfig config;
@Inject
private BlastFurnaceCofferOverlay(Client client, BlastFurnacePlugin plugin, BlastFurnaceConfig config)
{
super(plugin);
setPosition(OverlayPosition.TOP_LEFT);
this.client = client;
this.plugin = plugin;
this.config = config;
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Coffer overlay"));
}
@Override
public Dimension render(Graphics2D graphics)
{
if (plugin.getConveyorBelt() == null)
{
return null;
}
Widget sack = client.getWidget(WidgetInfo.BLAST_FURNACE_COFFER);
if (sack != null)
{
final int coffer = client.getVar(BLAST_FURNACE_COFFER);
sack.setHidden(true);
panelComponent.getChildren().add(LineComponent.builder()
.left("Coffer:")
.right(QuantityFormatter.quantityToStackSize(coffer) + " gp")
.build());
if (config.showCofferTime())
{
final long millis = (long) (coffer / COST_PER_HOUR * 60 * 60 * 1000);
panelComponent.getChildren().add(LineComponent.builder()
.left("Time:")
.right(formatDuration(millis, "H'h' m'm' s's'", true))
.build());
}
}
return super.render(graphics);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* 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.blastfurnace;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("blastfurnace")
public interface BlastFurnaceConfig extends Config
{
@ConfigItem(
keyName = "showConveyorBelt",
name = "Show conveyor belt clickbox",
description = "Configures whether or not the clickbox for the conveyor belt is displayed",
position = 1
)
default boolean showConveyorBelt()
{
return false;
}
@ConfigItem(
keyName = "showBarDispenser",
name = "Show bar dispenser clickbox",
description = "Configures whether or not the clickbox for the bar dispenser is displayed",
position = 2
)
default boolean showBarDispenser()
{
return false;
}
@ConfigItem(
keyName = "showCofferTime",
name = "Show coffer time remaining",
description = "Configures whether or not the coffer time remaining is displayed",
position = 3
)
default boolean showCofferTime()
{
return true;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* 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.blastfurnace;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.inject.Inject;
import net.runelite.api.Client;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
import net.runelite.client.game.ItemManager;
import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.components.ComponentOrientation;
import net.runelite.client.ui.overlay.components.ImageComponent;
class BlastFurnaceOverlay extends OverlayPanel
{
private final Client client;
private final BlastFurnacePlugin plugin;
@Inject
private ItemManager itemManager;
@Inject
BlastFurnaceOverlay(Client client, BlastFurnacePlugin plugin)
{
super(plugin);
this.plugin = plugin;
this.client = client;
setPosition(OverlayPosition.TOP_LEFT);
panelComponent.setOrientation(ComponentOrientation.HORIZONTAL);
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Blast furnace overlay"));
}
@Override
public Dimension render(Graphics2D graphics)
{
if (plugin.getConveyorBelt() == null)
{
return null;
}
for (BarsOres varbit : BarsOres.values())
{
int amount = client.getVar(varbit.getVarbit());
if (amount == 0)
{
continue;
}
panelComponent.getChildren().add(new ImageComponent(getImage(varbit.getItemID(), amount)));
}
return super.render(graphics);
}
private BufferedImage getImage(int itemID, int amount)
{
BufferedImage image = itemManager.getImage(itemID, amount, true);
return image;
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* Copyright (c) 2019, Brandon White <bmwqg@live.com>
* 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.blastfurnace;
import com.google.inject.Provides;
import java.time.Duration;
import java.time.Instant;
import javax.inject.Inject;
import lombok.AccessLevel;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.GameState;
import static net.runelite.api.NullObjectID.NULL_9092;
import static net.runelite.api.ObjectID.CONVEYOR_BELT;
import net.runelite.api.Skill;
import net.runelite.api.events.GameObjectDespawned;
import net.runelite.api.events.GameObjectSpawned;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.config.ConfigManager;
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.Text;
@PluginDescriptor(
name = "Blast Furnace",
description = "Show helpful information for the Blast Furnace minigame",
tags = {"minigame", "overlay", "skilling", "smithing"}
)
public class BlastFurnacePlugin extends Plugin
{
private static final int BAR_DISPENSER = NULL_9092;
private static final String FOREMAN_PERMISSION_TEXT = "Okay, you can use the furnace for ten minutes. Remember, you only need half as much coal as with a regular furnace.";
@Getter(AccessLevel.PACKAGE)
private GameObject conveyorBelt;
@Getter(AccessLevel.PACKAGE)
private GameObject barDispenser;
private ForemanTimer foremanTimer;
@Inject
private OverlayManager overlayManager;
@Inject
private BlastFurnaceOverlay overlay;
@Inject
private BlastFurnaceCofferOverlay cofferOverlay;
@Inject
private BlastFurnaceClickBoxOverlay clickBoxOverlay;
@Inject
private Client client;
@Inject
private ItemManager itemManager;
@Inject
private InfoBoxManager infoBoxManager;
@Override
protected void startUp() throws Exception
{
overlayManager.add(overlay);
overlayManager.add(cofferOverlay);
overlayManager.add(clickBoxOverlay);
}
@Override
protected void shutDown()
{
infoBoxManager.removeIf(ForemanTimer.class::isInstance);
overlayManager.remove(overlay);
overlayManager.remove(cofferOverlay);
overlayManager.remove(clickBoxOverlay);
conveyorBelt = null;
barDispenser = null;
foremanTimer = null;
}
@Provides
BlastFurnaceConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(BlastFurnaceConfig.class);
}
@Subscribe
public void onGameObjectSpawned(GameObjectSpawned event)
{
GameObject gameObject = event.getGameObject();
switch (gameObject.getId())
{
case CONVEYOR_BELT:
conveyorBelt = gameObject;
break;
case BAR_DISPENSER:
barDispenser = gameObject;
break;
}
}
@Subscribe
public void onGameObjectDespawned(GameObjectDespawned event)
{
GameObject gameObject = event.getGameObject();
switch (gameObject.getId())
{
case CONVEYOR_BELT:
conveyorBelt = null;
break;
case BAR_DISPENSER:
barDispenser = null;
break;
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
if (event.getGameState() == GameState.LOADING)
{
conveyorBelt = null;
barDispenser = null;
}
}
@Subscribe
public void onGameTick(GameTick event)
{
Widget npcDialog = client.getWidget(WidgetInfo.DIALOG_NPC_TEXT);
if (npcDialog == null)
{
return;
}
// blocking dialog check until 5 minutes needed to avoid re-adding while dialog message still displayed
boolean shouldCheckForemanFee = client.getRealSkillLevel(Skill.SMITHING) < 60
&& (foremanTimer == null || Duration.between(Instant.now(), foremanTimer.getEndTime()).toMinutes() <= 5);
if (shouldCheckForemanFee)
{
String npcText = Text.sanitizeMultilineText(npcDialog.getText());
if (npcText.equals(FOREMAN_PERMISSION_TEXT))
{
infoBoxManager.removeIf(ForemanTimer.class::isInstance);
foremanTimer = new ForemanTimer(this, itemManager);
infoBoxManager.addInfoBox(foremanTimer);
}
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2019, Brandon White <bmwqg@live.com>
* 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.blastfurnace;
import java.time.temporal.ChronoUnit;
import net.runelite.api.ItemID;
import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.overlay.infobox.Timer;
class ForemanTimer extends Timer
{
private static final String TOOLTIP_TEXT = "Foreman Fee";
ForemanTimer(BlastFurnacePlugin plugin, ItemManager itemManager)
{
super(10, ChronoUnit.MINUTES, itemManager.getImage(ItemID.COAL_BAG), plugin);
setTooltip(TOOLTIP_TEXT);
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2018, Unmoon <https://github.com/Unmoon>
* 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.blastmine;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.ItemID;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
import net.runelite.api.Varbits;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.game.ItemManager;
import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.components.ComponentOrientation;
import net.runelite.client.ui.overlay.components.ImageComponent;
class BlastMineOreCountOverlay extends OverlayPanel
{
private final Client client;
private final BlastMinePluginConfig config;
private final ItemManager itemManager;
@Inject
private BlastMineOreCountOverlay(BlastMinePlugin plugin, Client client, BlastMinePluginConfig config, ItemManager itemManager)
{
super(plugin);
setPosition(OverlayPosition.TOP_LEFT);
this.client = client;
this.config = config;
this.itemManager = itemManager;
panelComponent.setOrientation(ComponentOrientation.HORIZONTAL);
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Blast mine overlay"));
}
@Override
public Dimension render(Graphics2D graphics)
{
final Widget blastMineWidget = client.getWidget(WidgetInfo.BLAST_MINE);
if (blastMineWidget == null)
{
return null;
}
if (config.showOreOverlay())
{
blastMineWidget.setHidden(true);
panelComponent.getChildren().add(new ImageComponent(getImage(ItemID.COAL, client.getVar(Varbits.BLAST_MINE_COAL))));
panelComponent.getChildren().add(new ImageComponent(getImage(ItemID.GOLD_ORE, client.getVar(Varbits.BLAST_MINE_GOLD))));
panelComponent.getChildren().add(new ImageComponent(getImage(ItemID.MITHRIL_ORE, client.getVar(Varbits.BLAST_MINE_MITHRIL))));
panelComponent.getChildren().add(new ImageComponent(getImage(ItemID.ADAMANTITE_ORE, client.getVar(Varbits.BLAST_MINE_ADAMANTITE))));
panelComponent.getChildren().add(new ImageComponent(getImage(ItemID.RUNITE_ORE, client.getVar(Varbits.BLAST_MINE_RUNITE))));
}
else
{
blastMineWidget.setHidden(false);
}
return super.render(graphics);
}
private BufferedImage getImage(int itemID, int amount)
{
return itemManager.getImage(itemID, amount, true);
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (c) 2018, Unmoon <https://github.com/Unmoon>
* 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.blastmine;
import com.google.inject.Provides;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.GameState;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameObjectSpawned;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.overlay.OverlayManager;
@PluginDescriptor(
name = "Blast Mine",
description = "Show helpful information for the Blast Mine minigame",
tags = {"explode", "explosive", "mining", "minigame", "skilling"}
)
public class BlastMinePlugin extends Plugin
{
@Getter
private final Map<WorldPoint, BlastMineRock> rocks = new HashMap<>();
@Inject
private OverlayManager overlayManager;
@Inject
private Client client;
@Inject
private BlastMineRockOverlay blastMineRockOverlay;
@Inject
private BlastMineOreCountOverlay blastMineOreCountOverlay;
@Provides
BlastMinePluginConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(BlastMinePluginConfig.class);
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(blastMineRockOverlay);
overlayManager.add(blastMineOreCountOverlay);
}
@Override
protected void shutDown() throws Exception
{
overlayManager.remove(blastMineRockOverlay);
overlayManager.remove(blastMineOreCountOverlay);
final Widget blastMineWidget = client.getWidget(WidgetInfo.BLAST_MINE);
if (blastMineWidget != null)
{
blastMineWidget.setHidden(false);
}
}
@Subscribe
public void onGameObjectSpawned(GameObjectSpawned event)
{
final GameObject gameObject = event.getGameObject();
BlastMineRockType blastMineRockType = BlastMineRockType.getRockType(gameObject.getId());
if (blastMineRockType == null)
{
return;
}
final BlastMineRock newRock = new BlastMineRock(gameObject, blastMineRockType);
final BlastMineRock oldRock = rocks.get(gameObject.getWorldLocation());
if (oldRock == null || oldRock.getType() != newRock.getType())
{
rocks.put(gameObject.getWorldLocation(), newRock);
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
if (event.getGameState() == GameState.LOADING)
{
rocks.clear();
}
}
@Subscribe
public void onGameTick(GameTick gameTick)
{
if (rocks.isEmpty())
{
return;
}
rocks.values().removeIf(rock ->
(rock.getRemainingTimeRelative() == 1 && rock.getType() != BlastMineRockType.NORMAL) ||
(rock.getRemainingFuseTimeRelative() == 1 && rock.getType() == BlastMineRockType.LIT));
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2018, Unmoon <https://github.com/Unmoon>
* 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.blastmine;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import java.awt.Color;
@ConfigGroup("blastmine")
public interface BlastMinePluginConfig extends Config
{
@ConfigItem(
position = 0,
keyName = "showOreOverlay",
name = "Show ore overlay",
description = "Configures whether or not the ore count overlay is displayed"
)
default boolean showOreOverlay()
{
return true;
}
@ConfigItem(
position = 1,
keyName = "showRockIconOverlay",
name = "Show icons overlay",
description = "Configures whether or not the icon overlay is displayed"
)
default boolean showRockIconOverlay()
{
return true;
}
@ConfigItem(
position = 2,
keyName = "showTimerOverlay",
name = "Show timer overlay",
description = "Configures whether or not the timer overlay is displayed"
)
default boolean showTimerOverlay()
{
return true;
}
@ConfigItem(
position = 3,
keyName = "showWarningOverlay",
name = "Show explosion warning",
description = "Configures whether or not the explosion warning overlay is displayed"
)
default boolean showWarningOverlay()
{
return true;
}
@ConfigItem(
position = 4,
keyName = "hexTimerColor",
name = "Timer color",
description = "Color of timer overlay"
)
default Color getTimerColor()
{
return new Color(217, 54, 0);
}
@ConfigItem(
position = 5,
keyName = "hexWarningColor",
name = "Warning color",
description = "Color of warning overlay"
)
default Color getWarningColor()
{
return new Color(217, 54, 0);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2018, Unmoon <https://github.com/Unmoon>
* 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.blastmine;
import java.time.Duration;
import java.time.Instant;
import lombok.Getter;
import net.runelite.api.GameObject;
class BlastMineRock
{
private static final Duration PLANT_TIME = Duration.ofSeconds(30);
private static final Duration FUSE_TIME = Duration.ofMillis(4200);
@Getter
private final GameObject gameObject;
@Getter
private final BlastMineRockType type;
private final Instant creationTime = Instant.now();
BlastMineRock(final GameObject gameObject, BlastMineRockType blastMineRockType)
{
this.gameObject = gameObject;
this.type = blastMineRockType;
}
double getRemainingFuseTimeRelative()
{
Duration duration = Duration.between(creationTime, Instant.now());
return duration.compareTo(FUSE_TIME) < 0 ? (double) duration.toMillis() / FUSE_TIME.toMillis() : 1;
}
double getRemainingTimeRelative()
{
Duration duration = Duration.between(creationTime, Instant.now());
return duration.compareTo(PLANT_TIME) < 0 ? (double) duration.toMillis() / PLANT_TIME.toMillis() : 1;
}
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright (c) 2018, Unmoon <https://github.com/Unmoon>
* 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.blastmine;
import com.google.common.collect.ImmutableSet;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.util.Map;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.ItemID;
import net.runelite.api.NullObjectID;
import net.runelite.api.ObjectID;
import net.runelite.api.Perspective;
import net.runelite.api.Point;
import net.runelite.api.Tile;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.widgets.Widget;
import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.components.ProgressPieComponent;
public class BlastMineRockOverlay extends Overlay
{
private static final int MAX_DISTANCE = 16;
private static final int WARNING_DISTANCE = 2;
private static final ImmutableSet<Integer> WALL_OBJECTS = ImmutableSet.of(
NullObjectID.NULL_28570, NullObjectID.NULL_28571, NullObjectID.NULL_28572, NullObjectID.NULL_28573, NullObjectID.NULL_28574,
NullObjectID.NULL_28575, NullObjectID.NULL_28576, NullObjectID.NULL_28577, NullObjectID.NULL_28578,
ObjectID.HARD_ROCK, ObjectID.HARD_ROCK_28580, ObjectID.CAVITY, ObjectID.CAVITY_28582,
ObjectID.POT_OF_DYNAMITE, ObjectID.POT_OF_DYNAMITE_28584, ObjectID.POT_OF_DYNAMITE_28585, ObjectID.POT_OF_DYNAMITE_28586,
ObjectID.SHATTERED_ROCKFACE, ObjectID.SHATTERED_ROCKFACE_28588);
private final Client client;
private final BlastMinePlugin plugin;
private final BlastMinePluginConfig config;
private final BufferedImage chiselIcon;
private final BufferedImage dynamiteIcon;
private final BufferedImage tinderboxIcon;
@Inject
private BlastMineRockOverlay(Client client, BlastMinePlugin plugin, BlastMinePluginConfig config, ItemManager itemManager)
{
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_SCENE);
this.client = client;
this.plugin = plugin;
this.config = config;
chiselIcon = itemManager.getImage(ItemID.CHISEL);
dynamiteIcon = itemManager.getImage(ItemID.DYNAMITE);
tinderboxIcon = itemManager.getImage(ItemID.TINDERBOX);
}
@Override
public Dimension render(Graphics2D graphics)
{
Map<WorldPoint, BlastMineRock> rocks = plugin.getRocks();
if (rocks.isEmpty())
{
return null;
}
final Tile[][][] tiles = client.getScene().getTiles();
final Widget viewport = client.getViewportWidget();
for (final BlastMineRock rock : rocks.values())
{
if (viewport == null ||
rock.getGameObject().getCanvasLocation() == null ||
rock.getGameObject().getWorldLocation().distanceTo(client.getLocalPlayer().getWorldLocation()) > MAX_DISTANCE)
{
continue;
}
switch (rock.getType())
{
case NORMAL:
drawIconOnRock(graphics, rock, chiselIcon);
break;
case CHISELED:
drawIconOnRock(graphics, rock, dynamiteIcon);
break;
case LOADED:
drawIconOnRock(graphics, rock, tinderboxIcon);
break;
case LIT:
drawTimerOnRock(graphics, rock, config.getTimerColor());
drawAreaWarning(graphics, rock, config.getWarningColor(), tiles);
break;
}
}
return null;
}
private void drawIconOnRock(Graphics2D graphics, BlastMineRock rock, BufferedImage icon)
{
if (!config.showRockIconOverlay())
{
return;
}
Point loc = Perspective.getCanvasImageLocation(client, rock.getGameObject().getLocalLocation(), icon, 150);
if (loc != null)
{
graphics.drawImage(icon, loc.getX(), loc.getY(), null);
}
}
private void drawTimerOnRock(Graphics2D graphics, BlastMineRock rock, Color color)
{
if (!config.showTimerOverlay())
{
return;
}
Point loc = Perspective.localToCanvas(client, rock.getGameObject().getLocalLocation(), rock.getGameObject().getPlane(), 150);
if (loc != null)
{
final double timeLeft = 1 - rock.getRemainingFuseTimeRelative();
final ProgressPieComponent pie = new ProgressPieComponent();
pie.setFill(color);
pie.setBorderColor(color);
pie.setPosition(loc);
pie.setProgress(timeLeft);
pie.render(graphics);
}
}
private void drawAreaWarning(Graphics2D graphics, BlastMineRock rock, Color color, Tile[][][] tiles)
{
if (!config.showWarningOverlay())
{
return;
}
final int z = client.getPlane();
int x = rock.getGameObject().getLocalLocation().getX() / Perspective.LOCAL_TILE_SIZE;
int y = rock.getGameObject().getLocalLocation().getY() / Perspective.LOCAL_TILE_SIZE;
final int orientation = tiles[z][x][y].getWallObject().getOrientationA();
switch (orientation) //calculate explosion around the tile in front of the wall
{
case 1:
x--;
break;
case 4:
x++;
break;
case 8:
y--;
break;
default:
y++;
}
for (int i = -WARNING_DISTANCE; i <= WARNING_DISTANCE; i++)
{
for (int j = -WARNING_DISTANCE; j <= WARNING_DISTANCE; j++)
{
final GameObject gameObject = tiles[z][x + i][y + j].getGameObjects()[0];
//check if tile is empty, or is a wall...
if (gameObject == null || !WALL_OBJECTS.contains(gameObject.getId()))
{
final LocalPoint localTile = new LocalPoint(
(x + i) * Perspective.LOCAL_TILE_SIZE + Perspective.LOCAL_TILE_SIZE / 2,
(y + j) * Perspective.LOCAL_TILE_SIZE + Perspective.LOCAL_TILE_SIZE / 2);
final Polygon poly = Perspective.getCanvasTilePoly(client, localTile);
if (poly != null)
{
graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 100));
graphics.fillPolygon(poly);
}
}
}
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2018, Unmoon <https://github.com/Unmoon>
* 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.blastmine;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import lombok.Getter;
import net.runelite.api.ObjectID;
public enum BlastMineRockType
{
NORMAL(ObjectID.HARD_ROCK, ObjectID.HARD_ROCK_28580),
CHISELED(ObjectID.CAVITY, ObjectID.CAVITY_28582),
LOADED(ObjectID.POT_OF_DYNAMITE, ObjectID.POT_OF_DYNAMITE_28584),
LIT(ObjectID.POT_OF_DYNAMITE_28585, ObjectID.POT_OF_DYNAMITE_28586),
EXPLODED(ObjectID.SHATTERED_ROCKFACE, ObjectID.SHATTERED_ROCKFACE_28588);
private static final Map<Integer, BlastMineRockType> rockTypes;
static
{
ImmutableMap.Builder<Integer, BlastMineRockType> builder = new ImmutableMap.Builder<>();
for (BlastMineRockType type : values())
{
for (int spotId : type.getObjectIds())
{
builder.put(spotId, type);
}
}
rockTypes = builder.build();
}
@Getter
private final int[] objectIds;
BlastMineRockType(int... objectIds)
{
this.objectIds = objectIds;
}
public static BlastMineRockType getRockType(int objectId)
{
return rockTypes.get(objectId);
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2018 Kamiel
* 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.boosts;
import java.awt.Color;
import java.awt.image.BufferedImage;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.Skill;
import net.runelite.client.ui.overlay.infobox.InfoBox;
import net.runelite.client.ui.overlay.infobox.InfoBoxPriority;
public class BoostIndicator extends InfoBox
{
private final BoostsPlugin plugin;
private final BoostsConfig config;
private final Client client;
@Getter
private final Skill skill;
BoostIndicator(Skill skill, BufferedImage image, BoostsPlugin plugin, Client client, BoostsConfig config)
{
super(image, plugin);
this.plugin = plugin;
this.config = config;
this.client = client;
this.skill = skill;
setTooltip(skill.getName() + " boost");
setPriority(InfoBoxPriority.HIGH);
}
@Override
public String getText()
{
if (!config.useRelativeBoost())
{
return String.valueOf(client.getBoostedSkillLevel(skill));
}
int boost = client.getBoostedSkillLevel(skill) - client.getRealSkillLevel(skill);
String text = String.valueOf(boost);
if (boost > 0)
{
text = "+" + text;
}
return text;
}
@Override
public Color getTextColor()
{
int boosted = client.getBoostedSkillLevel(skill),
base = client.getRealSkillLevel(skill);
if (boosted < base)
{
return new Color(238, 51, 51);
}
return boosted - base <= config.boostThreshold() ? Color.YELLOW : Color.GREEN;
}
@Override
public boolean render()
{
return config.displayInfoboxes() && plugin.canShowBoosts() && plugin.getSkillsToDisplay().contains(getSkill());
}
@Override
public String getName()
{
return "Boost " + skill.getName();
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2017, Seth <Sethtroll3@gmail.com>
* 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.boosts;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("boosts")
public interface BoostsConfig extends Config
{
enum DisplayChangeMode
{
ALWAYS,
BOOSTED,
NEVER
}
enum DisplayBoosts
{
NONE,
COMBAT,
NON_COMBAT,
BOTH
}
@ConfigItem(
keyName = "displayBoosts",
name = "Display Boosts",
description = "Configures which skill boosts to display",
position = 1
)
default DisplayBoosts displayBoosts()
{
return DisplayBoosts.BOTH;
}
@ConfigItem(
keyName = "relativeBoost",
name = "Use Relative Boosts",
description = "Configures whether or not relative boost is used",
position = 2
)
default boolean useRelativeBoost()
{
return false;
}
@ConfigItem(
keyName = "displayIndicators",
name = "Display as infoboxes",
description = "Configures whether or not to display the boost as infoboxes",
position = 3
)
default boolean displayInfoboxes()
{
return false;
}
@ConfigItem(
keyName = "displayNextBuffChange",
name = "Display next buff change",
description = "Configures whether or not to display when the next buffed stat change will be",
position = 4
)
default DisplayChangeMode displayNextBuffChange()
{
return DisplayChangeMode.BOOSTED;
}
@ConfigItem(
keyName = "displayNextDebuffChange",
name = "Display next debuff change",
description = "Configures whether or not to display when the next debuffed stat change will be",
position = 5
)
default DisplayChangeMode displayNextDebuffChange()
{
return DisplayChangeMode.NEVER;
}
@ConfigItem(
keyName = "boostThreshold",
name = "Boost amount threshold",
description = "The threshold at which boosted levels will be displayed in a different color. A value of 0 will disable the feature.",
position = 6
)
default int boostThreshold()
{
return 0;
}
@ConfigItem(
keyName = "notifyOnBoost",
name = "Notify on boost threshold",
description = "Configures whether or not a notification will be sent for boosted stats.",
position = 7
)
default boolean notifyOnBoost()
{
return true;
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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.boosts;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.util.Set;
import javax.inject.Inject;
import net.runelite.api.Client;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
import net.runelite.api.Skill;
import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
import net.runelite.client.ui.overlay.components.LineComponent;
import net.runelite.client.util.ColorUtil;
class BoostsOverlay extends OverlayPanel
{
private final Client client;
private final BoostsConfig config;
private final BoostsPlugin plugin;
@Inject
private BoostsOverlay(Client client, BoostsConfig config, BoostsPlugin plugin)
{
super(plugin);
this.plugin = plugin;
this.client = client;
this.config = config;
setPosition(OverlayPosition.TOP_LEFT);
setPriority(OverlayPriority.MED);
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Boosts overlay"));
}
@Override
public Dimension render(Graphics2D graphics)
{
if (config.displayInfoboxes())
{
return null;
}
int nextChange = plugin.getChangeDownTicks();
if (nextChange != -1)
{
panelComponent.getChildren().add(LineComponent.builder()
.left("Next + restore in")
.right(String.valueOf(plugin.getChangeTime(nextChange)))
.build());
}
nextChange = plugin.getChangeUpTicks();
if (nextChange != -1)
{
panelComponent.getChildren().add(LineComponent.builder()
.left("Next - restore in")
.right(String.valueOf(plugin.getChangeTime(nextChange)))
.build());
}
final Set<Skill> boostedSkills = plugin.getSkillsToDisplay();
if (boostedSkills.isEmpty())
{
return super.render(graphics);
}
if (plugin.canShowBoosts())
{
for (Skill skill : boostedSkills)
{
final int boosted = client.getBoostedSkillLevel(skill);
final int base = client.getRealSkillLevel(skill);
final int boost = boosted - base;
final Color strColor = getTextColor(boost);
String str;
if (config.useRelativeBoost())
{
str = String.valueOf(boost);
if (boost > 0)
{
str = "+" + str;
}
}
else
{
str = ColorUtil.prependColorTag(Integer.toString(boosted), strColor)
+ ColorUtil.prependColorTag("/" + base, Color.WHITE);
}
panelComponent.getChildren().add(LineComponent.builder()
.left(skill.getName())
.right(str)
.rightColor(strColor)
.build());
}
}
return super.render(graphics);
}
private Color getTextColor(int boost)
{
if (boost < 0)
{
return new Color(238, 51, 51);
}
return boost <= config.boostThreshold() ? Color.YELLOW : Color.GREEN;
}
}

View File

@@ -0,0 +1,396 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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.boosts;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Provides;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.Prayer;
import net.runelite.api.Skill;
import net.runelite.client.events.ConfigChanged;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.StatChanged;
import net.runelite.client.Notifier;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.SkillIconManager;
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;
@PluginDescriptor(
name = "Boosts Information",
description = "Show combat and/or skill boost information",
tags = {"combat", "notifications", "skilling", "overlay"}
)
@Singleton
public class BoostsPlugin extends Plugin
{
private static final Set<Skill> BOOSTABLE_COMBAT_SKILLS = ImmutableSet.of(
Skill.ATTACK,
Skill.STRENGTH,
Skill.DEFENCE,
Skill.RANGED,
Skill.MAGIC);
private static final Set<Skill> BOOSTABLE_NON_COMBAT_SKILLS = ImmutableSet.of(
Skill.MINING, Skill.AGILITY, Skill.SMITHING, Skill.HERBLORE, Skill.FISHING, Skill.THIEVING,
Skill.COOKING, Skill.CRAFTING, Skill.FIREMAKING, Skill.FLETCHING, Skill.WOODCUTTING, Skill.RUNECRAFT,
Skill.SLAYER, Skill.FARMING, Skill.CONSTRUCTION, Skill.HUNTER);
@Inject
private Notifier notifier;
@Inject
private Client client;
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private OverlayManager overlayManager;
@Inject
private BoostsOverlay boostsOverlay;
@Inject
private BoostsConfig config;
@Inject
private SkillIconManager skillIconManager;
@Getter
private final Set<Skill> skillsToDisplay = EnumSet.noneOf(Skill.class);
private final Set<Skill> shownSkills = EnumSet.noneOf(Skill.class);
private boolean isChangedDown = false;
private boolean isChangedUp = false;
private final int[] lastSkillLevels = new int[Skill.values().length - 1];
private int lastChangeDown = -1;
private int lastChangeUp = -1;
private boolean preserveBeenActive = false;
private long lastTickMillis;
@Provides
BoostsConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(BoostsConfig.class);
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(boostsOverlay);
updateShownSkills();
Arrays.fill(lastSkillLevels, -1);
// Add infoboxes for everything at startup and then determine inside if it will be rendered
infoBoxManager.addInfoBox(new StatChangeIndicator(true, ImageUtil.getResourceStreamFromClass(getClass(), "debuffed.png"), this, config));
infoBoxManager.addInfoBox(new StatChangeIndicator(false, ImageUtil.getResourceStreamFromClass(getClass(), "buffed.png"), this, config));
for (final Skill skill : Skill.values())
{
if (skill != Skill.OVERALL)
{
infoBoxManager.addInfoBox(new BoostIndicator(skill, skillIconManager.getSkillImage(skill), this, client, config));
}
}
}
@Override
protected void shutDown() throws Exception
{
overlayManager.remove(boostsOverlay);
infoBoxManager.removeIf(t -> t instanceof BoostIndicator || t instanceof StatChangeIndicator);
preserveBeenActive = false;
lastChangeDown = -1;
lastChangeUp = -1;
isChangedUp = false;
isChangedDown = false;
skillsToDisplay.clear();
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
switch (event.getGameState())
{
case LOGIN_SCREEN:
case HOPPING:
// After world hop and log out timers are in undefined state so just reset
lastChangeDown = -1;
lastChangeUp = -1;
}
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (!event.getGroup().equals("boosts"))
{
return;
}
updateShownSkills();
if (config.displayNextBuffChange() == BoostsConfig.DisplayChangeMode.NEVER)
{
lastChangeDown = -1;
}
if (config.displayNextDebuffChange() == BoostsConfig.DisplayChangeMode.NEVER)
{
lastChangeUp = -1;
}
}
@Subscribe
public void onStatChanged(StatChanged statChanged)
{
Skill skill = statChanged.getSkill();
if (!BOOSTABLE_COMBAT_SKILLS.contains(skill) && !BOOSTABLE_NON_COMBAT_SKILLS.contains(skill))
{
return;
}
int skillIdx = skill.ordinal();
int last = lastSkillLevels[skillIdx];
int cur = client.getBoostedSkillLevel(skill);
if (cur == last - 1)
{
// Stat was restored down (from buff)
lastChangeDown = client.getTickCount();
}
if (cur == last + 1)
{
// Stat was restored up (from debuff)
lastChangeUp = client.getTickCount();
}
lastSkillLevels[skillIdx] = cur;
updateBoostedStats();
int boostThreshold = config.boostThreshold();
if (boostThreshold != 0 && config.notifyOnBoost())
{
int real = client.getRealSkillLevel(skill);
int lastBoost = last - real;
int boost = cur - real;
if (boost <= boostThreshold && boostThreshold < lastBoost)
{
notifier.notify(skill.getName() + " level is getting low!");
}
}
}
@Subscribe
public void onGameTick(GameTick event)
{
lastTickMillis = System.currentTimeMillis();
if (getChangeUpTicks() <= 0)
{
switch (config.displayNextDebuffChange())
{
case ALWAYS:
if (lastChangeUp != -1)
{
lastChangeUp = client.getTickCount();
}
break;
case BOOSTED:
case NEVER:
lastChangeUp = -1;
break;
}
}
if (getChangeDownTicks() <= 0)
{
switch (config.displayNextBuffChange())
{
case ALWAYS:
if (lastChangeDown != -1)
{
lastChangeDown = client.getTickCount();
}
break;
case BOOSTED:
case NEVER:
lastChangeDown = -1;
break;
}
}
}
private void updateShownSkills()
{
switch (config.displayBoosts())
{
case NONE:
shownSkills.removeAll(BOOSTABLE_COMBAT_SKILLS);
shownSkills.removeAll(BOOSTABLE_NON_COMBAT_SKILLS);
break;
case COMBAT:
shownSkills.addAll(BOOSTABLE_COMBAT_SKILLS);
shownSkills.removeAll(BOOSTABLE_NON_COMBAT_SKILLS);
break;
case NON_COMBAT:
shownSkills.removeAll(BOOSTABLE_COMBAT_SKILLS);
shownSkills.addAll(BOOSTABLE_NON_COMBAT_SKILLS);
break;
case BOTH:
shownSkills.addAll(BOOSTABLE_COMBAT_SKILLS);
shownSkills.addAll(BOOSTABLE_NON_COMBAT_SKILLS);
break;
}
updateBoostedStats();
}
private void updateBoostedStats()
{
// Reset is boosted
isChangedDown = false;
isChangedUp = false;
skillsToDisplay.clear();
// Check if we are still boosted
for (final Skill skill : Skill.values())
{
if (!shownSkills.contains(skill))
{
continue;
}
final int boosted = client.getBoostedSkillLevel(skill);
final int base = client.getRealSkillLevel(skill);
if (boosted > base)
{
isChangedUp = true;
}
else if (boosted < base)
{
isChangedDown = true;
}
if (boosted != base)
{
skillsToDisplay.add(skill);
}
}
}
boolean canShowBoosts()
{
return isChangedDown || isChangedUp;
}
/**
* Calculates the amount of time until boosted stats decay,
* accounting for the effect of preserve prayer.
* Preserve extends the time of boosted stats by 50% while active.
* The length of a boost is split into 4 sections of 15 seconds each.
* If the preserve prayer is active for the entire duration of the final
* section it will "activate" adding an additional 15 second section
* to the boost timing. If again the preserve prayer is active for that
* entire section a second 15 second section will be added.
*
* Preserve is only required to be on for the 4th and 5th sections of the boost timer
* to gain full effect (seconds 45-75).
*
* @return integer value in ticks until next boost change
*/
int getChangeDownTicks()
{
if (lastChangeDown == -1 ||
config.displayNextBuffChange() == BoostsConfig.DisplayChangeMode.NEVER ||
(config.displayNextBuffChange() == BoostsConfig.DisplayChangeMode.BOOSTED && !isChangedUp))
{
return -1;
}
int ticksSinceChange = client.getTickCount() - lastChangeDown;
boolean isPreserveActive = client.isPrayerActive(Prayer.PRESERVE);
if ((isPreserveActive && (ticksSinceChange < 75 || preserveBeenActive)) || ticksSinceChange > 125)
{
preserveBeenActive = true;
return 150 - ticksSinceChange;
}
preserveBeenActive = false;
return (ticksSinceChange > 100) ? 125 - ticksSinceChange : 100 - ticksSinceChange;
}
/**
* Restoration from debuff is separate timer as restoration from buff because of preserve messing up the buff timer.
* Restoration timer is always in 100 tick cycles.
*
* @return integer value in ticks until next stat restoration up
*/
int getChangeUpTicks()
{
if (lastChangeUp == -1 ||
config.displayNextDebuffChange() == BoostsConfig.DisplayChangeMode.NEVER ||
(config.displayNextDebuffChange() == BoostsConfig.DisplayChangeMode.BOOSTED && !isChangedDown))
{
return -1;
}
int ticksSinceChange = client.getTickCount() - lastChangeUp;
return 100 - ticksSinceChange;
}
/**
* Converts tick-based time to accurate second time
* @param time tick-based time
* @return second-based time
*/
int getChangeTime(final int time)
{
final long diff = System.currentTimeMillis() - lastTickMillis;
return time != -1 ? (int)((time * Constants.GAME_TICK_LENGTH - diff) / 1000d) : time;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2018, Seth <http://github.com/sethtroll>
* 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.boosts;
import java.awt.Color;
import java.awt.image.BufferedImage;
import net.runelite.client.ui.overlay.infobox.InfoBox;
import net.runelite.client.ui.overlay.infobox.InfoBoxPriority;
public class StatChangeIndicator extends InfoBox
{
private final boolean up;
private final BoostsPlugin plugin;
private final BoostsConfig config;
StatChangeIndicator(boolean up, BufferedImage image, BoostsPlugin plugin, BoostsConfig config)
{
super(image, plugin);
this.up = up;
this.plugin = plugin;
this.config = config;
setPriority(InfoBoxPriority.MED);
setTooltip(up ? "Next debuff change" : "Next buff change");
}
@Override
public String getText()
{
return String.format("%02d", plugin.getChangeTime(up ? plugin.getChangeUpTicks() : plugin.getChangeDownTicks()));
}
@Override
public Color getTextColor()
{
return (up ? plugin.getChangeUpTicks() : plugin.getChangeDownTicks()) < 10 ? Color.RED.brighter() : Color.WHITE;
}
@Override
public boolean render()
{
final int time = up ? plugin.getChangeUpTicks() : plugin.getChangeDownTicks();
return config.displayInfoboxes() && time != -1;
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (c) 2016-2017, Cameron Moberg <Moberg@tuta.io>
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.bosstimer;
import com.google.common.collect.ImmutableMap;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import net.runelite.api.ItemID;
import net.runelite.api.NpcID;
enum Boss
{
GENERAL_GRAARDOR(NpcID.GENERAL_GRAARDOR, 90, ChronoUnit.SECONDS, ItemID.PET_GENERAL_GRAARDOR),
KRIL_TSUTSAROTH(NpcID.KRIL_TSUTSAROTH, 90, ChronoUnit.SECONDS, ItemID.PET_KRIL_TSUTSAROTH),
KREEARRA(NpcID.KREEARRA, 90, ChronoUnit.SECONDS, ItemID.PET_KREEARRA),
COMMANDER_ZILYANA(NpcID.COMMANDER_ZILYANA, 90, ChronoUnit.SECONDS, ItemID.PET_ZILYANA),
CALLISTO(NpcID.CALLISTO_6609, 30, ChronoUnit.SECONDS, ItemID.CALLISTO_CUB),
CHAOS_ELEMENTAL(NpcID.CHAOS_ELEMENTAL, 60, ChronoUnit.SECONDS, ItemID.PET_CHAOS_ELEMENTAL),
CHAOS_FANATIC(NpcID.CHAOS_FANATIC, 30, ChronoUnit.SECONDS, ItemID.ANCIENT_STAFF),
CRAZY_ARCHAEOLOGIST(NpcID.CRAZY_ARCHAEOLOGIST, 30, ChronoUnit.SECONDS, ItemID.FEDORA),
KING_BLACK_DRAGON(NpcID.KING_BLACK_DRAGON, 9, ChronoUnit.SECONDS, ItemID.PRINCE_BLACK_DRAGON),
SCORPIA(NpcID.SCORPIA, 10, ChronoUnit.SECONDS, ItemID.SCORPIAS_OFFSPRING),
VENENATIS(NpcID.VENENATIS_6610, 30, ChronoUnit.SECONDS, ItemID.VENENATIS_SPIDERLING),
VETION(NpcID.VETION_REBORN, 30, ChronoUnit.SECONDS, ItemID.VETION_JR),
DAGANNOTH_PRIME(NpcID.DAGANNOTH_PRIME, 90, ChronoUnit.SECONDS, ItemID.PET_DAGANNOTH_PRIME),
DAGANNOTH_REX(NpcID.DAGANNOTH_REX, 90, ChronoUnit.SECONDS, ItemID.PET_DAGANNOTH_REX),
DAGANNOTH_SUPREME(NpcID.DAGANNOTH_SUPREME, 90, ChronoUnit.SECONDS, ItemID.PET_DAGANNOTH_SUPREME),
CORPOREAL_BEAST(NpcID.CORPOREAL_BEAST, 30, ChronoUnit.SECONDS, ItemID.PET_DARK_CORE),
GIANT_MOLE(NpcID.GIANT_MOLE, 9000, ChronoUnit.MILLIS, ItemID.BABY_MOLE),
DERANGED_ARCHAEOLOGIST(NpcID.DERANGED_ARCHAEOLOGIST, 29400, ChronoUnit.MILLIS, ItemID.UNIDENTIFIED_LARGE_FOSSIL),
CERBERUS(NpcID.CERBERUS, 8400, ChronoUnit.MILLIS, ItemID.HELLPUPPY),
THERMONUCLEAR_SMOKE_DEVIL(NpcID.THERMONUCLEAR_SMOKE_DEVIL, 8400, ChronoUnit.MILLIS, ItemID.PET_SMOKE_DEVIL),
KRAKEN(NpcID.KRAKEN, 8400, ChronoUnit.MILLIS, ItemID.PET_KRAKEN),
KALPHITE_QUEEN(NpcID.KALPHITE_QUEEN_965, 30, ChronoUnit.SECONDS, ItemID.KALPHITE_PRINCESS),
DUSK(NpcID.DUSK_7889, 2, ChronoUnit.MINUTES, ItemID.NOON),
ALCHEMICAL_HYDRA(NpcID.ALCHEMICAL_HYDRA_8622, 25200, ChronoUnit.MILLIS, ItemID.IKKLE_HYDRA),
SARACHNIS(NpcID.SARACHNIS, 10, ChronoUnit.SECONDS, ItemID.SRARACHA),
ZALCANO(NpcID.ZALCANO_9050, 21600, ChronoUnit.MILLIS, ItemID.SMOLCANO);
private static final Map<Integer, Boss> bosses;
private final int id;
private final Duration spawnTime;
private final int itemSpriteId;
static
{
ImmutableMap.Builder<Integer, Boss> builder = new ImmutableMap.Builder<>();
for (Boss boss : values())
{
builder.put(boss.getId(), boss);
}
bosses = builder.build();
}
Boss(int id, long period, ChronoUnit unit, int itemSpriteId)
{
this.id = id;
this.spawnTime = Duration.of(period, unit);
this.itemSpriteId = itemSpriteId;
}
public int getId()
{
return id;
}
public Duration getSpawnTime()
{
return spawnTime;
}
public int getItemSpriteId()
{
return itemSpriteId;
}
public static Boss find(int id)
{
return bosses.get(id);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2016-2017, Cameron Moberg <Moberg@tuta.io>
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.bosstimer;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.NPC;
import net.runelite.api.events.NpcDespawned;
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.infobox.InfoBoxManager;
@PluginDescriptor(
name = "Boss Timers",
description = "Show boss spawn timer overlays",
tags = {"combat", "pve", "overlay", "spawn"}
)
@Slf4j
public class BossTimersPlugin extends Plugin
{
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private ItemManager itemManager;
@Override
protected void shutDown() throws Exception
{
infoBoxManager.removeIf(t -> t instanceof RespawnTimer);
}
@Subscribe
public void onNpcDespawned(NpcDespawned npcDespawned)
{
NPC npc = npcDespawned.getNpc();
if (!npc.isDead())
{
return;
}
int npcId = npc.getId();
Boss boss = Boss.find(npcId);
if (boss == null)
{
return;
}
// remove existing timer
infoBoxManager.removeIf(t -> t instanceof RespawnTimer && ((RespawnTimer) t).getBoss() == boss);
log.debug("Creating spawn timer for {} ({} seconds)", npc.getName(), boss.getSpawnTime());
RespawnTimer timer = new RespawnTimer(boss, itemManager.getImage(boss.getItemSpriteId()), this);
timer.setTooltip(npc.getName());
infoBoxManager.addInfoBox(timer);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.bosstimer;
import java.awt.image.BufferedImage;
import java.time.temporal.ChronoUnit;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.ui.overlay.infobox.Timer;
class RespawnTimer extends Timer
{
private final Boss boss;
public RespawnTimer(Boss boss, BufferedImage bossImage, Plugin plugin)
{
super(boss.getSpawnTime().toMillis(), ChronoUnit.MILLIS, bossImage, plugin);
this.boss = boss;
}
public Boss getBoss()
{
return boss;
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 2016-2018, Seth <Sethtroll3@gmail.com>
* 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.cannon;
import java.awt.Color;
import net.runelite.client.config.Alpha;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Range;
import static net.runelite.client.plugins.cannon.CannonPlugin.MAX_CBALLS;
@ConfigGroup("cannon")
public interface CannonConfig extends Config
{
@ConfigItem(
keyName = "showEmptyCannonNotification",
name = "Enable cannon notifications",
description = "Configures whether to notify you when your cannon is low on cannonballs",
position = 1
)
default boolean showCannonNotifications()
{
return true;
}
@Range(
max = MAX_CBALLS
)
@ConfigItem(
keyName = "lowWarningThreshold",
name = "Low Warning Threshold",
description = "Configures the number of cannonballs remaining before a notification is sent. <br>Regardless of this value, a notification will still be sent when your cannon is empty.",
position = 2
)
default int lowWarningThreshold()
{
return 0;
}
@ConfigItem(
keyName = "showInfobox",
name = "Show Cannonball infobox",
description = "Configures whether to show the cannonballs in an infobox",
position = 3
)
default boolean showInfobox()
{
return false;
}
@ConfigItem(
keyName = "showDoubleHitSpot",
name = "Show double hit spots",
description = "Configures whether to show the NPC double hit spot",
position = 4
)
default boolean showDoubleHitSpot()
{
return false;
}
@Alpha
@ConfigItem(
keyName = "highlightDoubleHitColor",
name = "Color of double hit spots",
description = "Configures the highlight color of double hit spots",
position = 5
)
default Color highlightDoubleHitColor()
{
return Color.RED;
}
@ConfigItem(
keyName = "showCannonSpots",
name = "Show common cannon spots",
description = "Configures whether to show common cannon spots or not",
position = 6
)
default boolean showCannonSpots()
{
return true;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.cannon;
import java.awt.Color;
import java.awt.image.BufferedImage;
import net.runelite.client.ui.overlay.infobox.InfoBox;
class CannonCounter extends InfoBox
{
private final CannonPlugin plugin;
CannonCounter(BufferedImage img, CannonPlugin plugin)
{
super(img, plugin);
this.plugin = plugin;
}
@Override
public String getText()
{
return String.valueOf(plugin.getCballsLeft());
}
@Override
public Color getTextColor()
{
return plugin.getStateColor();
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2016-2018, Seth <Sethtroll3@gmail.com>
* 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.cannon;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Polygon;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Perspective;
import static net.runelite.api.Perspective.LOCAL_TILE_SIZE;
import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
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.ui.overlay.components.TextComponent;
class CannonOverlay extends Overlay
{
private static final int MAX_DISTANCE = 2500;
private final Client client;
private final CannonConfig config;
private final CannonPlugin plugin;
private final TextComponent textComponent = new TextComponent();
@Inject
CannonOverlay(Client client, CannonConfig config, CannonPlugin plugin)
{
setPosition(OverlayPosition.DYNAMIC);
setPriority(OverlayPriority.MED);
this.client = client;
this.config = config;
this.plugin = plugin;
}
@Override
public Dimension render(Graphics2D graphics)
{
if (!plugin.isCannonPlaced() || plugin.getCannonPosition() == null || plugin.getCannonWorld() != client.getWorld())
{
return null;
}
LocalPoint cannonPoint = LocalPoint.fromWorld(client, plugin.getCannonPosition());
if (cannonPoint == null)
{
return null;
}
LocalPoint localLocation = client.getLocalPlayer().getLocalLocation();
if (localLocation.distanceTo(cannonPoint) <= MAX_DISTANCE)
{
Point cannonLoc = Perspective.getCanvasTextLocation(client,
graphics,
cannonPoint,
String.valueOf(plugin.getCballsLeft()), 150);
if (cannonLoc != null)
{
textComponent.setText(String.valueOf(plugin.getCballsLeft()));
textComponent.setPosition(new java.awt.Point(cannonLoc.getX(), cannonLoc.getY()));
textComponent.setColor(plugin.getStateColor());
textComponent.render(graphics);
}
if (config.showDoubleHitSpot())
{
Color color = config.highlightDoubleHitColor();
drawDoubleHitSpots(graphics, cannonPoint, color);
}
}
return null;
}
/**
* Draw the double hit spots on a 6 by 6 grid around the cannon
* @param startTile The position of the cannon
*/
private void drawDoubleHitSpots(Graphics2D graphics, LocalPoint startTile, Color color)
{
for (int x = -3; x <= 3; x++)
{
for (int y = -3; y <= 3; y++)
{
if (y != 1 && x != 1 && y != -1 && x != -1)
{
continue;
}
//Ignore center square
if (y >= -1 && y <= 1 && x >= -1 && x <= 1)
{
continue;
}
int xPos = startTile.getX() - (x * LOCAL_TILE_SIZE);
int yPos = startTile.getY() - (y * LOCAL_TILE_SIZE);
LocalPoint marker = new LocalPoint(xPos, yPos);
Polygon poly = Perspective.getCanvasTilePoly(client, marker);
if (poly == null)
{
continue;
}
OverlayUtil.renderPolygon(graphics, poly, color);
}
}
}
}

View File

@@ -0,0 +1,420 @@
/*
* Copyright (c) 2016-2018, Seth <Sethtroll3@gmail.com>
* 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.cannon;
import com.google.inject.Provides;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import lombok.Getter;
import net.runelite.api.AnimationID;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.GameState;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemID;
import static net.runelite.api.ObjectID.CANNON_BASE;
import net.runelite.api.Player;
import net.runelite.api.Projectile;
import static net.runelite.api.ProjectileID.CANNONBALL;
import static net.runelite.api.ProjectileID.GRANITE_CANNONBALL;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameObjectSpawned;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.ItemContainerChanged;
import net.runelite.api.events.ProjectileMoved;
import net.runelite.client.Notifier;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
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;
@PluginDescriptor(
name = "Cannon",
description = "Show information about cannon placement and/or amount of cannonballs",
tags = {"combat", "notifications", "ranged", "overlay"}
)
public class CannonPlugin extends Plugin
{
private static final Pattern NUMBER_PATTERN = Pattern.compile("([0-9]+)");
static final int MAX_CBALLS = 30;
private CannonCounter counter;
private boolean skipProjectileCheckThisTick;
private boolean cannonBallNotificationSent;
@Getter
private int cballsLeft;
@Getter
private boolean cannonPlaced;
@Getter
private WorldPoint cannonPosition;
@Getter
private int cannonWorld = -1;
@Getter
private GameObject cannon;
@Getter
private List<WorldPoint> spotPoints = new ArrayList<>();
@Inject
private ItemManager itemManager;
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private Notifier notifier;
@Inject
private OverlayManager overlayManager;
@Inject
private CannonOverlay cannonOverlay;
@Inject
private CannonSpotOverlay cannonSpotOverlay;
@Inject
private CannonConfig config;
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Provides
CannonConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(CannonConfig.class);
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(cannonOverlay);
overlayManager.add(cannonSpotOverlay);
}
@Override
protected void shutDown() throws Exception
{
cannonSpotOverlay.setHidden(true);
overlayManager.remove(cannonOverlay);
overlayManager.remove(cannonSpotOverlay);
cannonPlaced = false;
cannonWorld = -1;
cannonPosition = null;
cannonBallNotificationSent = false;
cballsLeft = 0;
removeCounter();
skipProjectileCheckThisTick = false;
spotPoints.clear();
}
@Subscribe
public void onItemContainerChanged(ItemContainerChanged event)
{
if (event.getItemContainer() != client.getItemContainer(InventoryID.INVENTORY))
{
return;
}
boolean hasBase = false;
boolean hasStand = false;
boolean hasBarrels = false;
boolean hasFurnace = false;
boolean hasAll = false;
if (!cannonPlaced)
{
for (Item item : event.getItemContainer().getItems())
{
if (item == null)
{
continue;
}
switch (item.getId())
{
case ItemID.CANNON_BASE:
hasBase = true;
break;
case ItemID.CANNON_STAND:
hasStand = true;
break;
case ItemID.CANNON_BARRELS:
hasBarrels = true;
break;
case ItemID.CANNON_FURNACE:
hasFurnace = true;
break;
}
if (hasBase && hasStand && hasBarrels && hasFurnace)
{
hasAll = true;
break;
}
}
}
cannonSpotOverlay.setHidden(!hasAll);
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (event.getGroup().equals("cannon"))
{
if (!config.showInfobox())
{
removeCounter();
}
else
{
if (cannonPlaced)
{
clientThread.invoke(this::addCounter);
}
}
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
if (gameStateChanged.getGameState() != GameState.LOGGED_IN)
{
return;
}
spotPoints.clear();
for (WorldPoint spot : CannonSpots.getCannonSpots())
{
if (WorldPoint.isInScene(client, spot.getX(), spot.getY()))
{
spotPoints.add(spot);
}
}
}
@Subscribe
public void onGameObjectSpawned(GameObjectSpawned event)
{
GameObject gameObject = event.getGameObject();
Player localPlayer = client.getLocalPlayer();
if (gameObject.getId() == CANNON_BASE && !cannonPlaced)
{
if (localPlayer.getWorldLocation().distanceTo(gameObject.getWorldLocation()) <= 2
&& localPlayer.getAnimation() == AnimationID.BURYING_BONES)
{
cannonPosition = gameObject.getWorldLocation();
cannonWorld = client.getWorld();
cannon = gameObject;
}
}
}
@Subscribe
public void onProjectileMoved(ProjectileMoved event)
{
Projectile projectile = event.getProjectile();
if ((projectile.getId() == CANNONBALL || projectile.getId() == GRANITE_CANNONBALL) && cannonPosition != null && cannonWorld == client.getWorld())
{
WorldPoint projectileLoc = WorldPoint.fromLocal(client, projectile.getX1(), projectile.getY1(), client.getPlane());
//Check to see if projectile x,y is 0 else it will continuously decrease while ball is flying.
if (projectileLoc.equals(cannonPosition) && projectile.getX() == 0 && projectile.getY() == 0)
{
// When there's a chat message about cannon reloaded/unloaded/out of ammo,
// the message event runs before the projectile event. However they run
// 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;
}
}
}
}
}
@Subscribe
public void onChatMessage(ChatMessage event)
{
if (event.getType() != ChatMessageType.SPAM && event.getType() != ChatMessageType.GAMEMESSAGE)
{
return;
}
if (event.getMessage().equals("You add the furnace."))
{
cannonPlaced = true;
addCounter();
cballsLeft = 0;
}
if (event.getMessage().contains("You pick up the cannon")
|| event.getMessage().contains("Your cannon has decayed. Speak to Nulodion to get a new one!"))
{
cannonPlaced = false;
cballsLeft = 0;
removeCounter();
}
if (event.getMessage().startsWith("You load the cannon with"))
{
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;
}
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())
{
notifier.notify("Your cannon is out of ammo!");
}
}
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;
}
}
@Subscribe
public void onGameTick(GameTick event)
{
skipProjectileCheckThisTick = false;
}
Color getStateColor()
{
if (cballsLeft > 15)
{
return Color.green;
}
else if (cballsLeft > 5)
{
return Color.orange;
}
return Color.red;
}
private void addCounter()
{
if (!config.showInfobox() || counter != null)
{
return;
}
counter = new CannonCounter(itemManager.getImage(ItemID.CANNONBALL), this);
counter.setTooltip("Cannonballs");
infoBoxManager.addInfoBox(counter);
}
private void removeCounter()
{
if (counter == null)
{
return;
}
infoBoxManager.removeInfoBox(counter);
counter = null;
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (c) 2018, Seth <https://github.com/sethtroll>
* 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.cannon;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.util.List;
import javax.inject.Inject;
import lombok.AccessLevel;
import lombok.Setter;
import net.runelite.api.Client;
import static net.runelite.api.ItemID.CANNONBALL;
import net.runelite.api.Perspective;
import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayUtil;
class CannonSpotOverlay extends Overlay
{
private static final int MAX_DISTANCE = 2350;
private final Client client;
private final CannonPlugin plugin;
private final CannonConfig config;
@Inject
private ItemManager itemManager;
@Setter(AccessLevel.PACKAGE)
private boolean hidden;
@Inject
CannonSpotOverlay(Client client, CannonPlugin plugin, CannonConfig config)
{
setPosition(OverlayPosition.DYNAMIC);
this.client = client;
this.plugin = plugin;
this.config = config;
}
@Override
public Dimension render(Graphics2D graphics)
{
List<WorldPoint> spotPoints = plugin.getSpotPoints();
if (hidden || spotPoints.isEmpty() || !config.showCannonSpots() || plugin.isCannonPlaced())
{
return null;
}
for (WorldPoint spot : spotPoints)
{
if (spot.getPlane() != client.getPlane())
{
continue;
}
LocalPoint spotPoint = LocalPoint.fromWorld(client, spot);
LocalPoint localLocation = client.getLocalPlayer().getLocalLocation();
if (spotPoint != null && localLocation.distanceTo(spotPoint) <= MAX_DISTANCE)
{
renderCannonSpot(graphics, client, spotPoint, itemManager.getImage(CANNONBALL), Color.RED);
}
}
return null;
}
private void renderCannonSpot(Graphics2D graphics, Client client, LocalPoint point, BufferedImage image, Color color)
{
//Render tile
Polygon poly = Perspective.getCanvasTilePoly(client, point);
if (poly != null)
{
OverlayUtil.renderPolygon(graphics, poly, color);
}
//Render icon
Point imageLoc = Perspective.getCanvasImageLocation(client, point, image, 0);
if (imageLoc != null)
{
OverlayUtil.renderImageLocation(graphics, imageLoc, image);
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2018, Seth <https://github.com/sethtroll>
* 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.cannon;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
import net.runelite.api.coords.WorldPoint;
enum CannonSpots
{
ABERRANT_SPECTRES(new WorldPoint(2456, 9791, 0)),
ANKOU(new WorldPoint(3177, 10193, 0)),
BANDIT(new WorldPoint(3037, 3700, 0)),
BEAR(new WorldPoint(3113, 3672, 0)),
BLACK_DEMONS(new WorldPoint(2859, 9778, 0), new WorldPoint(2841, 9791, 0), new WorldPoint(1421, 10089, 1), new WorldPoint(3174, 10154, 0), new WorldPoint(3089, 9960, 0)),
BLACK_DRAGON(new WorldPoint(3239, 10206, 0)),
BLACK_KNIGHTS(new WorldPoint(2906, 9685, 0), new WorldPoint(3053, 3852, 0)),
BLOODVELDS(new WorldPoint(2439, 9821, 0), new WorldPoint(2448, 9821, 0), new WorldPoint(2472, 9832, 0), new WorldPoint(2453, 9817, 0), new WorldPoint(3597, 9743, 0)),
BLUE_DRAGON(new WorldPoint(1933, 8973, 1)),
BRINE_RAT(new WorldPoint(2707, 10132, 0)),
CAVE_HORROR(new WorldPoint(3785, 9460, 0)),
DAGGANOTH(new WorldPoint(2524, 10020, 0)),
DARK_BEAST(new WorldPoint(1992, 4655, 0)),
DARK_WARRIOR(new WorldPoint(3030, 3632, 0)),
DUST_DEVIL(new WorldPoint(3218, 9366, 0)),
EARTH_WARRIOR(new WorldPoint(3120, 9987, 0)),
ELDER_CHAOS_DRUID(new WorldPoint(3237, 3622, 0)),
ELVES(new WorldPoint(2044, 4635, 0), new WorldPoint(3278, 6098, 0)),
FIRE_GIANTS(new WorldPoint(2393, 9782, 0), new WorldPoint(2412, 9776, 0), new WorldPoint(2401, 9780, 0), new WorldPoint(3047, 10340, 0)),
GREATER_DEMONS(new WorldPoint(1435, 10086, 2), new WorldPoint(3224, 10132, 0)),
GREEN_DRAGON(new WorldPoint(3225, 10068, 0)),
HELLHOUNDS(new WorldPoint(2431, 9776, 0), new WorldPoint(2413, 9786, 0), new WorldPoint(2783, 9686, 0), new WorldPoint(3198, 10071, 0)),
HILL_GIANT(new WorldPoint(3044, 10318, 0)),
ICE_GIANT(new WorldPoint(3207, 10164, 0)),
ICE_WARRIOR(new WorldPoint(2955, 3876, 0)),
KALPHITE(new WorldPoint(3307, 9528, 0)),
LESSER_DEMON(new WorldPoint(2838, 9559, 0), new WorldPoint(3163, 10114, 0)),
LIZARDMEN(new WorldPoint(1500, 3703, 0)),
LIZARDMEN_SHAMAN(new WorldPoint(1423, 3715, 0)),
MAGIC_AXE(new WorldPoint(3190, 3960, 0)),
MAMMOTH(new WorldPoint(3168, 3595, 0)),
MINIONS_OF_SCARABAS(new WorldPoint(3297, 9252, 0)),
ROGUE(new WorldPoint(3285, 3930, 0)),
SCORPION(new WorldPoint(3233, 10335, 0)),
SKELETON(new WorldPoint(3018, 3592, 0)),
SMOKE_DEVIL(new WorldPoint(2398, 9444, 0)),
SPIDER(new WorldPoint(3169, 3886, 0)),
SUQAHS(new WorldPoint(2114, 3943, 0)),
TROLLS(new WorldPoint(2401, 3856, 0), new WorldPoint(1242, 3517, 0)),
ZOMBIE(new WorldPoint(3172, 3677, 0));
@Getter
private static final List<WorldPoint> cannonSpots = new ArrayList<>();
static
{
for (CannonSpots cannonSpot : values())
{
cannonSpots.addAll(Arrays.asList(cannonSpot.spots));
}
}
private final WorldPoint[] spots;
CannonSpots(WorldPoint... spots)
{
this.spots = spots;
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.chatboxperformance;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.ScriptID;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.api.widgets.WidgetType;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetPositionMode;
import net.runelite.api.widgets.WidgetSizeMode;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
@PluginDescriptor(
name = "Chatbox performance",
hidden = true
)
public class ChatboxPerformancePlugin extends Plugin
{
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Override
public void startUp()
{
if (client.getGameState() == GameState.LOGGED_IN)
{
clientThread.invokeLater(() -> client.runScript(ScriptID.MESSAGE_LAYER_CLOSE, 0, 0));
}
}
@Override
public void shutDown()
{
if (client.getGameState() == GameState.LOGGED_IN)
{
clientThread.invokeLater(() -> client.runScript(ScriptID.MESSAGE_LAYER_CLOSE, 0, 0));
}
}
@Subscribe
private void onScriptCallbackEvent(ScriptCallbackEvent ev)
{
if (!"chatboxBackgroundBuilt".equals(ev.getEventName()))
{
return;
}
fixDarkBackground();
fixWhiteLines(true);
fixWhiteLines(false);
}
private void fixDarkBackground()
{
int currOpacity = 256;
int prevY = 0;
Widget[] children = client.getWidget(WidgetInfo.CHATBOX_TRANSPARENT_BACKGROUND).getDynamicChildren();
Widget prev = null;
for (Widget w : children)
{
if (w.getType() != WidgetType.RECTANGLE)
{
continue;
}
if (prev != null)
{
int relY = w.getRelativeY();
prev.setHeightMode(WidgetSizeMode.ABSOLUTE);
prev.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
prev.setRelativeY(prevY);
prev.setOriginalY(prev.getRelativeY());
prev.setHeight(relY - prevY);
prev.setOriginalHeight(prev.getHeight());
prev.setOpacity(currOpacity);
}
prevY = w.getRelativeY();
currOpacity -= 3; // Rough number, can't get exactly the same as Jagex because of rounding
prev = w;
}
if (prev != null)
{
prev.setOpacity(currOpacity);
}
}
private void fixWhiteLines(boolean upperLine)
{
int currOpacity = 256;
int prevWidth = 0;
Widget[] children = client.getWidget(WidgetInfo.CHATBOX_TRANSPARENT_LINES).getDynamicChildren();
Widget prev = null;
for (Widget w : children)
{
if (w.getType() != WidgetType.RECTANGLE)
{
continue;
}
if ((w.getRelativeY() == 0 && !upperLine) ||
(w.getRelativeY() != 0 && upperLine))
{
continue;
}
if (prev != null)
{
int width = w.getWidth();
prev.setWidthMode(WidgetSizeMode.ABSOLUTE);
prev.setRelativeX(width);
prev.setOriginalX(width);
prev.setWidth(prevWidth - width);
prev.setOriginalWidth(prev.getWidth());
prev.setOpacity(currOpacity);
}
prevWidth = w.getWidth();
currOpacity -= upperLine ? 3 : 4; // Rough numbers, can't get exactly the same as Jagex because of rounding
prev = w;
}
if (prev != null)
{
prev.setOpacity(currOpacity);
}
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.chatcommands;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Keybind;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
@ConfigGroup("chatcommands")
public interface ChatCommandsConfig extends Config
{
@ConfigItem(
position = 0,
keyName = "price",
name = "Price Command",
description = "Configures whether the Price command is enabled<br> !price [item]"
)
default boolean price()
{
return true;
}
@ConfigItem(
position = 1,
keyName = "lvl",
name = "Level Command",
description = "Configures whether the Level command is enabled<br> !lvl [skill]"
)
default boolean lvl()
{
return true;
}
@ConfigItem(
position = 2,
keyName = "clue",
name = "Clue Command",
description = "Configures whether the Clue command is enabled<br> !clues"
)
default boolean clue()
{
return true;
}
@ConfigItem(
position = 3,
keyName = "killcount",
name = "Killcount Command",
description = "Configures whether the Killcount command is enabled<br> !kc [boss]"
)
default boolean killcount()
{
return true;
}
@ConfigItem(
position = 4,
keyName = "qp",
name = "QP Command",
description = "Configures whether the quest point command is enabled<br> !qp"
)
default boolean qp()
{
return true;
}
@ConfigItem(
position = 5,
keyName = "pb",
name = "PB Command",
description = "Configures whether the personal best command is enabled<br> !pb"
)
default boolean pb()
{
return true;
}
@ConfigItem(
position = 6,
keyName = "gc",
name = "GC Command",
description = "Configures whether the Barbarian Assault High gamble count command is enabled<br> !gc"
)
default boolean gc()
{
return true;
}
@ConfigItem(
position = 7,
keyName = "duels",
name = "Duels Command",
description = "Configures whether the duel arena command is enabled<br> !duels"
)
default boolean duels()
{
return true;
}
@ConfigItem(
position = 8,
keyName = "bh",
name = "BH Command",
description = "Configures whether the Bounty Hunter - Hunter command is enabled<br> !bh"
)
default boolean bh()
{
return true;
}
@ConfigItem(
position = 9,
keyName = "bhRogue",
name = "BH Rogue Command",
description = "Configures whether the Bounty Hunter - Rogue command is enabled<br> !bhrogue"
)
default boolean bhRogue()
{
return true;
}
@ConfigItem(
position = 10,
keyName = "lms",
name = "LMS Command",
description = "Configures whether the Last Man Standing command is enabled<br> !lms"
)
default boolean lms()
{
return true;
}
@ConfigItem(
position = 11,
keyName = "lp",
name = "LP Command",
description = "Configures whether the League Points command is enabled<br> !lp"
)
default boolean lp()
{
return true;
}
@ConfigItem(
position = 12,
keyName = "clearSingleWord",
name = "Clear Single Word",
description = "Enable hot key to clear single word at a time"
)
default Keybind clearSingleWord()
{
return new Keybind(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
}
@ConfigItem(
position = 13,
keyName = "clearEntireChatBox",
name = "Clear Chat Box",
description = "Enable hotkey to clear entire chat box"
)
default Keybind clearChatBox()
{
return new Keybind(KeyEvent.VK_BACK_SPACE, InputEvent.CTRL_DOWN_MASK);
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.chatcommands;
import java.awt.event.KeyEvent;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Client;
import net.runelite.api.ScriptID;
import net.runelite.api.VarClientInt;
import net.runelite.api.VarClientStr;
import net.runelite.api.vars.InputType;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.input.KeyListener;
@Singleton
public class ChatKeyboardListener implements KeyListener
{
@Inject
private ChatCommandsConfig chatCommandsConfig;
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Override
public void keyTyped(KeyEvent e)
{
}
@Override
public void keyPressed(KeyEvent e)
{
if (chatCommandsConfig.clearSingleWord().matches(e))
{
int inputTye = client.getVar(VarClientInt.INPUT_TYPE);
String input = inputTye == InputType.NONE.getType()
? client.getVar(VarClientStr.CHATBOX_TYPED_TEXT)
: client.getVar(VarClientStr.INPUT_TEXT);
if (input != null)
{
// remove trailing space
while (input.endsWith(" "))
{
input = input.substring(0, input.length() - 1);
}
// find next word
int idx = input.lastIndexOf(' ') + 1;
final String replacement = input.substring(0, idx);
clientThread.invoke(() -> applyText(inputTye, replacement));
}
}
else if (chatCommandsConfig.clearChatBox().matches(e))
{
int inputTye = client.getVar(VarClientInt.INPUT_TYPE);
clientThread.invoke(() -> applyText(inputTye, ""));
}
}
private void applyText(int inputType, String replacement)
{
if (inputType == InputType.NONE.getType())
{
client.setVar(VarClientStr.CHATBOX_TYPED_TEXT, replacement);
client.runScript(ScriptID.CHAT_PROMPT_INIT);
}
else if (inputType == InputType.PRIVATE_MESSAGE.getType())
{
client.setVar(VarClientStr.INPUT_TEXT, replacement);
client.runScript(ScriptID.CHAT_TEXT_INPUT_REBUILD, "");
}
}
@Override
public void keyReleased(KeyEvent e)
{
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.chatcommands;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import net.runelite.api.Skill;
class SkillAbbreviations
{
private static final Map<String, String> MAP;
static
{
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
builder.put("ATK", Skill.ATTACK.getName());
builder.put("ATT", Skill.ATTACK.getName());
builder.put("DEF", Skill.DEFENCE.getName());
builder.put("STR", Skill.STRENGTH.getName());
builder.put("HEALTH", Skill.HITPOINTS.getName());
builder.put("HIT", Skill.HITPOINTS.getName());
builder.put("HITPOINT", Skill.HITPOINTS.getName());
builder.put("HP", Skill.HITPOINTS.getName());
builder.put("RANGE", Skill.RANGED.getName());
builder.put("RANGING", Skill.RANGED.getName());
builder.put("RNG", Skill.RANGED.getName());
builder.put("PRAY", Skill.PRAYER.getName());
builder.put("MAG", Skill.MAGIC.getName());
builder.put("MAGE", Skill.MAGIC.getName());
builder.put("COOK", Skill.COOKING.getName());
builder.put("WC", Skill.WOODCUTTING.getName());
builder.put("WOOD", Skill.WOODCUTTING.getName());
builder.put("WOODCUT", Skill.WOODCUTTING.getName());
builder.put("FLETCH", Skill.FLETCHING.getName());
builder.put("FISH", Skill.FISHING.getName());
builder.put("FM", Skill.FIREMAKING.getName());
builder.put("FIRE", Skill.FIREMAKING.getName());
builder.put("CRAFT", Skill.CRAFTING.getName());
builder.put("SMITH", Skill.SMITHING.getName());
builder.put("MINE", Skill.MINING.getName());
builder.put("HL", Skill.HERBLORE.getName());
builder.put("HERB", Skill.HERBLORE.getName());
builder.put("AGI", Skill.AGILITY.getName());
builder.put("AGIL", Skill.AGILITY.getName());
builder.put("THIEF", Skill.THIEVING.getName());
builder.put("SLAY", Skill.SLAYER.getName());
builder.put("FARM", Skill.FARMING.getName());
builder.put("RC", Skill.RUNECRAFT.getName());
builder.put("RUNE", Skill.RUNECRAFT.getName());
builder.put("RUNECRAFTING", Skill.RUNECRAFT.getName());
builder.put("HUNT", Skill.HUNTER.getName());
builder.put("CON", Skill.CONSTRUCTION.getName());
builder.put("CONSTRUCT", Skill.CONSTRUCTION.getName());
builder.put("ALL", Skill.OVERALL.getName());
builder.put("TOTAL", Skill.OVERALL.getName());
MAP = builder.build();
}
/**
* Takes a string representing the name of a skill, and if abbreviated,
* expands it into its full canonical name. Case-insensitive.
*
* @param abbrev Skill name that may be abbreviated.
* @return Full skill name if recognized, else the original string.
*/
static String getFullName(String abbrev)
{
return MAP.getOrDefault(abbrev.toUpperCase(), abbrev);
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright (c) 2018, Magic fTail
* Copyright (c) 2019, osrs-music-map <osrs-music-map@users.noreply.github.com>
* 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.chatfilter;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.ConfigSection;
@ConfigGroup("chatfilter")
public interface ChatFilterConfig extends Config
{
@ConfigSection(
name = "Filter Lists",
description = "Custom Word, Regex, and Username filter lists",
position = 0,
closedByDefault = true
)
String filterLists = "filterLists";
@ConfigItem(
keyName = "filteredWords",
name = "Filtered Words",
description = "List of filtered words, separated by commas",
position = 1,
section = filterLists
)
default String filteredWords()
{
return "";
}
@ConfigItem(
keyName = "filteredRegex",
name = "Filtered Regex",
description = "List of regular expressions to filter, one per line",
position = 2,
section = filterLists
)
default String filteredRegex()
{
return "";
}
@ConfigItem(
keyName = "filteredNames",
name = "Filtered Names",
description = "List of filtered names, one per line. Accepts regular expressions",
position = 3,
section = filterLists
)
default String filteredNames()
{
return "";
}
@ConfigItem(
keyName = "filterType",
name = "Filter type",
description = "Configures how the messages are filtered",
position = 4
)
default ChatFilterType filterType()
{
return ChatFilterType.CENSOR_WORDS;
}
@ConfigItem(
keyName = "filterFriends",
name = "Filter Friends",
description = "Filter your friends' messages",
position = 5
)
default boolean filterFriends()
{
return false;
}
@ConfigItem(
keyName = "filterClan",
name = "Filter Friends Chat Members",
description = "Filter your friends chat members' messages",
position = 6
)
default boolean filterFriendsChat()
{
return false;
}
@ConfigItem(
keyName = "filterLogin",
name = "Filter Logged In/Out Messages",
description = "Filter your private chat to remove logged in/out messages",
position = 7
)
default boolean filterLogin()
{
return false;
}
@ConfigItem(
keyName = "filterGameChat",
name = "Filter Game Chat",
description = "Filter your game chat messages",
position = 8
)
default boolean filterGameChat()
{
return false;
}
@ConfigItem(
keyName = "collapseGameChat",
name = "Collapse Game Chat",
description = "Collapse duplicate game chat messages into a single line",
position = 9
)
default boolean collapseGameChat()
{
return false;
}
@ConfigItem(
keyName = "collapsePlayerChat",
name = "Collapse Player Chat",
description = "Collapse duplicate player chat messages into a single line",
position = 10
)
default boolean collapsePlayerChat()
{
return false;
}
@ConfigItem(
keyName = "maxRepeatedPublicChats",
name = "Max repeated public chats",
description = "Block player chat message if repeated this many times. 0 is off",
position = 11
)
default int maxRepeatedPublicChats()
{
return 0;
}
}

View File

@@ -0,0 +1,386 @@
/*
* Copyright (c) 2018, Magic fTail
* Copyright (c) 2019, osrs-music-map <osrs-music-map@users.noreply.github.com>
* 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.chatfilter;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Provides;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.inject.Inject;
import net.runelite.api.ChatMessageType;
import static net.runelite.api.ChatMessageType.ENGINE;
import static net.runelite.api.ChatMessageType.GAMEMESSAGE;
import static net.runelite.api.ChatMessageType.ITEM_EXAMINE;
import static net.runelite.api.ChatMessageType.MODCHAT;
import static net.runelite.api.ChatMessageType.NPC_EXAMINE;
import static net.runelite.api.ChatMessageType.OBJECT_EXAMINE;
import static net.runelite.api.ChatMessageType.PUBLICCHAT;
import static net.runelite.api.ChatMessageType.SPAM;
import net.runelite.api.Client;
import net.runelite.api.MessageNode;
import net.runelite.api.Player;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.OverheadTextChanged;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.game.FriendChatManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.Text;
import org.apache.commons.lang3.StringUtils;
@PluginDescriptor(
name = "Chat Filter",
description = "Censor user configurable words or patterns from chat",
enabledByDefault = false
)
public class ChatFilterPlugin extends Plugin
{
private static final Splitter NEWLINE_SPLITTER = Splitter
.on("\n")
.omitEmptyStrings()
.trimResults();
@VisibleForTesting
static final String CENSOR_MESSAGE = "Hey, everyone, I just tried to say something very silly!";
private static final Set<ChatMessageType> COLLAPSIBLE_MESSAGETYPES = ImmutableSet.of(
ENGINE,
GAMEMESSAGE,
ITEM_EXAMINE,
NPC_EXAMINE,
OBJECT_EXAMINE,
SPAM,
PUBLICCHAT,
MODCHAT
);
private final CharMatcher jagexPrintableCharMatcher = Text.JAGEX_PRINTABLE_CHAR_MATCHER;
private final List<Pattern> filteredPatterns = new ArrayList<>();
private final List<Pattern> filteredNamePatterns = new ArrayList<>();
private static class Duplicate
{
int messageId;
int count;
}
private final LinkedHashMap<String, Duplicate> duplicateChatCache = new LinkedHashMap<String, Duplicate>()
{
private static final int MAX_ENTRIES = 100;
@Override
protected boolean removeEldestEntry(Map.Entry<String, Duplicate> eldest)
{
return size() > MAX_ENTRIES;
}
};
@Inject
private Client client;
@Inject
private ChatFilterConfig config;
@Inject
private FriendChatManager friendChatManager;
@Provides
ChatFilterConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(ChatFilterConfig.class);
}
@Override
protected void startUp() throws Exception
{
updateFilteredPatterns();
client.refreshChat();
}
@Override
protected void shutDown() throws Exception
{
filteredPatterns.clear();
duplicateChatCache.clear();
client.refreshChat();
}
@Subscribe
public void onScriptCallbackEvent(ScriptCallbackEvent event)
{
if (!"chatFilterCheck".equals(event.getEventName()))
{
return;
}
int[] intStack = client.getIntStack();
int intStackSize = client.getIntStackSize();
String[] stringStack = client.getStringStack();
int stringStackSize = client.getStringStackSize();
final int messageType = intStack[intStackSize - 2];
final int messageId = intStack[intStackSize - 1];
String message = stringStack[stringStackSize - 1];
ChatMessageType chatMessageType = ChatMessageType.of(messageType);
final MessageNode messageNode = client.getMessages().get(messageId);
final String name = messageNode.getName();
int duplicateCount = 0;
boolean blockMessage = false;
// Only filter public chat and private messages
switch (chatMessageType)
{
case PUBLICCHAT:
case MODCHAT:
case AUTOTYPER:
case PRIVATECHAT:
case MODPRIVATECHAT:
case FRIENDSCHAT:
if (shouldFilterPlayerMessage(Text.removeTags(name)))
{
message = censorMessage(name, message);
blockMessage = message == null;
}
break;
case GAMEMESSAGE:
case ENGINE:
case ITEM_EXAMINE:
case NPC_EXAMINE:
case OBJECT_EXAMINE:
case SPAM:
if (config.filterGameChat())
{
message = censorMessage(null, message);
blockMessage = message == null;
}
break;
case LOGINLOGOUTNOTIFICATION:
if (config.filterLogin())
{
blockMessage = true;
}
break;
}
boolean shouldCollapse = chatMessageType == PUBLICCHAT || chatMessageType == MODCHAT
? config.collapsePlayerChat()
: COLLAPSIBLE_MESSAGETYPES.contains(chatMessageType) && config.collapseGameChat();
if (!blockMessage && shouldCollapse)
{
Duplicate duplicateCacheEntry = duplicateChatCache.get(name + ":" + message);
if (duplicateCacheEntry != null)
{
blockMessage = duplicateCacheEntry.messageId != messageId ||
((chatMessageType == PUBLICCHAT || chatMessageType == MODCHAT) &&
config.maxRepeatedPublicChats() > 0 && duplicateCacheEntry.count > config.maxRepeatedPublicChats());
duplicateCount = duplicateCacheEntry.count;
}
}
if (blockMessage)
{
// Block the message
intStack[intStackSize - 3] = 0;
}
else
{
// Replace the message
if (duplicateCount > 1)
{
message += " (" + duplicateCount + ")";
}
stringStack[stringStackSize - 1] = message;
}
}
@Subscribe
public void onOverheadTextChanged(OverheadTextChanged event)
{
if (!(event.getActor() instanceof Player) || !shouldFilterPlayerMessage(event.getActor().getName()))
{
return;
}
String message = censorMessage(event.getActor().getName(), event.getOverheadText());
if (message == null)
{
message = " ";
}
event.getActor().setOverheadText(message);
}
@Subscribe(priority = -2) // run after ChatMessageManager
public void onChatMessage(ChatMessage chatMessage)
{
if (COLLAPSIBLE_MESSAGETYPES.contains(chatMessage.getType()))
{
final MessageNode messageNode = chatMessage.getMessageNode();
// remove and re-insert into map to move to end of list
final String key = messageNode.getName() + ":" + messageNode.getValue();
Duplicate duplicate = duplicateChatCache.remove(key);
if (duplicate == null)
{
duplicate = new Duplicate();
}
duplicate.count++;
duplicate.messageId = messageNode.getId();
duplicateChatCache.put(key, duplicate);
}
}
boolean shouldFilterPlayerMessage(String playerName)
{
boolean isMessageFromSelf = playerName.equals(client.getLocalPlayer().getName());
return !isMessageFromSelf &&
(config.filterFriends() || !client.isFriended(playerName, false)) &&
(config.filterFriendsChat() || !friendChatManager.isMember(playerName));
}
String censorMessage(final String username, final String message)
{
String strippedMessage = jagexPrintableCharMatcher.retainFrom(message)
.replace('\u00A0', ' ');
if (username != null && shouldFilterByName(username))
{
switch (config.filterType())
{
case CENSOR_WORDS:
return StringUtils.repeat('*', strippedMessage.length());
case CENSOR_MESSAGE:
return CENSOR_MESSAGE;
case REMOVE_MESSAGE:
return null;
}
}
boolean filtered = false;
for (Pattern pattern : filteredPatterns)
{
Matcher m = pattern.matcher(strippedMessage);
StringBuffer sb = new StringBuffer();
while (m.find())
{
switch (config.filterType())
{
case CENSOR_WORDS:
m.appendReplacement(sb, StringUtils.repeat('*', m.group(0).length()));
filtered = true;
break;
case CENSOR_MESSAGE:
return CENSOR_MESSAGE;
case REMOVE_MESSAGE:
return null;
}
}
m.appendTail(sb);
strippedMessage = sb.toString();
}
return filtered ? strippedMessage : message;
}
void updateFilteredPatterns()
{
filteredPatterns.clear();
filteredNamePatterns.clear();
Text.fromCSV(config.filteredWords()).stream()
.map(s -> Pattern.compile(Pattern.quote(s), Pattern.CASE_INSENSITIVE))
.forEach(filteredPatterns::add);
NEWLINE_SPLITTER.splitToList(config.filteredRegex()).stream()
.map(ChatFilterPlugin::compilePattern)
.filter(Objects::nonNull)
.forEach(filteredPatterns::add);
NEWLINE_SPLITTER.splitToList(config.filteredNames()).stream()
.map(ChatFilterPlugin::compilePattern)
.filter(Objects::nonNull)
.forEach(filteredNamePatterns::add);
}
private static Pattern compilePattern(String pattern)
{
try
{
return Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
}
catch (PatternSyntaxException ex)
{
return null;
}
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (!"chatfilter".equals(event.getGroup()))
{
return;
}
updateFilteredPatterns();
//Refresh chat after config change to reflect current rules
client.refreshChat();
}
@VisibleForTesting
boolean shouldFilterByName(final String playerName)
{
String sanitizedName = Text.standardize(playerName);
for (Pattern pattern : filteredNamePatterns)
{
Matcher m = pattern.matcher(sanitizedName);
if (m.find())
{
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2018, Magic fTail
* 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.chatfilter;
public enum ChatFilterType
{
CENSOR_WORDS,
CENSOR_MESSAGE,
REMOVE_MESSAGE
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.chathistory;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("chathistory")
public interface ChatHistoryConfig extends Config
{
@ConfigItem(
keyName = "retainChatHistory",
name = "Retain Chat History",
description = "Retains chat history when logging in/out or world hopping",
position = 0
)
default boolean retainChatHistory()
{
return true;
}
@ConfigItem(
keyName = "pmTargetCycling",
name = "PM Target Cycling",
description = "Pressing Tab while sending a PM will cycle the target username based on PM history",
position = 1
)
default boolean pmTargetCycling()
{
return true;
}
@ConfigItem(
keyName = "copyToClipboard",
name = "Copy to clipboard",
description = "Add option on chat messages to copy them to clipboard",
position = 2
)
default boolean copyToClipboard()
{
return true;
}
@ConfigItem(
keyName = "clearHistory",
name = "Clear history option for all tabs",
description = "Add 'Clear history' option chatbox tab buttons",
position = 3
)
default boolean clearHistory()
{
return true;
}
}

View File

@@ -0,0 +1,425 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* Copyright (c) 2020, Anthony <https://github.com/while-loop>
* 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.chathistory;
import com.google.common.base.Strings;
import com.google.common.collect.EvictingQueue;
import com.google.inject.Provides;
import java.awt.Color;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyEvent;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.Queue;
import javax.inject.Inject;
import net.runelite.api.ChatLineBuffer;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.MessageNode;
import net.runelite.api.ScriptID;
import net.runelite.api.VarClientInt;
import net.runelite.api.VarClientStr;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.MenuOpened;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.vars.InputType;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import static net.runelite.api.widgets.WidgetInfo.TO_CHILD;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.input.KeyListener;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.ColorUtil;
import net.runelite.client.util.Text;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
@PluginDescriptor(
name = "Chat History",
description = "Retain your chat history when logging in/out or world hopping",
tags = {"chat", "history", "retain", "cycle", "pm"}
)
public class ChatHistoryPlugin extends Plugin implements KeyListener
{
private static final String WELCOME_MESSAGE = "Welcome to Old School RuneScape";
private static final String CLEAR_HISTORY = "Clear history";
private static final String COPY_TO_CLIPBOARD = "Copy to clipboard";
private static final int CYCLE_HOTKEY = KeyEvent.VK_TAB;
private static final int FRIENDS_MAX_SIZE = 5;
private Queue<QueuedMessage> messageQueue;
private Deque<String> friends;
private String currentMessage = null;
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Inject
private ChatHistoryConfig config;
@Inject
private KeyManager keyManager;
@Inject
private ChatMessageManager chatMessageManager;
@Provides
ChatHistoryConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(ChatHistoryConfig.class);
}
@Override
protected void startUp()
{
messageQueue = EvictingQueue.create(100);
friends = new ArrayDeque<>(FRIENDS_MAX_SIZE + 1);
keyManager.registerKeyListener(this);
}
@Override
protected void shutDown()
{
messageQueue.clear();
messageQueue = null;
friends.clear();
friends = null;
currentMessage = null;
keyManager.unregisterKeyListener(this);
}
@Subscribe
public void onChatMessage(ChatMessage chatMessage)
{
// Start sending old messages right after the welcome message, as that is most reliable source
// of information that chat history was reset
ChatMessageType chatMessageType = chatMessage.getType();
if (chatMessageType == ChatMessageType.WELCOME && StringUtils.startsWithIgnoreCase(chatMessage.getMessage(), WELCOME_MESSAGE))
{
if (!config.retainChatHistory())
{
return;
}
QueuedMessage queuedMessage;
while ((queuedMessage = messageQueue.poll()) != null)
{
chatMessageManager.queue(queuedMessage);
}
return;
}
switch (chatMessageType)
{
case PRIVATECHATOUT:
case PRIVATECHAT:
case MODPRIVATECHAT:
final String name = Text.removeTags(chatMessage.getName());
// Remove to ensure uniqueness & its place in history
if (!friends.remove(name))
{
// If the friend didn't previously exist ensure deque capacity doesn't increase by adding them
if (friends.size() >= FRIENDS_MAX_SIZE)
{
friends.remove();
}
}
friends.add(name);
// intentional fall-through
case PUBLICCHAT:
case MODCHAT:
case FRIENDSCHAT:
case CONSOLE:
final QueuedMessage queuedMessage = QueuedMessage.builder()
.type(chatMessageType)
.name(chatMessage.getName())
.sender(chatMessage.getSender())
.value(nbsp(chatMessage.getMessage()))
.runeLiteFormattedMessage(nbsp(chatMessage.getMessageNode().getRuneLiteFormatMessage()))
.timestamp(chatMessage.getTimestamp())
.build();
if (!messageQueue.contains(queuedMessage))
{
messageQueue.offer(queuedMessage);
}
}
}
@Subscribe
public void onMenuOpened(MenuOpened event)
{
if (event.getMenuEntries().length < 2 || !config.copyToClipboard())
{
return;
}
// Use second entry as first one can be walk here with transparent chatbox
final MenuEntry entry = event.getMenuEntries()[event.getMenuEntries().length - 2];
if (entry.getType() != MenuAction.CC_OP_LOW_PRIORITY.getId() && entry.getType() != MenuAction.RUNELITE.getId())
{
return;
}
final int groupId = TO_GROUP(entry.getParam1());
final int childId = TO_CHILD(entry.getParam1());
if (groupId != WidgetInfo.CHATBOX.getGroupId())
{
return;
}
final Widget widget = client.getWidget(groupId, childId);
final Widget parent = widget.getParent();
if (WidgetInfo.CHATBOX_MESSAGE_LINES.getId() != parent.getId())
{
return;
}
// Get child id of first chat message static child so we can substract this offset to link to dynamic child
// later
final int first = WidgetInfo.CHATBOX_FIRST_MESSAGE.getChildId();
// Convert current message static widget id to dynamic widget id of message node with message contents
// When message is right clicked, we are actually right clicking static widget that contains only sender.
// The actual message contents are stored in dynamic widgets that follow same order as static widgets.
// Every first dynamic widget is message sender and every second one is message contents.
final int dynamicChildId = (childId - first) * 2 + 1;
// Extract and store message contents when menu is opened because dynamic children can change while right click
// menu is open and dynamicChildId will be outdated
final Widget messageContents = parent.getChild(dynamicChildId);
if (messageContents == null)
{
return;
}
currentMessage = messageContents.getText();
final MenuEntry menuEntry = new MenuEntry();
menuEntry.setOption(COPY_TO_CLIPBOARD);
menuEntry.setTarget(entry.getTarget());
menuEntry.setType(MenuAction.RUNELITE.getId());
menuEntry.setParam0(entry.getParam0());
menuEntry.setParam1(entry.getParam1());
menuEntry.setIdentifier(entry.getIdentifier());
client.setMenuEntries(ArrayUtils.insert(1, client.getMenuEntries(), menuEntry));
}
@Subscribe
public void onMenuOptionClicked(MenuOptionClicked event)
{
final String menuOption = event.getMenuOption();
// The menu option for clear history is "<col=ffff00>Public:</col> Clear history"
if (menuOption.endsWith(CLEAR_HISTORY))
{
clearChatboxHistory(ChatboxTab.of(event.getWidgetId()));
}
else if (COPY_TO_CLIPBOARD.equals(menuOption) && !Strings.isNullOrEmpty(currentMessage))
{
final StringSelection stringSelection = new StringSelection(Text.removeTags(currentMessage));
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null);
}
}
@Subscribe
public void onMenuEntryAdded(MenuEntryAdded entry)
{
final ChatboxTab tab = ChatboxTab.of(entry.getActionParam1());
if (tab == null || !config.clearHistory() || !Text.removeTags(entry.getOption()).equals(tab.getAfter()))
{
return;
}
final MenuEntry clearEntry = new MenuEntry();
clearEntry.setTarget("");
clearEntry.setType(MenuAction.RUNELITE.getId());
clearEntry.setParam0(entry.getActionParam0());
clearEntry.setParam1(entry.getActionParam1());
if (tab == ChatboxTab.GAME)
{
// keep type as the original CC_OP to correctly group "Game: Clear history" with
// other tab "Game: *" options.
clearEntry.setType(entry.getType());
}
final StringBuilder messageBuilder = new StringBuilder();
if (tab != ChatboxTab.ALL)
{
messageBuilder.append(ColorUtil.wrapWithColorTag(tab.getName() + ": ", Color.YELLOW));
}
messageBuilder.append(CLEAR_HISTORY);
clearEntry.setOption(messageBuilder.toString());
final MenuEntry[] menuEntries = client.getMenuEntries();
client.setMenuEntries(ArrayUtils.insert(menuEntries.length - 1, menuEntries, clearEntry));
}
private void clearMessageQueue(ChatboxTab tab)
{
if (tab == ChatboxTab.ALL || tab == ChatboxTab.PRIVATE)
{
friends.clear();
}
messageQueue.removeIf(e -> ArrayUtils.contains(tab.getMessageTypes(), e.getType()));
}
private void clearChatboxHistory(ChatboxTab tab)
{
if (tab == null)
{
return;
}
boolean removed = false;
for (ChatMessageType msgType : tab.getMessageTypes())
{
final ChatLineBuffer lineBuffer = client.getChatLineMap().get(msgType.getType());
if (lineBuffer == null)
{
continue;
}
final MessageNode[] lines = lineBuffer.getLines().clone();
for (final MessageNode line : lines)
{
if (line != null)
{
lineBuffer.removeMessageNode(line);
removed = true;
}
}
}
if (removed)
{
clientThread.invoke(() -> client.runScript(ScriptID.BUILD_CHATBOX));
}
clearMessageQueue(tab);
}
/**
* Small hack to prevent plugins checking for specific messages to match
* @param message message
* @return message with nbsp
*/
private static String nbsp(final String message)
{
if (message != null)
{
return message.replace(' ', '\u00A0');
}
return null;
}
@Override
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() != CYCLE_HOTKEY || !config.pmTargetCycling())
{
return;
}
if (client.getVar(VarClientInt.INPUT_TYPE) != InputType.PRIVATE_MESSAGE.getType())
{
return;
}
clientThread.invoke(() ->
{
final String target = findPreviousFriend();
if (target == null)
{
return;
}
final String currentMessage = client.getVar(VarClientStr.INPUT_TEXT);
client.runScript(ScriptID.OPEN_PRIVATE_MESSAGE_INTERFACE, target);
client.setVar(VarClientStr.INPUT_TEXT, currentMessage);
client.runScript(ScriptID.CHAT_TEXT_INPUT_REBUILD, "");
});
}
@Override
public void keyTyped(KeyEvent e)
{
}
@Override
public void keyReleased(KeyEvent e)
{
}
private String findPreviousFriend()
{
final String currentTarget = client.getVar(VarClientStr.PRIVATE_MESSAGE_TARGET);
if (currentTarget == null || friends.isEmpty())
{
return null;
}
for (Iterator<String> it = friends.descendingIterator(); it.hasNext(); )
{
String friend = it.next();
if (friend.equals(currentTarget))
{
return it.hasNext() ? it.next() : friends.getLast();
}
}
return friends.getLast();
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2020, Anthony <https://github.com/while-loop>
* 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.chathistory;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import javax.annotation.Nullable;
import lombok.Getter;
import net.runelite.api.ChatMessageType;
import net.runelite.api.widgets.WidgetInfo;
@Getter
enum ChatboxTab
{
ALL("All", "Switch tab", WidgetInfo.CHATBOX_TAB_ALL,
ChatMessageType.values()),
// null 'after' var since we're not adding to menu
PRIVATE("Private", null, WidgetInfo.CHATBOX_TAB_PRIVATE,
ChatMessageType.PRIVATECHAT, ChatMessageType.PRIVATECHATOUT, ChatMessageType.MODPRIVATECHAT,
ChatMessageType.LOGINLOGOUTNOTIFICATION),
// null 'after' var since we're not adding to menu
PUBLIC("Public", null, WidgetInfo.CHATBOX_TAB_PUBLIC,
ChatMessageType.PUBLICCHAT, ChatMessageType.AUTOTYPER, ChatMessageType.MODCHAT, ChatMessageType.MODAUTOTYPER),
GAME("Game", "Game: Filter", WidgetInfo.CHATBOX_TAB_GAME,
ChatMessageType.GAMEMESSAGE, ChatMessageType.ENGINE, ChatMessageType.BROADCAST,
ChatMessageType.SNAPSHOTFEEDBACK, ChatMessageType.ITEM_EXAMINE, ChatMessageType.NPC_EXAMINE,
ChatMessageType.OBJECT_EXAMINE, ChatMessageType.FRIENDNOTIFICATION, ChatMessageType.IGNORENOTIFICATION,
ChatMessageType.CONSOLE, ChatMessageType.SPAM, ChatMessageType.PLAYERRELATED, ChatMessageType.TENSECTIMEOUT,
ChatMessageType.WELCOME, ChatMessageType.UNKNOWN),
CLAN("Clan", "Clan: Off", WidgetInfo.CHATBOX_TAB_CLAN,
ChatMessageType.FRIENDSCHATNOTIFICATION, ChatMessageType.FRIENDSCHAT, ChatMessageType.CHALREQ_FRIENDSCHAT),
TRADE("Trade", "Trade: Off", WidgetInfo.CHATBOX_TAB_TRADE,
ChatMessageType.TRADE_SENT, ChatMessageType.TRADEREQ, ChatMessageType.TRADE, ChatMessageType.CHALREQ_TRADE),
;
private static final Map<Integer, ChatboxTab> TAB_MESSAGE_TYPES;
private final String name;
@Nullable
private final String after;
private final int widgetId;
private final ChatMessageType[] messageTypes;
ChatboxTab(String name, String after, WidgetInfo widgetId, ChatMessageType... messageTypes)
{
this.name = name;
this.after = after;
this.widgetId = widgetId.getId();
this.messageTypes = messageTypes;
}
static
{
ImmutableMap.Builder<Integer, ChatboxTab> builder = ImmutableMap.builder();
for (ChatboxTab t : values())
{
builder.put(t.widgetId, t);
}
TAB_MESSAGE_TYPES = builder.build();
}
static ChatboxTab of(int widgetId)
{
return TAB_MESSAGE_TYPES.get(widgetId);
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2018, Hydrox6 <ikada@protonmail.ch>
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.chatnotifications;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("chatnotification")
public interface ChatNotificationsConfig extends Config
{
@ConfigItem(
position = 0,
keyName = "highlightOwnName",
name = "Highlight own name",
description = "Highlights any instance of your username in chat"
)
default boolean highlightOwnName()
{
return true;
}
@ConfigItem(
position = 1,
keyName = "highlightWordsString",
name = "Highlight words",
description = "Highlights the following words in chat"
)
default String highlightWordsString()
{
return "";
}
@ConfigItem(
position = 2,
keyName = "notifyOnOwnName",
name = "Notify on own name",
description = "Notifies you whenever someone mentions you by name"
)
default boolean notifyOnOwnName()
{
return false;
}
@ConfigItem(
position = 3,
keyName = "notifyOnHighlight",
name = "Notify on highlight",
description = "Notifies you whenever a highlighted word is matched"
)
default boolean notifyOnHighlight()
{
return false;
}
@ConfigItem(
position = 4,
keyName = "notifyOnTrade",
name = "Notify on trade",
description = "Notifies you whenever you are traded"
)
default boolean notifyOnTrade()
{
return false;
}
@ConfigItem(
position = 5,
keyName = "notifyOnDuel",
name = "Notify on duel",
description = "Notifies you whenever you are challenged to a duel"
)
default boolean notifyOnDuel()
{
return false;
}
@ConfigItem(
position = 6,
keyName = "notifyOnBroadcast",
name = "Notify on broadcast",
description = "Notifies you whenever you receive a broadcast message"
)
default boolean notifyOnBroadcast()
{
return false;
}
}

View File

@@ -0,0 +1,331 @@
/*
* Copyright (c) 2018, Hydrox6 <ikada@protonmail.ch>
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.chatnotifications;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.inject.Provides;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.inject.Inject;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.MessageNode;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameStateChanged;
import net.runelite.client.Notifier;
import net.runelite.client.RuneLiteProperties;
import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.Text;
@PluginDescriptor(
name = "Chat Notifications",
description = "Highlight and notify you of chat messages",
tags = {"duel", "messages", "notifications", "trade", "username"},
enabledByDefault = false
)
public class ChatNotificationsPlugin extends Plugin
{
@Inject
private Client client;
@Inject
private ChatNotificationsConfig config;
@Inject
private ChatMessageManager chatMessageManager;
@Inject
private Notifier notifier;
//Custom Highlights
private Pattern usernameMatcher = null;
private String usernameReplacer = "";
private Pattern highlightMatcher = null;
@Provides
ChatNotificationsConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(ChatNotificationsConfig.class);
}
@Override
public void startUp()
{
updateHighlights();
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
switch (event.getGameState())
{
case LOGIN_SCREEN:
case HOPPING:
usernameMatcher = null;
break;
}
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (event.getGroup().equals("chatnotification"))
{
updateHighlights();
}
}
private void updateHighlights()
{
highlightMatcher = null;
if (!config.highlightWordsString().trim().equals(""))
{
List<String> items = Text.fromCSV(config.highlightWordsString());
String joined = items.stream()
.map(Text::escapeJagex) // we compare these strings to the raw Jagex ones
.map(this::quoteAndIgnoreColor) // regex escape and ignore nested colors in the target message
.collect(Collectors.joining("|"));
// To match <word> \b doesn't work due to <> not being in \w,
// so match \b or \s, as well as \A and \z for beginning and end of input respectively
highlightMatcher = Pattern.compile("(?:\\b|(?<=\\s)|\\A)(?:" + joined + ")(?:\\b|(?=\\s)|\\z)", Pattern.CASE_INSENSITIVE);
}
}
@Subscribe
public void onChatMessage(ChatMessage chatMessage)
{
MessageNode messageNode = chatMessage.getMessageNode();
boolean update = false;
switch (chatMessage.getType())
{
case TRADEREQ:
if (chatMessage.getMessage().contains("wishes to trade with you.") && config.notifyOnTrade())
{
notifier.notify(chatMessage.getMessage());
}
break;
case CHALREQ_TRADE:
if (chatMessage.getMessage().contains("wishes to duel with you.") && config.notifyOnDuel())
{
notifier.notify(chatMessage.getMessage());
}
break;
case BROADCAST:
if (config.notifyOnBroadcast())
{
// Some broadcasts have links attached, notated by `|` followed by a number, while others contain color tags.
// We don't want to see either in the printed notification.
String broadcast = chatMessage.getMessage();
int urlTokenIndex = broadcast.lastIndexOf('|');
if (urlTokenIndex != -1)
{
broadcast = broadcast.substring(0, urlTokenIndex);
}
notifier.notify(Text.removeFormattingTags(broadcast));
}
break;
case CONSOLE:
// Don't notify for notification messages
if (chatMessage.getName().equals(RuneLiteProperties.getTitle()))
{
return;
}
break;
}
if (usernameMatcher == null && client.getLocalPlayer() != null && client.getLocalPlayer().getName() != null)
{
String username = client.getLocalPlayer().getName();
String pattern = Arrays.stream(username.split(" "))
.map(s -> s.isEmpty() ? "" : Pattern.quote(s))
.collect(Collectors.joining("[\u00a0\u0020]")); // space or nbsp
usernameMatcher = Pattern.compile("\\b" + pattern + "\\b", Pattern.CASE_INSENSITIVE);
usernameReplacer = "<col" + ChatColorType.HIGHLIGHT.name() + "><u>" + username + "</u><col" + ChatColorType.NORMAL.name() + ">";
}
if (config.highlightOwnName() && usernameMatcher != null)
{
Matcher matcher = usernameMatcher.matcher(messageNode.getValue());
if (matcher.find())
{
messageNode.setValue(matcher.replaceAll(usernameReplacer));
update = true;
if (config.notifyOnOwnName() && (chatMessage.getType() == ChatMessageType.PUBLICCHAT
|| chatMessage.getType() == ChatMessageType.PRIVATECHAT
|| chatMessage.getType() == ChatMessageType.FRIENDSCHAT
|| chatMessage.getType() == ChatMessageType.MODCHAT
|| chatMessage.getType() == ChatMessageType.MODPRIVATECHAT))
{
sendNotification(chatMessage);
}
}
}
if (highlightMatcher != null)
{
String nodeValue = messageNode.getValue();
Matcher matcher = highlightMatcher.matcher(nodeValue);
boolean found = false;
StringBuffer stringBuffer = new StringBuffer();
while (matcher.find())
{
String value = matcher.group();
// Determine the ending color by:
// 1) use the color from value if it has one
// 2) use the last color from stringBuffer + <content between last match and current match>
// To do #2 we just search for the last col tag after calling appendReplacement
String endColor = getLastColor(value);
// Strip color tags from the highlighted region so that it remains highlighted correctly
value = stripColor(value);
matcher.appendReplacement(stringBuffer, "<col" + ChatColorType.HIGHLIGHT + '>' + value);
if (endColor == null)
{
endColor = getLastColor(stringBuffer.toString());
}
// Append end color
stringBuffer.append(endColor == null ? "<col" + ChatColorType.NORMAL + ">" : endColor);
update = true;
found = true;
}
if (found)
{
matcher.appendTail(stringBuffer);
messageNode.setValue(stringBuffer.toString());
if (config.notifyOnHighlight())
{
sendNotification(chatMessage);
}
}
}
if (update)
{
messageNode.setRuneLiteFormatMessage(messageNode.getValue());
chatMessageManager.update(messageNode);
}
}
private void sendNotification(ChatMessage message)
{
String name = Text.removeTags(message.getName());
String sender = message.getSender();
StringBuilder stringBuilder = new StringBuilder();
if (!Strings.isNullOrEmpty(sender))
{
stringBuilder.append('[').append(sender).append("] ");
}
if (!Strings.isNullOrEmpty(name))
{
stringBuilder.append(name).append(": ");
}
stringBuilder.append(Text.removeTags(message.getMessage()));
String notification = stringBuilder.toString();
notifier.notify(notification);
}
private String quoteAndIgnoreColor(String str)
{
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < str.length(); ++i)
{
char c = str.charAt(i);
stringBuilder.append(Pattern.quote(String.valueOf(c)));
stringBuilder.append("(?:<col=[^>]*?>)?");
}
return stringBuilder.toString();
}
/**
* Get the last color tag from a string, or null if there was none
*
* @param str
* @return
*/
private static String getLastColor(String str)
{
int colIdx = str.lastIndexOf("<col=");
int colEndIdx = str.lastIndexOf("</col>");
if (colEndIdx > colIdx)
{
// ends in a </col> which resets the color to normal
return "<col" + ChatColorType.NORMAL + ">";
}
if (colIdx == -1)
{
return null; // no color
}
int closeIdx = str.indexOf('>', colIdx);
if (closeIdx == -1)
{
return null; // unclosed col tag
}
return str.substring(colIdx, closeIdx + 1); // include the >
}
/**
* Strip color tags from a string.
*
* @param str
* @return
*/
@VisibleForTesting
static String stripColor(String str)
{
return str.replaceAll("(<col=[0-9a-f]+>|</col>)", "");
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2018, Brett Middle <https://github.com/bmiddle>
* 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.combatlevel;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("combatlevel")
public interface CombatLevelConfig extends Config
{
@ConfigItem(
keyName = "showLevelsUntil",
name = "Calculate next level",
description = "Mouse over the combat level to calculate what skill levels will increase combat."
)
default boolean showLevelsUntil()
{
return true;
}
@ConfigItem(
keyName = "showPreciseCombatLevel",
name = "Show precise combat level",
description = "Displays your combat level with accurate decimals."
)
default boolean showPreciseCombatLevel()
{
return true;
}
@ConfigItem(
keyName = "wildernessAttackLevelRange",
name = "Show level range in wilderness",
description = "Displays a PVP-world-like attack level range in the wilderness"
)
default boolean wildernessAttackLevelRange()
{
return true;
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2018, Brett Middle <https://github.com/bmiddle>
* 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.combatlevel;
import com.google.common.annotations.VisibleForTesting;
import net.runelite.api.Client;
import net.runelite.api.Experience;
import net.runelite.api.Skill;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.tooltip.Tooltip;
import net.runelite.client.ui.overlay.tooltip.TooltipManager;
import net.runelite.client.util.ColorUtil;
import javax.inject.Inject;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
class CombatLevelOverlay extends Overlay
{
private static final Color COMBAT_LEVEL_COLOUR = new Color(0xff981f);
private final Client client;
private final CombatLevelConfig config;
private final TooltipManager tooltipManager;
@Inject
private CombatLevelOverlay(Client client, CombatLevelConfig config, TooltipManager tooltipManager)
{
this.client = client;
this.config = config;
this.tooltipManager = tooltipManager;
}
@Override
public Dimension render(Graphics2D graphics)
{
Widget combatLevelWidget = client.getWidget(WidgetInfo.COMBAT_LEVEL);
if (!config.showLevelsUntil()
|| client.getLocalPlayer().getCombatLevel() == Experience.MAX_COMBAT_LEVEL
|| combatLevelWidget == null || combatLevelWidget.isHidden())
{
return null;
}
Rectangle combatCanvas = combatLevelWidget.getBounds();
if (combatCanvas == null)
{
return null;
}
if (combatCanvas.contains(client.getMouseCanvasPosition().getX(), client.getMouseCanvasPosition().getY()))
{
tooltipManager.add(new Tooltip(getLevelsUntilTooltip()));
}
return null;
}
@VisibleForTesting
String getLevelsUntilTooltip()
{
// grab combat skills from player
int attackLevel = client.getRealSkillLevel(Skill.ATTACK);
int strengthLevel = client.getRealSkillLevel(Skill.STRENGTH);
int defenceLevel = client.getRealSkillLevel(Skill.DEFENCE);
int hitpointsLevel = client.getRealSkillLevel(Skill.HITPOINTS);
int magicLevel = client.getRealSkillLevel(Skill.MAGIC);
int rangeLevel = client.getRealSkillLevel(Skill.RANGED);
int prayerLevel = client.getRealSkillLevel(Skill.PRAYER);
// find the needed levels until level up
int meleeNeed = Experience.getNextCombatLevelMelee(attackLevel, strengthLevel, defenceLevel, hitpointsLevel,
magicLevel, rangeLevel, prayerLevel);
int hpDefNeed = Experience.getNextCombatLevelHpDef(attackLevel, strengthLevel, defenceLevel, hitpointsLevel,
magicLevel, rangeLevel, prayerLevel);
int rangeNeed = Experience.getNextCombatLevelRange(attackLevel, strengthLevel, defenceLevel, hitpointsLevel,
magicLevel, rangeLevel, prayerLevel);
int magicNeed = Experience.getNextCombatLevelMagic(attackLevel, strengthLevel, defenceLevel, hitpointsLevel,
magicLevel, rangeLevel, prayerLevel);
int prayerNeed = Experience.getNextCombatLevelPrayer(attackLevel, strengthLevel, defenceLevel, hitpointsLevel,
magicLevel, rangeLevel, prayerLevel);
// create tooltip string
StringBuilder sb = new StringBuilder();
sb.append(ColorUtil.wrapWithColorTag("Next combat level:</br>", COMBAT_LEVEL_COLOUR));
if ((attackLevel + strengthLevel) < Experience.MAX_REAL_LEVEL * 2)
{
sb.append(meleeNeed).append(" Attack/Strength</br>");
}
if ((hitpointsLevel + defenceLevel) < Experience.MAX_REAL_LEVEL * 2)
{
sb.append(hpDefNeed).append(" Defence/Hitpoints</br>");
}
if (rangeLevel < Experience.MAX_REAL_LEVEL)
{
sb.append(rangeNeed).append(" Ranged</br>");
}
if (magicLevel < Experience.MAX_REAL_LEVEL)
{
sb.append(magicNeed).append(" Magic</br>");
}
if (prayerLevel < Experience.MAX_REAL_LEVEL)
{
sb.append(prayerNeed).append(" Prayer");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright (c) 2017, Devin French <https://github.com/devinfrench>
* Copyright (c) 2019, Jordan Atwood <nightfirecat@protonmail.com>
* 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.combatlevel;
import com.google.inject.Provides;
import java.text.DecimalFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Experience;
import net.runelite.api.GameState;
import net.runelite.api.ScriptID;
import net.runelite.api.Skill;
import net.runelite.api.WorldType;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.ScriptPostFired;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.overlay.OverlayManager;
@PluginDescriptor(
name = "Combat Level",
description = "Show a more accurate combat level in Combat Options panel and other combat level functions",
tags = {"wilderness", "attack", "range"}
)
public class CombatLevelPlugin extends Plugin
{
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.###");
private static final String CONFIG_GROUP = "combatlevel";
private static final String ATTACK_RANGE_CONFIG_KEY = "wildernessAttackLevelRange";
private static final Pattern WILDERNESS_LEVEL_PATTERN = Pattern.compile("^Level: (\\d+)$");
private static final int SKULL_CONTAINER_ADJUSTED_ORIGINAL_Y = 6;
private static final int WILDERNESS_LEVEL_TEXT_ADJUSTED_ORIGINAL_Y = 3;
private static final int MIN_COMBAT_LEVEL = 3;
private int originalWildernessLevelTextPosition = -1;
private int originalSkullContainerPosition = -1;
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Inject
private CombatLevelConfig config;
@Inject
private CombatLevelOverlay overlay;
@Inject
private OverlayManager overlayManager;
@Provides
CombatLevelConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(CombatLevelConfig.class);
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(overlay);
if (config.wildernessAttackLevelRange())
{
appendAttackLevelRangeText();
}
}
@Override
protected void shutDown() throws Exception
{
overlayManager.remove(overlay);
Widget combatLevelWidget = client.getWidget(WidgetInfo.COMBAT_LEVEL);
if (combatLevelWidget != null)
{
String widgetText = combatLevelWidget.getText();
if (widgetText.contains("."))
{
combatLevelWidget.setText(widgetText.substring(0, widgetText.indexOf(".")));
}
}
shutDownAttackLevelRange();
}
@Subscribe
public void onGameTick(GameTick event)
{
if (client.getGameState() != GameState.LOGGED_IN)
{
return;
}
Widget combatLevelWidget = client.getWidget(WidgetInfo.COMBAT_LEVEL);
if (combatLevelWidget == null || !config.showPreciseCombatLevel())
{
return;
}
double combatLevelPrecise = Experience.getCombatLevelPrecise(
client.getRealSkillLevel(Skill.ATTACK),
client.getRealSkillLevel(Skill.STRENGTH),
client.getRealSkillLevel(Skill.DEFENCE),
client.getRealSkillLevel(Skill.HITPOINTS),
client.getRealSkillLevel(Skill.MAGIC),
client.getRealSkillLevel(Skill.RANGED),
client.getRealSkillLevel(Skill.PRAYER)
);
combatLevelWidget.setText("Combat Lvl: " + DECIMAL_FORMAT.format(combatLevelPrecise));
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (!CONFIG_GROUP.equals(event.getGroup()) || !ATTACK_RANGE_CONFIG_KEY.equals(event.getKey()))
{
return;
}
if (config.wildernessAttackLevelRange())
{
appendAttackLevelRangeText();
}
else
{
shutDownAttackLevelRange();
}
}
@Subscribe
public void onScriptPostFired(ScriptPostFired scriptPostFired)
{
if (scriptPostFired.getScriptId() == ScriptID.PVP_WIDGET_BUILDER && config.wildernessAttackLevelRange())
{
appendAttackLevelRangeText();
}
}
private void appendAttackLevelRangeText()
{
final Widget wildernessLevelWidget = client.getWidget(WidgetInfo.PVP_WILDERNESS_LEVEL);
if (wildernessLevelWidget == null)
{
return;
}
final String wildernessLevelText = wildernessLevelWidget.getText();
final Matcher m = WILDERNESS_LEVEL_PATTERN.matcher(wildernessLevelText);
if (!m.matches()
|| WorldType.isPvpWorld(client.getWorldType()))
{
return;
}
final Widget skullContainer = client.getWidget(WidgetInfo.PVP_SKULL_CONTAINER);
if (originalWildernessLevelTextPosition == -1)
{
originalWildernessLevelTextPosition = wildernessLevelWidget.getOriginalY();
}
if (originalSkullContainerPosition == -1)
{
originalSkullContainerPosition = skullContainer.getRelativeY();
}
final int wildernessLevel = Integer.parseInt(m.group(1));
final int combatLevel = client.getLocalPlayer().getCombatLevel();
wildernessLevelWidget.setText(wildernessLevelText + "<br>" + combatAttackRange(combatLevel, wildernessLevel));
wildernessLevelWidget.setOriginalY(WILDERNESS_LEVEL_TEXT_ADJUSTED_ORIGINAL_Y);
skullContainer.setOriginalY(SKULL_CONTAINER_ADJUSTED_ORIGINAL_Y);
clientThread.invoke(wildernessLevelWidget::revalidate);
clientThread.invoke(skullContainer::revalidate);
}
private void shutDownAttackLevelRange()
{
if (WorldType.isPvpWorld(client.getWorldType()))
{
return;
}
final Widget wildernessLevelWidget = client.getWidget(WidgetInfo.PVP_WILDERNESS_LEVEL);
if (wildernessLevelWidget != null)
{
String wildernessLevelText = wildernessLevelWidget.getText();
if (wildernessLevelText.contains("<br>"))
{
wildernessLevelWidget.setText(wildernessLevelText.substring(0, wildernessLevelText.indexOf("<br>")));
}
wildernessLevelWidget.setOriginalY(originalWildernessLevelTextPosition);
clientThread.invoke(wildernessLevelWidget::revalidate);
}
originalWildernessLevelTextPosition = -1;
final Widget skullContainer = client.getWidget(WidgetInfo.PVP_SKULL_CONTAINER);
if (skullContainer != null)
{
skullContainer.setOriginalY(originalSkullContainerPosition);
clientThread.invoke(skullContainer::revalidate);
}
originalSkullContainerPosition = -1;
}
private static String combatAttackRange(final int combatLevel, final int wildernessLevel)
{
return Math.max(MIN_COMBAT_LEVEL, combatLevel - wildernessLevel) + "-" + Math.min(Experience.MAX_COMBAT_LEVEL, combatLevel + wildernessLevel);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2018, Morgan Lewis <https://github.com/MESLewis>
* 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.mousehighlight;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("mousehighlight")
public interface MouseHighlightConfig extends Config
{
@ConfigItem(
position = 0,
keyName = "uiTooltip",
name = "Interface Tooltips",
description = "Whether or not tooltips are shown on interfaces"
)
default boolean uiTooltip()
{
return true;
}
@ConfigItem(
position = 1,
keyName = "chatboxTooltip",
name = "Chatbox Tooltips",
description = "Whether or not tooltips are shown over the chatbox"
)
default boolean chatboxTooltip()
{
return true;
}
@ConfigItem(
position = 2,
keyName = "disableSpellbooktooltip",
name = "Disable Spellbook Tooltips",
description = "Disable Spellbook Tooltips so they don't cover descriptions"
)
default boolean disableSpellbooktooltip()
{
return false;
}
}

View File

@@ -0,0 +1,174 @@
/*
* Copyright (c) 2017, Aria <aria@ar1as.space>
* 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.mousehighlight;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.util.Set;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.VarClientInt;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.tooltip.Tooltip;
import net.runelite.client.ui.overlay.tooltip.TooltipManager;
class MouseHighlightOverlay extends Overlay
{
/**
* Menu types which are on widgets.
*/
private static final Set<MenuAction> WIDGET_MENU_ACTIONS = ImmutableSet.of(
MenuAction.WIDGET_TYPE_1,
MenuAction.WIDGET_TYPE_2,
MenuAction.WIDGET_TYPE_3,
MenuAction.WIDGET_TYPE_4,
MenuAction.WIDGET_TYPE_5,
MenuAction.WIDGET_TYPE_6,
MenuAction.ITEM_USE_ON_WIDGET_ITEM,
MenuAction.ITEM_USE_ON_WIDGET,
MenuAction.ITEM_FIRST_OPTION,
MenuAction.ITEM_SECOND_OPTION,
MenuAction.ITEM_THIRD_OPTION,
MenuAction.ITEM_FOURTH_OPTION,
MenuAction.ITEM_FIFTH_OPTION,
MenuAction.ITEM_USE,
MenuAction.ITEM_DROP,
MenuAction.WIDGET_FIRST_OPTION,
MenuAction.WIDGET_SECOND_OPTION,
MenuAction.WIDGET_THIRD_OPTION,
MenuAction.WIDGET_FOURTH_OPTION,
MenuAction.WIDGET_FIFTH_OPTION,
MenuAction.EXAMINE_ITEM,
MenuAction.SPELL_CAST_ON_WIDGET,
MenuAction.CC_OP_LOW_PRIORITY,
MenuAction.CC_OP
);
private final TooltipManager tooltipManager;
private final Client client;
private final MouseHighlightConfig config;
@Inject
MouseHighlightOverlay(Client client, TooltipManager tooltipManager, MouseHighlightConfig config)
{
setPosition(OverlayPosition.DYNAMIC);
this.client = client;
this.tooltipManager = tooltipManager;
this.config = config;
}
@Override
public Dimension render(Graphics2D graphics)
{
if (client.isMenuOpen())
{
return null;
}
MenuEntry[] menuEntries = client.getMenuEntries();
int last = menuEntries.length - 1;
if (last < 0)
{
return null;
}
MenuEntry menuEntry = menuEntries[last];
String target = menuEntry.getTarget();
String option = menuEntry.getOption();
MenuAction type = MenuAction.of(menuEntry.getType());
if (type == MenuAction.RUNELITE_OVERLAY || type == MenuAction.CC_OP_LOW_PRIORITY)
{
// These are always right click only
return null;
}
if (Strings.isNullOrEmpty(option))
{
return null;
}
// Trivial options that don't need to be highlighted, add more as they appear.
switch (option)
{
case "Walk here":
case "Cancel":
case "Continue":
return null;
case "Move":
// Hide overlay on sliding puzzle boxes
if (target.contains("Sliding piece"))
{
return null;
}
}
if (WIDGET_MENU_ACTIONS.contains(type))
{
final int widgetId = menuEntry.getParam1();
final int groupId = WidgetInfo.TO_GROUP(widgetId);
if (!config.uiTooltip())
{
return null;
}
if (!config.chatboxTooltip() && groupId == WidgetInfo.CHATBOX.getGroupId())
{
return null;
}
if (config.disableSpellbooktooltip() && groupId == WidgetID.SPELLBOOK_GROUP_ID)
{
return null;
}
}
// If this varc is set, a tooltip will be displayed soon
int tooltipTimeout = client.getVar(VarClientInt.TOOLTIP_TIMEOUT);
if (tooltipTimeout > client.getGameCycle())
{
return null;
}
// If this varc is set, a tooltip is already being displayed
int tooltipDisplayed = client.getVar(VarClientInt.TOOLTIP_VISIBLE);
if (tooltipDisplayed == 1)
{
return null;
}
tooltipManager.addFront(new Tooltip(option + (Strings.isNullOrEmpty(target) ? "" : " " + target)));
return null;
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2017, Aria <aria@ar1as.space>
* 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.mousehighlight;
import com.google.inject.Provides;
import javax.inject.Inject;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.overlay.OverlayManager;
@PluginDescriptor(
name = "Mouse Tooltips",
description = "Render default actions as a tooltip",
tags = {"actions", "overlay"}
)
public class MouseHighlightPlugin extends Plugin
{
@Inject
private OverlayManager overlayManager;
@Inject
private MouseHighlightOverlay overlay;
@Provides
MouseHighlightConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(MouseHighlightConfig.class);
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(overlay);
}
@Override
protected void shutDown() throws Exception
{
overlayManager.remove(overlay);
}
}