Merge remote-tracking branch 'runelite/master'

This commit is contained in:
Owain van Brakel
2022-05-11 11:41:54 +02:00
53 changed files with 1979 additions and 601 deletions

View File

@@ -27,6 +27,7 @@ package net.runelite.client.plugins.chatcommands;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
@@ -90,6 +91,7 @@ import net.runelite.client.hiscore.Skill;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.AsyncBufferedImage;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.QuantityFormatter;
import net.runelite.client.util.Text;
@@ -106,7 +108,7 @@ import org.apache.commons.text.WordUtils;
@Slf4j
public class ChatCommandsPlugin extends Plugin
{
private static final Pattern KILLCOUNT_PATTERN = Pattern.compile("Your (?:completion count for |subdued |completed )?(.+?) (?:(?:kill|harvest|lap|completion) )?(?:count )?is: <col=ff0000>(\\d+)</col>");
private static final Pattern KILLCOUNT_PATTERN = Pattern.compile("Your (?<pre>completion count for |subdued |completed )?(?<boss>.+?) (?<post>(?:(?:kill|harvest|lap|completion) )?(?:count )?)is: <col=ff0000>(?<kc>\\d+)</col>");
private static final String TEAM_SIZES = "(?<teamsize>\\d+(?:\\+|-\\d+)? players?|Solo)";
private static final Pattern RAIDS_PB_PATTERN = Pattern.compile("<col=ef20ff>Congratulations - your raid is complete!</col><br>Team size: <col=ff0000>" + TEAM_SIZES + "</col> Duration:</col> <col=ff0000>(?<pb>[0-9:]+(?:\\.[0-9]+)?)</col> \\(new personal best\\)</col>");
private static final Pattern RAIDS_DURATION_PATTERN = Pattern.compile("<col=ef20ff>Congratulations - your raid is complete!</col><br>Team size: <col=ff0000>" + TEAM_SIZES + "</col> Duration:</col> <col=ff0000>[0-9:.]+</col> Personal best: </col><col=ff0000>(?<pb>[0-9:]+(?:\\.[0-9]+)?)</col>");
@@ -220,7 +222,15 @@ public class ChatCommandsPlugin extends Plugin
chatCommandManager.registerCommandAsync(SOUL_WARS_ZEAL_COMMAND, this::soulWarsZealLookup);
chatCommandManager.registerCommandAsync(PET_LIST_COMMAND, this::petListLookup, this::petListSubmit);
clientThread.invoke(this::loadPetIcons);
clientThread.invoke(() ->
{
if (client.getModIcons() == null)
{
return false;
}
loadPetIcons();
return true;
});
}
@Override
@@ -295,13 +305,14 @@ public class ChatCommandsPlugin extends Plugin
private void loadPetIcons()
{
final IndexedSprite[] modIcons = client.getModIcons();
if (modIconIdx != -1 || modIcons == null)
if (modIconIdx != -1)
{
return;
}
final Pet[] pets = Pet.values();
final IndexedSprite[] modIcons = client.getModIcons();
assert modIcons != null;
final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + pets.length);
modIconIdx = modIcons.length;
@@ -309,9 +320,16 @@ public class ChatCommandsPlugin extends Plugin
{
final Pet pet = pets[i];
final BufferedImage image = ImageUtil.resizeImage(itemManager.getImage(pet.getIconID()), 18, 16);
final IndexedSprite sprite = ImageUtil.getImageIndexedSprite(image, client);
newModIcons[modIconIdx + i] = sprite;
final AsyncBufferedImage abi = itemManager.getImage(pet.getIconID());
final int idx = modIconIdx + i;
Runnable r = () ->
{
final BufferedImage image = ImageUtil.resizeImage(abi, 18, 16);
final IndexedSprite sprite = ImageUtil.getImageIndexedSprite(image, client);
newModIcons[idx] = sprite;
};
abi.onLoaded(r);
r.run();
}
client.setModIcons(newModIcons);
@@ -371,8 +389,16 @@ public class ChatCommandsPlugin extends Plugin
Matcher matcher = KILLCOUNT_PATTERN.matcher(message);
if (matcher.find())
{
String boss = matcher.group(1);
int kc = Integer.parseInt(matcher.group(2));
final String boss = matcher.group("boss");
final int kc = Integer.parseInt(matcher.group("kc"));
final String pre = matcher.group("pre");
final String post = matcher.group("post");
if (Strings.isNullOrEmpty(pre) && Strings.isNullOrEmpty(post))
{
unsetKc(boss);
return;
}
String renamedBoss = KILLCOUNT_RENAMES
.getOrDefault(boss, boss)
@@ -784,9 +810,6 @@ public class ChatCommandsPlugin extends Plugin
case HOPPING:
pohOwner = null;
break;
case LOGGED_IN:
loadPetIcons();
break;
}
}

View File

@@ -164,4 +164,15 @@ public interface ChatFilterConfig extends Config
{
return 0;
}
@ConfigItem(
keyName = "stripAccents",
name = "Strip accents",
description = "Remove accents before applying filters",
position = 13
)
default boolean stripAccents()
{
return false;
}
}

View File

@@ -321,7 +321,7 @@ public class ChatFilterPlugin extends Plugin
{
String strippedMessage = jagexPrintableCharMatcher.retainFrom(message)
.replace('\u00A0', ' ');
String strippedAccents = StringUtils.stripAccents(strippedMessage);
String strippedAccents = stripAccents(strippedMessage);
assert strippedMessage.length() == strippedAccents.length();
if (username != null && shouldFilterByName(username))
@@ -377,23 +377,28 @@ public class ChatFilterPlugin extends Plugin
filteredNamePatterns.clear();
Text.fromCSV(config.filteredWords()).stream()
.map(StringUtils::stripAccents)
.map(this::stripAccents)
.map(s -> Pattern.compile(Pattern.quote(s), Pattern.CASE_INSENSITIVE))
.forEach(filteredPatterns::add);
NEWLINE_SPLITTER.splitToList(config.filteredRegex()).stream()
.map(StringUtils::stripAccents)
.map(this::stripAccents)
.map(ChatFilterPlugin::compilePattern)
.filter(Objects::nonNull)
.forEach(filteredPatterns::add);
NEWLINE_SPLITTER.splitToList(config.filteredNames()).stream()
.map(StringUtils::stripAccents)
.map(this::stripAccents)
.map(ChatFilterPlugin::compilePattern)
.filter(Objects::nonNull)
.forEach(filteredNamePatterns::add);
}
private String stripAccents(String input)
{
return config.stripAccents() ? StringUtils.stripAccents(input) : input;
}
private static Pattern compilePattern(String pattern)
{
try

View File

@@ -70,7 +70,20 @@ public class ClueScrollOverlay extends OverlayPanel
item(KANDARIN_HEADGEAR_4),
item(BRUMA_TORCH),
item(MAX_CAPE),
item(MAX_CAPE_13342));
item(MAX_CAPE_13342),
item(ABYSSAL_LANTERN_NORMAL_LOGS),
item(ABYSSAL_LANTERN_BLUE_LOGS),
item(ABYSSAL_LANTERN_RED_LOGS),
item(ABYSSAL_LANTERN_WHITE_LOGS),
item(ABYSSAL_LANTERN_PURPLE_LOGS),
item(ABYSSAL_LANTERN_GREEN_LOGS),
item(ABYSSAL_LANTERN_OAK_LOGS),
item(ABYSSAL_LANTERN_WILLOW_LOGS),
item(ABYSSAL_LANTERN_MAPLE_LOGS),
item(ABYSSAL_LANTERN_YEW_LOGS),
item(ABYSSAL_LANTERN_BLISTERWOOD_LOGS),
item(ABYSSAL_LANTERN_MAGIC_LOGS),
item(ABYSSAL_LANTERN_REDWOOD_LOGS));
public static final Color TITLED_CONTENT_COLOR = new Color(190, 190, 190);

View File

@@ -230,8 +230,8 @@ public class AnagramClue extends ClueScroll implements TextClueScroll, NpcClueSc
.build(),
AnagramClue.builder()
.text("DEKAGRAM")
.npc("Dark mage")
.location(new WorldPoint(3039, 4835, 0))
.npc("Dark Mage")
.location(new WorldPoint(3039, 4834, 0))
.area("Centre of the Abyss")
.question("How many rifts are found here in the abyss?")
.answer("13")

View File

@@ -37,7 +37,6 @@ import net.runelite.api.IndexedSprite;
import net.runelite.api.MessageNode;
import net.runelite.api.Player;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.OverheadTextChanged;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.eventbus.Subscribe;
@@ -67,27 +66,27 @@ public class EmojiPlugin extends Plugin
@Override
protected void startUp()
{
clientThread.invoke(this::loadEmojiIcons);
}
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
if (gameStateChanged.getGameState() == GameState.LOGGED_IN)
clientThread.invoke(() ->
{
if (client.getModIcons() == null)
{
return false;
}
loadEmojiIcons();
}
return true;
});
}
private void loadEmojiIcons()
{
final IndexedSprite[] modIcons = client.getModIcons();
if (modIconsStart != -1 || modIcons == null)
if (modIconsStart != -1)
{
return;
}
final Emoji[] emojis = Emoji.values();
final IndexedSprite[] modIcons = client.getModIcons();
assert modIcons != null;
final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + emojis.length);
modIconsStart = modIcons.length;

View File

@@ -44,7 +44,6 @@ import net.runelite.api.IndexedSprite;
import net.runelite.api.MenuAction;
import net.runelite.api.Nameable;
import net.runelite.api.ScriptID;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.NameableNameChanged;
import net.runelite.api.events.RemovedFriend;
@@ -116,7 +115,15 @@ public class FriendNotesPlugin extends Plugin
protected void startUp() throws Exception
{
overlayManager.add(overlay);
clientThread.invoke(this::loadIcon);
clientThread.invoke(() ->
{
if (client.getModIcons() == null)
{
return false;
}
loadIcon();
return true;
});
if (client.getGameState() == GameState.LOGGED_IN)
{
rebuildFriendsList();
@@ -135,15 +142,6 @@ public class FriendNotesPlugin extends Plugin
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
if (event.getGameState() == GameState.LOGGED_IN)
{
loadIcon();
}
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
@@ -380,8 +378,7 @@ public class FriendNotesPlugin extends Plugin
private void loadIcon()
{
final IndexedSprite[] modIcons = client.getModIcons();
if (iconIdx != -1 || modIcons == null)
if (iconIdx != -1)
{
return;
}
@@ -394,6 +391,8 @@ public class FriendNotesPlugin extends Plugin
final BufferedImage resized = ImageUtil.resizeImage(iconImg, ICON_WIDTH, ICON_HEIGHT);
final IndexedSprite[] modIcons = client.getModIcons();
assert modIcons != null;
final IndexedSprite[] newIcons = Arrays.copyOf(modIcons, modIcons.length + 1);
newIcons[newIcons.length - 1] = ImageUtil.getImageIndexedSprite(resized, client);

View File

@@ -100,7 +100,7 @@ public enum MagicAction implements SkillAction
FIRE_BLAST("Fire Blast", 59, 34.5f, SpriteID.SPELL_FIRE_BLAST),
MARK_OF_DARKNESS("Mark of Darkness", 59, 70, SpriteID.SPELL_MARK_OF_DARKNESS),
SENNTISTEN_TELEPORT("Senntisten Teleport", 60, 70, SpriteID.SPELL_SENNTISTEN_TELEPORT),
CLAWS_OF_GUTHIX("Claws Of Guthix", 60, 35, SpriteID.SPELL_CLAWS_OF_GUTHIC),
CLAWS_OF_GUTHIX("Claws Of Guthix", 60, 35, SpriteID.SPELL_CLAWS_OF_GUTHIX),
FLAMES_OF_ZAMORAK("Flames Of Zamorak", 60, 35, SpriteID.SPELL_FLAMES_OF_ZAMORAK),
SARADOMIN_STRIKE("Saradomin Strike", 60, 35, SpriteID.SPELL_SARADOMIN_STRIKE),
CHARGE_EARTH_ORB("Charge Earth Orb", 60, 70, SpriteID.SPELL_CHARGE_EARTH_ORB),

View File

@@ -29,9 +29,9 @@ import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.MenuEntry;
@@ -39,8 +39,8 @@ import net.runelite.api.Point;
import net.runelite.api.Prayer;
import net.runelite.api.Skill;
import net.runelite.api.SpriteID;
import net.runelite.api.Varbits;
import net.runelite.api.VarPlayer;
import net.runelite.api.Varbits;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.game.AlternateSprites;
@@ -86,10 +86,10 @@ class StatusBarsOverlay extends Overlay
private final SpriteManager spriteManager;
private final Image prayerIcon;
private final Image heartDisease;
private final Image heartPoison;
private final Image heartVenom;
private Image heartIcon;
private Image heartDisease;
private Image heartPoison;
private Image heartVenom;
private Image specialIcon;
private Image energyIcon;
private final Map<BarMode, BarRenderer> barRenderers = new EnumMap<>(BarMode.class);
@@ -106,6 +106,10 @@ class StatusBarsOverlay extends Overlay
this.spriteManager = spriteManager;
prayerIcon = ImageUtil.resizeCanvas(ImageUtil.resizeImage(skillIconManager.getSkillImage(Skill.PRAYER, true), IMAGE_SIZE, IMAGE_SIZE), ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
heartDisease = ImageUtil.resizeCanvas(ImageUtil.loadImageResource(AlternateSprites.class, AlternateSprites.DISEASE_HEART), ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
heartPoison = ImageUtil.resizeCanvas(ImageUtil.loadImageResource(AlternateSprites.class, AlternateSprites.POISON_HEART), ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
heartVenom = ImageUtil.resizeCanvas(ImageUtil.loadImageResource(AlternateSprites.class, AlternateSprites.VENOM_HEART), ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
initRenderers();
}
@@ -321,16 +325,28 @@ class StatusBarsOverlay extends Overlay
private void buildIcons()
{
if (heartIcon != null && heartDisease != null && heartPoison != null && heartVenom != null && energyIcon != null && specialIcon != null)
if (heartIcon == null)
{
return;
heartIcon = loadAndResize(SpriteID.MINIMAP_ORB_HITPOINTS_ICON);
}
if (energyIcon == null)
{
energyIcon = loadAndResize(SpriteID.MINIMAP_ORB_WALK_ICON);
}
if (specialIcon == null)
{
specialIcon = loadAndResize(SpriteID.MINIMAP_ORB_SPECIAL_ICON);
}
}
private BufferedImage loadAndResize(int spriteId)
{
BufferedImage image = spriteManager.getSprite(spriteId, 0);
if (image == null)
{
return null;
}
heartIcon = ImageUtil.resizeCanvas(Objects.requireNonNull(spriteManager.getSprite(SpriteID.MINIMAP_ORB_HITPOINTS_ICON, 0)), ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
heartDisease = ImageUtil.resizeCanvas(ImageUtil.loadImageResource(AlternateSprites.class, AlternateSprites.DISEASE_HEART), ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
heartPoison = ImageUtil.resizeCanvas(ImageUtil.loadImageResource(AlternateSprites.class, AlternateSprites.POISON_HEART), ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
heartVenom = ImageUtil.resizeCanvas(ImageUtil.loadImageResource(AlternateSprites.class, AlternateSprites.VENOM_HEART), ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
energyIcon = ImageUtil.resizeCanvas(Objects.requireNonNull(spriteManager.getSprite(SpriteID.MINIMAP_ORB_WALK_ICON, 0)), ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
specialIcon = ImageUtil.resizeCanvas(Objects.requireNonNull(spriteManager.getSprite(SpriteID.MINIMAP_ORB_SPECIAL_ICON, 0)), ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
return ImageUtil.resizeCanvas(image, ICON_DIMENSIONS.width, ICON_DIMENSIONS.height);
}
}

View File

@@ -137,7 +137,6 @@ public class TimersPlugin extends Plugin
private static final int NMZ_MAP_REGION_ID = 9033;
private static final Pattern TZHAAR_WAVE_MESSAGE = Pattern.compile("Wave: (\\d+)");
private static final String TZHAAR_DEFEATED_MESSAGE = "You have been defeated!";
private static final Pattern TZHAAR_COMPLETE_MESSAGE = Pattern.compile("Your (?:TzTok-Jad|TzKal-Zuk) kill count is:");
private static final Pattern TZHAAR_PAUSED_MESSAGE = Pattern.compile("The (?:Inferno|Fight Cave) has been paused. You may now log out.");
private TimerTimer freezeTimer;
@@ -776,7 +775,7 @@ public class TimersPlugin extends Plugin
}
}
if (message.equals(TZHAAR_DEFEATED_MESSAGE) || TZHAAR_COMPLETE_MESSAGE.matcher(message).matches())
if (message.equals(TZHAAR_DEFEATED_MESSAGE))
{
log.debug("Stopping tzhaar timer");
removeTzhaarTimer();

View File

@@ -43,6 +43,7 @@ public interface TimeTrackingConfig extends Config
String PREFER_SOONEST = "preferSoonest";
String NOTIFY = "notify";
String BIRDHOUSE_NOTIFY = "birdHouseNotification";
String COMPOST = "compost";
@ConfigItem(
keyName = "timeFormatMode",

View File

@@ -44,6 +44,7 @@ import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetModalMode;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.RuneScapeProfileChanged;
@@ -54,6 +55,7 @@ import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.PREFER
import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.STOPWATCHES;
import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.TIMERS;
import net.runelite.client.plugins.timetracking.clocks.ClockManager;
import net.runelite.client.plugins.timetracking.farming.CompostTracker;
import net.runelite.client.plugins.timetracking.farming.FarmingContractManager;
import net.runelite.client.plugins.timetracking.farming.FarmingTracker;
import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker;
@@ -77,6 +79,12 @@ public class TimeTrackingPlugin extends Plugin
@Inject
private Client client;
@Inject
private EventBus eventBus;
@Inject
private CompostTracker compostTracker;
@Inject
private FarmingTracker farmingTracker;
@@ -125,6 +133,8 @@ public class TimeTrackingPlugin extends Plugin
birdHouseTracker.loadFromConfig();
farmingTracker.loadCompletionTimes();
eventBus.register(compostTracker);
final BufferedImage icon = ImageUtil.loadImageResource(getClass(), "watch.png");
panel = injector.getInstance(TimeTrackingPanel.class);
@@ -148,6 +158,8 @@ public class TimeTrackingPlugin extends Plugin
lastTickLocation = null;
lastTickPostLogin = false;
eventBus.unregister(compostTracker);
if (panelUpdateFuture != null)
{
panelUpdateFuture.cancel(true);

View File

@@ -29,8 +29,11 @@ import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.border.EmptyBorder;
@@ -48,9 +51,20 @@ public class TimeablePanel<T> extends JPanel
{
private static final ImageIcon NOTIFY_ICON = new ImageIcon(ImageUtil.loadImageResource(TimeTrackingPlugin.class, "notify_icon.png"));
private static final ImageIcon NOTIFY_SELECTED_ICON = new ImageIcon(ImageUtil.loadImageResource(TimeTrackingPlugin.class, "notify_selected_icon.png"));
private static final Rectangle OVERLAY_ICON_BOUNDS;
static
{
int width = Constants.ITEM_SPRITE_WIDTH * 2 / 3;
int height = Constants.ITEM_SPRITE_HEIGHT * 2 / 3;
int x = Constants.ITEM_SPRITE_WIDTH - width;
int y = Constants.ITEM_SPRITE_HEIGHT - height;
OVERLAY_ICON_BOUNDS = new Rectangle(x, y, width, height);
}
private final T timeable;
private final JLabel icon = new JLabel();
private final JLabel overlayIcon = new JLabel();
private final JLabel farmingContractIcon = new JLabel();
private final JToggleButton notifyButton = new JToggleButton();
private final JLabel estimate = new JLabel();
@@ -70,6 +84,7 @@ public class TimeablePanel<T> extends JPanel
topContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
icon.setMinimumSize(new Dimension(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT));
overlayIcon.setMinimumSize(OVERLAY_ICON_BOUNDS.getSize());
farmingContractIcon.setMinimumSize(new Dimension(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT));
JPanel infoPanel = new JPanel();
@@ -105,8 +120,15 @@ public class TimeablePanel<T> extends JPanel
iconPanel.add(notifyPanel, BorderLayout.EAST);
iconPanel.add(farmingContractIcon, BorderLayout.WEST);
JLayeredPane layeredIconPane = new JLayeredPane();
layeredIconPane.setPreferredSize(new Dimension(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT));
layeredIconPane.add(icon, Integer.valueOf(0));
layeredIconPane.add(overlayIcon, Integer.valueOf(1));
icon.setBounds(0, 0, Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT);
overlayIcon.setBounds(OVERLAY_ICON_BOUNDS);
topContainer.add(iconPanel, BorderLayout.EAST);
topContainer.add(icon, BorderLayout.WEST);
topContainer.add(layeredIconPane, BorderLayout.WEST);
topContainer.add(infoPanel, BorderLayout.CENTER);
progress.setValue(0);
@@ -115,4 +137,21 @@ public class TimeablePanel<T> extends JPanel
add(topContainer, BorderLayout.NORTH);
add(progress, BorderLayout.SOUTH);
}
public void setOverlayIconImage(BufferedImage overlayImg)
{
if (overlayImg == null)
{
overlayIcon.setIcon(null);
return;
}
if (OVERLAY_ICON_BOUNDS.width != overlayImg.getWidth() || OVERLAY_ICON_BOUNDS.height != overlayImg.getHeight())
{
overlayImg = ImageUtil.resizeImage(overlayImg, OVERLAY_ICON_BOUNDS.width, OVERLAY_ICON_BOUNDS.height);
}
overlayIcon.setIcon(new ImageIcon(overlayImg));
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2022 LlemonDuck
* 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.timetracking.farming;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.ItemID;
@RequiredArgsConstructor
@Getter
public enum CompostState
{
COMPOST(ItemID.COMPOST),
SUPERCOMPOST(ItemID.SUPERCOMPOST),
ULTRACOMPOST(ItemID.ULTRACOMPOST),
;
private final int itemId;
}

View File

@@ -0,0 +1,293 @@
/*
* Copyright (c) 2022 LlemonDuck
* 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.timetracking.farming;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.ItemID;
import net.runelite.api.ObjectComposition;
import net.runelite.api.Tile;
import net.runelite.api.annotations.Varbit;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.MenuOptionClicked;
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.timetracking.TimeTrackingConfig;
@Singleton
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Inject))
public class CompostTracker
{
@Value
@VisibleForTesting
static class PendingCompost
{
Instant timeout;
WorldPoint patchLocation;
FarmingPatch farmingPatch;
}
private static final Duration COMPOST_ACTION_TIMEOUT = Duration.ofSeconds(30);
private static final Pattern COMPOST_USED_ON_PATCH = Pattern.compile(
"You treat the .+ with (?<compostType>ultra|super|)compost\\.");
private static final Pattern FERTILE_SOIL_CAST = Pattern.compile(
"The .+ has been treated with (?<compostType>ultra|super|)compost\\.");
private static final Pattern ALREADY_TREATED = Pattern.compile(
"This .+ has already been (treated|fertilised) with (?<compostType>ultra|super|)compost(?: - the spell can't make it any more fertile)?\\.");
private static final Pattern INSPECT_PATCH = Pattern.compile(
"This is an? .+\\. The soil has been treated with (?<compostType>ultra|super|)compost\\..*");
private static final ImmutableSet<Integer> COMPOST_ITEMS = ImmutableSet.of(
ItemID.COMPOST,
ItemID.SUPERCOMPOST,
ItemID.ULTRACOMPOST,
ItemID.BOTTOMLESS_COMPOST_BUCKET_22997
);
private final Client client;
private final FarmingWorld farmingWorld;
private final ConfigManager configManager;
@VisibleForTesting
final Map<FarmingPatch, PendingCompost> pendingCompostActions = new HashMap<>();
private static String configKey(FarmingPatch fp)
{
return fp.configKey() + "." + TimeTrackingConfig.COMPOST;
}
public void setCompostState(FarmingPatch fp, CompostState state)
{
log.debug("Storing compost state [{}] for patch [{}]", state, fp);
if (state == null)
{
configManager.unsetRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, configKey(fp));
}
else
{
configManager.setRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, configKey(fp), state);
}
}
public CompostState getCompostState(FarmingPatch fp)
{
return configManager.getRSProfileConfiguration(
TimeTrackingConfig.CONFIG_GROUP,
configKey(fp),
CompostState.class
);
}
@Subscribe
public void onMenuOptionClicked(MenuOptionClicked e)
{
if (!isCompostAction(e))
{
return;
}
ObjectComposition patchDef = client.getObjectDefinition(e.getId());
WorldPoint actionLocation = WorldPoint.fromScene(client, e.getParam0(), e.getParam1(), client.getPlane());
FarmingPatch targetPatch = farmingWorld.getRegionsForLocation(actionLocation)
.stream()
.flatMap(fr -> Arrays.stream(fr.getPatches()))
.filter(fp -> fp.getVarbit() == patchDef.getVarbitId())
.findFirst()
.orElse(null);
if (targetPatch == null)
{
return;
}
log.debug("Storing pending compost action for patch [{}]", targetPatch);
PendingCompost pc = new PendingCompost(
Instant.now().plus(COMPOST_ACTION_TIMEOUT),
actionLocation,
targetPatch
);
pendingCompostActions.put(targetPatch, pc);
}
private boolean isCompostAction(MenuOptionClicked e)
{
switch (e.getMenuAction())
{
case WIDGET_TARGET_ON_GAME_OBJECT:
Widget w = client.getSelectedWidget();
assert w != null;
return COMPOST_ITEMS.contains(w.getItemId()) || w.getId() == WidgetInfo.SPELL_LUNAR_FERTILE_SOIL.getPackedId();
case GAME_OBJECT_FIRST_OPTION:
case GAME_OBJECT_SECOND_OPTION:
case GAME_OBJECT_THIRD_OPTION:
case GAME_OBJECT_FOURTH_OPTION:
case GAME_OBJECT_FIFTH_OPTION:
return "Inspect".equals(e.getMenuOption());
default:
return false;
}
}
@Subscribe
public void onChatMessage(ChatMessage e)
{
if (e.getType() != ChatMessageType.GAMEMESSAGE && e.getType() != ChatMessageType.SPAM)
{
return;
}
CompostState compostUsed = determineCompostUsed(e.getMessage());
if (compostUsed == null)
{
return;
}
this.expirePendingActions();
pendingCompostActions.values()
.stream()
.filter(this::playerIsBesidePatch)
.findFirst()
.ifPresent(pc ->
{
setCompostState(pc.getFarmingPatch(), compostUsed);
pendingCompostActions.remove(pc.getFarmingPatch());
});
}
@Subscribe
public void onGameStateChanged(GameStateChanged e)
{
switch (e.getGameState())
{
case LOGGED_IN:
case LOADING:
return;
default:
pendingCompostActions.clear();
}
}
private boolean playerIsBesidePatch(PendingCompost pendingCompost)
{
// find gameobject instance in scene
// it is possible that the scene has reloaded between use and action occurring so we use worldpoint
// instead of storing scene coords in the menuoptionclicked event
LocalPoint localPatchLocation = LocalPoint.fromWorld(client, pendingCompost.getPatchLocation());
if (localPatchLocation == null)
{
return false;
}
@Varbit int patchVarb = pendingCompost.getFarmingPatch().getVarbit();
Tile patchTile = client.getScene()
.getTiles()[client.getPlane()][localPatchLocation.getSceneX()][localPatchLocation.getSceneY()];
GameObject patchObject = null;
for (GameObject go : patchTile.getGameObjects())
{
if (go != null && client.getObjectDefinition(go.getId()).getVarbitId() == patchVarb)
{
patchObject = go;
break;
}
}
assert patchObject != null;
// player coords
final WorldPoint playerPos = client.getLocalPlayer().getWorldLocation();
final int playerX = playerPos.getX();
final int playerY = playerPos.getY();
// patch coords
final WorldPoint patchBase = pendingCompost.getPatchLocation();
final int minX = patchBase.getX();
final int minY = patchBase.getY();
final int maxX = minX + patchObject.sizeX() - 1;
final int maxY = minY + patchObject.sizeY() - 1;
// player should be within one tile of these coords
return playerX >= (minX - 1) && playerX <= (maxX + 1) && playerY >= (minY - 1) && playerY <= (maxY + 1);
}
private void expirePendingActions()
{
pendingCompostActions.values().removeIf(e -> Instant.now().isAfter(e.getTimeout()));
}
@VisibleForTesting
static CompostState determineCompostUsed(String chatMessage)
{
if (!chatMessage.contains("compost"))
{
return null;
}
Matcher matcher;
if ((matcher = COMPOST_USED_ON_PATCH.matcher(chatMessage)).matches() ||
(matcher = FERTILE_SOIL_CAST.matcher(chatMessage)).matches() ||
(matcher = ALREADY_TREATED.matcher(chatMessage)).matches() ||
(matcher = INSPECT_PATCH.matcher(chatMessage)).matches())
{
String compostGroup = matcher.group("compostType");
switch (compostGroup)
{
case "ultra":
return CompostState.ULTRACOMPOST;
case "super":
return CompostState.SUPERCOMPOST;
default:
return CompostState.COMPOST;
}
}
return null;
}
}

View File

@@ -28,6 +28,7 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import net.runelite.api.annotations.Varbit;
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
@@ -35,13 +36,17 @@ import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
access = AccessLevel.PACKAGE
)
@Getter
@ToString(onlyExplicitlyIncluded = true)
class FarmingPatch
{
@Setter(AccessLevel.PACKAGE)
@ToString.Include
private FarmingRegion region;
@ToString.Include
private final String name;
@Getter(onMethod_ = {@Varbit})
private final int varbit;
@ToString.Include
private final PatchImplementation implementation;
String configKey()

View File

@@ -43,10 +43,12 @@ import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
import net.runelite.client.plugins.timetracking.TimeablePanel;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import net.runelite.client.util.AsyncBufferedImage;
public class FarmingTabPanel extends TabContentPanel
{
private final FarmingTracker farmingTracker;
private final CompostTracker compostTracker;
private final ItemManager itemManager;
private final ConfigManager configManager;
private final TimeTrackingConfig config;
@@ -55,6 +57,7 @@ public class FarmingTabPanel extends TabContentPanel
FarmingTabPanel(
FarmingTracker farmingTracker,
CompostTracker compostTracker,
ItemManager itemManager,
ConfigManager configManager,
TimeTrackingConfig config,
@@ -63,6 +66,7 @@ public class FarmingTabPanel extends TabContentPanel
)
{
this.farmingTracker = farmingTracker;
this.compostTracker = compostTracker;
this.itemManager = itemManager;
this.configManager = configManager;
this.config = config;
@@ -131,7 +135,6 @@ public class FarmingTabPanel extends TabContentPanel
p.setBorder(null);
}
}
}
@Override
@@ -150,10 +153,26 @@ public class FarmingTabPanel extends TabContentPanel
FarmingPatch patch = panel.getTimeable();
PatchPrediction prediction = farmingTracker.predictPatch(patch);
CompostState compostState = compostTracker.getCompostState(patch);
String compostTooltip = "";
if (compostState != null)
{
AsyncBufferedImage compostImg = itemManager.getImage(compostState.getItemId());
Runnable compostOverlayRunnable = () -> panel.setOverlayIconImage(compostImg);
compostImg.onLoaded(compostOverlayRunnable);
compostOverlayRunnable.run();
compostTooltip = " with " + compostState.name().toLowerCase();
}
else
{
panel.setOverlayIconImage(null);
}
if (prediction == null)
{
itemManager.getImage(Produce.WEEDS.getItemID()).addTo(panel.getIcon());
panel.getIcon().setToolTipText("Unknown state");
panel.getIcon().setToolTipText("Unknown state" + compostTooltip);
panel.getProgress().setMaximumValue(0);
panel.getProgress().setValue(0);
panel.getProgress().setVisible(false);
@@ -165,12 +184,12 @@ public class FarmingTabPanel extends TabContentPanel
if (prediction.getProduce().getItemID() < 0)
{
panel.getIcon().setIcon(null);
panel.getIcon().setToolTipText("Unknown state");
panel.getIcon().setToolTipText("Unknown state" + compostTooltip);
}
else
{
itemManager.getImage(prediction.getProduce().getItemID()).addTo(panel.getIcon());
panel.getIcon().setToolTipText(prediction.getProduce().getName());
panel.getIcon().setToolTipText(prediction.getProduce().getName() + compostTooltip);
}
switch (prediction.getCropState())

View File

@@ -63,6 +63,7 @@ public class FarmingTracker
private final TimeTrackingConfig config;
private final FarmingWorld farmingWorld;
private final Notifier notifier;
private final CompostTracker compostTracker;
private final Map<Tab, SummaryState> summaries = new EnumMap<>(Tab.class);
@@ -78,7 +79,7 @@ public class FarmingTracker
private boolean firstNotifyCheck = true;
@Inject
private FarmingTracker(Client client, ItemManager itemManager, ConfigManager configManager, TimeTrackingConfig config, FarmingWorld farmingWorld, Notifier notifier)
private FarmingTracker(Client client, ItemManager itemManager, ConfigManager configManager, TimeTrackingConfig config, FarmingWorld farmingWorld, Notifier notifier, CompostTracker compostTracker)
{
this.client = client;
this.itemManager = itemManager;
@@ -86,11 +87,12 @@ public class FarmingTracker
this.config = config;
this.farmingWorld = farmingWorld;
this.notifier = notifier;
this.compostTracker = compostTracker;
}
public FarmingTabPanel createTabPanel(Tab tab, FarmingContractManager farmingContractManager)
{
return new FarmingTabPanel(this, itemManager, configManager, config, farmingWorld.getTabs().get(tab), farmingContractManager);
return new FarmingTabPanel(this, compostTracker, itemManager, configManager, config, farmingWorld.getTabs().get(tab), farmingContractManager);
}
/**
@@ -148,6 +150,12 @@ public class FarmingTracker
String strVarbit = Integer.toString(client.getVarbitValue(varbit));
String storedValue = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, key);
PatchState currentPatchState = patch.getImplementation().forVarbitValue(client.getVarbitValue(varbit));
if (currentPatchState == null)
{
continue;
}
if (storedValue != null)
{
String[] parts = storedValue.split(":");
@@ -172,9 +180,8 @@ public class FarmingTracker
else if (!newRegionLoaded && timeSinceModalClose > 1)
{
PatchState previousPatchState = patch.getImplementation().forVarbitValue(Integer.parseInt(parts[0]));
PatchState currentPatchState = patch.getImplementation().forVarbitValue(client.getVarbitValue(varbit));
if (previousPatchState == null || currentPatchState == null)
if (previousPatchState == null)
{
continue;
}
@@ -217,6 +224,11 @@ public class FarmingTracker
}
}
if (currentPatchState.getCropState() == CropState.DEAD || currentPatchState.getCropState() == CropState.HARVESTABLE)
{
compostTracker.setCompostState(patch, null);
}
String value = strVarbit + ":" + unixNow;
configManager.setRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, key, value);
changed = true;

View File

@@ -113,7 +113,7 @@ public enum Produce
POTATO_CACTUS("Potato cactus", "Potato cacti", PatchImplementation.CACTUS, ItemID.POTATO_CACTUS, 10, 8, 5, 7),
// Hardwood
TEAK("Teak", PatchImplementation.HARDWOOD_TREE, ItemID.TEAK_LOGS, 560, 8),
TEAK("Teak", PatchImplementation.HARDWOOD_TREE, ItemID.TEAK_LOGS, 640, 8),
MAHOGANY("Mahogany", PatchImplementation.HARDWOOD_TREE, ItemID.MAHOGANY_LOGS, 640, 9),
// Anima

View File

@@ -66,12 +66,15 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import org.mockito.Mock;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.junit.MockitoJUnitRunner;
@@ -1156,4 +1159,13 @@ public class ChatCommandsPluginTest
verify(configManager).setRSProfileConfiguration("killcount", "guardians of the rift", 167);
}
@Test
public void testReward()
{
ChatMessage chatMessage = new ChatMessage(null, GAMEMESSAGE, "", "Your reward is: <col=ff0000>1</col> x <col=ff0000>Kebab</col>.", null, 0);
chatCommandsPlugin.onChatMessage(chatMessage);
verify(configManager, never()).setRSProfileConfiguration(anyString(), anyString(), anyInt());
}
}

View File

@@ -191,6 +191,7 @@ public class ChatFilterPluginTest
{
when(chatFilterConfig.filterType()).thenReturn(ChatFilterType.CENSOR_WORDS);
when(chatFilterConfig.filteredWords()).thenReturn("filterme");
when(chatFilterConfig.stripAccents()).thenReturn(true);
chatFilterPlugin.updateFilteredPatterns();
assertEquals("plëäsë ******** plügïn", chatFilterPlugin.censorMessage("Blue", "plëäsë fïltërmë plügïn"));
@@ -211,6 +212,7 @@ public class ChatFilterPluginTest
{
when(chatFilterConfig.filterType()).thenReturn(ChatFilterType.CENSOR_WORDS);
when(chatFilterConfig.filteredWords()).thenReturn("plëäsë, filterme");
when(chatFilterConfig.stripAccents()).thenReturn(true);
chatFilterPlugin.updateFilteredPatterns();
assertEquals("****** ******** plügïn", chatFilterPlugin.censorMessage("Blue", "plëäsë fïltërmë plügïn"));

View File

@@ -28,24 +28,27 @@ import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.testing.fieldbinder.Bind;
import com.google.inject.testing.fieldbinder.BoundFieldModule;
import java.util.function.BooleanSupplier;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.IndexedSprite;
import net.runelite.api.MessageNode;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameStateChanged;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.chat.ChatMessageManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.mockito.ArgumentMatchers.any;
import org.mockito.Mock;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.junit.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(MockitoJUnitRunner.class)
public class EmojiPluginTest
@@ -58,6 +61,10 @@ public class EmojiPluginTest
@Bind
private ChatMessageManager chatMessageManager;
@Mock
@Bind
private ClientThread clientThread;
@Inject
private EmojiPlugin emojiPlugin;
@@ -65,19 +72,23 @@ public class EmojiPluginTest
public void before()
{
Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this);
when(client.getModIcons()).thenReturn(new IndexedSprite[0]);
when(client.createIndexedSprite()).thenReturn(mock(IndexedSprite.class));
doAnswer(a ->
{
final BooleanSupplier b = a.getArgument(0);
return b.getAsBoolean();
}).when(clientThread).invoke(any(BooleanSupplier.class));
emojiPlugin.startUp();
}
@Test
public void testOnChatMessage()
{
when(client.getGameState()).thenReturn(GameState.LOGGED_IN);
when(client.getModIcons()).thenReturn(new IndexedSprite[0]);
when(client.createIndexedSprite()).thenReturn(mock(IndexedSprite.class));
// Trip emoji loading
GameStateChanged gameStateChanged = new GameStateChanged();
gameStateChanged.setGameState(GameState.LOGGED_IN);
emojiPlugin.onGameStateChanged(gameStateChanged);
MessageNode messageNode = mock(MessageNode.class);
// With chat recolor, message may be wrapped in col tags
@@ -96,13 +107,6 @@ public class EmojiPluginTest
public void testGtLt()
{
when(client.getGameState()).thenReturn(GameState.LOGGED_IN);
when(client.getModIcons()).thenReturn(new IndexedSprite[0]);
when(client.createIndexedSprite()).thenReturn(mock(IndexedSprite.class));
// Trip emoji loading
GameStateChanged gameStateChanged = new GameStateChanged();
gameStateChanged.setGameState(GameState.LOGGED_IN);
emojiPlugin.onGameStateChanged(gameStateChanged);
MessageNode messageNode = mock(MessageNode.class);
when(messageNode.getValue()).thenReturn("<gt>:D<lt>");
@@ -119,8 +123,8 @@ public class EmojiPluginTest
@Test
public void testEmojiUpdateMessage()
{
String PARTY_POPPER = "<img=" + (-1 + Emoji.getEmoji("@@@").ordinal()) + '>';
String OPEN_MOUTH = "<img=" + (-1 + Emoji.getEmoji(":O").ordinal()) + '>';
String PARTY_POPPER = "<img=" + Emoji.getEmoji("@@@").ordinal() + '>';
String OPEN_MOUTH = "<img=" + Emoji.getEmoji(":O").ordinal() + '>';
assertNull(emojiPlugin.updateMessage("@@@@@"));
assertEquals(PARTY_POPPER, emojiPlugin.updateMessage("@@@"));
assertEquals(PARTY_POPPER + ' ' + PARTY_POPPER, emojiPlugin.updateMessage("@@@ @@@"));

View File

@@ -0,0 +1,323 @@
/*
* Copyright (c) 2022 LlemonDuck
* 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.timetracking.farming;
import com.google.inject.Guice;
import com.google.inject.testing.fieldbinder.Bind;
import com.google.inject.testing.fieldbinder.BoundFieldModule;
import java.time.Instant;
import java.util.Collections;
import javax.inject.Inject;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.ItemID;
import net.runelite.api.MenuAction;
import net.runelite.api.ObjectComposition;
import net.runelite.api.Player;
import net.runelite.api.Scene;
import net.runelite.api.Tile;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.config.ConfigManager;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import org.junit.runner.RunWith;
import static org.mockito.ArgumentMatchers.any;
import org.mockito.Mock;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class CompostTrackerTest
{
@Inject
private CompostTracker compostTracker;
@Mock
@Bind
private Client client;
@Mock
@Bind
private FarmingWorld farmingWorld;
@Mock
@Bind
private ConfigManager configManager;
@Mock
@Bind
private FarmingRegion farmingRegion;
@Mock
@Bind
private FarmingPatch farmingPatch;
@Mock
@Bind
private GameObject patchObject;
@Mock
@Bind
private Player player;
@Mock
@Bind
private Scene scene;
@Mock
@Bind
private Tile tile;
@Mock
@Bind
private ObjectComposition patchDef;
@Rule
public ErrorCollector collector = new ErrorCollector();
private static final int PATCH_ID = 12345;
private static final int PATCH_VARBIT = 54321;
private static final WorldPoint worldPoint = new WorldPoint(1, 2, 0);
@Before
public void before()
{
Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this);
compostTracker.pendingCompostActions.clear();
when(client.getBaseX()).thenReturn(0);
when(client.getBaseY()).thenReturn(0);
when(client.getPlane()).thenReturn(0);
when(client.getLocalPlayer()).thenReturn(player);
when(player.getWorldLocation()).thenReturn(worldPoint);
when(client.getScene()).thenReturn(scene);
when(client.getObjectDefinition(PATCH_ID)).thenReturn(patchDef);
when(scene.getTiles()).thenReturn(new Tile[][][]{{null, {null, null, tile}}}); // indices match worldPoint
when(tile.getGameObjects()).thenReturn(new GameObject[]{patchObject});
when(farmingWorld.getRegionsForLocation(any())).thenReturn(Collections.singleton(farmingRegion));
when(farmingRegion.getPatches()).thenReturn(new FarmingPatch[]{farmingPatch});
when(farmingPatch.getVarbit()).thenReturn(PATCH_VARBIT);
when(farmingPatch.configKey()).thenReturn("MOCK");
when(patchObject.getId()).thenReturn(PATCH_ID);
when(patchObject.sizeX()).thenReturn(1);
when(patchObject.sizeY()).thenReturn(1);
when(patchDef.getVarbitId()).thenReturn(PATCH_VARBIT);
}
@Test
public void setCompostState_storesNonNullChangesToConfig()
{
compostTracker.setCompostState(farmingPatch, CompostState.COMPOST);
verify(configManager).setRSProfileConfiguration("timetracking", "MOCK.compost", CompostState.COMPOST);
}
@Test
public void setCompostState_storesNullChangesByClearingConfig()
{
compostTracker.setCompostState(farmingPatch, null);
verify(configManager).unsetRSProfileConfiguration("timetracking", "MOCK.compost");
}
@Test
public void getCompostState_directlyReturnsFromConfig()
{
when(configManager.getRSProfileConfiguration("timetracking", "MOCK.compost", CompostState.class)).thenReturn(
CompostState.SUPERCOMPOST);
assertThat(compostTracker.getCompostState(farmingPatch), is(CompostState.SUPERCOMPOST));
}
@Test
public void determineCompostUsed_returnsAppropriateCompostValues()
{
// invalid
collector.checkThat(
CompostTracker.determineCompostUsed("This is not a farming chat message."),
is((CompostState) null)
);
collector.checkThat(
CompostTracker.determineCompostUsed("Contains word compost but is not examine message."),
is((CompostState) null)
);
// inspect
collector.checkThat(
CompostTracker.determineCompostUsed("This is an allotment. The soil has been treated with supercompost. The patch is empty and weeded."),
is(CompostState.SUPERCOMPOST)
);
// fertile soil on existing patch
collector.checkThat(
CompostTracker.determineCompostUsed("This patch has already been fertilised with ultracompost - the spell can't make it any more fertile."),
is(CompostState.ULTRACOMPOST)
);
// fertile soil on cleared patch
collector.checkThat(
CompostTracker.determineCompostUsed("The herb patch has been treated with supercompost."),
is(CompostState.SUPERCOMPOST)
);
// bucket on cleared patch
collector.checkThat(
CompostTracker.determineCompostUsed("You treat the herb patch with ultracompost."),
is(CompostState.ULTRACOMPOST)
);
collector.checkThat(
CompostTracker.determineCompostUsed("You treat the tree patch with compost."),
is(CompostState.COMPOST)
);
collector.checkThat(
CompostTracker.determineCompostUsed("You treat the fruit tree patch with supercompost."),
is(CompostState.SUPERCOMPOST)
);
}
@Test
public void onMenuOptionClicked_queuesPendingCompostForInspectActions()
{
MenuOptionClicked inspectPatchAction = mock(MenuOptionClicked.class);
when(inspectPatchAction.getMenuAction()).thenReturn(MenuAction.GAME_OBJECT_SECOND_OPTION);
when(inspectPatchAction.getMenuOption()).thenReturn("Inspect");
when(inspectPatchAction.getId()).thenReturn(PATCH_ID);
when(inspectPatchAction.getParam0()).thenReturn(1);
when(inspectPatchAction.getParam1()).thenReturn(2);
compostTracker.onMenuOptionClicked(inspectPatchAction);
CompostTracker.PendingCompost actual = compostTracker.pendingCompostActions.get(farmingPatch);
assertThat(actual.getFarmingPatch(), is(farmingPatch));
assertThat(actual.getPatchLocation(), is(new WorldPoint(1, 2, 0)));
}
@Test
public void onMenuOptionClicked_queuesPendingCompostForCompostActions()
{
Widget widget = mock(Widget.class);
when(client.getSelectedWidget()).thenReturn(widget);
when(widget.getItemId()).thenReturn(ItemID.ULTRACOMPOST);
MenuOptionClicked inspectPatchAction = mock(MenuOptionClicked.class);
when(inspectPatchAction.getMenuAction()).thenReturn(MenuAction.WIDGET_TARGET_ON_GAME_OBJECT);
when(inspectPatchAction.getId()).thenReturn(PATCH_ID);
when(inspectPatchAction.getParam0()).thenReturn(1);
when(inspectPatchAction.getParam1()).thenReturn(2);
compostTracker.onMenuOptionClicked(inspectPatchAction);
CompostTracker.PendingCompost actual = compostTracker.pendingCompostActions.get(farmingPatch);
assertThat(actual.getFarmingPatch(), is(farmingPatch));
assertThat(actual.getPatchLocation(), is(new WorldPoint(1, 2, 0)));
}
@Test
public void onMenuOptionClicked_queuesPendingCompostForFertileSoilSpellActions()
{
Widget widget = mock(Widget.class);
when(client.getSelectedWidget()).thenReturn(widget);
when(widget.getId()).thenReturn(WidgetInfo.SPELL_LUNAR_FERTILE_SOIL.getPackedId());
MenuOptionClicked inspectPatchAction = mock(MenuOptionClicked.class);
when(inspectPatchAction.getMenuAction()).thenReturn(MenuAction.WIDGET_TARGET_ON_GAME_OBJECT);
when(inspectPatchAction.getId()).thenReturn(PATCH_ID);
when(inspectPatchAction.getParam0()).thenReturn(1);
when(inspectPatchAction.getParam1()).thenReturn(2);
compostTracker.onMenuOptionClicked(inspectPatchAction);
CompostTracker.PendingCompost actual = compostTracker.pendingCompostActions.get(farmingPatch);
assertThat(actual.getFarmingPatch(), is(farmingPatch));
assertThat(actual.getPatchLocation(), is(new WorldPoint(1, 2, 0)));
}
@Test
public void onChatMessage_ignoresInvalidTypes()
{
ChatMessage chatEvent = mock(ChatMessage.class);
when(chatEvent.getType()).thenReturn(ChatMessageType.PUBLICCHAT);
compostTracker.onChatMessage(chatEvent);
verifyNoInteractions(client);
verifyNoInteractions(farmingWorld);
}
@Test
public void onChatMessage_handlesInspectMessages()
{
ChatMessage chatEvent = mock(ChatMessage.class);
when(chatEvent.getType()).thenReturn(ChatMessageType.SPAM);
when(chatEvent.getMessage()).thenReturn("This is a tree patch. The soil has been treated with ultracompost. The patch is empty and weeded.");
compostTracker.pendingCompostActions.put(farmingPatch, new CompostTracker.PendingCompost(Instant.MAX, worldPoint, farmingPatch));
compostTracker.onChatMessage(chatEvent);
verify(configManager).setRSProfileConfiguration("timetracking", "MOCK.compost", CompostState.ULTRACOMPOST);
}
@Test
public void onChatMessage_handlesBucketUseMessages()
{
ChatMessage chatEvent = mock(ChatMessage.class);
when(chatEvent.getType()).thenReturn(ChatMessageType.SPAM);
when(chatEvent.getMessage()).thenReturn("You treat the herb patch with compost.");
compostTracker.pendingCompostActions.put(farmingPatch, new CompostTracker.PendingCompost(Instant.MAX, worldPoint, farmingPatch));
compostTracker.onChatMessage(chatEvent);
verify(configManager).setRSProfileConfiguration("timetracking", "MOCK.compost", CompostState.COMPOST);
}
@Test
public void onChatMessage_handlesFertileSoilMessages()
{
ChatMessage chatEvent = mock(ChatMessage.class);
when(chatEvent.getType()).thenReturn(ChatMessageType.SPAM);
when(chatEvent.getMessage()).thenReturn("The allotment has been treated with supercompost.");
compostTracker.pendingCompostActions.put(farmingPatch, new CompostTracker.PendingCompost(Instant.MAX, worldPoint, farmingPatch));
compostTracker.onChatMessage(chatEvent);
verify(configManager).setRSProfileConfiguration("timetracking", "MOCK.compost", CompostState.SUPERCOMPOST);
}
}